180 lines
7.6 KiB
Java
180 lines
7.6 KiB
Java
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 + "\"";
|
||
}
|
||
}
|