Files
AI-Check-Test/.gitea/class-checker/src/main/java/com/aicheck/notify/WeComNotifier.java
dongzi ec3bd1d0b2
Some checks failed
类变更检测 / class-change-check (push) Failing after 4s
commit
2026-06-05 18:21:45 +08:00

180 lines
7.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.aicheck.notify;
import com.aicheck.model.ApiEndpoint;
import com.aicheck.model.ClassChangeReport;
import com.aicheck.model.ClassType;
import com.aicheck.model.FieldChange;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class WeComNotifier {
private static final int MAX_LENGTH = 3800;
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
public int sendAll(String webhookUrl, List<ClassChangeReport> reports, String modifier, String modifyTime) {
if (reports == null || reports.isEmpty()) {
System.out.println("无类变更,不发送到企业微信");
return 0;
}
int sent = 0;
for (ClassChangeReport report : reports) {
String markdown = buildMarkdown(report, modifier, modifyTime);
if (postMarkdown(webhookUrl, markdown)) {
sent++;
System.out.println("已发送类变更通知: " + report.getClassName());
}
}
if (sent > 0) {
System.out.println("总共发送 " + sent + " 条类变更通知到企业微信");
}
return sent;
}
public String buildMarkdown(ClassChangeReport report, String modifier, String modifyTime) {
StringBuilder sb = new StringBuilder();
sb.append("# 【类变更通知】").append("\n\n");
sb.append("■ 变更对象:**").append(report.getClassName()).append("**")
.append(report.getClassType().getLabel()).append("").append("\n");
sb.append("■ 修改人:").append(modifier).append("\n");
sb.append("■ 修改时间:").append(modifyTime).append("\n\n");
sb.append("────────────────────────────────").append("\n");
sb.append("▶ 对象变更细节").append("\n");
sb.append("────────────────────────────────").append("\n");
if (report.isDeleted()) {
sb.append("[已删除] 该类文件已被移除").append("\n\n");
} else {
sb.append("字段变更列表:").append("\n");
for (FieldChange change : report.getFieldChanges()) {
sb.append(formatFieldChange(change)).append("\n");
}
sb.append("\n");
}
sb.append("────────────────────────────────").append("\n");
sb.append("▶ 影响范围").append("\n");
sb.append("────────────────────────────────").append("\n");
appendImpactSections(sb, report);
return truncate(sb.toString());
}
private void appendImpactSections(StringBuilder sb, ClassChangeReport report) {
if (report.getClassType() == ClassType.ENTITY) {
sb.append("① 入参影响Dto变更导致接口参数变化").append("\n");
sb.append("").append("\n\n");
sb.append("② 类转换影响Dto → Entity 转换,已开启检测):").append("\n");
sb.append("").append("\n\n");
sb.append("③ 前端影响Vo变更导致返回结构变化").append("\n");
sb.append("").append("\n");
return;
}
sb.append("① 入参影响Dto变更导致接口参数变化").append("\n");
appendEndpointList(sb, report.getInputImpactEndpoints());
sb.append("\n");
sb.append("② 类转换影响Dto → Entity 转换");
if (report.isConversionCheckEnabled()) {
sb.append(",已开启检测");
}
sb.append("").append("\n");
if (!report.isConversionCheckEnabled()) {
sb.append(" 未开启检测").append("\n\n");
} else if (report.getConversionEntities().isEmpty()) {
sb.append("").append("\n\n");
} else {
for (String entity : report.getConversionEntities()) {
sb.append(" 涉及Entity类").append(entity).append("\n");
}
sb.append("\n");
}
sb.append("③ 前端影响Vo变更导致返回结构变化").append("\n");
appendEndpointList(sb, report.getFrontendImpactEndpoints());
}
private void appendEndpointList(StringBuilder sb, List<ApiEndpoint> endpoints) {
if (endpoints == null || endpoints.isEmpty()) {
sb.append("").append("\n");
return;
}
sb.append(" 影响接口列表:").append("\n");
for (ApiEndpoint endpoint : endpoints) {
sb.append(" - ").append(endpoint.displayLine()).append("\n");
}
}
private String formatFieldChange(FieldChange change) {
String desc = change.getDescription() == null || change.getDescription().isBlank()
? "" : change.getDescription();
switch (change.getKind()) {
case ADDED:
return " [新增] 字段名: " + change.getFieldName() + " 说明: " + desc;
case REMOVED:
return " [删除] 字段名: " + change.getFieldName() + " 说明: " + desc;
case MODIFIED:
default:
String detail = change.getDetail() == null || change.getDetail().isBlank()
? desc : change.getDetail();
return " [修改] 字段名: " + change.getFieldName() + " 说明: " + detail;
}
}
private boolean postMarkdown(String webhookUrl, String content) {
if (webhookUrl == null || webhookUrl.isBlank() || webhookUrl.contains("YOUR_WECOM")) {
System.out.println("[警告] 未配置有效的企业微信 Webhook URL");
System.out.println("--- 通知预览 ---");
System.out.println(content.length() > 1000 ? content.substring(0, 1000) : content);
return false;
}
String payload = "{\"msgtype\":\"markdown\",\"markdown\":{\"content\":"
+ jsonEscape(content) + "}}";
Request request = new Request.Builder()
.url(webhookUrl)
.post(RequestBody.create(payload, JSON))
.build();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful() && response.body() != null) {
String body = response.body().string();
return body.contains("\"errcode\":0");
}
System.out.println("[错误] 企微返回异常: " + response.code()
+ (response.body() != null ? " " + response.body().string() : ""));
return false;
} catch (IOException e) {
System.out.println("[错误] 发送企微消息失败: " + e.getMessage());
return false;
}
}
private String truncate(String text) {
if (text.length() <= MAX_LENGTH) {
return text;
}
return text.substring(0, MAX_LENGTH) + "\n\n... 消息过长,已截断";
}
private String jsonEscape(String text) {
String escaped = text
.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "");
return "\"" + escaped + "\"";
}
}