179 lines
8.0 KiB
Java
179 lines
8.0 KiB
Java
package com.codechecker;
|
||
|
||
import com.codechecker.analyzer.ClassChangeAnalyzer;
|
||
import com.codechecker.analyzer.DtoNestIndex;
|
||
import com.codechecker.analyzer.EndpointIndexBuilder;
|
||
import com.codechecker.api.analyzer.ApiChangeAnalyzer;
|
||
import com.codechecker.api.analyzer.DtoImpactedApiAnalyzer;
|
||
import com.codechecker.api.model.ApiChangeKind;
|
||
import com.codechecker.api.model.EndpointChangeReport;
|
||
import com.codechecker.api.notify.ApiChangeNotifier;
|
||
import com.codechecker.api.scanner.ApiFileChangeScanner;
|
||
import com.codechecker.config.AppConfig;
|
||
import com.codechecker.git.GitChangeScanner;
|
||
import com.codechecker.model.ApiEndpoint;
|
||
import com.codechecker.model.ClassChangeReport;
|
||
import com.codechecker.notify.OverlapNotificationFilter;
|
||
import com.codechecker.notify.WeComNotifier;
|
||
import picocli.CommandLine;
|
||
import picocli.CommandLine.Command;
|
||
import picocli.CommandLine.Option;
|
||
|
||
import java.nio.file.Path;
|
||
import java.util.ArrayList;
|
||
import java.util.LinkedHashMap;
|
||
import java.util.LinkedHashSet;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.Set;
|
||
import java.util.concurrent.Callable;
|
||
|
||
/**
|
||
* CLI 入口:加载配置 → 扫描 git 变更 → 分析影响 → 输出/发送企微通知。
|
||
*/
|
||
@Command(name = "code-checker", mixinStandardHelpOptions = true,
|
||
description = "检测类变更与 API 变更并发送企业微信通知")
|
||
public class CodeCheckMain implements Callable<Integer> {
|
||
@Option(names = "--config", required = true, description = "配置文件路径")
|
||
private Path config;
|
||
|
||
@Option(names = "--repo-root", required = true, description = "仓库根目录")
|
||
private Path repoRoot;
|
||
|
||
@Option(names = "--old-sha", required = true, description = "旧提交 SHA")
|
||
private String oldSha;
|
||
|
||
@Option(names = "--new-sha", required = true, description = "新提交 SHA")
|
||
private String newSha;
|
||
|
||
@Option(names = "--modifier", required = true, description = "修改人")
|
||
private String modifier;
|
||
|
||
@Option(names = "--modify-time", required = true, description = "修改时间")
|
||
private String modifyTime;
|
||
|
||
/** 程序入口 */
|
||
public static void main(String[] args) {
|
||
int exitCode = new CommandLine(new CodeCheckMain()).execute(args);
|
||
System.exit(exitCode);
|
||
}
|
||
|
||
/** 主流程:类变更与 API 变更检测,支持 Dto 跟进与重叠通知策略 */
|
||
@Override
|
||
public Integer call() throws Exception {
|
||
AppConfig appConfig = AppConfig.load(config.toAbsolutePath());
|
||
if (!appConfig.isMasterEnabled()) {
|
||
System.out.println("变更检测已全部关闭(checker.enabled=false)");
|
||
return 0;
|
||
}
|
||
|
||
GitChangeScanner gitScanner = new GitChangeScanner(repoRoot.toAbsolutePath());
|
||
DtoNestIndex nestIndex = DtoNestIndex.build(repoRoot.toAbsolutePath(), appConfig);
|
||
List<ClassChangeReport> classReports = List.of();
|
||
List<EndpointChangeReport> apiReports = List.of();
|
||
|
||
if (appConfig.isClassCheckEnabled()) {
|
||
classReports = analyzeClassChanges(appConfig, gitScanner, nestIndex);
|
||
} else {
|
||
System.out.println("类变更检测已关闭(class_check.enabled=false)");
|
||
}
|
||
|
||
if (appConfig.isApiCheckEnabled()) {
|
||
apiReports = analyzeApiChanges(appConfig, gitScanner, classReports, nestIndex);
|
||
} else {
|
||
System.out.println("API 变更检测已关闭(api_check.enabled=false)");
|
||
}
|
||
|
||
OverlapNotificationFilter.FilterResult filtered = OverlapNotificationFilter.apply(
|
||
classReports, apiReports, appConfig.getDtoOverlapMode(), nestIndex);
|
||
int totalSent = sendClassNotifications(appConfig, filtered.classReports())
|
||
+ sendApiNotifications(appConfig, filtered.apiReports());
|
||
|
||
if (totalSent == 0 && appConfig.isOnlyOnChange()) {
|
||
System.out.println("无变更,静默退出");
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
private List<ClassChangeReport> analyzeClassChanges(AppConfig appConfig, GitChangeScanner gitScanner,
|
||
DtoNestIndex nestIndex) throws Exception {
|
||
System.out.println("=== 类变更检测 ===");
|
||
EndpointIndexBuilder indexBuilder = new EndpointIndexBuilder();
|
||
Map<String, ApiEndpoint> endpointIndex = indexBuilder.buildIndex(repoRoot.toAbsolutePath(), appConfig);
|
||
System.out.println("已索引接口数量: " + endpointIndex.size());
|
||
|
||
ClassChangeAnalyzer analyzer = new ClassChangeAnalyzer(gitScanner);
|
||
List<ClassChangeReport> reports = analyzer.analyze(
|
||
repoRoot.toAbsolutePath(), appConfig, oldSha, newSha, endpointIndex, nestIndex);
|
||
System.out.println("检测到需通知的类变更数量: " + reports.size());
|
||
return reports;
|
||
}
|
||
|
||
private List<EndpointChangeReport> analyzeApiChanges(AppConfig appConfig, GitChangeScanner gitScanner,
|
||
List<ClassChangeReport> classReports,
|
||
DtoNestIndex nestIndex) throws Exception {
|
||
System.out.println("=== API 变更检测 ===");
|
||
ApiFileChangeScanner fileScanner = new ApiFileChangeScanner(gitScanner);
|
||
Set<String> changedApiFiles = new LinkedHashSet<>(fileScanner.scanChangedFiles(
|
||
repoRoot.toAbsolutePath(), appConfig.getAllApiScanDirs(), oldSha, newSha));
|
||
|
||
ApiChangeAnalyzer analyzer = new ApiChangeAnalyzer(gitScanner);
|
||
List<EndpointChangeReport> reports = new ArrayList<>();
|
||
if (!changedApiFiles.isEmpty()) {
|
||
reports.addAll(analyzer.analyze(repoRoot.toAbsolutePath(), appConfig, oldSha, newSha));
|
||
}
|
||
|
||
if (appConfig.isDtoApiFollowUpEnabled() && !classReports.isEmpty()) {
|
||
DtoImpactedApiAnalyzer dtoAnalyzer = new DtoImpactedApiAnalyzer(gitScanner);
|
||
List<EndpointChangeReport> followUpReports = dtoAnalyzer.analyze(
|
||
repoRoot.toAbsolutePath(), appConfig, oldSha, newSha, classReports, changedApiFiles, nestIndex);
|
||
if (!followUpReports.isEmpty()) {
|
||
System.out.println("Dto 跟进检测到 API 参数变更数量: " + followUpReports.size());
|
||
reports.addAll(followUpReports);
|
||
}
|
||
}
|
||
|
||
reports = dedupeApiReports(reports);
|
||
System.out.println("检测到需通知的 API 变更数量: " + reports.size());
|
||
return reports;
|
||
}
|
||
|
||
private List<EndpointChangeReport> dedupeApiReports(List<EndpointChangeReport> reports) {
|
||
Map<String, EndpointChangeReport> merged = new LinkedHashMap<>();
|
||
for (EndpointChangeReport report : reports) {
|
||
String key = report.getChangeKind() + "|" + report.getHttpMethod() + "|" + report.getUri();
|
||
EndpointChangeReport existing = merged.get(key);
|
||
if (existing == null) {
|
||
merged.put(key, report);
|
||
continue;
|
||
}
|
||
if (report.getChangeKind() == ApiChangeKind.PARAM_CHANGED
|
||
&& existing.getChangeKind() == ApiChangeKind.PARAM_CHANGED) {
|
||
report.getParameterChanges().forEach(existing::addParameterChange);
|
||
}
|
||
}
|
||
return new ArrayList<>(merged.values());
|
||
}
|
||
|
||
private int sendClassNotifications(AppConfig appConfig, List<ClassChangeReport> reports) {
|
||
if (reports.isEmpty()) {
|
||
return 0;
|
||
}
|
||
WeComNotifier notifier = new WeComNotifier();
|
||
if (appConfig.isWecomEnabled()) {
|
||
return notifier.sendAll(appConfig.getWecomWebhookUrl(), reports, modifier, modifyTime);
|
||
}
|
||
notifier.logAll(reports, modifier, modifyTime);
|
||
return reports.size();
|
||
}
|
||
|
||
private int sendApiNotifications(AppConfig appConfig, List<EndpointChangeReport> reports) {
|
||
if (reports.isEmpty()) {
|
||
return 0;
|
||
}
|
||
ApiChangeNotifier notifier = new ApiChangeNotifier();
|
||
return notifier.sendAll(appConfig.getWecomWebhookUrl(), reports, modifier, modifyTime,
|
||
appConfig.isWecomEnabled());
|
||
}
|
||
}
|