|
|
@@ -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/")
|