Browse Source

架构护栏新增日志

WangKang 1 week ago
parent
commit
161aa3a216

+ 100 - 9
emoon-admin/src/test/java/com/emoon/architecture/AiPlatformArchitectureTest.java

@@ -42,21 +42,67 @@ class AiPlatformArchitectureTest {
     void architectureViolationsMustNotGrowBeyondBaseline() throws IOException {
         Path root = findRepoRoot();
         Set<String> baseline = readBaseline(root.resolve(BASELINE));
-        List<String> currentViolations = scanViolations(root);
 
-        List<String> newViolations = currentViolations.stream()
-            .filter(violation -> !baseline.contains(pathOf(violation)))
-            .sorted()
-            .toList();
+        System.out.println("===========================================================");
+        System.out.println("  AI 中台架构护栏 — 源码扫描测试");
+        System.out.println("===========================================================");
+        System.out.println();
+        System.out.println("基线文件: " + BASELINE + " (" + baseline.size() + " 条历史违规已冻结)");
+        System.out.println();
+        System.out.println("扫描规则:");
+        System.out.println("  R1. API 模块禁止包含 Impl / Mapper / Controller / EngineImpl");
+        System.out.println("  R2. API 模块禁止引入 Web / MyBatis / AI SDK / PDFBox / Tika 等重依赖");
+        System.out.println("  R3. openplatform 入口层禁止直接 import Mapper");
+        System.out.println("  R4. Controller 禁止直接 import Mapper");
+        System.out.println("  R5. Card 模块禁止绕过 Agent 直接依赖 MCP 实现层");
+        System.out.println("  R6. Billing 模块禁止反向依赖 Agent 实现层");
+        System.out.println("  R7. Operation 模块禁止依赖核心链路 infrastructure 做复杂联查");
+        System.out.println();
+
+        ScanResult result = scanViolations(root, baseline);
+
+        System.out.println("扫描结果:");
+        System.out.println("  扫描文件总数:       " + result.totalFiles);
+        System.out.println("  API 模块文件:       " + result.apiModuleFiles);
+        System.out.println("  openplatform 文件:  " + result.openPlatformFiles);
+        System.out.println("  Controller 文件:    " + result.controllerFiles);
+        System.out.println("-----------------------------------------------------------");
+        System.out.println("  检测到违规总数:     " + result.violations.size());
+        System.out.println("  其中基线已豁免:     " + result.baselineExemptCount);
+        System.out.println("  本次新增违规:       " + result.newViolations.size());
+
+        if (!result.newViolations.isEmpty()) {
+            System.out.println();
+            System.out.println("  === 新增违规明细 ===");
+            for (String v : result.newViolations) {
+                System.out.println("  " + v);
+            }
+        }
+
+        if (!result.violations.isEmpty()) {
+            System.out.println();
+            System.out.println("  违规分布:");
+            for (Map.Entry<String, Long> entry : result.violationsByRule.entrySet()) {
+                System.out.println("    " + entry.getKey() + ": " + entry.getValue() + " 条");
+            }
+        }
+
+        System.out.println();
+        if (result.newViolations.isEmpty()) {
+            System.out.println("  ✓ 架构测试通过 — 无新增违规");
+        } else {
+            System.out.println("  ✗ 架构测试失败 — 存在 " + result.newViolations.size() + " 条新增违规");
+        }
+        System.out.println("===========================================================");
 
-        if (!newViolations.isEmpty()) {
+        if (!result.newViolations.isEmpty()) {
             fail("""
                 检测到新的 Maven/架构边界违规。历史违规已经冻结为基线,新代码不能继续扩大问题。
                 如确实需要例外,请先补 ADR,并由技术负责人更新 baseline。
 
                 新增违规:
                 %s
-                """.formatted(String.join(System.lineSeparator(), newViolations)));
+                """.formatted(String.join(System.lineSeparator(), result.newViolations)));
         }
     }
 
@@ -87,25 +133,70 @@ class AiPlatformArchitectureTest {
         return baseline;
     }
 
-    private List<String> scanViolations(Path root) throws IOException {
+    private ScanResult scanViolations(Path root, Set<String> baseline) throws IOException {
         List<String> violations = new ArrayList<>();
+        int totalFiles = 0;
+        int apiModuleFiles = 0;
+        int openPlatformFiles = 0;
+        int controllerFiles = 0;
+
         try (Stream<Path> files = Files.walk(root)) {
             for (Path file : files
                 .filter(path -> path.toString().endsWith(".java"))
                 .filter(this::isMainSource)
                 .sorted(Comparator.comparing(Path::toString))
                 .toList()) {
+                totalFiles++;
                 String relativePath = normalize(root.relativize(file));
                 String source = Files.readString(file, StandardCharsets.UTF_8);
+
+                if (relativePath.contains("/emoon-modules-api/")
+                    || (relativePath.contains("/com/emoon/ai/") && relativePath.contains("/api/"))) {
+                    apiModuleFiles++;
+                }
+                if (relativePath.startsWith("emoon-openplatform/src/main/java/")) {
+                    openPlatformFiles++;
+                }
+                if (relativePath.contains("/controller/")) {
+                    controllerFiles++;
+                }
+
                 scanApiModuleRules(violations, relativePath, source);
                 scanOpenPlatformRules(violations, relativePath, source);
                 scanControllerRules(violations, relativePath, source);
                 scanTargetAiRules(violations, relativePath, source);
             }
         }
-        return violations;
+
+        List<String> newViolations = violations.stream()
+            .filter(v -> !baseline.contains(pathOf(v)))
+            .sorted()
+            .toList();
+        long baselineExemptCount = violations.size() - newViolations.size();
+
+        Map<String, Long> violationsByRule = new LinkedHashMap<>();
+        for (String v : violations) {
+            String rule = v.contains("|") ? v.substring(0, v.indexOf('|')) : "UNKNOWN";
+            violationsByRule.merge(rule, 1L, Long::sum);
+        }
+
+        return new ScanResult(
+            totalFiles, apiModuleFiles, openPlatformFiles, controllerFiles,
+            violations, newViolations, baselineExemptCount, violationsByRule
+        );
     }
 
+    private record ScanResult(
+        int totalFiles,
+        int apiModuleFiles,
+        int openPlatformFiles,
+        int controllerFiles,
+        List<String> violations,
+        List<String> newViolations,
+        long baselineExemptCount,
+        Map<String, Long> violationsByRule
+    ) {}
+
     private boolean isMainSource(Path path) {
         String normalized = normalize(path.toAbsolutePath());
         return normalized.contains("/src/main/java/")