Compare commits
8
Commits
c4acef5887
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cb2f7fe62 | ||
|
|
afdd50c857 | ||
|
|
ed4000b834 | ||
|
|
90af1d199d | ||
|
|
ea42aa89d9 | ||
|
|
2fca4f38df | ||
|
|
233b7f4c36 | ||
|
|
023f0b9590 |
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
🤖 基于飞书 Webhook 实现仓库代码更新 | AI 摘要
|
🤖 基于飞书 Webhook 实现仓库代码更新 | AI 摘要
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
> [飞书开放平台 - 服务端API - 事件概述](https://open.feishu.cn/document/server-docs/event-subscription-guide/overview)
|
> [飞书开放平台 - 服务端API - 事件概述](https://open.feishu.cn/document/server-docs/event-subscription-guide/overview)
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package cn.hezhaohui.webhook.constant;
|
|||||||
public class AIConstant {
|
public class AIConstant {
|
||||||
public static final String AI_URL = "https://api.siliconflow.cn/v1/chat/completions";
|
public static final String AI_URL = "https://api.siliconflow.cn/v1/chat/completions";
|
||||||
public static final String AI_TOKEN = "sk-htzofywujywqlpwdddcrwxuotefavvqnibrrhvrbdnbiilkw";
|
public static final String AI_TOKEN = "sk-htzofywujywqlpwdddcrwxuotefavvqnibrrhvrbdnbiilkw";
|
||||||
public static final String AI_MODEL = "Qwen/Qwen2.5-Coder-32B-Instruct";
|
public static final String AI_MODEL = "Qwen/Qwen3-Coder-30B-A3B-Instruct";
|
||||||
|
|
||||||
public static final String AI_SYSTEM_PROMPT = "【身份】你是一位代码总结专家,你擅长总结用户给出的代码并且做出有见解的回答。" +
|
public static final String AI_SYSTEM_PROMPT = "【身份】你是一位代码总结专家,你擅长总结用户给出的代码并且做出有见解的回答。" +
|
||||||
"【要求】你需要根据用户给出的 commit messages 以及 代码文件的变化,总结出代码的改动内容,并给出有见解的回答。回答结果需要简洁精炼专业,默认情况下不分点描述。对于简单的技术内容,通过概括性的语言描述即可,避免口语化回答。你不需要输出带有 MarkDown 格式的文本,因为用户的终端不支持该选项。在适宜的地方需要使用换行。" +
|
"【要求】你需要根据用户给出的 commit messages 以及 代码文件的变化,总结出代码的改动内容,并给出有见解的回答。回答结果需要简洁精炼专业,默认情况下不分点描述。对于简单的技术内容,通过概括性的语言描述即可,避免口语化回答。你不需要输出带有 MarkDown 格式的文本,因为用户的终端不支持该选项。在适宜的地方需要使用换行。" +
|
||||||
@@ -14,4 +14,6 @@ public class AIConstant {
|
|||||||
"[Modified Files]: ...\n" +
|
"[Modified Files]: ...\n" +
|
||||||
"【输出格式】" +
|
"【输出格式】" +
|
||||||
"[总结]: ...";
|
"[总结]: ...";
|
||||||
|
|
||||||
|
public static final String DEFAULT_ERROR_RESPONSE = "*变化文件数量过多,请在仓库中查看";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import cn.hezhaohui.webhook.constant.AIConstant;
|
|||||||
import cn.hutool.http.HttpRequest;
|
import cn.hutool.http.HttpRequest;
|
||||||
import cn.hutool.http.HttpResponse;
|
import cn.hutool.http.HttpResponse;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -25,6 +26,10 @@ public class AIService {
|
|||||||
.header("Authorization", "Bearer " + AIConstant.AI_TOKEN)
|
.header("Authorization", "Bearer " + AIConstant.AI_TOKEN)
|
||||||
.body(jsonBody)
|
.body(jsonBody)
|
||||||
.execute();
|
.execute();
|
||||||
return "AI response: " + response.body();
|
if (response.getStatus() != 200) {
|
||||||
|
return AIConstant.DEFAULT_ERROR_RESPONSE;
|
||||||
|
}
|
||||||
|
JSONObject jsonResponse = JSONUtil.parseObj(response.body());
|
||||||
|
return jsonResponse.getJSONArray("choices").getJSONObject(0).getJSONObject("message").getStr("content");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,19 @@ import cn.hutool.http.HttpUtil;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class FileService {
|
public class FileService {
|
||||||
|
private static final Set<String> UNSUPPORTED_FILE_TYPES = Set.of(
|
||||||
|
".jpg", ".jpeg", ".png", ".gif", ".bmp", ".ico", // 图片
|
||||||
|
".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", // 文档
|
||||||
|
".zip", ".rar", ".7z", ".tar", ".gz", // 压缩文件
|
||||||
|
".exe", ".dll", ".so", ".class", // 可执行文件
|
||||||
|
".jar", ".war", ".ear", // Java归档
|
||||||
|
".gitignore", ".gitattributes", ".gitmodules" // Git配置文件
|
||||||
|
);
|
||||||
|
|
||||||
// repo: http://47.121.181.112:3000/wonder/lark-webhook-sender
|
// repo: http://47.121.181.112:3000/wonder/lark-webhook-sender
|
||||||
// prefix: /raw/branch/main/
|
// prefix: /raw/branch/main/
|
||||||
// raw: gitea-webhook/src/main/java/cn/hezhaohui/webhook/controller/HookController.java
|
// raw: gitea-webhook/src/main/java/cn/hezhaohui/webhook/controller/HookController.java
|
||||||
@@ -19,6 +29,13 @@ public class FileService {
|
|||||||
public String getRawContents(String repo, String raw) {
|
public String getRawContents(String repo, String raw) {
|
||||||
String url = getRawUrl(repo, raw);
|
String url = getRawUrl(repo, raw);
|
||||||
String fileName = raw.substring(raw.lastIndexOf("/")+1);
|
String fileName = raw.substring(raw.lastIndexOf("/")+1);
|
||||||
|
|
||||||
|
// 检查文件拓展名
|
||||||
|
String fileExtension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
|
||||||
|
if (UNSUPPORTED_FILE_TYPES.contains(fileExtension)) {
|
||||||
|
return "[FileName]" + fileName + "\n[FileContent]*不支持显示的文件类型,请忽略\n";
|
||||||
|
}
|
||||||
|
|
||||||
String fileContent = HttpUtil.get(url);
|
String fileContent = HttpUtil.get(url);
|
||||||
return "[FileName]" + fileName + "\n" + "[FileContent]" + fileContent + "\n";
|
return "[FileName]" + fileName + "\n" + "[FileContent]" + fileContent + "\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package cn.hezhaohui.webhook.service;
|
|||||||
|
|
||||||
import cn.hezhaohui.lark.entity.CardData;
|
import cn.hezhaohui.lark.entity.CardData;
|
||||||
import cn.hezhaohui.lark.util.Lark;
|
import cn.hezhaohui.lark.util.Lark;
|
||||||
|
import cn.hezhaohui.webhook.constant.AIConstant;
|
||||||
import cn.hezhaohui.webhook.entity.GiteaPushEvent;
|
import cn.hezhaohui.webhook.entity.GiteaPushEvent;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -13,6 +15,12 @@ import java.util.stream.Stream;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class HookService {
|
public class HookService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AIService aiService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FileService fileService;
|
||||||
|
|
||||||
public void handlePush(GiteaPushEvent event) {
|
public void handlePush(GiteaPushEvent event) {
|
||||||
List<String> commitMsgList = event.getCommits().stream()
|
List<String> commitMsgList = event.getCommits().stream()
|
||||||
.map(GiteaPushEvent.Commit::getMessage)
|
.map(GiteaPushEvent.Commit::getMessage)
|
||||||
@@ -20,22 +28,49 @@ public class HookService {
|
|||||||
|
|
||||||
List<String> addedFiles = event.getCommits().stream()
|
List<String> addedFiles = event.getCommits().stream()
|
||||||
.flatMap(commit -> commit.getAdded().stream())
|
.flatMap(commit -> commit.getAdded().stream())
|
||||||
|
.distinct()
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
List<String> removedFiles = event.getCommits().stream()
|
List<String> removedFiles = event.getCommits().stream()
|
||||||
.flatMap(commit -> commit.getRemoved().stream())
|
.flatMap(commit -> commit.getRemoved().stream())
|
||||||
|
.distinct()
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
List<String> modifiedFiles = event.getCommits().stream()
|
List<String> modifiedFiles = event.getCommits().stream()
|
||||||
.flatMap(commit -> commit.getModified().stream())
|
.flatMap(commit -> commit.getModified().stream())
|
||||||
|
.distinct()
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
if (addedFiles.isEmpty() && removedFiles.isEmpty() && modifiedFiles.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ai = AIConstant.DEFAULT_ERROR_RESPONSE;
|
||||||
|
|
||||||
|
|
||||||
|
if (addedFiles.size() + removedFiles.size() + modifiedFiles.size() <= 20) {
|
||||||
|
String addedFilesContent = fileService.getAllFiles(event.getRepository().getHtml_url(), addedFiles);
|
||||||
|
// 对于已被删除的文件,访问不到对应文件的url,只需要返回文件名即可
|
||||||
|
String removedFilesContent = removedFiles.toString();
|
||||||
|
String modifiedFilesContent = fileService.getAllFiles(event.getRepository().getHtml_url(), modifiedFiles);
|
||||||
|
|
||||||
|
String allFilesContentString = "[Added Files]: \n" +
|
||||||
|
addedFilesContent +
|
||||||
|
"\n[Removed Files]: \n" +
|
||||||
|
removedFilesContent +
|
||||||
|
"\n[Modified Files]: \n" +
|
||||||
|
modifiedFilesContent;
|
||||||
|
String input = "[Commit Messages]: \n" +
|
||||||
|
String.join("\n", commitMsgList) +
|
||||||
|
"\n" + allFilesContentString;
|
||||||
|
ai = aiService.getAIResponse(input);
|
||||||
|
}
|
||||||
|
|
||||||
log.info("commitMsgList: {}\n", commitMsgList);
|
log.info("commitMsgList: {}\n", commitMsgList);
|
||||||
log.info("addedFiles: {}\n", addedFiles);
|
log.info("addedFiles: {}\n", addedFiles);
|
||||||
log.info("removedFiles: {}\n", removedFiles);
|
log.info("removedFiles: {}\n", removedFiles);
|
||||||
log.info("modifiedFiles: {}\n", modifiedFiles);
|
log.info("modifiedFiles: {}\n", modifiedFiles);
|
||||||
|
|
||||||
// TODO: 调用 AI 模块
|
|
||||||
|
|
||||||
CardData cardData = CardData.builder()
|
CardData cardData = CardData.builder()
|
||||||
.title("[Gitea] 仓库状态变化")
|
.title("[Gitea] 仓库状态变化")
|
||||||
@@ -44,7 +79,7 @@ public class HookService {
|
|||||||
.msg(String.join("\n", commitMsgList))
|
.msg(String.join("\n", commitMsgList))
|
||||||
.tag("PUSH")
|
.tag("PUSH")
|
||||||
.subtitle("Ciallo~(∠・ω< )⌒★")
|
.subtitle("Ciallo~(∠・ω< )⌒★")
|
||||||
.ai("待开发")
|
.ai(ai)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ public class AppConstant {
|
|||||||
public static final String APP_ID = "cli_a860966d8eb69013";
|
public static final String APP_ID = "cli_a860966d8eb69013";
|
||||||
public static final String APP_SECRET = "srCSYZQDaglgSkLSs5MGrfEsCW4fCQE2";
|
public static final String APP_SECRET = "srCSYZQDaglgSkLSs5MGrfEsCW4fCQE2";
|
||||||
public static final String TEMPATE_ID = "AAqxBOlIDzI20";
|
public static final String TEMPATE_ID = "AAqxBOlIDzI20";
|
||||||
public static final String TEMPLATE_VERSION = "1.0.1";
|
public static final String TEMPLATE_VERSION = "1.0.2";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user