diff --git a/.gitea/class-checker/dependency-reduced-pom.xml b/.gitea/class-checker/dependency-reduced-pom.xml
deleted file mode 100644
index cd6d6a9..0000000
--- a/.gitea/class-checker/dependency-reduced-pom.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
- 4.0.0
- com.aicheck
- class-checker
- 1.0.0
-
- class-checker
-
-
- maven-compiler-plugin
- 3.13.0
-
-
- maven-shade-plugin
- 3.6.0
-
-
- package
-
- shade
-
-
-
-
- com.aicheck.ClassCheckMain
-
-
-
-
- *:*
-
- META-INF/*.SF
- META-INF/*.DSA
- META-INF/*.RSA
-
-
-
-
-
-
-
-
-
-
- 11
- 3.25.10
- 11
- UTF-8
-
-
diff --git a/.gitea/class-checker/pom.xml b/.gitea/class-checker/pom.xml
deleted file mode 100644
index f4200ec..0000000
--- a/.gitea/class-checker/pom.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
- 4.0.0
-
- com.aicheck
- class-checker
- 1.0.0
- jar
-
-
- 11
- 11
- UTF-8
- 3.25.10
-
-
-
-
- com.github.javaparser
- javaparser-symbol-solver-core
- ${javaparser.version}
-
-
- org.yaml
- snakeyaml
- 2.2
-
-
- com.squareup.okhttp3
- okhttp
- 4.12.0
-
-
- info.picocli
- picocli
- 4.7.6
-
-
-
-
- class-checker
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.13.0
-
-
- org.apache.maven.plugins
- maven-shade-plugin
- 3.6.0
-
-
- package
-
- shade
-
-
-
-
- com.aicheck.ClassCheckMain
-
-
-
-
- *:*
-
- META-INF/*.SF
- META-INF/*.DSA
- META-INF/*.RSA
-
-
-
-
-
-
-
-
-
-
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/ClassCheckMain.java b/.gitea/class-checker/src/main/java/com/aicheck/ClassCheckMain.java
deleted file mode 100644
index 7ce48fc..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/ClassCheckMain.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.aicheck;
-
-import com.aicheck.analyzer.ClassChangeAnalyzer;
-import com.aicheck.analyzer.EndpointIndexBuilder;
-import com.aicheck.config.AppConfig;
-import com.aicheck.git.GitChangeScanner;
-import com.aicheck.model.ApiEndpoint;
-import com.aicheck.model.ClassChangeReport;
-import com.aicheck.notify.WeComNotifier;
-import picocli.CommandLine;
-import picocli.CommandLine.Command;
-import picocli.CommandLine.Option;
-
-import java.nio.file.Path;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-
-@Command(name = "class-checker", mixinStandardHelpOptions = true,
- description = "检测 Vo/Dto/Entity/Model 类变更并发送企业微信通知")
-public class ClassCheckMain implements Callable {
- @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 ClassCheckMain()).execute(args);
- System.exit(exitCode);
- }
-
- @Override
- public Integer call() throws Exception {
- AppConfig appConfig = AppConfig.load(config.toAbsolutePath());
- if (!appConfig.isEnabled()) {
- System.out.println("类变更检测已关闭(class_check.enabled=false)");
- return 0;
- }
-
- GitChangeScanner gitScanner = new GitChangeScanner(repoRoot.toAbsolutePath());
- EndpointIndexBuilder indexBuilder = new EndpointIndexBuilder();
- Map endpointIndex = indexBuilder.buildIndex(repoRoot.toAbsolutePath(), appConfig);
- System.out.println("已索引接口数量: " + endpointIndex.size());
-
- ClassChangeAnalyzer analyzer = new ClassChangeAnalyzer(gitScanner);
- List reports = analyzer.analyze(
- repoRoot.toAbsolutePath(), appConfig, oldSha, newSha, endpointIndex);
- System.out.println("检测到需通知的类变更数量: " + reports.size());
-
- if (reports.isEmpty()) {
- if (appConfig.isOnlyOnChange()) {
- System.out.println("无类变更,静默退出");
- }
- return 0;
- }
-
- WeComNotifier notifier = new WeComNotifier();
- notifier.sendAll(appConfig.getWecomWebhookUrl(), reports, modifier, modifyTime);
- return 0;
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/analyzer/ClassChangeAnalyzer.java b/.gitea/class-checker/src/main/java/com/aicheck/analyzer/ClassChangeAnalyzer.java
deleted file mode 100644
index eb64caa..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/analyzer/ClassChangeAnalyzer.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.aicheck.analyzer;
-
-import com.aicheck.config.AppConfig;
-import com.aicheck.git.GitChangeScanner;
-import com.aicheck.model.ChangedClassFile;
-import com.aicheck.model.ClassChangeReport;
-import com.aicheck.model.FieldChange;
-import com.aicheck.model.FieldInfo;
-import com.aicheck.parser.ClassFieldParser;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-public class ClassChangeAnalyzer {
- private final GitChangeScanner gitScanner;
- private final ClassFieldParser classFieldParser = new ClassFieldParser();
- private final FieldDiffEngine fieldDiffEngine = new FieldDiffEngine();
- private final ImpactAnalyzer impactAnalyzer = new ImpactAnalyzer();
-
- public ClassChangeAnalyzer(GitChangeScanner gitScanner) {
- this.gitScanner = gitScanner;
- }
-
- public List analyze(Path repoRoot, AppConfig config, String oldSha, String newSha,
- Map endpointIndex) throws IOException {
- List changedFiles = gitScanner.scanChangedClasses(oldSha, newSha);
- List reports = new ArrayList<>();
-
- for (ChangedClassFile changedFile : changedFiles) {
- if (changedFile.getStatus() == ChangedClassFile.ChangeStatus.DELETED) {
- ClassChangeReport report = new ClassChangeReport(
- changedFile.getClassName(),
- changedFile.getClassType(),
- changedFile.getRelativePath(),
- true,
- config.isDtoEntityConversionEnabled()
- );
- String oldSource = gitScanner.readFileAtCommit(oldSha, changedFile.getRelativePath());
- impactAnalyzer.analyze(report, endpointIndex, config, repoRoot, oldSource);
- reports.add(report);
- continue;
- }
-
- String oldSource = gitScanner.readFileAtCommit(oldSha, changedFile.getRelativePath());
- String newSource = gitScanner.readFileAtCommit(newSha, changedFile.getRelativePath());
- if (newSource == null || newSource.isBlank()) {
- newSource = gitScanner.readFileAtHead(changedFile.getRelativePath());
- }
- List oldFields = classFieldParser.parseFields(oldSource, changedFile.getClassName());
- List newFields = classFieldParser.parseFields(newSource, changedFile.getClassName());
- List fieldChanges = fieldDiffEngine.diff(oldFields, newFields);
- if (fieldChanges.isEmpty()) {
- continue;
- }
-
- ClassChangeReport report = new ClassChangeReport(
- changedFile.getClassName(),
- changedFile.getClassType(),
- changedFile.getRelativePath(),
- false,
- config.isDtoEntityConversionEnabled()
- );
- fieldChanges.forEach(report::addFieldChange);
- impactAnalyzer.analyze(report, endpointIndex, config, repoRoot, newSource);
- reports.add(report);
- }
- return reports;
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/analyzer/EndpointIndexBuilder.java b/.gitea/class-checker/src/main/java/com/aicheck/analyzer/EndpointIndexBuilder.java
deleted file mode 100644
index 6edb92a..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/analyzer/EndpointIndexBuilder.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.aicheck.analyzer;
-
-import com.aicheck.config.AppConfig;
-import com.aicheck.model.ApiEndpoint;
-import com.aicheck.parser.EndpointParser;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-public class EndpointIndexBuilder {
- private final EndpointParser endpointParser = new EndpointParser();
-
- public Map buildIndex(Path repoRoot, AppConfig config) throws IOException {
- Map index = new LinkedHashMap<>();
- for (String dir : config.getControllerScanDirs()) {
- addEndpoints(index, endpointParser.scanControllerDirectory(repoRoot.resolve(dir), dir));
- }
- for (String dir : config.getFeignScanDirs()) {
- addEndpoints(index, endpointParser.scanFeignDirectory(repoRoot.resolve(dir), dir));
- }
- return index;
- }
-
- private void addEndpoints(Map index, List endpoints) {
- for (ApiEndpoint endpoint : endpoints) {
- index.putIfAbsent(endpoint.endpointKey(), endpoint);
- }
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/analyzer/FieldDiffEngine.java b/.gitea/class-checker/src/main/java/com/aicheck/analyzer/FieldDiffEngine.java
deleted file mode 100644
index aebac05..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/analyzer/FieldDiffEngine.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.aicheck.analyzer;
-
-import com.aicheck.model.FieldChange;
-import com.aicheck.model.FieldInfo;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-public class FieldDiffEngine {
- public List diff(List oldFields, List newFields) {
- Map oldMap = toMap(oldFields);
- Map newMap = toMap(newFields);
- List changes = new ArrayList<>();
-
- for (Map.Entry entry : newMap.entrySet()) {
- FieldInfo oldField = oldMap.get(entry.getKey());
- if (oldField == null) {
- changes.add(FieldChange.added(entry.getValue()));
- } else if (!oldField.equals(entry.getValue())) {
- changes.add(FieldChange.modified(oldField, entry.getValue(), buildDetail(oldField, entry.getValue())));
- }
- }
-
- for (Map.Entry entry : oldMap.entrySet()) {
- if (!newMap.containsKey(entry.getKey())) {
- changes.add(FieldChange.removed(entry.getValue()));
- }
- }
- return changes;
- }
-
- private Map toMap(List fields) {
- Map map = new LinkedHashMap<>();
- for (FieldInfo field : fields) {
- map.put(field.getName(), field);
- }
- return map;
- }
-
- private String buildDetail(FieldInfo oldField, FieldInfo newField) {
- List parts = new ArrayList<>();
- if (!oldField.getType().equals(newField.getType())) {
- parts.add(oldField.getType() + " → " + newField.getType());
- }
- if (!oldField.getDescription().equals(newField.getDescription())) {
- String oldDesc = oldField.getDescription().isBlank() ? "无" : oldField.getDescription();
- String newDesc = newField.getDescription().isBlank() ? "无" : newField.getDescription();
- parts.add("说明:" + oldDesc + " → " + newDesc);
- }
- return String.join(";", parts);
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/analyzer/ImpactAnalyzer.java b/.gitea/class-checker/src/main/java/com/aicheck/analyzer/ImpactAnalyzer.java
deleted file mode 100644
index 2c7aff5..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/analyzer/ImpactAnalyzer.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.aicheck.analyzer;
-
-import com.aicheck.config.AppConfig;
-import com.aicheck.model.ApiEndpoint;
-import com.aicheck.model.ClassChangeReport;
-import com.aicheck.model.ClassType;
-import com.aicheck.parser.ConversionParser;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-public class ImpactAnalyzer {
- private final ConversionParser conversionParser = new ConversionParser();
-
- public void analyze(ClassChangeReport report, Map endpointIndex,
- AppConfig config, Path repoRoot, String classSource) throws IOException {
- if (report.getClassType() == ClassType.ENTITY) {
- return;
- }
-
- String className = report.getClassName();
- List inputImpacts = new ArrayList<>();
- List frontendImpacts = new ArrayList<>();
-
- for (ApiEndpoint endpoint : endpointIndex.values()) {
- if (matchesType(endpoint.getParamTypes(), className)) {
- inputImpacts.add(endpoint);
- }
- if (matchesType(endpoint.getReturnTypes(), className)) {
- frontendImpacts.add(endpoint);
- }
- }
-
- inputImpacts.forEach(report::addInputImpact);
- frontendImpacts.forEach(report::addFrontendImpact);
-
- if (!config.isDtoEntityConversionEnabled()) {
- return;
- }
-
- if (classSource != null && !classSource.isBlank()) {
- conversionParser.findConvertTargetsInClass(classSource, className)
- .forEach(report::addConversionEntity);
- }
-
- for (String scanDir : config.getConversionScanDirs()) {
- conversionParser.findBeanUtilsTargets(repoRoot.resolve(scanDir), className)
- .forEach(report::addConversionEntity);
- }
- }
-
- private boolean matchesType(Collection types, String className) {
- return types != null && types.contains(className);
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/config/AppConfig.java b/.gitea/class-checker/src/main/java/com/aicheck/config/AppConfig.java
deleted file mode 100644
index 7811a95..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/config/AppConfig.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package com.aicheck.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;
-
-public class AppConfig {
- private boolean enabled = 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 onlyOnChange = true;
-
- @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 classCheck = mapOrEmpty(root.get("class_check"));
- config.enabled = boolOrDefault(classCheck.get("enabled"), true);
-
- 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"));
-
- Map notify = mapOrEmpty(root.get("notify"));
- config.onlyOnChange = boolOrDefault(notify.get("only_on_change"), true);
-
- return config;
- }
-
- @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<>();
- }
-
- private static boolean boolOrDefault(Object value, boolean defaultValue) {
- if (value instanceof Boolean) {
- return (Boolean) value;
- }
- return defaultValue;
- }
-
- private static String stringOrEmpty(Object value) {
- return value == null ? "" : value.toString();
- }
-
- public boolean isEnabled() {
- return enabled;
- }
-
- public boolean isDtoEntityConversionEnabled() {
- return dtoEntityConversionEnabled;
- }
-
- public List getModelDirs() {
- return modelDirs;
- }
-
- public List getControllerScanDirs() {
- return controllerScanDirs;
- }
-
- public List getFeignScanDirs() {
- return feignScanDirs;
- }
-
- public List getConversionScanDirs() {
- return conversionScanDirs;
- }
-
- public String getWecomWebhookUrl() {
- return wecomWebhookUrl;
- }
-
- public boolean isOnlyOnChange() {
- return onlyOnChange;
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/git/GitChangeScanner.java b/.gitea/class-checker/src/main/java/com/aicheck/git/GitChangeScanner.java
deleted file mode 100644
index 6e86cf2..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/git/GitChangeScanner.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package com.aicheck.git;
-
-import com.aicheck.model.ChangedClassFile;
-import com.aicheck.model.ClassType;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-public class GitChangeScanner {
- private final Path repoRoot;
-
- public GitChangeScanner(Path repoRoot) {
- this.repoRoot = repoRoot;
- }
-
- public List scanChangedClasses(String oldSha, String newSha) throws IOException {
- List lines = runGit("diff", "--name-status", oldSha, newSha);
- List result = new ArrayList<>();
-
- for (String line : lines) {
- if (line.isBlank()) {
- continue;
- }
- String[] parts = line.split("\t");
- if (parts.length < 2) {
- continue;
- }
- String status = parts[0].trim();
- String path = normalizePath(parts[parts.length - 1]);
- if (!path.endsWith(".java")) {
- continue;
- }
-
- String className = extractClassName(path);
- ClassType classType = ClassType.fromClassName(className);
- if (classType == null) {
- continue;
- }
-
- if (status.equals("A")) {
- continue;
- }
- if (status.equals("D")) {
- result.add(new ChangedClassFile(path, ChangedClassFile.ChangeStatus.DELETED, className, classType));
- } else if (status.startsWith("M") || status.startsWith("R")) {
- result.add(new ChangedClassFile(path, ChangedClassFile.ChangeStatus.MODIFIED, className, classType));
- }
- }
- return result;
- }
-
- public String readFileAtCommit(String commitSha, String relativePath) throws IOException {
- List lines = runGit("show", commitSha + ":" + relativePath);
- if (lines.isEmpty()) {
- return "";
- }
- if (lines.size() == 1 && lines.get(0).startsWith("fatal:")) {
- return "";
- }
- return String.join("\n", lines);
- }
-
- public String readFileAtHead(String relativePath) throws IOException {
- Path file = repoRoot.resolve(relativePath);
- if (!Files.exists(file)) {
- return null;
- }
- return Files.readString(file, StandardCharsets.UTF_8);
- }
-
- private List runGit(String... args) throws IOException {
- String[] command = new String[args.length + 3];
- command[0] = "git";
- command[1] = "-C";
- command[2] = repoRoot.toString();
- System.arraycopy(args, 0, command, 3, args.length);
-
- ProcessBuilder builder = new ProcessBuilder(command);
- builder.redirectErrorStream(true);
- Process process = builder.start();
-
- List output = new ArrayList<>();
- try (BufferedReader reader = new BufferedReader(
- new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
- String line;
- while ((line = reader.readLine()) != null) {
- output.add(line);
- }
- }
-
- try {
- int exitCode = process.waitFor();
- if (exitCode != 0 && !isBenignGitShowFailure(args, output)) {
- throw new IOException("git 命令失败: " + String.join(" ", command)
- + "\n" + String.join("\n", output));
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new IOException("git 命令被中断", e);
- }
- return output;
- }
-
- private boolean isBenignGitShowFailure(String[] args, List output) {
- if (args.length > 0 && "show".equals(args[0])) {
- String joined = String.join("\n", output).toLowerCase(Locale.ROOT);
- return joined.contains("exists on disk") || joined.contains("bad object")
- || joined.contains("path") && joined.contains("does not exist");
- }
- return false;
- }
-
- private String normalizePath(String path) {
- return path.replace("\\", "/");
- }
-
- private String extractClassName(String path) {
- String fileName = path.substring(path.lastIndexOf('/') + 1);
- return fileName.substring(0, fileName.length() - 5);
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/model/ApiEndpoint.java b/.gitea/class-checker/src/main/java/com/aicheck/model/ApiEndpoint.java
deleted file mode 100644
index 6f454b4..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/model/ApiEndpoint.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.aicheck.model;
-
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-public class ApiEndpoint {
- private final String httpMethod;
- private final String uri;
- private final String sourceFile;
- private final Set paramTypes;
- private final Set returnTypes;
-
- public ApiEndpoint(String httpMethod, String uri, String sourceFile,
- Set paramTypes, Set returnTypes) {
- this.httpMethod = httpMethod;
- this.uri = uri;
- this.sourceFile = sourceFile;
- this.paramTypes = paramTypes == null ? Set.of() : new LinkedHashSet<>(paramTypes);
- this.returnTypes = returnTypes == null ? Set.of() : new LinkedHashSet<>(returnTypes);
- }
-
- public String getHttpMethod() {
- return httpMethod;
- }
-
- public String getUri() {
- return uri;
- }
-
- public String getSourceFile() {
- return sourceFile;
- }
-
- public Set getParamTypes() {
- return paramTypes;
- }
-
- public Set getReturnTypes() {
- return returnTypes;
- }
-
- public String endpointKey() {
- return httpMethod + " " + uri;
- }
-
- public String displayLine() {
- return httpMethod + " " + uri;
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/model/ChangedClassFile.java b/.gitea/class-checker/src/main/java/com/aicheck/model/ChangedClassFile.java
deleted file mode 100644
index abc4854..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/model/ChangedClassFile.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.aicheck.model;
-
-public class ChangedClassFile {
- public enum ChangeStatus {
- MODIFIED, DELETED
- }
-
- private final String relativePath;
- private final ChangeStatus status;
- private final String className;
- private final ClassType classType;
-
- public ChangedClassFile(String relativePath, ChangeStatus status, String className, ClassType classType) {
- this.relativePath = relativePath;
- this.status = status;
- this.className = className;
- this.classType = classType;
- }
-
- public String getRelativePath() {
- return relativePath;
- }
-
- public ChangeStatus getStatus() {
- return status;
- }
-
- public String getClassName() {
- return className;
- }
-
- public ClassType getClassType() {
- return classType;
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/model/ClassChangeReport.java b/.gitea/class-checker/src/main/java/com/aicheck/model/ClassChangeReport.java
deleted file mode 100644
index c4f8f8b..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/model/ClassChangeReport.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.aicheck.model;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class ClassChangeReport {
- private final String className;
- private final ClassType classType;
- private final String sourceFile;
- private final boolean deleted;
- private final List fieldChanges = new ArrayList<>();
- private final List inputImpactEndpoints = new ArrayList<>();
- private final List conversionEntities = new ArrayList<>();
- private final List frontendImpactEndpoints = new ArrayList<>();
- private final boolean conversionCheckEnabled;
-
- public ClassChangeReport(String className, ClassType classType, String sourceFile,
- boolean deleted, boolean conversionCheckEnabled) {
- this.className = className;
- this.classType = classType;
- this.sourceFile = sourceFile;
- this.deleted = deleted;
- this.conversionCheckEnabled = conversionCheckEnabled;
- }
-
- public String getClassName() {
- return className;
- }
-
- public ClassType getClassType() {
- return classType;
- }
-
- public String getSourceFile() {
- return sourceFile;
- }
-
- public boolean isDeleted() {
- return deleted;
- }
-
- public List getFieldChanges() {
- return fieldChanges;
- }
-
- public List getInputImpactEndpoints() {
- return inputImpactEndpoints;
- }
-
- public List getConversionEntities() {
- return conversionEntities;
- }
-
- public List getFrontendImpactEndpoints() {
- return frontendImpactEndpoints;
- }
-
- public boolean isConversionCheckEnabled() {
- return conversionCheckEnabled;
- }
-
- public void addFieldChange(FieldChange change) {
- fieldChanges.add(change);
- }
-
- public void addInputImpact(ApiEndpoint endpoint) {
- inputImpactEndpoints.add(endpoint);
- }
-
- public void addConversionEntity(String entityName) {
- if (!conversionEntities.contains(entityName)) {
- conversionEntities.add(entityName);
- }
- }
-
- public void addFrontendImpact(ApiEndpoint endpoint) {
- frontendImpactEndpoints.add(endpoint);
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/model/ClassType.java b/.gitea/class-checker/src/main/java/com/aicheck/model/ClassType.java
deleted file mode 100644
index 85c126b..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/model/ClassType.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.aicheck.model;
-
-public enum ClassType {
- DTO("Dto"),
- VO("Vo"),
- ENTITY("Entity"),
- MODEL("Model");
-
- private final String label;
-
- ClassType(String label) {
- this.label = label;
- }
-
- public String getLabel() {
- return label;
- }
-
- public static ClassType fromClassName(String className) {
- if (className.endsWith("Dto")) {
- return DTO;
- }
- if (className.endsWith("VO")) {
- return VO;
- }
- if (className.endsWith("Vo")) {
- return VO;
- }
- if (className.endsWith("Entity")) {
- return ENTITY;
- }
- if (className.endsWith("Model")) {
- return MODEL;
- }
- return null;
- }
-
- public boolean isTargetSuffix(String className) {
- return fromClassName(className) == this;
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/model/FieldChange.java b/.gitea/class-checker/src/main/java/com/aicheck/model/FieldChange.java
deleted file mode 100644
index 2122654..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/model/FieldChange.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.aicheck.model;
-
-public class FieldChange {
- public enum ChangeKind {
- ADDED, REMOVED, MODIFIED
- }
-
- private final ChangeKind kind;
- private final String fieldName;
- private final String description;
- private final String oldType;
- private final String newType;
- private final String oldDescription;
- private final String detail;
-
- private FieldChange(ChangeKind kind, String fieldName, String description,
- String oldType, String newType, String oldDescription, String detail) {
- this.kind = kind;
- this.fieldName = fieldName;
- this.description = description;
- this.oldType = oldType;
- this.newType = newType;
- this.oldDescription = oldDescription;
- this.detail = detail;
- }
-
- public static FieldChange added(FieldInfo field) {
- return new FieldChange(ChangeKind.ADDED, field.getName(), field.getDescription(),
- null, field.getType(), null, null);
- }
-
- public static FieldChange removed(FieldInfo field) {
- return new FieldChange(ChangeKind.REMOVED, field.getName(), field.getDescription(),
- field.getType(), null, field.getDescription(), null);
- }
-
- public static FieldChange modified(FieldInfo oldField, FieldInfo newField, String detail) {
- return new FieldChange(ChangeKind.MODIFIED, newField.getName(), newField.getDescription(),
- oldField.getType(), newField.getType(), oldField.getDescription(), detail);
- }
-
- public ChangeKind getKind() {
- return kind;
- }
-
- public String getFieldName() {
- return fieldName;
- }
-
- public String getDescription() {
- return description;
- }
-
- public String getOldType() {
- return oldType;
- }
-
- public String getNewType() {
- return newType;
- }
-
- public String getOldDescription() {
- return oldDescription;
- }
-
- public String getDetail() {
- return detail;
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/model/FieldInfo.java b/.gitea/class-checker/src/main/java/com/aicheck/model/FieldInfo.java
deleted file mode 100644
index ed96958..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/model/FieldInfo.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.aicheck.model;
-
-import java.util.Objects;
-
-public class FieldInfo {
- private final String name;
- private final String type;
- private final String description;
-
- public FieldInfo(String name, String type, String description) {
- this.name = name;
- this.type = type;
- this.description = description == null ? "" : description;
- }
-
- public String getName() {
- return name;
- }
-
- public String getType() {
- return type;
- }
-
- public String getDescription() {
- return description;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof FieldInfo)) {
- return false;
- }
- FieldInfo other = (FieldInfo) o;
- return Objects.equals(name, other.name)
- && Objects.equals(type, other.type)
- && Objects.equals(description, other.description);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(name, type, description);
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/notify/WeComNotifier.java b/.gitea/class-checker/src/main/java/com/aicheck/notify/WeComNotifier.java
deleted file mode 100644
index 3907e22..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/notify/WeComNotifier.java
+++ /dev/null
@@ -1,179 +0,0 @@
-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 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 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 + "\"";
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/parser/ClassFieldParser.java b/.gitea/class-checker/src/main/java/com/aicheck/parser/ClassFieldParser.java
deleted file mode 100644
index 7e37c65..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/parser/ClassFieldParser.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.aicheck.parser;
-
-import com.aicheck.model.FieldInfo;
-import com.github.javaparser.StaticJavaParser;
-import com.github.javaparser.ast.CompilationUnit;
-import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
-import com.github.javaparser.ast.body.FieldDeclaration;
-import com.github.javaparser.ast.body.TypeDeclaration;
-import com.github.javaparser.ast.body.VariableDeclarator;
-import com.github.javaparser.ast.comments.JavadocComment;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-public class ClassFieldParser {
- public List parseFields(String source, String expectedClassName) {
- if (source == null || source.isBlank()) {
- return List.of();
- }
- CompilationUnit cu = StaticJavaParser.parse(source);
- for (TypeDeclaration> type : cu.getTypes()) {
- if (type instanceof ClassOrInterfaceDeclaration) {
- ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) type;
- if (classDecl.getNameAsString().equals(expectedClassName)) {
- return parseClassFields(classDecl);
- }
- }
- }
- return List.of();
- }
-
- private List parseClassFields(ClassOrInterfaceDeclaration classDecl) {
- Map fields = new LinkedHashMap<>();
- for (FieldDeclaration fieldDecl : classDecl.getFields()) {
- if (fieldDecl.isStatic() && fieldDecl.isFinal()) {
- continue;
- }
- String type = TypeNameUtils.typeToString(fieldDecl.getElementType());
- String description = extractDescription(fieldDecl);
- for (VariableDeclarator variable : fieldDecl.getVariables()) {
- fields.put(variable.getNameAsString(), new FieldInfo(variable.getNameAsString(), type, description));
- }
- }
- return new ArrayList<>(fields.values());
- }
-
- private String extractDescription(FieldDeclaration fieldDecl) {
- Optional javadoc = fieldDecl.getJavadocComment();
- if (javadoc.isEmpty()) {
- return "";
- }
- String text = javadoc.get().parse().getDescription().toText();
- return text == null ? "" : text.trim().replaceAll("\\s+", " ");
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/parser/ConversionParser.java b/.gitea/class-checker/src/main/java/com/aicheck/parser/ConversionParser.java
deleted file mode 100644
index 95fa0ec..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/parser/ConversionParser.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package com.aicheck.parser;
-
-import com.github.javaparser.StaticJavaParser;
-import com.github.javaparser.ast.CompilationUnit;
-import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
-import com.github.javaparser.ast.body.MethodDeclaration;
-import com.github.javaparser.ast.body.TypeDeclaration;
-import com.github.javaparser.ast.expr.MethodCallExpr;
-import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Stream;
-
-public class ConversionParser {
- public Set findConvertTargetsInClass(String source, String className) {
- Set entities = new LinkedHashSet<>();
- if (source == null || source.isBlank()) {
- return entities;
- }
- CompilationUnit cu = StaticJavaParser.parse(source);
- for (TypeDeclaration> type : cu.getTypes()) {
- if (type instanceof ClassOrInterfaceDeclaration) {
- ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) type;
- if (!classDecl.getNameAsString().equals(className)) {
- continue;
- }
- for (MethodDeclaration method : classDecl.getMethods()) {
- if (!"convert".equals(method.getNameAsString())) {
- continue;
- }
- String returnType = TypeNameUtils.simpleName(TypeNameUtils.typeToString(method.getType()));
- if (returnType.endsWith("Entity")) {
- entities.add(returnType);
- }
- }
- }
- }
- return entities;
- }
-
- public Set findBeanUtilsTargets(Path rootDir, String sourceClassName) throws IOException {
- Set entities = new LinkedHashSet<>();
- if (!Files.exists(rootDir)) {
- return entities;
- }
- try (Stream paths = Files.walk(rootDir)) {
- paths.filter(path -> path.toString().endsWith(".java")).forEach(path -> {
- try {
- String source = Files.readString(path, StandardCharsets.UTF_8);
- entities.addAll(scanBeanUtilsInSource(source, sourceClassName));
- } catch (IOException ignored) {
- // 跳过
- }
- });
- }
- return entities;
- }
-
- private Set scanBeanUtilsInSource(String source, String sourceClassName) {
- Set entities = new LinkedHashSet<>();
- CompilationUnit cu = StaticJavaParser.parse(source);
- cu.accept(new VoidVisitorAdapter() {
- @Override
- public void visit(MethodCallExpr call, Void arg) {
- super.visit(call, arg);
- if (!call.getNameAsString().equals("copyProperties")) {
- return;
- }
- if (call.getScope().isEmpty()) {
- return;
- }
- String scope = call.getScope().get().toString();
- if (!scope.endsWith("BeanUtils")) {
- return;
- }
- if (call.getArguments().size() < 2) {
- return;
- }
- String firstArg = TypeNameUtils.simpleName(call.getArguments().get(0).toString());
- String secondArg = TypeNameUtils.simpleName(call.getArguments().get(1).toString());
- if (sourceClassName.equals(firstArg) && secondArg.endsWith("Entity")) {
- entities.add(secondArg);
- }
- }
- }, null);
- return entities;
- }
-}
diff --git a/.gitea/class-checker/src/main/java/com/aicheck/parser/EndpointParser.java b/.gitea/class-checker/src/main/java/com/aicheck/parser/EndpointParser.java
deleted file mode 100644
index a9bc4d3..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/parser/EndpointParser.java
+++ /dev/null
@@ -1,282 +0,0 @@
-package com.aicheck.parser;
-
-import com.aicheck.model.ApiEndpoint;
-import com.github.javaparser.StaticJavaParser;
-import com.github.javaparser.ast.CompilationUnit;
-import com.github.javaparser.ast.NodeList;
-import com.github.javaparser.ast.expr.Expression;
-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.type.Type;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Stream;
-
-public class EndpointParser {
- 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"
- );
-
- public List scanControllerDirectory(Path rootDir, String relativePrefix) throws IOException {
- return scanDirectory(rootDir, relativePrefix, ScanMode.CONTROLLER);
- }
-
- public List scanFeignDirectory(Path rootDir, String relativePrefix) throws IOException {
- return scanDirectory(rootDir, relativePrefix, ScanMode.FEIGN);
- }
-
- private List scanDirectory(Path rootDir, String relativePrefix, ScanMode mode) throws IOException {
- if (!Files.exists(rootDir)) {
- return List.of();
- }
- List endpoints = new ArrayList<>();
- try (Stream paths = Files.walk(rootDir)) {
- paths.filter(path -> path.toString().endsWith(".java")).forEach(path -> {
- try {
- String source = Files.readString(path, StandardCharsets.UTF_8);
- String relativePath = toRelativePath(relativePrefix, rootDir, path);
- endpoints.addAll(parseCompilationUnit(source, relativePath, mode));
- } catch (IOException ignored) {
- // 跳过无法读取的文件
- }
- });
- }
- return endpoints;
- }
-
- private List parseCompilationUnit(String source, String relativePath, ScanMode mode) {
- CompilationUnit cu = StaticJavaParser.parse(source);
- List endpoints = new ArrayList<>();
-
- for (TypeDeclaration> type : cu.getTypes()) {
- if (!(type instanceof ClassOrInterfaceDeclaration)) {
- continue;
- }
- ClassOrInterfaceDeclaration declaration = (ClassOrInterfaceDeclaration) type;
- if (mode == ScanMode.CONTROLLER && !isController(declaration)) {
- continue;
- }
- if (mode == ScanMode.FEIGN && !isFeignClient(declaration)) {
- continue;
- }
-
- String basePath = mode == ScanMode.FEIGN
- ? joinPaths(extractFeignBasePath(declaration), extractTypeLevelPath(declaration))
- : extractTypeLevelPath(declaration);
- for (MethodDeclaration method : declaration.getMethods()) {
- if (mode == ScanMode.FEIGN && declaration.isInterface()) {
- endpoints.addAll(parseMethod(method, basePath, relativePath));
- } else if (mode == ScanMode.CONTROLLER && !declaration.isInterface()) {
- endpoints.addAll(parseMethod(method, basePath, relativePath));
- }
- }
- }
- return endpoints;
- }
-
- private List parseMethod(MethodDeclaration method, String basePath, String sourceFile) {
- List endpoints = new ArrayList<>();
- for (AnnotationExpr annotation : method.getAnnotations()) {
- String annName = annotation.getNameAsString();
- if (!MAPPING_ANNOTATIONS.contains(annName)) {
- continue;
- }
- List subPaths = extractPaths(annotation);
- List httpMethods = extractHttpMethods(annotation, annName);
- for (String httpMethod : httpMethods) {
- for (String subPath : subPaths) {
- String uri = joinPaths(basePath, subPath);
- Set paramTypes = extractParamTypes(method);
- Set returnTypes = TypeNameUtils.peelDirectTypeNames(method.getType());
- endpoints.add(new ApiEndpoint(httpMethod, uri, sourceFile, paramTypes, returnTypes));
- }
- }
- }
- return endpoints;
- }
-
- private Set extractParamTypes(MethodDeclaration method) {
- Set paramTypes = new LinkedHashSet<>();
- for (Parameter parameter : method.getParameters()) {
- Type type = parameter.getType();
- paramTypes.add(TypeNameUtils.simpleName(TypeNameUtils.typeToString(type)));
- paramTypes.addAll(TypeNameUtils.peelDirectTypeNames(type));
- }
- return paramTypes;
- }
-
- private boolean isController(ClassOrInterfaceDeclaration declaration) {
- return declaration.getAnnotations().stream()
- .anyMatch(ann -> {
- String name = ann.getNameAsString();
- return "RestController".equals(name) || "Controller".equals(name);
- });
- }
-
- private boolean isFeignClient(ClassOrInterfaceDeclaration declaration) {
- return declaration.isInterface() && declaration.getAnnotations().stream()
- .anyMatch(ann -> "FeignClient".equals(ann.getNameAsString()));
- }
-
- private String extractTypeLevelPath(ClassOrInterfaceDeclaration declaration) {
- for (AnnotationExpr annotation : declaration.getAnnotations()) {
- if ("RequestMapping".equals(annotation.getNameAsString())) {
- List paths = extractPaths(annotation);
- if (!paths.isEmpty()) {
- return paths.get(0);
- }
- }
- }
- return "";
- }
-
- private String extractFeignBasePath(ClassOrInterfaceDeclaration declaration) {
- for (AnnotationExpr annotation : declaration.getAnnotations()) {
- if ("FeignClient".equals(annotation.getNameAsString())) {
- List paths = AnnotationValueReader.readStringArray(annotation, "path");
- if (!paths.isEmpty()) {
- return paths.get(0);
- }
- }
- }
- return "";
- }
-
- private List extractPaths(AnnotationExpr annotation) {
- return AnnotationValueReader.readStringArray(annotation, "value", "path");
- }
-
- private List extractHttpMethods(AnnotationExpr annotation, String annName) {
- if (!"RequestMapping".equals(annName)) {
- return List.of(MAPPING_DEFAULT_METHOD.getOrDefault(annName, "GET"));
- }
- List methods = AnnotationValueReader.readEnumArray(annotation, "method");
- if (methods.isEmpty()) {
- return List.of("GET");
- }
- return 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;
- }
- String joined = normalizedBase + "/" + normalizedSub.substring(1);
- return joined.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 String toRelativePath(String relativePrefix, Path rootDir, Path file) {
- String relative = rootDir.relativize(file).toString().replace("\\", "/");
- if (relativePrefix == null || relativePrefix.isBlank()) {
- return relative;
- }
- String prefix = relativePrefix.endsWith("/")
- ? relativePrefix.substring(0, relativePrefix.length() - 1)
- : relativePrefix;
- return prefix + "/" + relative;
- }
-
- private enum ScanMode {
- CONTROLLER, FEIGN
- }
-
- static final class AnnotationValueReader {
- private AnnotationValueReader() {
- }
-
- static 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;
- }
-
- static 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 static 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/.gitea/class-checker/src/main/java/com/aicheck/parser/TypeNameUtils.java b/.gitea/class-checker/src/main/java/com/aicheck/parser/TypeNameUtils.java
deleted file mode 100644
index ac844af..0000000
--- a/.gitea/class-checker/src/main/java/com/aicheck/parser/TypeNameUtils.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package com.aicheck.parser;
-
-import com.github.javaparser.ast.type.ClassOrInterfaceType;
-import com.github.javaparser.ast.type.Type;
-
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-public final class TypeNameUtils {
- private static final Set WRAPPER_TYPES = Set.of(
- "ActionResult", "List", "PageListVO", "Set", "Collection", "Iterable", "Optional"
- );
-
- private TypeNameUtils() {
- }
-
- public static String typeToString(Type type) {
- if (type == null) {
- return "Object";
- }
- return type.toString().replaceAll("\\s+", "");
- }
-
- public static String simpleName(String typeName) {
- if (typeName == null || typeName.isBlank()) {
- return "";
- }
- String cleaned = typeName.replaceAll("\\s+", "");
- int genericStart = cleaned.indexOf('<');
- String base = genericStart >= 0 ? cleaned.substring(0, genericStart) : cleaned;
- int dot = base.lastIndexOf('.');
- return dot >= 0 ? base.substring(dot + 1) : base;
- }
-
- public static Set peelDirectTypeNames(Type type) {
- Set result = new LinkedHashSet<>();
- collectPeelTargets(type, result);
- return result;
- }
-
- public static Set peelDirectTypeNames(String typeName) {
- Set result = new LinkedHashSet<>();
- collectPeelTargets(typeName, result);
- return result;
- }
-
- private static void collectPeelTargets(Type type, Set result) {
- if (type == null) {
- return;
- }
- if (type.isClassOrInterfaceType()) {
- ClassOrInterfaceType classType = type.asClassOrInterfaceType();
- String name = simpleName(classType.getNameAsString());
- if (WRAPPER_TYPES.contains(name) && classType.getTypeArguments().isPresent()) {
- for (Type arg : classType.getTypeArguments().get()) {
- collectPeelTargets(arg, result);
- }
- return;
- }
- result.add(name);
- return;
- }
- result.add(simpleName(typeToString(type)));
- }
-
- private static void collectPeelTargets(String typeName, Set result) {
- String cleaned = typeName.replaceAll("\\s+", "");
- int genericStart = cleaned.indexOf('<');
- if (genericStart < 0) {
- result.add(simpleName(cleaned));
- return;
- }
- String outer = simpleName(cleaned.substring(0, genericStart));
- String inner = cleaned.substring(genericStart + 1, cleaned.lastIndexOf('>'));
- if (WRAPPER_TYPES.contains(outer)) {
- for (String part : splitGenericArgs(inner)) {
- collectPeelTargets(part, result);
- }
- return;
- }
- result.add(outer);
- }
-
- private static List splitGenericArgs(String inner) {
- List parts = new java.util.ArrayList<>();
- int depth = 0;
- StringBuilder current = new StringBuilder();
- for (char ch : inner.toCharArray()) {
- if (ch == '<') {
- depth++;
- } else if (ch == '>') {
- depth--;
- } else if (ch == ',' && depth == 0) {
- parts.add(current.toString().trim());
- current.setLength(0);
- continue;
- }
- current.append(ch);
- }
- if (current.length() > 0) {
- parts.add(current.toString().trim());
- }
- return parts;
- }
-}
diff --git a/.gitea/class-checker/target/maven-archiver/pom.properties b/.gitea/class-checker/target/maven-archiver/pom.properties
deleted file mode 100644
index 1a9049c..0000000
--- a/.gitea/class-checker/target/maven-archiver/pom.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-#Generated by Maven
-#Fri Jun 05 18:18:14 GMT+08:00 2026
-groupId=com.aicheck
-artifactId=class-checker
-version=1.0.0
diff --git a/.gitea/class-checker/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/.gitea/class-checker/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
deleted file mode 100644
index df65d79..0000000
--- a/.gitea/class-checker/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
+++ /dev/null
@@ -1,24 +0,0 @@
-com\aicheck\model\FieldChange$ChangeKind.class
-com\aicheck\analyzer\ClassChangeAnalyzer.class
-com\aicheck\notify\WeComNotifier.class
-com\aicheck\model\ClassType.class
-com\aicheck\notify\WeComNotifier$1.class
-com\aicheck\model\ChangedClassFile.class
-com\aicheck\analyzer\FieldDiffEngine.class
-com\aicheck\parser\ConversionParser$1.class
-com\aicheck\parser\ConversionParser.class
-com\aicheck\config\AppConfig.class
-com\aicheck\parser\ClassFieldParser.class
-com\aicheck\analyzer\ImpactAnalyzer.class
-com\aicheck\ClassCheckMain.class
-com\aicheck\model\ApiEndpoint.class
-com\aicheck\model\ClassChangeReport.class
-com\aicheck\parser\EndpointParser.class
-com\aicheck\analyzer\EndpointIndexBuilder.class
-com\aicheck\parser\TypeNameUtils.class
-com\aicheck\model\ChangedClassFile$ChangeStatus.class
-com\aicheck\parser\EndpointParser$AnnotationValueReader.class
-com\aicheck\model\FieldInfo.class
-com\aicheck\git\GitChangeScanner.class
-com\aicheck\model\FieldChange.class
-com\aicheck\parser\EndpointParser$ScanMode.class
diff --git a/.gitea/class-checker/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/.gitea/class-checker/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
deleted file mode 100644
index cc52d98..0000000
--- a/.gitea/class-checker/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
+++ /dev/null
@@ -1,18 +0,0 @@
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\analyzer\ClassChangeAnalyzer.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\analyzer\EndpointIndexBuilder.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\analyzer\FieldDiffEngine.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\analyzer\ImpactAnalyzer.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\ClassCheckMain.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\config\AppConfig.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\git\GitChangeScanner.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\model\ApiEndpoint.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\model\ChangedClassFile.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\model\ClassChangeReport.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\model\ClassType.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\model\FieldChange.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\model\FieldInfo.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\notify\WeComNotifier.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\parser\ClassFieldParser.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\parser\ConversionParser.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\parser\EndpointParser.java
-C:\Users\EDY\Desktop\Demo\AI-Check-Test\.gitea\class-checker\src\main\java\com\aicheck\parser\TypeNameUtils.java