Compare commits
22
Commits
a904db5d06
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cb2f7fe62 | ||
|
|
afdd50c857 | ||
|
|
ed4000b834 | ||
|
|
90af1d199d | ||
|
|
ea42aa89d9 | ||
|
|
2fca4f38df | ||
|
|
233b7f4c36 | ||
|
|
023f0b9590 | ||
|
|
c4acef5887 | ||
|
|
fe8dde8c73 | ||
|
|
ff2494dcf2 | ||
|
|
7866db7cbb | ||
|
|
ef2bd4ec21 | ||
|
|
ce1d5dd7fe | ||
|
|
75687d45b9 | ||
|
|
ea837b6b94 | ||
|
|
adad851991 | ||
|
|
f93c49659d | ||
|
|
8d37ea78cc | ||
|
|
1d7e6a9d6e | ||
|
|
c5e3bb47a8 | ||
|
|
c5d3b9b996 |
@@ -0,0 +1,3 @@
|
|||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
target
|
||||||
@@ -1,3 +1,35 @@
|
|||||||
# lark-webhook-sender
|
# lark-webhook-sender
|
||||||
|
|
||||||
🤖 基于飞书 Webhook 实现仓库代码更新 | AI 摘要
|
🤖 基于飞书 Webhook 实现仓库代码更新 | AI 摘要
|
||||||
|
|
||||||
|

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

|
||||||
|
|
||||||
|
> [飞书开放平台 - 服务端API - 事件概述](https://open.feishu.cn/document/server-docs/event-subscription-guide/overview)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Gitea as Gitea 仓库
|
||||||
|
participant Service as 你的服务 (Java)
|
||||||
|
participant AISummary as AI摘要服务
|
||||||
|
participant Lark as 飞书机器人
|
||||||
|
|
||||||
|
Gitea->>Service: 1. Git push事件触发
|
||||||
|
Service->>Service: 2. 验证Gitea请求(必须!)
|
||||||
|
Service-->>Service: 2.1 检查 X-Gitea-Event: push
|
||||||
|
Service-->>Service: 2.2 用 SECRET 校验 signature (HMAC-SHA256)
|
||||||
|
alt 验证通过
|
||||||
|
Service->>Service: 3. 解析Gitea Payload
|
||||||
|
Service-->>Service: 3.1 示例:
|
||||||
|
Service-->>Service: { "ref": "main", "commits": [{"message": "feat: ..."}] }
|
||||||
|
Service->>AISummary: 4. 调用AI摘要服务
|
||||||
|
AISummary->>Service: 4.1 返回总结结果
|
||||||
|
Service->>Lark: 5. 发送飞书机器人通知
|
||||||
|
Lark->>Lark: 5.1 格式化:
|
||||||
|
Lark->>Lark: { "msg_type": "text", "content": "【代码更新】feat: ... [AI总结]" }
|
||||||
|
Service-->>Gitea: 6. 返回 HTTP 200
|
||||||
|
else 验证失败
|
||||||
|
Service-->>Gitea: 6. 返回 HTTP 401
|
||||||
|
end
|
||||||
|
```
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>cn.hezhaohui</groupId>
|
||||||
|
<artifactId>lark-webhook-sender</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>gitea-webhook</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hezhaohui</groupId>
|
||||||
|
<artifactId>lark-sender</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-http -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-http</artifactId>
|
||||||
|
<version>5.8.27</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-json -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-json</artifactId>
|
||||||
|
<version>5.8.27</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package cn.hezhaohui.webhook;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class Application {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(Application.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cn.hezhaohui.webhook.constant;
|
||||||
|
|
||||||
|
public class AIConstant {
|
||||||
|
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_MODEL = "Qwen/Qwen3-Coder-30B-A3B-Instruct";
|
||||||
|
|
||||||
|
public static final String AI_SYSTEM_PROMPT = "【身份】你是一位代码总结专家,你擅长总结用户给出的代码并且做出有见解的回答。" +
|
||||||
|
"【要求】你需要根据用户给出的 commit messages 以及 代码文件的变化,总结出代码的改动内容,并给出有见解的回答。回答结果需要简洁精炼专业,默认情况下不分点描述。对于简单的技术内容,通过概括性的语言描述即可,避免口语化回答。你不需要输出带有 MarkDown 格式的文本,因为用户的终端不支持该选项。在适宜的地方需要使用换行。" +
|
||||||
|
"【输入格式】" +
|
||||||
|
"[Commit Messages]: ...\n" +
|
||||||
|
"[Added Files]: ...\n" +
|
||||||
|
"[Removed Files]: ...\n" +
|
||||||
|
"[Modified Files]: ...\n" +
|
||||||
|
"【输出格式】" +
|
||||||
|
"[总结]: ...";
|
||||||
|
|
||||||
|
public static final String DEFAULT_ERROR_RESPONSE = "*变化文件数量过多,请在仓库中查看";
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package cn.hezhaohui.webhook.controller;
|
||||||
|
|
||||||
|
import cn.hezhaohui.webhook.entity.GiteaPushEvent;
|
||||||
|
import cn.hezhaohui.webhook.service.HookService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpStatusCode;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/hook")
|
||||||
|
@Slf4j
|
||||||
|
public class HookController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private HookService hookService;
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<String> handlePush(@RequestBody GiteaPushEvent event) {
|
||||||
|
log.info(event.toString());
|
||||||
|
log.info("\n============================\n");
|
||||||
|
hookService.handlePush(event);
|
||||||
|
return ResponseEntity.ok("Ciallo~(∠・ω< )⌒★");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package cn.hezhaohui.webhook.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class GiteaPushEvent {
|
||||||
|
private String ref;
|
||||||
|
private String before;
|
||||||
|
private String after;
|
||||||
|
private String compare_url;
|
||||||
|
private List<Commit> commits;
|
||||||
|
private Repository repository;
|
||||||
|
private User pusher;
|
||||||
|
private User sender;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Repository {
|
||||||
|
private Integer id;
|
||||||
|
private User owner;
|
||||||
|
private String name;
|
||||||
|
private String full_name;
|
||||||
|
private String description;
|
||||||
|
private String html_url;
|
||||||
|
private String clone_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Commit {
|
||||||
|
private String id;
|
||||||
|
private String message;
|
||||||
|
private String url;
|
||||||
|
private User author;
|
||||||
|
private User committer;
|
||||||
|
private String timestamp;
|
||||||
|
private List<String> added;
|
||||||
|
private List<String> removed;
|
||||||
|
private List<String> modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class User {
|
||||||
|
private Integer id;
|
||||||
|
private String login;
|
||||||
|
private String username;
|
||||||
|
private String email;
|
||||||
|
private String avatar_url;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package cn.hezhaohui.webhook.service;
|
||||||
|
|
||||||
|
import cn.hezhaohui.webhook.constant.AIConstant;
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class AIService {
|
||||||
|
public String getAIResponse(String prompt) {
|
||||||
|
Map<String, Object> body = new HashMap<>();
|
||||||
|
body.put("model", AIConstant.AI_MODEL);
|
||||||
|
body.put("messages", List.of(Map.of("role", "system", "content", AIConstant.AI_SYSTEM_PROMPT), Map.of("role", "user", "content", prompt)));
|
||||||
|
|
||||||
|
String jsonBody = JSONUtil.toJsonStr(body);
|
||||||
|
|
||||||
|
HttpResponse response = HttpRequest.post(AIConstant.AI_URL)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Authorization", "Bearer " + AIConstant.AI_TOKEN)
|
||||||
|
.body(jsonBody)
|
||||||
|
.execute();
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package cn.hezhaohui.webhook.service;
|
||||||
|
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
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
|
||||||
|
// prefix: /raw/branch/main/
|
||||||
|
// raw: gitea-webhook/src/main/java/cn/hezhaohui/webhook/controller/HookController.java
|
||||||
|
// parse: http://47.121.181.112:3000/wonder/lark-webhook-sender/raw/branch/main/gitea-webhook/src/main/java/cn/hezhaohui/webhook/controller/HookController.java
|
||||||
|
|
||||||
|
public String getRawUrl(String repo, String raw) {
|
||||||
|
return repo +"/raw/branch/main/" +raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRawContents(String repo, String raw) {
|
||||||
|
String url = getRawUrl(repo, raw);
|
||||||
|
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);
|
||||||
|
return "[FileName]" + fileName + "\n" + "[FileContent]" + fileContent + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAllFiles(String repo, List<String> files) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String file : files) {
|
||||||
|
sb.append(getRawContents(repo, file));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package cn.hezhaohui.webhook.service;
|
||||||
|
|
||||||
|
import cn.hezhaohui.lark.entity.CardData;
|
||||||
|
import cn.hezhaohui.lark.util.Lark;
|
||||||
|
import cn.hezhaohui.webhook.constant.AIConstant;
|
||||||
|
import cn.hezhaohui.webhook.entity.GiteaPushEvent;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class HookService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AIService aiService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FileService fileService;
|
||||||
|
|
||||||
|
public void handlePush(GiteaPushEvent event) {
|
||||||
|
List<String> commitMsgList = event.getCommits().stream()
|
||||||
|
.map(GiteaPushEvent.Commit::getMessage)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<String> addedFiles = event.getCommits().stream()
|
||||||
|
.flatMap(commit -> commit.getAdded().stream())
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<String> removedFiles = event.getCommits().stream()
|
||||||
|
.flatMap(commit -> commit.getRemoved().stream())
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<String> modifiedFiles = event.getCommits().stream()
|
||||||
|
.flatMap(commit -> commit.getModified().stream())
|
||||||
|
.distinct()
|
||||||
|
.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("addedFiles: {}\n", addedFiles);
|
||||||
|
log.info("removedFiles: {}\n", removedFiles);
|
||||||
|
log.info("modifiedFiles: {}\n", modifiedFiles);
|
||||||
|
|
||||||
|
|
||||||
|
CardData cardData = CardData.builder()
|
||||||
|
.title("[Gitea] 仓库状态变化")
|
||||||
|
.repo(event.getRepository().getName())
|
||||||
|
.link(event.getRepository().getHtml_url())
|
||||||
|
.msg(String.join("\n", commitMsgList))
|
||||||
|
.tag("PUSH")
|
||||||
|
.subtitle("Ciallo~(∠・ω< )⌒★")
|
||||||
|
.ai(ai)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Lark.sendCardMessage(cardData);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
server:
|
||||||
|
port: 9000
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package cn.hezhaohui.webhook.service;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
public class AIServiceTest extends TestCase {
|
||||||
|
|
||||||
|
public void testGetAIResponse() {
|
||||||
|
AIService aiService = new AIService();
|
||||||
|
String response = aiService.getAIResponse("[Commit Messages]: Chore: 增加日志\n" +
|
||||||
|
" Fix: 修复缺失的响应头\n" +
|
||||||
|
" ✨Feat: 获取文件修改" +
|
||||||
|
"[Added Files]:" +
|
||||||
|
"[FileName] AIService.java\n" +
|
||||||
|
"package cn.hezhaohui.webhook.service;\n" +
|
||||||
|
"\n" +
|
||||||
|
"import cn.hezhaohui.webhook.constant.AIConstant;\n" +
|
||||||
|
"import cn.hutool.http.HttpRequest;\n" +
|
||||||
|
"import cn.hutool.http.HttpResponse;\n" +
|
||||||
|
"import cn.hutool.http.HttpUtil;\n" +
|
||||||
|
"import cn.hutool.json.JSONUtil;\n" +
|
||||||
|
"import org.springframework.stereotype.Service;\n" +
|
||||||
|
"\n" +
|
||||||
|
"import java.util.HashMap;\n" +
|
||||||
|
"import java.util.List;\n" +
|
||||||
|
"import java.util.Map;\n" +
|
||||||
|
"\n" +
|
||||||
|
"@Service\n" +
|
||||||
|
"public class AIService {\n" +
|
||||||
|
" public String getAIResponse(String prompt) {\n" +
|
||||||
|
" Map<String, Object> body = new HashMap<>();\n" +
|
||||||
|
" body.put(\"model\", AIConstant.AI_MODEL);\n" +
|
||||||
|
" body.put(\"messages\", List.of(Map.of(\"role\", \"system\", \"content\", AIConstant.AI_SYSTEM_PROMPT), Map.of(\"role\", \"user\", \"content\", prompt)));\n" +
|
||||||
|
"\n" +
|
||||||
|
" String jsonBody = JSONUtil.toJsonStr(body);\n" +
|
||||||
|
"\n" +
|
||||||
|
" HttpResponse response = HttpRequest.post(AIConstant.AI_URL)\n" +
|
||||||
|
" .header(\"Content-Type\", \"application/json\")\n" +
|
||||||
|
" .header(\"Authorization\", \"Bearer \" + AIConstant.AI_TOKEN)\n" +
|
||||||
|
" .body(jsonBody)\n" +
|
||||||
|
" .execute();\n" +
|
||||||
|
" return \"AI response: \" + response.body();\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}\n" +
|
||||||
|
"[Removed Files]:" +
|
||||||
|
"[Modified Files]:");
|
||||||
|
System.out.println(response);
|
||||||
|
assertNotNull(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>cn.hezhaohui</groupId>
|
||||||
|
<artifactId>lark-webhook-sender</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>lark-sender</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.larksuite.oapi/oapi-sdk -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.larksuite.oapi</groupId>
|
||||||
|
<artifactId>oapi-sdk</artifactId>
|
||||||
|
<version>2.4.16</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>3.2.2</version>
|
||||||
|
<configuration>
|
||||||
|
<skip>
|
||||||
|
true
|
||||||
|
</skip>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package cn.hezhaohui.lark.constant;
|
||||||
|
|
||||||
|
public class AppConstant {
|
||||||
|
public static final String APP_ID = "cli_a860966d8eb69013";
|
||||||
|
public static final String APP_SECRET = "srCSYZQDaglgSkLSs5MGrfEsCW4fCQE2";
|
||||||
|
public static final String TEMPATE_ID = "AAqxBOlIDzI20";
|
||||||
|
public static final String TEMPLATE_VERSION = "1.0.2";
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package cn.hezhaohui.lark.constant;
|
||||||
|
|
||||||
|
public class UserConstant {
|
||||||
|
public static final String USER_OPEN_ID = "ou_728072d781317a402a0ba6b6d30390a6";
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package cn.hezhaohui.lark.entity;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class CardData {
|
||||||
|
private String ai;
|
||||||
|
private String link;
|
||||||
|
private String msg;
|
||||||
|
private String repo;
|
||||||
|
private String subtitle;
|
||||||
|
private String tag;
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package cn.hezhaohui.lark.util;
|
||||||
|
|
||||||
|
import cn.hezhaohui.lark.constant.AppConstant;
|
||||||
|
import cn.hezhaohui.lark.constant.UserConstant;
|
||||||
|
import cn.hezhaohui.lark.entity.CardData;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.lark.oapi.Client;
|
||||||
|
import com.lark.oapi.core.utils.Jsons;
|
||||||
|
import com.lark.oapi.service.im.v1.model.*;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
||||||
|
public class Lark {
|
||||||
|
|
||||||
|
// SDK 使用文档:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/java-sdk-guide/preparations
|
||||||
|
// 复制该 Demo 后, 需要将 "YOUR_APP_ID", "YOUR_APP_SECRET" 替换为自己应用的 APP_ID, APP_SECRET.
|
||||||
|
// 以下示例代码默认根据文档示例值填充,如果存在代码问题,请在 API 调试台填上相关必要参数后再复制代码使用
|
||||||
|
|
||||||
|
|
||||||
|
public static void sendTextMessage(String content) throws Exception {
|
||||||
|
// 构建client
|
||||||
|
Client client = Client.newBuilder(AppConstant.APP_ID, AppConstant.APP_SECRET).build();
|
||||||
|
|
||||||
|
// 生成uuid
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
Map<String, String> messageContent = new HashMap<>();
|
||||||
|
messageContent.put("text", content);
|
||||||
|
String jsonContent = Jsons.DEFAULT.toJson(messageContent);
|
||||||
|
|
||||||
|
// 创建请求对象
|
||||||
|
CreateMessageReq req = CreateMessageReq.newBuilder()
|
||||||
|
.receiveIdType("open_id")
|
||||||
|
.createMessageReqBody(CreateMessageReqBody.newBuilder()
|
||||||
|
.receiveId(UserConstant.USER_OPEN_ID)
|
||||||
|
.msgType("text")
|
||||||
|
.content(jsonContent)
|
||||||
|
.uuid(uuid)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 发起请求
|
||||||
|
CreateMessageResp resp = client.im().v1().message().create(req);
|
||||||
|
|
||||||
|
// 处理服务端错误
|
||||||
|
if(!resp.success()) {
|
||||||
|
System.out.println(String.format("code:%s,msg:%s,reqId:%s, resp:%s",
|
||||||
|
resp.getCode(), resp.getMsg(), resp.getRequestId(), Jsons.createGSON(true, false).toJson(JsonParser.parseString(new String(resp.getRawResponse().getBody(), StandardCharsets.UTF_8)))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 业务数据处理
|
||||||
|
System.out.println(Jsons.DEFAULT.toJson(resp.getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// SDK 使用文档:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/java-sdk-guide/preparations
|
||||||
|
// 复制该 Demo 后, 需要将 "YOUR_APP_ID", "YOUR_APP_SECRET" 替换为自己应用的 APP_ID, APP_SECRET.
|
||||||
|
// 以下示例代码默认根据文档示例值填充,如果存在代码问题,请在 API 调试台填上相关必要参数后再复制代码使用
|
||||||
|
|
||||||
|
|
||||||
|
public static void sendCardMessage(CardData cardData) throws Exception {
|
||||||
|
// 构建client
|
||||||
|
Client client = Client.newBuilder(AppConstant.APP_ID, AppConstant.APP_SECRET).build();
|
||||||
|
|
||||||
|
// 生成uuid
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
// 创建卡片内容
|
||||||
|
Map<String, String> cardContent = new HashMap<>();
|
||||||
|
cardContent.put("ai", cardData.getAi());
|
||||||
|
cardContent.put("link", cardData.getLink());
|
||||||
|
cardContent.put("msg", cardData.getMsg());
|
||||||
|
cardContent.put("repo", cardData.getRepo());
|
||||||
|
cardContent.put("subtitle", cardData.getSubtitle());
|
||||||
|
cardContent.put("tag", cardData.getTag());
|
||||||
|
cardContent.put("title", cardData.getTitle());
|
||||||
|
// String jsonCardContent = Jsons.DEFAULT.toJson(cardContent);
|
||||||
|
|
||||||
|
Map<String, Object> templateData = new HashMap<>();
|
||||||
|
templateData.put("template_id", AppConstant.TEMPATE_ID);
|
||||||
|
templateData.put("template_version_name", AppConstant.TEMPLATE_VERSION);
|
||||||
|
templateData.put("template_variable", cardContent);
|
||||||
|
// String jsonCardContent = Jsons.DEFAULT.toJson(templateData);
|
||||||
|
|
||||||
|
Map<String, Object> jsonContent = new HashMap<>();
|
||||||
|
jsonContent.put("data", templateData);
|
||||||
|
jsonContent.put("type", "template");
|
||||||
|
String json = Jsons.DEFAULT.toJson(jsonContent);
|
||||||
|
|
||||||
|
// 创建请求对象
|
||||||
|
CreateMessageReq req = CreateMessageReq.newBuilder()
|
||||||
|
.receiveIdType("open_id")
|
||||||
|
.createMessageReqBody(CreateMessageReqBody.newBuilder()
|
||||||
|
.receiveId(UserConstant.USER_OPEN_ID)
|
||||||
|
.msgType("interactive")
|
||||||
|
.content(json)
|
||||||
|
.uuid(uuid)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 发起请求
|
||||||
|
CreateMessageResp resp = client.im().v1().message().create(req);
|
||||||
|
|
||||||
|
// 处理服务端错误
|
||||||
|
if(!resp.success()) {
|
||||||
|
System.out.println(String.format("code:%s,msg:%s,reqId:%s, resp:%s",
|
||||||
|
resp.getCode(), resp.getMsg(), resp.getRequestId(), Jsons.createGSON(true, false).toJson(JsonParser.parseString(new String(resp.getRawResponse().getBody(), StandardCharsets.UTF_8)))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 业务数据处理
|
||||||
|
System.out.println(Jsons.DEFAULT.toJson(resp.getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package cn.hezhaohui.lark.util;
|
||||||
|
|
||||||
|
import cn.hezhaohui.lark.entity.CardData;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class LarkTest {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sendTextMessage() throws Exception {
|
||||||
|
Lark.sendTextMessage("测试消息");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSendCardMessage() throws Exception{
|
||||||
|
CardData cardData = CardData.builder()
|
||||||
|
.ai("待开发")
|
||||||
|
.tag("PUSH")
|
||||||
|
.msg("[1d7e6a]✨Feat: 实现文本消息发送\n" +
|
||||||
|
"[c5e3bb]\uD83D\uDD27Chore: 构建 Maven 项目框架")
|
||||||
|
.title("[Gitea] 仓库状态变化")
|
||||||
|
.repo("lark-webhook-sender")
|
||||||
|
.link("http://47.121.181.112:3000/wonder/lark-webhook-sender")
|
||||||
|
.subtitle("Ciallo~(∠・ω< )⌒★")
|
||||||
|
.build();
|
||||||
|
Lark.sendCardMessage(cardData);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>cn.hezhaohui</groupId>
|
||||||
|
<artifactId>lark-webhook-sender</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.2.2</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>lark-sender</module>
|
||||||
|
<module>gitea-webhook</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
Reference in New Issue
Block a user