commit
Some checks failed
类变更检测 / class-change-check (push) Failing after 4s

This commit is contained in:
2026-06-05 18:21:45 +08:00
parent c3c73b6fb3
commit ec3bd1d0b2
25 changed files with 1835 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
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 + "\"";
}
}