classNames) {
- if (types == null) {
- return false;
- }
- for (String type : types) {
- if (classNames.contains(type)) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/src/main/java/com/codechecker/analyzer/NestedObjectRoleResolver.java b/src/main/java/com/codechecker/analyzer/NestedObjectRoleResolver.java
deleted file mode 100644
index 0944862..0000000
--- a/src/main/java/com/codechecker/analyzer/NestedObjectRoleResolver.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.codechecker.analyzer;
-
-import com.codechecker.model.ApiEndpoint;
-import com.codechecker.model.ClassChangeReport;
-import com.codechecker.model.ClassType;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * 判定 Dto/Vo 在类变更通知中的对象角色标签(方案 B:嵌套 + 可选顶层)。
- *
- * 仅当存在嵌套祖先时标注;纯顶层不标注;既嵌套又直接作接口根类型时同时标注。
- */
-public final class NestedObjectRoleResolver {
-
- private NestedObjectRoleResolver() {
- }
-
- public static List resolve(ClassChangeReport report, DtoNestIndex nestIndex,
- Map endpointIndex) {
- if (report.getClassType() != ClassType.DTO && report.getClassType() != ClassType.VO) {
- return List.of();
- }
- if (nestIndex == null) {
- return List.of();
- }
- String className = report.getClassName();
- if (!nestIndex.hasAncestors(className)) {
- return List.of();
- }
- List labels = new ArrayList<>();
- labels.add("嵌套对象");
- if (isDirectEndpointType(className, endpointIndex)) {
- labels.add("顶层对象");
- }
- return List.copyOf(labels);
- }
-
- /** 是否直接出现在接口入参或返回值类型(非仅经祖先传播) */
- private static boolean isDirectEndpointType(String className, Map endpointIndex) {
- if (className == null || className.isBlank() || endpointIndex == null) {
- return false;
- }
- for (ApiEndpoint endpoint : endpointIndex.values()) {
- if (endpoint.getParamTypes().contains(className)
- || endpoint.getReturnTypes().contains(className)) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/src/main/java/com/codechecker/api/analyzer/ApiChangeAnalyzer.java b/src/main/java/com/codechecker/api/analyzer/ApiChangeAnalyzer.java
deleted file mode 100644
index 4515d0d..0000000
--- a/src/main/java/com/codechecker/api/analyzer/ApiChangeAnalyzer.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.codechecker.api.analyzer;
-
-import com.codechecker.api.model.EndpointChangeReport;
-import com.codechecker.api.model.EndpointSnapshot;
-import com.codechecker.api.parser.EndpointSnapshotParser;
-import com.codechecker.api.scanner.ApiFileChangeScanner;
-import com.codechecker.config.AppConfig;
-import com.codechecker.git.GitChangeScanner;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * API 变更分析编排(与 {@link com.codechecker.analyzer.ClassChangeAnalyzer} 平行、互不调用)。
- */
-public class ApiChangeAnalyzer {
- private final GitChangeScanner gitScanner;
- private final ApiFileChangeScanner fileScanner;
-
- public ApiChangeAnalyzer(GitChangeScanner gitScanner) {
- this.gitScanner = gitScanner;
- this.fileScanner = new ApiFileChangeScanner(gitScanner);
- }
-
- public List analyze(Path repoRoot, AppConfig config,
- String oldSha, String newSha) throws IOException {
- List changedFiles = fileScanner.scanChangedFiles(
- repoRoot, config.getAllApiScanDirs(), oldSha, newSha);
- if (changedFiles.isEmpty()) {
- return List.of();
- }
-
- EndpointSnapshotParser parser = new EndpointSnapshotParser(config.isApiExcludeFrameworkParams());
- ParameterDiffEngine parameterDiffEngine = new ParameterDiffEngine(
- repoRoot, buildSearchDirs(config), gitScanner, oldSha, newSha, config.getNestMaxDepth());
- EndpointDiffEngine endpointDiffEngine = new EndpointDiffEngine(parameterDiffEngine);
-
- List oldSnapshots = new ArrayList<>();
- List newSnapshots = new ArrayList<>();
-
- for (String path : changedFiles) {
- boolean feign = isFeignPath(path, config);
- String oldSource = gitScanner.readFileAtCommit(oldSha, path);
- String newSource = gitScanner.readFileAtCommit(newSha, path);
- oldSnapshots.addAll(parser.parseSource(oldSource, path, feign));
- newSnapshots.addAll(parser.parseSource(newSource, path, feign));
- }
-
- return endpointDiffEngine.diff(oldSnapshots, newSnapshots);
- }
-
- private List buildSearchDirs(AppConfig config) {
- List dirs = new ArrayList<>();
- dirs.addAll(config.getModelDirs());
- dirs.addAll(config.getAllApiScanDirs());
- return dirs;
- }
-
- private boolean isFeignPath(String path, AppConfig config) {
- String normalized = path.replace('\\', '/');
- for (String dir : config.getApiFeignScanDirs()) {
- String prefix = dir.replace('\\', '/');
- if (!prefix.endsWith("/")) {
- prefix = prefix + "/";
- }
- if (normalized.startsWith(prefix)) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/src/main/java/com/codechecker/api/analyzer/DtoImpactedApiAnalyzer.java b/src/main/java/com/codechecker/api/analyzer/DtoImpactedApiAnalyzer.java
deleted file mode 100644
index 3b0736d..0000000
--- a/src/main/java/com/codechecker/api/analyzer/DtoImpactedApiAnalyzer.java
+++ /dev/null
@@ -1,150 +0,0 @@
-package com.codechecker.api.analyzer;
-
-import com.codechecker.analyzer.DtoNestIndex;
-import com.codechecker.api.model.ApiChangeKind;
-import com.codechecker.api.model.EndpointChangeReport;
-import com.codechecker.api.model.EndpointSnapshot;
-import com.codechecker.api.model.ParameterChange;
-import com.codechecker.api.parser.EndpointSnapshotParser;
-import com.codechecker.config.AppConfig;
-import com.codechecker.git.GitChangeScanner;
-import com.codechecker.model.ApiEndpoint;
-import com.codechecker.model.ClassChangeReport;
-import com.codechecker.model.ClassType;
-
-import java.io.IOException;
-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;
-
-/**
- * 类变更(Dto/Vo 嵌套字段)后,对受影响的 Controller 继续 API 参数 diff,产出 PARAM_CHANGED 报告。
- */
-public class DtoImpactedApiAnalyzer {
- private final GitChangeScanner gitScanner;
-
- public DtoImpactedApiAnalyzer(GitChangeScanner gitScanner) {
- this.gitScanner = gitScanner;
- }
-
- public List analyze(Path repoRoot, AppConfig config,
- String oldSha, String newSha,
- List classReports,
- Set alreadyScannedFiles,
- DtoNestIndex nestIndex) throws IOException {
- Map> controllerToDtos = collectImpactedControllers(classReports, alreadyScannedFiles,
- nestIndex);
- if (controllerToDtos.isEmpty()) {
- return List.of();
- }
-
- EndpointSnapshotParser parser = new EndpointSnapshotParser(config.isApiExcludeFrameworkParams());
- ParameterDiffEngine parameterDiffEngine = new ParameterDiffEngine(
- repoRoot, buildSearchDirs(config), gitScanner, oldSha, newSha, config.getNestMaxDepth());
- EndpointDiffEngine endpointDiffEngine = new EndpointDiffEngine(parameterDiffEngine);
-
- List oldSnapshots = new ArrayList<>();
- List newSnapshots = new ArrayList<>();
- for (String path : controllerToDtos.keySet()) {
- boolean feign = isFeignPath(path, config);
- String oldSource = gitScanner.readFileAtCommit(oldSha, path);
- String newSource = gitScanner.readFileAtCommit(newSha, path);
- oldSnapshots.addAll(parser.parseSource(oldSource, path, feign));
- newSnapshots.addAll(parser.parseSource(newSource, path, feign));
- }
-
- List reports = new ArrayList<>();
- for (EndpointChangeReport report : endpointDiffEngine.diff(oldSnapshots, newSnapshots)) {
- if (report.getChangeKind() != ApiChangeKind.PARAM_CHANGED || !report.hasParameterChanges()) {
- continue;
- }
- String relatedDto = findRelatedDto(report, controllerToDtos);
- if (relatedDto == null) {
- continue;
- }
- reports.add(EndpointChangeReport.dtoFollowUp(report, relatedDto));
- }
- return reports;
- }
-
- private Map> collectImpactedControllers(List classReports,
- Set alreadyScannedFiles,
- DtoNestIndex nestIndex) {
- Map> controllerToDtos = new LinkedHashMap<>();
- for (ClassChangeReport report : classReports) {
- if (report.getFieldChanges().isEmpty()) {
- continue;
- }
- if (report.getClassType() != ClassType.DTO && report.getClassType() != ClassType.VO) {
- continue;
- }
- Set bodyRoots = resolveBodyRoots(report, nestIndex);
- if (bodyRoots.isEmpty()) {
- continue;
- }
- for (ApiEndpoint endpoint : report.getInputImpactEndpoints()) {
- String controllerFile = endpoint.getSourceFile();
- if (alreadyScannedFiles.contains(controllerFile)) {
- continue;
- }
- controllerToDtos.computeIfAbsent(controllerFile, k -> new LinkedHashSet<>()).addAll(bodyRoots);
- }
- }
- return controllerToDtos;
- }
-
- private Set resolveBodyRoots(ClassChangeReport report, DtoNestIndex nestIndex) {
- if (nestIndex == null) {
- Set names = new LinkedHashSet<>();
- if (report.getClassName().endsWith("Dto")) {
- names.add(report.getClassName());
- }
- return names;
- }
- Set roots = new LinkedHashSet<>();
- roots.addAll(nestIndex.findRequestBodyRoots(report.getClassName()));
- if (report.getOldClassName() != null && !report.getOldClassName().isBlank()) {
- roots.addAll(nestIndex.findRequestBodyRoots(report.getOldClassName()));
- }
- return roots;
- }
-
- private String findRelatedDto(EndpointChangeReport report, Map> controllerToDtos) {
- Set impactedDtos = controllerToDtos.getOrDefault(report.getSourceFile(), Set.of());
- for (ParameterChange change : report.getParameterChanges()) {
- if (!"body".equals(change.getSource())) {
- continue;
- }
- String parentDto = change.getParentDto();
- if (parentDto != null && impactedDtos.contains(parentDto)) {
- return parentDto;
- }
- }
- return null;
- }
-
- private List buildSearchDirs(AppConfig config) {
- List dirs = new ArrayList<>();
- dirs.addAll(config.getModelDirs());
- dirs.addAll(config.getAllApiScanDirs());
- return dirs;
- }
-
- private boolean isFeignPath(String path, AppConfig config) {
- String normalized = path.replace('\\', '/');
- for (String dir : config.getApiFeignScanDirs()) {
- String prefix = dir.replace('\\', '/');
- if (!prefix.endsWith("/")) {
- prefix = prefix + "/";
- }
- if (normalized.startsWith(prefix)) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/src/main/java/com/codechecker/api/analyzer/EndpointDiffEngine.java b/src/main/java/com/codechecker/api/analyzer/EndpointDiffEngine.java
deleted file mode 100644
index 84fd5f5..0000000
--- a/src/main/java/com/codechecker/api/analyzer/EndpointDiffEngine.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.codechecker.api.analyzer;
-
-import com.codechecker.api.model.ApiChangeKind;
-import com.codechecker.api.model.EndpointChangeReport;
-import com.codechecker.api.model.EndpointSnapshot;
-import com.codechecker.api.model.ParameterChange;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * 接口快照对比:路径 / 方法 / 增删 / 参数(拆分报告,互不混合类型)。
- */
-public class EndpointDiffEngine {
- private final ParameterDiffEngine parameterDiffEngine;
-
- public EndpointDiffEngine(ParameterDiffEngine parameterDiffEngine) {
- this.parameterDiffEngine = parameterDiffEngine;
- }
-
- public List diff(List oldSnapshots,
- List newSnapshots) throws IOException {
- Map oldMap = indexByFingerprint(oldSnapshots);
- Map newMap = indexByFingerprint(newSnapshots);
- List reports = new ArrayList<>();
-
- for (String fp : newMap.keySet()) {
- if (!oldMap.containsKey(fp)) {
- EndpointSnapshot snap = newMap.get(fp);
- reports.add(new EndpointChangeReport(
- ApiChangeKind.NEW_ENDPOINT,
- snap.getHttpMethod(), null,
- snap.getUri(), null,
- snap.getSourceFile(), snap.getControllerClass(),
- snap.getMethodDescription()));
- }
- }
- for (String fp : oldMap.keySet()) {
- if (!newMap.containsKey(fp)) {
- EndpointSnapshot snap = oldMap.get(fp);
- reports.add(new EndpointChangeReport(
- ApiChangeKind.REMOVED_ENDPOINT,
- snap.getHttpMethod(), null,
- snap.getUri(), null,
- snap.getSourceFile(), snap.getControllerClass(),
- snap.getMethodDescription()));
- }
- }
- for (String fp : oldMap.keySet()) {
- if (!newMap.containsKey(fp)) {
- continue;
- }
- EndpointSnapshot oldSnap = oldMap.get(fp);
- EndpointSnapshot newSnap = newMap.get(fp);
- reports.addAll(diffMatched(oldSnap, newSnap));
- }
- return reports;
- }
-
- private List diffMatched(EndpointSnapshot oldSnap,
- EndpointSnapshot newSnap) throws IOException {
- List reports = new ArrayList<>();
- boolean pathChanged = !oldSnap.getUri().equals(newSnap.getUri());
- boolean methodChanged = !oldSnap.getHttpMethod().equalsIgnoreCase(newSnap.getHttpMethod());
-
- if (pathChanged) {
- reports.add(new EndpointChangeReport(
- ApiChangeKind.PATH_CHANGED,
- newSnap.getHttpMethod(), null,
- newSnap.getUri(), oldSnap.getUri(),
- newSnap.getSourceFile(), newSnap.getControllerClass(),
- preferDescription(newSnap, oldSnap)));
- }
- if (methodChanged) {
- reports.add(new EndpointChangeReport(
- ApiChangeKind.METHOD_CHANGED,
- newSnap.getHttpMethod(), oldSnap.getHttpMethod(),
- newSnap.getUri(), null,
- newSnap.getSourceFile(), newSnap.getControllerClass(),
- preferDescription(newSnap, oldSnap)));
- }
-
- List paramChanges = parameterDiffEngine.diff(oldSnap, newSnap);
- if (!paramChanges.isEmpty()) {
- if (pathChanged || methodChanged) {
- EndpointChangeReport paramReport = new EndpointChangeReport(
- ApiChangeKind.PARAM_CHANGED,
- newSnap.getHttpMethod(), methodChanged ? oldSnap.getHttpMethod() : null,
- newSnap.getUri(), pathChanged ? oldSnap.getUri() : null,
- newSnap.getSourceFile(), newSnap.getControllerClass(),
- preferDescription(newSnap, oldSnap));
- paramChanges.forEach(paramReport::addParameterChange);
- reports.add(paramReport);
- } else {
- EndpointChangeReport paramReport = new EndpointChangeReport(
- ApiChangeKind.PARAM_CHANGED,
- newSnap.getHttpMethod(), null,
- newSnap.getUri(), null,
- newSnap.getSourceFile(), newSnap.getControllerClass(),
- preferDescription(newSnap, oldSnap));
- paramChanges.forEach(paramReport::addParameterChange);
- reports.add(paramReport);
- }
- }
- return reports;
- }
-
- private String preferDescription(EndpointSnapshot primary, EndpointSnapshot fallback) {
- if (primary != null && primary.getMethodDescription() != null
- && !primary.getMethodDescription().isBlank()) {
- return primary.getMethodDescription();
- }
- return fallback == null ? "" : fallback.getMethodDescription();
- }
-
- private Map indexByFingerprint(List snapshots) {
- Map map = new LinkedHashMap<>();
- for (EndpointSnapshot snap : snapshots) {
- map.putIfAbsent(snap.getFingerprint(), snap);
- }
- return map;
- }
-}
diff --git a/src/main/java/com/codechecker/api/analyzer/ParameterDiffEngine.java b/src/main/java/com/codechecker/api/analyzer/ParameterDiffEngine.java
deleted file mode 100644
index 4a241c5..0000000
--- a/src/main/java/com/codechecker/api/analyzer/ParameterDiffEngine.java
+++ /dev/null
@@ -1,234 +0,0 @@
-package com.codechecker.api.analyzer;
-
-import com.codechecker.analyzer.FieldDiffEngine;
-import com.codechecker.api.model.EndpointSnapshot;
-import com.codechecker.api.model.MethodParameterSnapshot;
-import com.codechecker.api.model.ParameterChange;
-import com.codechecker.api.parser.NestedDtoFieldParser;
-import com.codechecker.api.parser.NestedFieldInfo;
-import com.codechecker.git.GitChangeScanner;
-import com.codechecker.model.FieldChange;
-import com.codechecker.model.FieldInfo;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * 接口入参 diff(普通参数 + RequestBody 嵌套 Dto 字段)。
- *
- * path/query 规则:
- * - 形参名+类型相同,仅绑定名变 → 重命名
- * - 形参名+绑定名相同,仅类型变 → 类型变更
- * - 仅形参名变(绑定名不变)→ 不通知
- * - 类型与绑定名同时变,或三者都变 → 先删除后新增
- */
-public class ParameterDiffEngine {
- private final NestedDtoFieldParser nestedDtoFieldParser;
- private final FieldDiffEngine fieldDiffEngine = new FieldDiffEngine();
-
- public ParameterDiffEngine(Path repoRoot, List searchDirs,
- GitChangeScanner gitScanner, String oldSha, String newSha, int maxDepth) {
- this.nestedDtoFieldParser = new NestedDtoFieldParser(repoRoot, searchDirs, gitScanner, oldSha, newSha, maxDepth);
- }
-
- public List diff(EndpointSnapshot oldSnap, EndpointSnapshot newSnap) throws IOException {
- List changes = new ArrayList<>();
- changes.addAll(diffBodyParams(oldSnap, newSnap));
- changes.addAll(diffBindingParams(oldSnap, newSnap));
- return changes;
- }
-
- private List diffBodyParams(EndpointSnapshot oldSnap, EndpointSnapshot newSnap)
- throws IOException {
- Map oldParams = filterBySource(oldSnap, "body");
- Map newParams = filterBySource(newSnap, "body");
- List changes = new ArrayList<>();
-
- for (Map.Entry entry : newParams.entrySet()) {
- MethodParameterSnapshot oldParam = oldParams.get(entry.getKey());
- MethodParameterSnapshot newParam = entry.getValue();
- if (oldParam == null) {
- changes.addAll(addedBodyChanges(newParam));
- } else {
- changes.addAll(diffBodyDto(oldParam, newParam));
- }
- }
- for (Map.Entry entry : oldParams.entrySet()) {
- if (!newParams.containsKey(entry.getKey())) {
- changes.addAll(removedBodyChanges(entry.getValue()));
- }
- }
- return changes;
- }
-
- private List diffBindingParams(EndpointSnapshot oldSnap, EndpointSnapshot newSnap) {
- Map oldParams = filterBindingParams(oldSnap);
- Map newParams = filterBindingParams(newSnap);
- List changes = new ArrayList<>();
-
- List unmatchedOld = new ArrayList<>();
- List unmatchedNew = new ArrayList<>();
-
- for (Map.Entry entry : newParams.entrySet()) {
- MethodParameterSnapshot oldParam = oldParams.get(entry.getKey());
- MethodParameterSnapshot newParam = entry.getValue();
- if (oldParam == null) {
- unmatchedNew.add(newParam);
- } else if (!oldParam.getType().equals(newParam.getType())) {
- changes.add(ParameterChange.modified(
- newParam.displayName(),
- newParam.getType(),
- newParam.getDescription(),
- oldParam.getType() + " → " + newParam.getType(),
- newParam.getSource(),
- null, null, null));
- }
- }
- for (Map.Entry entry : oldParams.entrySet()) {
- if (!newParams.containsKey(entry.getKey())) {
- unmatchedOld.add(entry.getValue());
- }
- }
-
- pairRenamedBindingParams(unmatchedOld, unmatchedNew, changes);
-
- for (MethodParameterSnapshot removed : unmatchedOld) {
- changes.add(ParameterChange.removed(
- removed.displayName(), removed.getType(), removed.getDescription(),
- removed.getSource(), null, null, null));
- }
- for (MethodParameterSnapshot added : unmatchedNew) {
- changes.add(ParameterChange.added(
- added.displayName(), added.getType(), added.getDescription(),
- added.getSource(), null, null, null));
- }
- return changes;
- }
-
- /** 形参名+类型相同,仅绑定名变化 → 重命名 */
- private void pairRenamedBindingParams(List unmatchedOld,
- List unmatchedNew,
- List changes) {
- Set pairedOld = new HashSet<>();
- Set pairedNew = new HashSet<>();
-
- for (MethodParameterSnapshot oldParam : unmatchedOld) {
- if (pairedOld.contains(oldParam)) {
- continue;
- }
- for (MethodParameterSnapshot newParam : unmatchedNew) {
- if (pairedNew.contains(newParam)) {
- continue;
- }
- if (!oldParam.getSource().equals(newParam.getSource())) {
- continue;
- }
- if (!oldParam.getName().equals(newParam.getName())) {
- continue;
- }
- if (!oldParam.getType().equals(newParam.getType())) {
- continue;
- }
- changes.add(ParameterChange.renamed(
- oldParam.getBindingName(),
- newParam.getBindingName(),
- newParam.getType(),
- newParam.getDescription(),
- newParam.getSource(),
- null, null, null));
- pairedOld.add(oldParam);
- pairedNew.add(newParam);
- break;
- }
- }
- unmatchedOld.removeIf(pairedOld::contains);
- unmatchedNew.removeIf(pairedNew::contains);
- }
-
- private Map filterBindingParams(EndpointSnapshot snap) {
- Map map = new LinkedHashMap<>();
- for (MethodParameterSnapshot p : snap.getParameters()) {
- if (isBindingParam(p)) {
- map.put(p.identityKey(), p);
- }
- }
- return map;
- }
-
- private Map filterBySource(EndpointSnapshot snap, String source) {
- Map map = new LinkedHashMap<>();
- for (MethodParameterSnapshot p : snap.getParameters()) {
- if (source.equals(p.getSource())) {
- map.put(p.identityKey(), p);
- }
- }
- return map;
- }
-
- private boolean isBindingParam(MethodParameterSnapshot param) {
- return "path".equals(param.getSource()) || "query".equals(param.getSource());
- }
-
- private List diffBodyDto(MethodParameterSnapshot oldParam,
- MethodParameterSnapshot newParam) throws IOException {
- List oldFields = nestedDtoFieldParser.parseNestedFieldsAtOldCommit(oldParam.getDtoClassName());
- List newFields = nestedDtoFieldParser.parseNestedFieldsAtNewCommit(newParam.getDtoClassName());
- List fieldChanges = fieldDiffEngine.diff(toFieldInfo(oldFields), toFieldInfo(newFields));
- List result = new ArrayList<>();
- for (FieldChange fc : fieldChanges) {
- result.add(mapFieldChange(fc, newParam.getName(), newParam.getDtoClassName()));
- }
- return result;
- }
-
- private ParameterChange mapFieldChange(FieldChange fc, String bodyParamName, String dtoName) {
- String path = fc.getFieldName();
- switch (fc.getKind()) {
- case ADDED:
- return ParameterChange.added(path, fc.getNewType(), fc.getDescription(),
- "body", bodyParamName, dtoName, path);
- case REMOVED:
- return ParameterChange.removed(path, fc.getOldType(), fc.getDescription(),
- "body", bodyParamName, dtoName, path);
- case RENAMED:
- return ParameterChange.renamed(fc.getOldFieldName(), fc.getFieldName(),
- fc.getNewType(), fc.getDescription(), "body", bodyParamName, dtoName, path);
- case MODIFIED:
- default:
- return ParameterChange.modified(path, fc.getNewType(), fc.getDescription(),
- fc.getDetail(), "body", bodyParamName, dtoName, path);
- }
- }
-
- private List addedBodyChanges(MethodParameterSnapshot param) throws IOException {
- List list = new ArrayList<>();
- for (NestedFieldInfo field : nestedDtoFieldParser.parseNestedFieldsAtNewCommit(param.getDtoClassName())) {
- list.add(ParameterChange.added(field.getPath(), field.getType(), field.getDescription(),
- "body", param.getName(), param.getDtoClassName(), field.getPath()));
- }
- return list;
- }
-
- private List removedBodyChanges(MethodParameterSnapshot param) throws IOException {
- List list = new ArrayList<>();
- for (NestedFieldInfo field : nestedDtoFieldParser.parseNestedFieldsAtOldCommit(param.getDtoClassName())) {
- list.add(ParameterChange.removed(field.getPath(), field.getType(), field.getDescription(),
- "body", param.getName(), param.getDtoClassName(), field.getPath()));
- }
- return list;
- }
-
- private List toFieldInfo(List nested) {
- List result = new ArrayList<>();
- for (NestedFieldInfo info : nested) {
- result.add(new FieldInfo(info.getPath(), info.getType(), info.getDescription()));
- }
- return result;
- }
-}
diff --git a/src/main/java/com/codechecker/api/model/ApiChangeKind.java b/src/main/java/com/codechecker/api/model/ApiChangeKind.java
deleted file mode 100644
index e731ffc..0000000
--- a/src/main/java/com/codechecker/api/model/ApiChangeKind.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.codechecker.api.model;
-
-/**
- * API 变更类型(与类变更 {@link com.codechecker.model.ClassChangeKind} 独立)。
- */
-public enum ApiChangeKind {
- NEW_ENDPOINT,
- REMOVED_ENDPOINT,
- PATH_CHANGED,
- METHOD_CHANGED,
- PARAM_CHANGED
-}
diff --git a/src/main/java/com/codechecker/api/model/EndpointChangeReport.java b/src/main/java/com/codechecker/api/model/EndpointChangeReport.java
deleted file mode 100644
index 3765c28..0000000
--- a/src/main/java/com/codechecker/api/model/EndpointChangeReport.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package com.codechecker.api.model;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 单条 API 变更报告(路径 / 方法 / 参数各自独立,不与其他类型混合)。
- */
-public class EndpointChangeReport {
- private final ApiChangeKind changeKind;
- private final String httpMethod;
- private final String oldHttpMethod;
- private final String uri;
- private final String oldUri;
- private final String sourceFile;
- private final String controllerClass;
- private final String endpointDescription;
- private final boolean dtoFollowUp;
- private final String relatedDtoClassName;
- private final List parameterChanges = new ArrayList<>();
-
- public EndpointChangeReport(ApiChangeKind changeKind, String httpMethod, String oldHttpMethod,
- String uri, String oldUri, String sourceFile, String controllerClass,
- String endpointDescription) {
- this(changeKind, httpMethod, oldHttpMethod, uri, oldUri, sourceFile, controllerClass,
- endpointDescription, false, null);
- }
-
- public EndpointChangeReport(ApiChangeKind changeKind, String httpMethod, String oldHttpMethod,
- String uri, String oldUri, String sourceFile, String controllerClass,
- String endpointDescription, boolean dtoFollowUp, String relatedDtoClassName) {
- this.changeKind = changeKind;
- this.httpMethod = httpMethod;
- this.oldHttpMethod = oldHttpMethod;
- this.uri = uri;
- this.oldUri = oldUri;
- this.sourceFile = sourceFile;
- this.controllerClass = controllerClass;
- this.endpointDescription = endpointDescription == null ? "" : endpointDescription;
- this.dtoFollowUp = dtoFollowUp;
- this.relatedDtoClassName = relatedDtoClassName;
- }
-
- /** 基于已有报告创建 Dto 跟进产生的副本 */
- public static EndpointChangeReport dtoFollowUp(EndpointChangeReport source, String relatedDtoClassName) {
- EndpointChangeReport copy = new EndpointChangeReport(
- source.getChangeKind(),
- source.getHttpMethod(),
- source.getOldHttpMethod(),
- source.getUri(),
- source.getOldUri(),
- source.getSourceFile(),
- source.getControllerClass(),
- source.getEndpointDescription(),
- true,
- relatedDtoClassName);
- source.getParameterChanges().forEach(copy::addParameterChange);
- return copy;
- }
-
- public ApiChangeKind getChangeKind() {
- return changeKind;
- }
-
- public String getHttpMethod() {
- return httpMethod;
- }
-
- public String getOldHttpMethod() {
- return oldHttpMethod;
- }
-
- public String getUri() {
- return uri;
- }
-
- public String getOldUri() {
- return oldUri;
- }
-
- public String getSourceFile() {
- return sourceFile;
- }
-
- public String getControllerClass() {
- return controllerClass;
- }
-
- public String getEndpointDescription() {
- return endpointDescription;
- }
-
- public List getParameterChanges() {
- return parameterChanges;
- }
-
- public void addParameterChange(ParameterChange change) {
- parameterChanges.add(change);
- }
-
- public boolean hasParameterChanges() {
- return !parameterChanges.isEmpty();
- }
-
- public boolean isDtoFollowUp() {
- return dtoFollowUp;
- }
-
- public String getRelatedDtoClassName() {
- return relatedDtoClassName;
- }
-}
diff --git a/src/main/java/com/codechecker/api/model/EndpointSnapshot.java b/src/main/java/com/codechecker/api/model/EndpointSnapshot.java
deleted file mode 100644
index 4087283..0000000
--- a/src/main/java/com/codechecker/api/model/EndpointSnapshot.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.codechecker.api.model;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 单个 HTTP/Feign 接口快照。
- */
-public class EndpointSnapshot {
- private final String fingerprint;
- private final String httpMethod;
- private final String uri;
- private final String sourceFile;
- private final String controllerClass;
- private final String methodName;
- private final String methodDescription;
- private final List parameters;
-
- public EndpointSnapshot(String fingerprint, String httpMethod, String uri, String sourceFile,
- String controllerClass, String methodName, String methodDescription,
- List parameters) {
- this.fingerprint = fingerprint;
- this.httpMethod = httpMethod;
- this.uri = uri;
- this.sourceFile = sourceFile;
- this.controllerClass = controllerClass;
- this.methodName = methodName;
- this.methodDescription = methodDescription == null ? "" : methodDescription;
- this.parameters = parameters == null ? List.of() : new ArrayList<>(parameters);
- }
-
- /** 跨 commit 配对同一 Java 方法;不含参数信息,参数 diff 由 ParameterDiffEngine 负责 */
- public static String buildFingerprint(String sourceFile, String methodName) {
- return sourceFile + "#" + methodName;
- }
-
- public String getFingerprint() {
- return fingerprint;
- }
-
- public String getHttpMethod() {
- return httpMethod;
- }
-
- public String getUri() {
- return uri;
- }
-
- public String getSourceFile() {
- return sourceFile;
- }
-
- public String getControllerClass() {
- return controllerClass;
- }
-
- public String getMethodName() {
- return methodName;
- }
-
- public String getMethodDescription() {
- return methodDescription;
- }
-
- public List getParameters() {
- return parameters;
- }
-
- public String endpointKey() {
- return httpMethod + " " + uri;
- }
-}
diff --git a/src/main/java/com/codechecker/api/model/MethodParameterSnapshot.java b/src/main/java/com/codechecker/api/model/MethodParameterSnapshot.java
deleted file mode 100644
index 718d1a5..0000000
--- a/src/main/java/com/codechecker/api/model/MethodParameterSnapshot.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.codechecker.api.model;
-
-/**
- * 接口方法入参快照。
- */
-public class MethodParameterSnapshot {
- private final String name;
- private final String bindingName;
- private final String type;
- private final String source;
- private final boolean required;
- private final String description;
- private final String dtoClassName;
-
- public MethodParameterSnapshot(String name, String bindingName, String type, String source,
- boolean required, String description, String dtoClassName) {
- this.name = name;
- this.bindingName = bindingName == null || bindingName.isBlank() ? name : bindingName;
- this.type = type;
- this.source = source;
- this.required = required;
- this.description = description;
- this.dtoClassName = dtoClassName;
- }
-
- public String getName() {
- return name;
- }
-
- /** 对外绑定名(@PathVariable / @RequestParam 的 value/name,缺省为形参名) */
- public String getBindingName() {
- return bindingName;
- }
-
- public String getType() {
- return type;
- }
-
- /** body / path / query / simple */
- public String getSource() {
- return source;
- }
-
- public boolean isRequired() {
- return required;
- }
-
- public String getDescription() {
- return description;
- }
-
- public String getDtoClassName() {
- return dtoClassName;
- }
-
- /** path/query 按绑定名匹配,避免仅 Java 形参重命名误报 */
- public String identityKey() {
- if ("path".equals(source) || "query".equals(source)) {
- return source + ":" + bindingName;
- }
- return source + ":" + name;
- }
-
- /** 通知展示名:path/query 展示绑定名 */
- public String displayName() {
- if ("path".equals(source) || "query".equals(source)) {
- return bindingName;
- }
- return name;
- }
-}
diff --git a/src/main/java/com/codechecker/api/model/ParameterChange.java b/src/main/java/com/codechecker/api/model/ParameterChange.java
deleted file mode 100644
index 3e0af5c..0000000
--- a/src/main/java/com/codechecker/api/model/ParameterChange.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package com.codechecker.api.model;
-
-/**
- * API 参数或 RequestBody 嵌套字段变更。
- */
-public class ParameterChange {
- public enum ChangeType {
- ADDED, REMOVED, MODIFIED, RENAMED
- }
-
- private final ChangeType changeType;
- private final String paramName;
- private final String oldName;
- private final String paramType;
- private final String description;
- private final String oldDescription;
- private final String source;
- private final String bodyParamName;
- private final String parentDto;
- private final String fieldPath;
- private final String detail;
-
- private ParameterChange(ChangeType changeType, String paramName, String oldName,
- String paramType, String description, String oldDescription,
- String source, String bodyParamName, String parentDto,
- String fieldPath, String detail) {
- this.changeType = changeType;
- this.paramName = paramName;
- this.oldName = oldName;
- this.paramType = paramType;
- this.description = description;
- this.oldDescription = oldDescription;
- this.source = source;
- this.bodyParamName = bodyParamName;
- this.parentDto = parentDto;
- this.fieldPath = fieldPath;
- this.detail = detail;
- }
-
- public static ParameterChange added(String name, String type, String desc, String source,
- String bodyParam, String dto, String fieldPath) {
- return new ParameterChange(ChangeType.ADDED, name, null, type, desc, null,
- source, bodyParam, dto, fieldPath, null);
- }
-
- public static ParameterChange removed(String name, String type, String desc, String source,
- String bodyParam, String dto, String fieldPath) {
- return new ParameterChange(ChangeType.REMOVED, name, null, type, desc, null,
- source, bodyParam, dto, fieldPath, null);
- }
-
- public static ParameterChange modified(String name, String type, String desc,
- String detail, String source, String bodyParam,
- String dto, String fieldPath) {
- return new ParameterChange(ChangeType.MODIFIED, name, null, type, desc, null,
- source, bodyParam, dto, fieldPath, detail);
- }
-
- public static ParameterChange renamed(String oldName, String newName, String type, String desc,
- String source, String bodyParam, String dto, String fieldPath) {
- return new ParameterChange(ChangeType.RENAMED, newName, oldName, type, desc, null,
- source, bodyParam, dto, fieldPath, null);
- }
-
- public ChangeType getChangeType() {
- return changeType;
- }
-
- public String getParamName() {
- return paramName;
- }
-
- public String getOldName() {
- return oldName;
- }
-
- public String getParamType() {
- return paramType;
- }
-
- public String getDescription() {
- return description;
- }
-
- public String getSource() {
- return source;
- }
-
- public String getBodyParamName() {
- return bodyParamName;
- }
-
- public String getParentDto() {
- return parentDto;
- }
-
- public String getFieldPath() {
- return fieldPath;
- }
-
- public String getDetail() {
- return detail;
- }
-
- public boolean isBodyField() {
- return "body".equals(source);
- }
-
- public String displayName() {
- return fieldPath == null || fieldPath.isBlank() ? paramName : fieldPath;
- }
-}
diff --git a/src/main/java/com/codechecker/api/notify/ApiChangeNotifier.java b/src/main/java/com/codechecker/api/notify/ApiChangeNotifier.java
deleted file mode 100644
index f620d05..0000000
--- a/src/main/java/com/codechecker/api/notify/ApiChangeNotifier.java
+++ /dev/null
@@ -1,272 +0,0 @@
-package com.codechecker.api.notify;
-
-import com.codechecker.api.model.ApiChangeKind;
-import com.codechecker.api.model.EndpointChangeReport;
-import com.codechecker.api.model.ParameterChange;
-import com.codechecker.common.MarkdownStyles;
-import com.codechecker.common.WeComMarkdownSender;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * API 变更通知(路径 / 请求方式 / 参数分类型、分条发送,与类变更通知解耦)。
- */
-public class ApiChangeNotifier {
- private final WeComMarkdownSender sender = new WeComMarkdownSender();
-
- public int sendAll(String webhookUrl, List reports,
- String modifier, String modifyTime, boolean wecomEnabled) {
- if (reports == null || reports.isEmpty()) {
- System.out.println("无 API 变更,不发送通知");
- return 0;
- }
- int sent = 0;
- for (EndpointChangeReport report : reports) {
- String markdown = buildMarkdown(report, modifier, modifyTime);
- if (wecomEnabled) {
- if (sender.send(webhookUrl, markdown)) {
- sent++;
- System.out.println("已发送 API 变更通知: " + report.getChangeKind()
- + " " + report.getHttpMethod() + " " + report.getUri());
- }
- } else {
- sender.logPreview("API 变更 [" + report.getChangeKind() + "]", markdown);
- sent++;
- }
- }
- if (sent > 0) {
- System.out.println("总共发送 " + sent + " 条 API 变更通知");
- }
- return sent;
- }
-
- public String buildMarkdown(EndpointChangeReport report, String modifier, String modifyTime) {
- ApiChangeKind kind = report.getChangeKind();
- if (kind == ApiChangeKind.PATH_CHANGED
- || kind == ApiChangeKind.NEW_ENDPOINT
- || kind == ApiChangeKind.REMOVED_ENDPOINT) {
- return buildPathMarkdown(report, modifier, modifyTime);
- }
- if (kind == ApiChangeKind.METHOD_CHANGED) {
- return buildMethodMarkdown(report, modifier, modifyTime);
- }
- return buildParamMarkdown(report, modifier, modifyTime);
- }
-
- private String buildPathMarkdown(EndpointChangeReport report, String modifier, String modifyTime) {
- String changeLabel;
- switch (report.getChangeKind()) {
- case NEW_ENDPOINT:
- changeLabel = "新增接口";
- break;
- case REMOVED_ENDPOINT:
- changeLabel = "删除接口";
- break;
- default:
- changeLabel = "修改路径";
- break;
- }
- StringBuilder sb = new StringBuilder();
- sb.append("# 【API路径变更通知】").append("\n\n");
- sb.append(MarkdownStyles.quoteKvBold("变更类型", MarkdownStyles.colorWarning(changeLabel))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("路径",
- MarkdownStyles.colorInfo(MarkdownStyles.safe(report.getSourceFile())))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("修改人", MarkdownStyles.colorComment(modifier))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("时间", MarkdownStyles.colorComment(modifyTime))).append("\n");
- sb.append("\n## 【URI变更详情】").append("\n\n");
- sb.append(MarkdownStyles.quoteKvBold("接口说明", formatEndpointDescription(report))).append("\n");
- appendPathUriLines(sb, report, changeLabel);
- return sb.toString();
- }
-
- private void appendPathUriLines(StringBuilder sb, EndpointChangeReport report, String changeLabel) {
- if ("新增接口".equals(changeLabel)) {
- sb.append(MarkdownStyles.quoteKvBold("原路径", "`-`")).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("新路径",
- formatUriWithMethod(report.getHttpMethod(), report.getUri(), true)
- + " " + MarkdownStyles.colorInfo("[新增]"))).append("\n");
- } else if ("删除接口".equals(changeLabel)) {
- sb.append(MarkdownStyles.quoteKvBold("原路径",
- formatUriWithMethod(report.getHttpMethod(), report.getUri(), false)
- + " " + MarkdownStyles.colorWarning("[已删除]"))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("新路径", "`已删除`")).append("\n");
- } else {
- sb.append(MarkdownStyles.quoteKvBold("原路径",
- formatUriWithMethod(report.getHttpMethod(), report.getOldUri(), false)
- + " " + MarkdownStyles.colorWarning("[旧路径]"))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("新路径",
- formatUriWithMethod(report.getHttpMethod(), report.getUri(), true)
- + " " + MarkdownStyles.colorInfo("[新路径]"))).append("\n");
- }
- }
-
- private String buildMethodMarkdown(EndpointChangeReport report, String modifier, String modifyTime) {
- StringBuilder sb = new StringBuilder();
- sb.append("# 【API请求方式变更通知】").append("\n\n");
- sb.append(MarkdownStyles.quoteKvBold("变更类型", MarkdownStyles.colorWarning("修改请求方式"))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("路径",
- MarkdownStyles.colorInfo(MarkdownStyles.safe(report.getSourceFile())))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("修改人", MarkdownStyles.colorComment(modifier))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("时间", MarkdownStyles.colorComment(modifyTime))).append("\n");
- sb.append("\n## 【请求方式变更详情】").append("\n\n");
- sb.append(MarkdownStyles.quoteKvBold("接口说明", formatEndpointDescription(report))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("URI", MarkdownStyles.colorInfo(report.getUri()))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("原请求方式",
- MarkdownStyles.colorWarning(report.getOldHttpMethod()))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("新请求方式",
- MarkdownStyles.colorInfo(report.getHttpMethod()) + " "
- + MarkdownStyles.colorInfo("[请求方式已变更]"))).append("\n");
- return sb.toString();
- }
-
- private String buildParamMarkdown(EndpointChangeReport report, String modifier, String modifyTime) {
- StringBuilder sb = new StringBuilder();
- sb.append("# 【API参数变更通知】").append("\n\n");
- sb.append(MarkdownStyles.quoteKvBold("修改人", MarkdownStyles.colorComment(modifier))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("时间", MarkdownStyles.colorComment(modifyTime))).append("\n");
- //sb.append(MarkdownStyles.quoteKvBold("变更类型", MarkdownStyles.colorWarning("修改参数"))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("URI",
- MarkdownStyles.colorInfo(report.getHttpMethod()) + " "
- + MarkdownStyles.inlineCode(report.getUri()))).append("\n");
- sb.append(MarkdownStyles.quoteKvBold("路径",
- MarkdownStyles.colorInfo(MarkdownStyles.safe(report.getSourceFile())))).append("\n");
- sb.append("\n## 【接口参数变动详情】").append("\n\n");
- appendParameterDetails(sb, report);
- return sb.toString();
- }
-
- private void appendParameterDetails(StringBuilder sb, EndpointChangeReport report) {
- List bodyChanges = new ArrayList<>();
- List regularChanges = new ArrayList<>();
- for (ParameterChange change : report.getParameterChanges()) {
- if (change.isBodyField()) {
- bodyChanges.add(change);
- } else {
- regularChanges.add(change);
- }
- }
- if (!bodyChanges.isEmpty()) {
- sb.append("**类对象变更(含嵌套对象字段)**").append("\n\n");
- appendBodyGroups(sb, bodyChanges);
- sb.append("\n");
- }
- if (!regularChanges.isEmpty()) {
- sb.append("**普通参数变更**").append("\n\n");
- sb.append(MarkdownStyles.quoteLine("**共 "
- + MarkdownStyles.colorWarning(String.valueOf(regularChanges.size()))
- + " 项变更**")).append("\n\n");
- for (ParameterChange change : regularChanges) {
- sb.append(formatParameterLine(change)).append("\n\n");
- }
- }
- if (bodyChanges.isEmpty() && regularChanges.isEmpty()) {
- sb.append(MarkdownStyles.quoteLine(MarkdownStyles.colorComment("无"))).append("\n");
- }
- }
-
- private void appendBodyGroups(StringBuilder sb, List bodyChanges) {
- Map> groups = new LinkedHashMap<>();
- for (ParameterChange change : bodyChanges) {
- String key = change.getParentDto() == null || change.getParentDto().isBlank()
- ? (change.getBodyParamName() == null ? "body" : change.getBodyParamName())
- : change.getParentDto();
- groups.computeIfAbsent(key, k -> new ArrayList<>()).add(change);
- }
- int total = bodyChanges.size();
- sb.append(MarkdownStyles.quoteLine("**共 "
- + MarkdownStyles.colorWarning(String.valueOf(groups.size()))
- + " 个类对象 · "
- + MarkdownStyles.colorWarning(String.valueOf(total))
- + " 项变更**")).append("\n\n");
- for (List group : groups.values()) {
- ParameterChange first = group.get(0);
- if (first.getParentDto() != null && !first.getParentDto().isBlank()) {
- sb.append("**").append(MarkdownStyles.inlineCode(first.getParentDto())).append("**");
- } else if (first.getBodyParamName() != null && !first.getBodyParamName().isBlank()) {
- sb.append("**").append(MarkdownStyles.inlineCode(first.getBodyParamName())).append("**");
- }
- sb.append("\n\n");
- for (ParameterChange change : group) {
- sb.append(formatParameterLine(change)).append("\n\n");
- }
- }
- }
-
- private String formatParameterLine(ParameterChange change) {
- String tag;
- switch (change.getChangeType()) {
- case ADDED:
- tag = MarkdownStyles.colorInfo("[新增]");
- break;
- case REMOVED:
- tag = MarkdownStyles.colorWarning("[删除]");
- break;
- case RENAMED:
- tag = MarkdownStyles.colorWarning("[重命名]");
- break;
- case MODIFIED:
- tag = MarkdownStyles.colorWarning("[类型变更]");
- break;
- default:
- tag = MarkdownStyles.colorWarning("[修改]");
- break;
- }
- String name = MarkdownStyles.inlineCode(MarkdownStyles.safe(change.displayName()));
- String desc = change.getDescription() == null || change.getDescription().isBlank()
- ? MarkdownStyles.colorComment("(无说明)")
- : MarkdownStyles.colorComment(change.getDescription());
- StringBuilder line = new StringBuilder();
- if (change.getChangeType() == ParameterChange.ChangeType.RENAMED) {
- line.append(tag).append(" ")
- .append(MarkdownStyles.colorComment(MarkdownStyles.safe(change.getOldName()))).append(" → ")
- .append(MarkdownStyles.colorInfo(MarkdownStyles.safe(change.getParamName())))
- .append(" 说明: ").append(desc);
- } else {
- line.append(tag).append(" ").append(name).append(" 说明: ").append(desc);
- }
- appendParameterType(line, change);
- return MarkdownStyles.quoteLine(line.toString());
- }
-
- private void appendParameterType(StringBuilder line, ParameterChange change) {
- String typePart = resolveTypePart(change);
- if (!typePart.isBlank()) {
- line.append(" 类型: ").append(typePart);
- }
- }
-
- private String formatUriWithMethod(String httpMethod, String uri, boolean isNew) {
- String path = MarkdownStyles.inlineCode(MarkdownStyles.safe(uri));
- if (httpMethod == null || httpMethod.isBlank()) {
- return path;
- }
- String methodPart = isNew
- ? MarkdownStyles.colorInfo(httpMethod.toUpperCase())
- : MarkdownStyles.colorWarning(httpMethod.toUpperCase());
- return methodPart + " " + path;
- }
-
- private String formatEndpointDescription(EndpointChangeReport report) {
- String desc = report.getEndpointDescription();
- if (desc == null || desc.isBlank()) {
- return MarkdownStyles.colorComment("(无说明)");
- }
- return MarkdownStyles.colorComment(MarkdownStyles.safe(desc));
- }
-
- private String resolveTypePart(ParameterChange change) {
- if (change.getChangeType() == ParameterChange.ChangeType.MODIFIED
- && change.getDetail() != null && !change.getDetail().isBlank()) {
- return MarkdownStyles.formatTypeChange(change.getDetail());
- }
- if (change.getParamType() != null && !change.getParamType().isBlank()) {
- boolean isNew = change.getChangeType() == ParameterChange.ChangeType.ADDED
- || change.getChangeType() == ParameterChange.ChangeType.RENAMED;
- return MarkdownStyles.formatSingleType(change.getParamType(), isNew);
- }
- return "";
- }
-}
diff --git a/src/main/java/com/codechecker/api/parser/EndpointSnapshotParser.java b/src/main/java/com/codechecker/api/parser/EndpointSnapshotParser.java
deleted file mode 100644
index 7a92204..0000000
--- a/src/main/java/com/codechecker/api/parser/EndpointSnapshotParser.java
+++ /dev/null
@@ -1,313 +0,0 @@
-package com.codechecker.api.parser;
-
-import com.codechecker.api.model.EndpointSnapshot;
-import com.codechecker.api.model.MethodParameterSnapshot;
-import com.codechecker.parser.TypeNameUtils;
-import com.github.javaparser.StaticJavaParser;
-import com.github.javaparser.ast.CompilationUnit;
-import com.github.javaparser.ast.NodeList;
-import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
-import com.github.javaparser.ast.body.MethodDeclaration;
-import com.github.javaparser.ast.body.Parameter;
-import com.github.javaparser.ast.body.TypeDeclaration;
-import com.github.javaparser.ast.expr.AnnotationExpr;
-import com.github.javaparser.ast.expr.Expression;
-import com.github.javaparser.ast.expr.NormalAnnotationExpr;
-import com.github.javaparser.ast.type.Type;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * 解析 Controller / Feign 接口完整快照(含入参明细)。
- */
-public class EndpointSnapshotParser {
- private static final Set MAPPING_ANNOTATIONS = Set.of(
- "GetMapping", "PostMapping", "PutMapping", "DeleteMapping", "PatchMapping", "RequestMapping"
- );
- private static final Map MAPPING_DEFAULT_METHOD = Map.of(
- "GetMapping", "GET",
- "PostMapping", "POST",
- "PutMapping", "PUT",
- "DeleteMapping", "DELETE",
- "PatchMapping", "PATCH"
- );
- private static final Set FRAMEWORK_PARAM_TYPES = Set.of(
- "HttpServletRequest", "HttpServletResponse", "BindingResult", "Principal",
- "Authentication", "Model", "ModelMap", "UriComponentsBuilder", "WebRequest",
- "NativeWebRequest", "Errors", "Locale"
- );
-
- private final boolean excludeFrameworkParams;
-
- public EndpointSnapshotParser(boolean excludeFrameworkParams) {
- this.excludeFrameworkParams = excludeFrameworkParams;
- }
-
- public List parseSource(String source, String sourceFile, boolean feignMode) {
- if (source == null || source.isBlank()) {
- return List.of();
- }
- CompilationUnit cu = StaticJavaParser.parse(source);
- List snapshots = new ArrayList<>();
- for (TypeDeclaration> type : cu.getTypes()) {
- if (!(type instanceof ClassOrInterfaceDeclaration)) {
- continue;
- }
- ClassOrInterfaceDeclaration decl = (ClassOrInterfaceDeclaration) type;
- if (feignMode && !isFeignClient(decl)) {
- continue;
- }
- if (!feignMode && !isController(decl)) {
- continue;
- }
- String basePath = feignMode
- ? joinPaths(extractFeignBasePath(decl), extractTypeLevelPath(decl))
- : extractTypeLevelPath(decl);
- String className = decl.getNameAsString();
- for (MethodDeclaration method : decl.getMethods()) {
- if (feignMode && !decl.isInterface()) {
- continue;
- }
- if (!feignMode && decl.isInterface()) {
- continue;
- }
- snapshots.addAll(parseMethod(method, basePath, sourceFile, className));
- }
- }
- return snapshots;
- }
-
- private List parseMethod(MethodDeclaration method, String basePath,
- String sourceFile, String className) {
- List result = new ArrayList<>();
- for (AnnotationExpr annotation : method.getAnnotations()) {
- String annName = annotation.getNameAsString();
- if (!MAPPING_ANNOTATIONS.contains(annName)) {
- continue;
- }
- List subPaths = readStringArray(annotation, "value", "path");
- List httpMethods = extractHttpMethods(annotation, annName);
- List params = extractParameters(method);
- String methodDescription = MethodDescriptionExtractor.extract(method);
- String fingerprint = EndpointSnapshot.buildFingerprint(sourceFile, method.getNameAsString());
- for (String httpMethod : httpMethods) {
- for (String subPath : subPaths) {
- String uri = joinPaths(basePath, subPath);
- result.add(new EndpointSnapshot(fingerprint, httpMethod, uri, sourceFile,
- className, method.getNameAsString(), methodDescription, params));
- }
- }
- }
- return result;
- }
-
- private List extractParameters(MethodDeclaration method) {
- Map paramDescriptions = MethodParamJavadocExtractor.extract(method);
- List params = new ArrayList<>();
- for (Parameter parameter : method.getParameters()) {
- String typeName = TypeNameUtils.typeToString(parameter.getType());
- String simple = TypeNameUtils.simpleName(typeName);
- if (excludeFrameworkParams && FRAMEWORK_PARAM_TYPES.contains(simple)) {
- continue;
- }
- String source = resolveParamSource(parameter);
- String paramName = parameter.getNameAsString();
- String bindingName = resolveBindingName(parameter, source, paramName);
- boolean required = resolveRequired(parameter, source);
- String dtoName = "body".equals(source) ? simple : "";
- String description = paramDescriptions.getOrDefault(paramName, "");
- params.add(new MethodParameterSnapshot(
- paramName,
- bindingName,
- typeName,
- source,
- required,
- description,
- dtoName
- ));
- }
- return params;
- }
-
- private String resolveBindingName(Parameter parameter, String source, String paramName) {
- if (!"path".equals(source) && !"query".equals(source)) {
- return paramName;
- }
- String annName = "path".equals(source) ? "PathVariable" : "RequestParam";
- for (AnnotationExpr ann : parameter.getAnnotations()) {
- if (!annName.equals(ann.getNameAsString())) {
- continue;
- }
- List bindings = readStringArray(ann, "value", "name");
- for (String binding : bindings) {
- if (binding != null && !binding.isBlank()) {
- return binding;
- }
- }
- }
- return paramName;
- }
-
- private String resolveParamSource(Parameter parameter) {
- for (AnnotationExpr ann : parameter.getAnnotations()) {
- String name = ann.getNameAsString();
- if ("RequestBody".equals(name)) {
- return "body";
- }
- if ("PathVariable".equals(name)) {
- return "path";
- }
- if ("RequestParam".equals(name)) {
- return "query";
- }
- }
- return "simple";
- }
-
- private boolean resolveRequired(Parameter parameter, String source) {
- if ("query".equals(source)) {
- for (AnnotationExpr ann : parameter.getAnnotations()) {
- if ("RequestParam".equals(ann.getNameAsString()) && ann.isNormalAnnotationExpr()) {
- for (var pair : ann.asNormalAnnotationExpr().getPairs()) {
- if ("required".equals(pair.getNameAsString())) {
- return !"false".equalsIgnoreCase(pair.getValue().toString().trim());
- }
- }
- }
- }
- }
- return !"query".equals(source);
- }
-
- private boolean isController(ClassOrInterfaceDeclaration decl) {
- return decl.getAnnotations().stream()
- .anyMatch(ann -> {
- String n = ann.getNameAsString();
- return "RestController".equals(n) || "Controller".equals(n);
- });
- }
-
- private boolean isFeignClient(ClassOrInterfaceDeclaration decl) {
- return decl.isInterface() && decl.getAnnotations().stream()
- .anyMatch(ann -> "FeignClient".equals(ann.getNameAsString()));
- }
-
- private String extractTypeLevelPath(ClassOrInterfaceDeclaration decl) {
- for (AnnotationExpr annotation : decl.getAnnotations()) {
- if ("RequestMapping".equals(annotation.getNameAsString())) {
- List paths = readStringArray(annotation, "value", "path");
- if (!paths.isEmpty()) {
- return paths.get(0);
- }
- }
- }
- return "";
- }
-
- private String extractFeignBasePath(ClassOrInterfaceDeclaration decl) {
- for (AnnotationExpr annotation : decl.getAnnotations()) {
- if ("FeignClient".equals(annotation.getNameAsString())) {
- List paths = readStringArray(annotation, "path");
- if (!paths.isEmpty()) {
- return paths.get(0);
- }
- }
- }
- return "";
- }
-
- private List extractHttpMethods(AnnotationExpr annotation, String annName) {
- if (!"RequestMapping".equals(annName)) {
- return List.of(MAPPING_DEFAULT_METHOD.getOrDefault(annName, "GET"));
- }
- List methods = readEnumArray(annotation, "method");
- return methods.isEmpty() ? List.of("GET") : methods;
- }
-
- private String joinPaths(String base, String sub) {
- String normalizedBase = normalizePath(base);
- String normalizedSub = normalizePath(sub);
- if (normalizedBase.isEmpty()) {
- return normalizedSub.isEmpty() ? "/" : normalizedSub;
- }
- if (normalizedSub.isEmpty()) {
- return normalizedBase;
- }
- return (normalizedBase + "/" + normalizedSub.substring(1)).replaceAll("/+", "/");
- }
-
- private String normalizePath(String path) {
- if (path == null || path.isBlank()) {
- return "";
- }
- String trimmed = path.trim();
- if (!trimmed.startsWith("/")) {
- trimmed = "/" + trimmed;
- }
- return trimmed.replaceAll("/+", "/");
- }
-
- private List readStringArray(AnnotationExpr annotation, String... keys) {
- NodeList> values = readArrayValues(annotation, keys);
- List result = new ArrayList<>();
- for (Object value : values) {
- String text = value.toString().replace("\"", "").trim();
- if (!text.isBlank()) {
- result.add(text);
- }
- }
- if (result.isEmpty()) {
- result.add("");
- }
- return result;
- }
-
- private List readEnumArray(AnnotationExpr annotation, String key) {
- NodeList> values = readArrayValues(annotation, key);
- List result = new ArrayList<>();
- for (Object value : values) {
- String text = value.toString().trim();
- if (text.contains(".")) {
- text = text.substring(text.lastIndexOf('.') + 1);
- }
- result.add(text.toUpperCase(Locale.ROOT));
- }
- return result;
- }
-
- private NodeList> readArrayValues(AnnotationExpr annotation, String... keys) {
- if (annotation.isSingleMemberAnnotationExpr()) {
- Expression value = annotation.asSingleMemberAnnotationExpr().getMemberValue();
- if (value.isArrayInitializerExpr()) {
- return value.asArrayInitializerExpr().getValues();
- }
- return new NodeList<>(value);
- }
- if (annotation.isNormalAnnotationExpr()) {
- var pairs = annotation.asNormalAnnotationExpr().getPairs();
- for (var pair : pairs) {
- for (String key : keys) {
- if (pair.getNameAsString().equals(key)) {
- if (pair.getValue().isArrayInitializerExpr()) {
- return pair.getValue().asArrayInitializerExpr().getValues();
- }
- return new NodeList<>(pair.getValue());
- }
- }
- }
- for (var pair : pairs) {
- if ("value".equals(pair.getNameAsString())) {
- if (pair.getValue().isArrayInitializerExpr()) {
- return pair.getValue().asArrayInitializerExpr().getValues();
- }
- return new NodeList<>(pair.getValue());
- }
- }
- }
- return new NodeList<>();
- }
-}
diff --git a/src/main/java/com/codechecker/api/parser/JavaSourceLocator.java b/src/main/java/com/codechecker/api/parser/JavaSourceLocator.java
deleted file mode 100644
index 80cf6bc..0000000
--- a/src/main/java/com/codechecker/api/parser/JavaSourceLocator.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.codechecker.api.parser;
-
-import com.codechecker.git.GitChangeScanner;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-/**
- * 按简单类名在仓库中定位 .java 源文件。
- */
-public class JavaSourceLocator {
- private final Path repoRoot;
- private final List searchDirs;
-
- public JavaSourceLocator(Path repoRoot, List searchDirs) {
- this.repoRoot = repoRoot;
- this.searchDirs = searchDirs;
- }
-
- public Optional readSourceBySimpleName(String simpleClassName) throws IOException {
- Optional path = findFile(simpleClassName);
- if (path.isEmpty()) {
- return Optional.empty();
- }
- return Optional.of(Files.readString(path.get()));
- }
-
- public Optional readSourceAtCommit(GitChangeScanner gitScanner, String sha,
- String simpleClassName) throws IOException {
- Optional relativePath = findRelativePath(simpleClassName);
- if (relativePath.isEmpty()) {
- return Optional.empty();
- }
- String source = gitScanner.readFileAtCommit(sha, relativePath.get());
- if (source == null || source.isBlank()) {
- return Optional.empty();
- }
- return Optional.of(source);
- }
-
- public Optional findRelativePath(String simpleClassName) throws IOException {
- Optional path = findFile(simpleClassName);
- return path.map(p -> repoRoot.relativize(p).toString().replace('\\', '/'));
- }
-
- public Optional findFile(String simpleClassName) throws IOException {
- String fileName = simpleClassName + ".java";
- for (String dir : searchDirs) {
- Path root = repoRoot.resolve(dir.replace('\\', '/'));
- if (!Files.exists(root)) {
- continue;
- }
- try (Stream walk = Files.walk(root)) {
- Optional found = walk
- .filter(p -> p.getFileName().toString().equals(fileName))
- .findFirst();
- if (found.isPresent()) {
- return found;
- }
- }
- }
- return Optional.empty();
- }
-}
diff --git a/src/main/java/com/codechecker/api/parser/MethodDescriptionExtractor.java b/src/main/java/com/codechecker/api/parser/MethodDescriptionExtractor.java
deleted file mode 100644
index bbea177..0000000
--- a/src/main/java/com/codechecker/api/parser/MethodDescriptionExtractor.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.codechecker.api.parser;
-
-import com.github.javaparser.ast.body.MethodDeclaration;
-import com.github.javaparser.ast.comments.JavadocComment;
-import com.github.javaparser.ast.expr.AnnotationExpr;
-import com.github.javaparser.ast.expr.Expression;
-import com.github.javaparser.ast.expr.NormalAnnotationExpr;
-import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
-
-import java.util.Optional;
-
-/**
- * 提取接口方法中文说明:@Operation(summary) > @Operation(description) > Javadoc 首段。
- */
-public final class MethodDescriptionExtractor {
- private MethodDescriptionExtractor() {
- }
-
- public static String extract(MethodDeclaration method) {
- if (method == null) {
- return "";
- }
- for (AnnotationExpr annotation : method.getAnnotations()) {
- if (!"Operation".equals(annotation.getNameAsString())) {
- continue;
- }
- String summary = readAnnotationStringValue(annotation, "summary");
- if (!summary.isEmpty()) {
- return summary;
- }
- String description = readAnnotationStringValue(annotation, "description");
- if (!description.isEmpty()) {
- return description;
- }
- }
- return extractMethodJavadoc(method);
- }
-
- private static String extractMethodJavadoc(MethodDeclaration method) {
- Optional javadoc = method.getJavadocComment();
- if (javadoc.isEmpty()) {
- return "";
- }
- String text = javadoc.get().parse().getDescription().toText();
- return text == null ? "" : text.trim().replaceAll("\\s+", " ");
- }
-
- private static String readAnnotationStringValue(AnnotationExpr annotation, String attributeName) {
- if (annotation.isNormalAnnotationExpr()) {
- NormalAnnotationExpr normal = annotation.asNormalAnnotationExpr();
- for (var pair : normal.getPairs()) {
- if (pair.getNameAsString().equals(attributeName)) {
- return literalString(pair.getValue());
- }
- }
- return "";
- }
- if (annotation.isSingleMemberAnnotationExpr()) {
- SingleMemberAnnotationExpr single = annotation.asSingleMemberAnnotationExpr();
- if ("value".equals(attributeName) || "description".equals(attributeName)
- || "summary".equals(attributeName)) {
- return literalString(single.getMemberValue());
- }
- }
- return "";
- }
-
- private static String literalString(Expression expression) {
- if (expression.isStringLiteralExpr()) {
- return expression.asStringLiteralExpr().getValue().trim();
- }
- return "";
- }
-}
diff --git a/src/main/java/com/codechecker/api/parser/MethodParamJavadocExtractor.java b/src/main/java/com/codechecker/api/parser/MethodParamJavadocExtractor.java
deleted file mode 100644
index a3dbcd7..0000000
--- a/src/main/java/com/codechecker/api/parser/MethodParamJavadocExtractor.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.codechecker.api.parser;
-
-import com.github.javaparser.ast.body.MethodDeclaration;
-import com.github.javaparser.ast.comments.JavadocComment;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * 从方法 Javadoc 的 @param 标签提取形参说明(按 Java 形参名匹配)。
- */
-public final class MethodParamJavadocExtractor {
- private static final Pattern PARAM_TAG = Pattern.compile(
- "@param\\s+(\\w+)\\s+(.+?)(?=\\s*@param\\s+|\\s*@return\\s+|\\s*@throws\\s+|\\s*@see\\s+|\\s*\\*/|$)",
- Pattern.DOTALL);
-
- private MethodParamJavadocExtractor() {
- }
-
- public static Map extract(MethodDeclaration method) {
- Map descriptions = new HashMap<>();
- if (method == null) {
- return descriptions;
- }
- Optional javadoc = method.getJavadocComment();
- if (javadoc.isEmpty()) {
- return descriptions;
- }
- String raw = javadoc.get().getContent();
- if (raw == null || raw.isBlank()) {
- return descriptions;
- }
- String normalized = raw.replace('\r', '\n');
- Matcher matcher = PARAM_TAG.matcher(normalized);
- while (matcher.find()) {
- String paramName = matcher.group(1).trim();
- String desc = cleanDescription(matcher.group(2));
- if (!paramName.isBlank()) {
- descriptions.put(paramName, desc);
- }
- }
- return descriptions;
- }
-
- private static String cleanDescription(String text) {
- if (text == null) {
- return "";
- }
- StringBuilder sb = new StringBuilder();
- for (String line : text.split("\n")) {
- String trimmed = line.trim();
- if (trimmed.startsWith("*")) {
- trimmed = trimmed.substring(1).trim();
- }
- if (!trimmed.isEmpty()) {
- if (sb.length() > 0) {
- sb.append(' ');
- }
- sb.append(trimmed);
- }
- }
- return sb.toString().trim();
- }
-}
diff --git a/src/main/java/com/codechecker/api/parser/NestedDtoFieldParser.java b/src/main/java/com/codechecker/api/parser/NestedDtoFieldParser.java
deleted file mode 100644
index d44603f..0000000
--- a/src/main/java/com/codechecker/api/parser/NestedDtoFieldParser.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.codechecker.api.parser;
-
-import com.codechecker.git.GitChangeScanner;
-import com.codechecker.model.FieldInfo;
-import com.codechecker.parser.ClassFieldParser;
-import com.codechecker.parser.TypeNameUtils;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-/**
- * 递归展开 Dto/Vo 嵌套字段(dot path),与类变更字段解析解耦但复用 ClassFieldParser。
- */
-public class NestedDtoFieldParser {
- private static final Set LEAF_TYPES = Set.of(
- "String", "Integer", "int", "Long", "long", "Boolean", "boolean", "Double", "double",
- "Float", "float", "Short", "short", "Byte", "byte", "Character", "char",
- "BigDecimal", "BigInteger", "Date", "LocalDate", "LocalDateTime", "LocalTime",
- "Instant", "Timestamp", "Object", "Void", "void"
- );
-
- private final ClassFieldParser classFieldParser = new ClassFieldParser();
- private final JavaSourceLocator sourceLocator;
- private final GitChangeScanner gitScanner;
- private final String oldSha;
- private final String newSha;
- private final int maxDepth;
-
- public NestedDtoFieldParser(Path repoRoot, List searchDirs,
- GitChangeScanner gitScanner, String oldSha, String newSha, int maxDepth) {
- this.sourceLocator = new JavaSourceLocator(repoRoot, searchDirs);
- this.gitScanner = gitScanner;
- this.oldSha = oldSha;
- this.newSha = newSha;
- this.maxDepth = maxDepth;
- }
-
- public List parseNestedFieldsAtOldCommit(String dtoClassName) throws IOException {
- return parseNestedFields(dtoClassName, oldSha);
- }
-
- public List parseNestedFieldsAtNewCommit(String dtoClassName) throws IOException {
- return parseNestedFields(dtoClassName, newSha);
- }
-
- private List parseNestedFields(String dtoClassName, String sha) throws IOException {
- Set visiting = new HashSet<>();
- List result = new ArrayList<>();
- collectFields(dtoClassName, "", visiting, result, sha, 1);
- return result;
- }
-
- private void collectFields(String className, String prefix, Set visiting,
- List out, String sha, int depth) throws IOException {
- if (className == null || className.isBlank() || visiting.contains(className) || depth > maxDepth) {
- return;
- }
- visiting.add(className);
- Optional source = readSource(className, sha);
- if (source.isEmpty()) {
- visiting.remove(className);
- return;
- }
- List fields = classFieldParser.parseFields(source.get(), className);
- for (FieldInfo field : fields) {
- String path = prefix.isBlank() ? field.getName() : prefix + "." + field.getName();
- Set nestedTypes = TypeNameUtils.peelDirectTypeNames(field.getType());
- boolean expanded = false;
- for (String nestedType : nestedTypes) {
- if (isLeafType(nestedType) || nestedType.equals(className)) {
- continue;
- }
- expanded = true;
- // 嵌套字段路径用类型简单类名(如 UserSelfDto.nickName),不用成员名(userDtos.nickName)
- collectFields(nestedType, nestedType, visiting, out, sha, depth + 1);
- }
- if (!expanded) {
- out.add(new NestedFieldInfo(path, field.getType(), field.getDescription()));
- }
- }
- visiting.remove(className);
- }
-
- private Optional readSource(String className, String sha) throws IOException {
- if (sha != null && gitScanner != null) {
- return sourceLocator.readSourceAtCommit(gitScanner, sha, className);
- }
- return sourceLocator.readSourceBySimpleName(className);
- }
-
- private boolean isLeafType(String simpleType) {
- return LEAF_TYPES.contains(simpleType) || simpleType.endsWith("[]");
- }
-}
diff --git a/src/main/java/com/codechecker/api/parser/NestedFieldInfo.java b/src/main/java/com/codechecker/api/parser/NestedFieldInfo.java
deleted file mode 100644
index 870817e..0000000
--- a/src/main/java/com/codechecker/api/parser/NestedFieldInfo.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.codechecker.api.parser;
-
-/**
- * DTO 嵌套字段扁平化条目(dot path)。
- */
-public class NestedFieldInfo {
- private final String path;
- private final String type;
- private final String description;
-
- public NestedFieldInfo(String path, String type, String description) {
- this.path = path;
- this.type = type;
- this.description = description;
- }
-
- public String getPath() {
- return path;
- }
-
- public String getType() {
- return type;
- }
-
- public String getDescription() {
- return description;
- }
-}
diff --git a/src/main/java/com/codechecker/api/scanner/ApiFileChangeScanner.java b/src/main/java/com/codechecker/api/scanner/ApiFileChangeScanner.java
deleted file mode 100644
index 40b3686..0000000
--- a/src/main/java/com/codechecker/api/scanner/ApiFileChangeScanner.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.codechecker.api.scanner;
-
-import com.codechecker.git.GitChangeScanner;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * 扫描 API 相关 Java 文件变更(Controller / Feign),与类变更扫描解耦。
- */
-public class ApiFileChangeScanner {
- private final GitChangeScanner gitScanner;
-
- public ApiFileChangeScanner(GitChangeScanner gitScanner) {
- this.gitScanner = gitScanner;
- }
-
- /** 返回两次提交间变更的 .java 相对路径(位于 scanDirs 下) */
- public List scanChangedFiles(Path repoRoot, List scanDirs,
- String oldSha, String newSha) throws IOException {
- Set changed = new LinkedHashSet<>();
- List diffLines = gitScanner.diffNameOnly(oldSha, newSha);
- for (String line : diffLines) {
- String path = normalize(line);
- if (!path.endsWith(".java")) {
- continue;
- }
- if (isUnderScanDirs(path, scanDirs)) {
- changed.add(path);
- }
- }
- return new ArrayList<>(changed);
- }
-
- private boolean isUnderScanDirs(String relativePath, List scanDirs) {
- String normalized = relativePath.replace('\\', '/');
- for (String dir : scanDirs) {
- String prefix = dir.replace('\\', '/');
- if (!prefix.endsWith("/")) {
- prefix = prefix + "/";
- }
- if (normalized.startsWith(prefix)) {
- return true;
- }
- }
- return false;
- }
-
- private String normalize(String path) {
- return path.replace('\\', '/').trim();
- }
-}
diff --git a/src/main/java/com/codechecker/common/MarkdownStyles.java b/src/main/java/com/codechecker/common/MarkdownStyles.java
deleted file mode 100644
index 9a579f7..0000000
--- a/src/main/java/com/codechecker/common/MarkdownStyles.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.codechecker.common;
-
-/**
- * 企微 Markdown v1 公共样式(类变更 / API 变更通知共用)。
- */
-public final class MarkdownStyles {
- private MarkdownStyles() {
- }
-
- public static String colorInfo(String text) {
- return "" + text + "";
- }
-
- public static String colorComment(String text) {
- return "" + safe(text) + "";
- }
-
- public static String colorWarning(String text) {
- return "" + text + "";
- }
-
- public static String quoteKvBold(String key, String value) {
- return "> **" + key + ": " + value + "**";
- }
-
- public static String quoteKv(String key, String value) {
- return "> " + key + ": " + value;
- }
-
- public static String quoteLine(String content) {
- return "> " + content;
- }
-
- public static String inlineCode(String text) {
- return "`" + text.replace("`", "'") + "`";
- }
-
- /** 类型展示:泛型尖括号不转义 */
- public static String formatTypeChange(String detail) {
- int arrow = detail.indexOf(" → ");
- if (arrow < 0) {
- return colorWarning(detail);
- }
- String oldType = detail.substring(0, arrow).trim();
- String newType = detail.substring(arrow + 3).trim();
- return colorWarning(oldType) + " → " + colorInfo(newType);
- }
-
- public static String formatSingleType(String type, boolean isNew) {
- if (type == null || type.isBlank()) {
- return "";
- }
- return isNew ? colorInfo(type) : colorWarning(type);
- }
-
- public static String safe(String text) {
- if (text == null) {
- return "";
- }
- return text.replace("&", "&").replace("<", "<").replace(">", ">");
- }
-}
diff --git a/src/main/java/com/codechecker/common/WeComMarkdownSender.java b/src/main/java/com/codechecker/common/WeComMarkdownSender.java
deleted file mode 100644
index 3d6c56c..0000000
--- a/src/main/java/com/codechecker/common/WeComMarkdownSender.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.codechecker.common;
-
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 企微 Markdown 发送(与具体变更类型解耦)。
- */
-public class WeComMarkdownSender {
- 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 boolean send(String webhookUrl, String content) {
- return postMarkdown(webhookUrl, truncate(content));
- }
-
- public void logPreview(String title, String content) {
- System.out.println("========== " + title + " ==========");
- System.out.println(content);
- System.out.println("========== 结束 ==========");
- }
-
- 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) {
- return response.body().string().contains("\"errcode\":0");
- }
- System.out.println("[错误] 企微返回异常: " + response.code());
- 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 + "\"";
- }
-}
diff --git a/src/main/java/com/codechecker/config/AppConfig.java b/src/main/java/com/codechecker/config/AppConfig.java
deleted file mode 100644
index 6b35058..0000000
--- a/src/main/java/com/codechecker/config/AppConfig.java
+++ /dev/null
@@ -1,239 +0,0 @@
-package com.codechecker.config;
-
-import org.yaml.snakeyaml.Yaml;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * 读取 .gitea/workflows/code-check-config.yaml,提供检测开关、扫描目录、企微配置等。
- */
-public class AppConfig {
- private boolean masterEnabled = true;
- private boolean classCheckEnabled = true;
- private boolean dtoEntityConversionEnabled = true;
- private List modelDirs = new ArrayList<>();
- private List controllerScanDirs = new ArrayList<>();
- private List feignScanDirs = new ArrayList<>();
- private List conversionScanDirs = new ArrayList<>();
- private String wecomWebhookUrl = "";
- private boolean wecomEnabled = true;
- private boolean onlyOnChange = true;
-
- private boolean dtoApiFollowUpEnabled = true;
- private int nestMaxDepth = 3;
- private boolean apiCheckEnabled = true;
- private boolean apiExcludeFrameworkParams = true;
- private List apiControllerScanDirs = new ArrayList<>();
- private List apiFeignScanDirs = new ArrayList<>();
- private DtoOverlapMode dtoOverlapMode = DtoOverlapMode.BOTH;
-
- /** 从 YAML 文件加载配置 */
- @SuppressWarnings("unchecked")
- public static AppConfig load(Path configPath) throws IOException {
- Yaml yaml = new Yaml();
- Map root;
- try (InputStream in = Files.newInputStream(configPath)) {
- root = yaml.load(in);
- }
- if (root == null) {
- root = Map.of();
- }
-
- AppConfig config = new AppConfig();
- Map checker = mapOrEmpty(root.get("checker"));
- config.masterEnabled = boolOrDefault(checker.get("enabled"), true);
-
- Map classCheck = mapOrEmpty(root.get("class_check"));
- config.classCheckEnabled = boolOrDefault(classCheck.get("enabled"), true);
-
- Map dtoApiFollowUp = mapOrEmpty(classCheck.get("dto_api_follow_up"));
- config.dtoApiFollowUpEnabled = boolOrDefault(dtoApiFollowUp.get("enabled"), true);
-
- Map nestIndex = mapOrEmpty(classCheck.get("nest_index"));
- config.nestMaxDepth = intOrDefault(nestIndex.get("max_depth"), 3);
-
- Map conversion = mapOrEmpty(classCheck.get("dto_entity_conversion"));
- config.dtoEntityConversionEnabled = boolOrDefault(conversion.get("enabled"), true);
-
- config.modelDirs = stringList(classCheck.get("model_dirs"));
- Map endpointScan = mapOrEmpty(classCheck.get("endpoint_scan"));
- config.controllerScanDirs = stringList(endpointScan.get("controllers"));
- config.feignScanDirs = stringList(endpointScan.get("feign_apis"));
- config.conversionScanDirs = stringList(classCheck.get("conversion_scan"));
-
- Map wecom = mapOrEmpty(root.get("wecom"));
- config.wecomWebhookUrl = stringOrEmpty(wecom.get("webhook_url"));
- config.wecomEnabled = boolOrDefault(wecom.get("enabled"), true);
-
- Map notify = mapOrEmpty(root.get("notify"));
- config.onlyOnChange = boolOrDefault(notify.get("only_on_change"), true);
- config.dtoOverlapMode = DtoOverlapMode.fromString(stringOrEmpty(notify.get("dto_overlap_mode")));
-
- Map apiCheck = mapOrEmpty(root.get("api_check"));
- config.apiCheckEnabled = boolOrDefault(apiCheck.get("enabled"), true);
- config.apiExcludeFrameworkParams = boolOrDefault(apiCheck.get("exclude_framework_params"), true);
- Map apiEndpointScan = mapOrEmpty(apiCheck.get("endpoint_scan"));
- config.apiControllerScanDirs = stringList(apiEndpointScan.get("controllers"));
- config.apiFeignScanDirs = stringList(apiEndpointScan.get("feign_apis"));
- if (config.apiControllerScanDirs.isEmpty()) {
- config.apiControllerScanDirs = new ArrayList<>(config.controllerScanDirs);
- }
- if (config.apiFeignScanDirs.isEmpty()) {
- config.apiFeignScanDirs = new ArrayList<>(config.feignScanDirs);
- }
-
- return config;
- }
-
- /** 安全转为 Map,非 Map 则返回空 Map */
- @SuppressWarnings("unchecked")
- private static Map mapOrEmpty(Object value) {
- if (value instanceof Map) {
- return (Map) value;
- }
- return Map.of();
- }
-
- /** 安全转为字符串列表 */
- @SuppressWarnings("unchecked")
- private static List stringList(Object value) {
- if (value instanceof List) {
- List> list = (List>) value;
- List result = new ArrayList<>();
- for (Object item : list) {
- if (item != null) {
- result.add(item.toString());
- }
- }
- return result;
- }
- return new ArrayList<>();
- }
-
- /** 安全转为 boolean,缺省用 defaultValue */
- private static boolean boolOrDefault(Object value, boolean defaultValue) {
- if (value instanceof Boolean) {
- return (Boolean) value;
- }
- return defaultValue;
- }
-
- /** 安全转为 int,缺省用 defaultValue */
- private static int intOrDefault(Object value, int defaultValue) {
- if (value instanceof Number) {
- return ((Number) value).intValue();
- }
- if (value != null) {
- try {
- return Integer.parseInt(value.toString().trim());
- } catch (NumberFormatException ignored) {
- // 使用默认值
- }
- }
- return defaultValue;
- }
-
- /** 安全转为字符串,null 则空串 */
- private static String stringOrEmpty(Object value) {
- return value == null ? "" : value.toString();
- }
-
- /** 变更检测总开关(checker.enabled,控制 class_check + api_check) */
- public boolean isMasterEnabled() {
- return masterEnabled;
- }
-
- /** 类变更检测开关(class_check.enabled) */
- public boolean isClassCheckEnabled() {
- return classCheckEnabled;
- }
-
- /** Dto→Entity 类转换检测开关 */
- public boolean isDtoEntityConversionEnabled() {
- return dtoEntityConversionEnabled;
- }
-
- /** 模型类目录(预留,当前扫描仍按类名后缀) */
- public List getModelDirs() {
- return modelDirs;
- }
-
- /** Controller 扫描目录 */
- public List getControllerScanDirs() {
- return controllerScanDirs;
- }
-
- /** Feign 接口扫描目录 */
- public List