From a5749339a12641f3eb41611de46fb2c0b6ff528e Mon Sep 17 00:00:00 2001 From: dongzi Date: Mon, 8 Jun 2026 14:01:05 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=AE=E5=BD=95=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../class-checker/dependency-reduced-pom.xml | 51 ---- .gitea/class-checker/pom.xml | 82 ----- .../main/java/com/aicheck/ClassCheckMain.java | 74 ----- .../aicheck/analyzer/ClassChangeAnalyzer.java | 72 ----- .../analyzer/EndpointIndexBuilder.java | 32 -- .../com/aicheck/analyzer/FieldDiffEngine.java | 54 ---- .../com/aicheck/analyzer/ImpactAnalyzer.java | 59 ---- .../java/com/aicheck/config/AppConfig.java | 121 -------- .../com/aicheck/git/GitChangeScanner.java | 128 -------- .../java/com/aicheck/model/ApiEndpoint.java | 49 --- .../com/aicheck/model/ChangedClassFile.java | 35 --- .../com/aicheck/model/ClassChangeReport.java | 79 ----- .../java/com/aicheck/model/ClassType.java | 41 --- .../java/com/aicheck/model/FieldChange.java | 69 ----- .../java/com/aicheck/model/FieldInfo.java | 46 --- .../com/aicheck/notify/WeComNotifier.java | 179 ----------- .../com/aicheck/parser/ClassFieldParser.java | 58 ---- .../com/aicheck/parser/ConversionParser.java | 94 ------ .../com/aicheck/parser/EndpointParser.java | 282 ------------------ .../com/aicheck/parser/TypeNameUtils.java | 106 ------- .../target/maven-archiver/pom.properties | 5 - .../compile/default-compile/createdFiles.lst | 24 -- .../compile/default-compile/inputFiles.lst | 18 -- 23 files changed, 1758 deletions(-) delete mode 100644 .gitea/class-checker/dependency-reduced-pom.xml delete mode 100644 .gitea/class-checker/pom.xml delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/ClassCheckMain.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/analyzer/ClassChangeAnalyzer.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/analyzer/EndpointIndexBuilder.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/analyzer/FieldDiffEngine.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/analyzer/ImpactAnalyzer.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/config/AppConfig.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/git/GitChangeScanner.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/model/ApiEndpoint.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/model/ChangedClassFile.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/model/ClassChangeReport.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/model/ClassType.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/model/FieldChange.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/model/FieldInfo.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/notify/WeComNotifier.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/parser/ClassFieldParser.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/parser/ConversionParser.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/parser/EndpointParser.java delete mode 100644 .gitea/class-checker/src/main/java/com/aicheck/parser/TypeNameUtils.java delete mode 100644 .gitea/class-checker/target/maven-archiver/pom.properties delete mode 100644 .gitea/class-checker/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst delete mode 100644 .gitea/class-checker/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst 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