From a51ab9098a962bf77e59f5304849f2455ea9202a Mon Sep 17 00:00:00 2001 From: dongzi Date: Tue, 9 Jun 2026 17:42:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B5=8C=E5=A5=97=E5=AF=B9=E8=B1=A1=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/checker/notify-templates/README.md | 6 ++- .gitea/checker/notify-templates/dto.md | 17 +++++- .gitea/checker/notify-templates/vo.md | 3 +- .../codechecker/analyzer/DtoNestIndex.java | 6 +++ .../codechecker/analyzer/ImpactAnalyzer.java | 2 + .../analyzer/NestedObjectRoleResolver.java | 54 +++++++++++++++++++ .../codechecker/model/ClassChangeReport.java | 10 ++++ .../com/codechecker/notify/WeComNotifier.java | 25 ++++----- 8 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 .gitea/checker/src/main/java/com/codechecker/analyzer/NestedObjectRoleResolver.java diff --git a/.gitea/checker/notify-templates/README.md b/.gitea/checker/notify-templates/README.md index 38eee78..aa3831b 100644 --- a/.gitea/checker/notify-templates/README.md +++ b/.gitea/checker/notify-templates/README.md @@ -37,10 +37,12 @@ Push 触发 CI 后,按变更类的后缀(`Dto` / `Vo` / `Entity` / `Model` | 类类型 | request | response | 类转换 | |--------|:-------:|:--------:|:------:| -| Dto | ✅ | ❌ | ✅ | -| Vo | ❌ | ✅ | ✅ | +| Dto | ✅ | ✅ | ✅ | +| Vo | ✅ | ✅ | ✅ | | Entity / Model | ❌ | ❌ | ✅ | +Dto/Vo 均固定展示 request、response 两栏;无匹配接口时显示「无」。类转换栏仅在 `dto_entity_conversion.enabled: true` 时展示,关闭时不出现该小节。实际影响由接口索引 + 嵌套关系传播,不假定 Dto 仅 request、Vo 仅 response。 + ## 模版文件 | 文件 | 场景 | diff --git a/.gitea/checker/notify-templates/dto.md b/.gitea/checker/notify-templates/dto.md index e5f034f..b192bd2 100644 --- a/.gitea/checker/notify-templates/dto.md +++ b/.gitea/checker/notify-templates/dto.md @@ -1,7 +1,8 @@ # Dto 类变更通知模版 **识别规则**:类名以 `Dto` 结尾。 -**影响范围**:request + 类转换。 +**影响范围**:request + response + 类转换(无匹配时对应栏显示「无」)。 +**嵌套标识**:被其他 Dto/Vo 嵌套时在「变更对象」行追加 `(嵌套对象)`;若同时直接作接口入参/返回值根类型,再追加 `(顶层对象)`。纯顶层不标注。 --- @@ -39,6 +40,20 @@ --- +## 示例(嵌套对象) + +``` +> **变更对象: UserSelfDto嵌套对象)** +``` + +若该类同时直接出现在某接口 `@RequestBody` 或返回值类型中: + +``` +> **变更对象: SomeDto说明)(嵌套对象)(顶层对象)** +``` + +--- + ## 示例(类删除) ``` diff --git a/.gitea/checker/notify-templates/vo.md b/.gitea/checker/notify-templates/vo.md index 02c8a10..1289030 100644 --- a/.gitea/checker/notify-templates/vo.md +++ b/.gitea/checker/notify-templates/vo.md @@ -1,7 +1,8 @@ # Vo 类变更通知模版 **识别规则**:类名以 `Vo` 或 `VO` 结尾。 -**影响范围**:response + 类转换。 +**影响范围**:request + response + 类转换(无匹配时对应栏显示「无」)。 +**嵌套标识**:规则同 Dto——仅嵌套时标注 `(嵌套对象)`,嵌套且直接作接口根类型时追加 `(顶层对象)`。 --- diff --git a/.gitea/checker/src/main/java/com/codechecker/analyzer/DtoNestIndex.java b/.gitea/checker/src/main/java/com/codechecker/analyzer/DtoNestIndex.java index 2539339..65e027a 100644 --- a/.gitea/checker/src/main/java/com/codechecker/analyzer/DtoNestIndex.java +++ b/.gitea/checker/src/main/java/com/codechecker/analyzer/DtoNestIndex.java @@ -81,6 +81,12 @@ public class DtoNestIndex { return names; } + /** 是否被其他 Dto/Vo 嵌套引用(存在至少一个祖先容器) */ + public boolean hasAncestors(String className) { + Set ancestors = ancestorsOf.get(className); + return ancestors != null && !ancestors.isEmpty(); + } + /** 嵌套类型的 @RequestBody 根 Dto 祖先(仅 Dto 后缀) */ public Set findRequestBodyRoots(String className) { Set roots = new LinkedHashSet<>(); diff --git a/.gitea/checker/src/main/java/com/codechecker/analyzer/ImpactAnalyzer.java b/.gitea/checker/src/main/java/com/codechecker/analyzer/ImpactAnalyzer.java index 44410d7..f2db5e4 100644 --- a/.gitea/checker/src/main/java/com/codechecker/analyzer/ImpactAnalyzer.java +++ b/.gitea/checker/src/main/java/com/codechecker/analyzer/ImpactAnalyzer.java @@ -35,6 +35,8 @@ public class ImpactAnalyzer { matchEndpoints(report, endpointIndex, matchNames); } + report.setObjectRoleLabels(NestedObjectRoleResolver.resolve(report, nestIndex, endpointIndex)); + if (!config.isDtoEntityConversionEnabled()) { return; } diff --git a/.gitea/checker/src/main/java/com/codechecker/analyzer/NestedObjectRoleResolver.java b/.gitea/checker/src/main/java/com/codechecker/analyzer/NestedObjectRoleResolver.java new file mode 100644 index 0000000..0944862 --- /dev/null +++ b/.gitea/checker/src/main/java/com/codechecker/analyzer/NestedObjectRoleResolver.java @@ -0,0 +1,54 @@ +package com.codechecker.analyzer; + +import com.codechecker.model.ApiEndpoint; +import com.codechecker.model.ClassChangeReport; +import com.codechecker.model.ClassType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 判定 Dto/Vo 在类变更通知中的对象角色标签(方案 B:嵌套 + 可选顶层)。 + *

+ * 仅当存在嵌套祖先时标注;纯顶层不标注;既嵌套又直接作接口根类型时同时标注。 + */ +public final class NestedObjectRoleResolver { + + private NestedObjectRoleResolver() { + } + + public static List resolve(ClassChangeReport report, DtoNestIndex nestIndex, + Map endpointIndex) { + if (report.getClassType() != ClassType.DTO && report.getClassType() != ClassType.VO) { + return List.of(); + } + if (nestIndex == null) { + return List.of(); + } + String className = report.getClassName(); + if (!nestIndex.hasAncestors(className)) { + return List.of(); + } + List labels = new ArrayList<>(); + labels.add("嵌套对象"); + if (isDirectEndpointType(className, endpointIndex)) { + labels.add("顶层对象"); + } + return List.copyOf(labels); + } + + /** 是否直接出现在接口入参或返回值类型(非仅经祖先传播) */ + private static boolean isDirectEndpointType(String className, Map endpointIndex) { + if (className == null || className.isBlank() || endpointIndex == null) { + return false; + } + for (ApiEndpoint endpoint : endpointIndex.values()) { + if (endpoint.getParamTypes().contains(className) + || endpoint.getReturnTypes().contains(className)) { + return true; + } + } + return false; + } +} diff --git a/.gitea/checker/src/main/java/com/codechecker/model/ClassChangeReport.java b/.gitea/checker/src/main/java/com/codechecker/model/ClassChangeReport.java index 1dfc0d7..195632b 100644 --- a/.gitea/checker/src/main/java/com/codechecker/model/ClassChangeReport.java +++ b/.gitea/checker/src/main/java/com/codechecker/model/ClassChangeReport.java @@ -18,6 +18,7 @@ public class ClassChangeReport { private final List conversionEntities = new ArrayList<>(); private final List frontendImpactEndpoints = new ArrayList<>(); private final boolean conversionCheckEnabled; + private List objectRoleLabels = List.of(); public ClassChangeReport(String className, String oldClassName, ClassType classType, ClassChangeKind changeKind, String sourceFile, @@ -98,6 +99,15 @@ public class ClassChangeReport { return conversionCheckEnabled; } + /** 对象角色标签(如「嵌套对象」「顶层对象」),仅 Dto/Vo 且存在嵌套时非空 */ + public List getObjectRoleLabels() { + return objectRoleLabels; + } + + public void setObjectRoleLabels(List labels) { + this.objectRoleLabels = labels == null || labels.isEmpty() ? List.of() : List.copyOf(labels); + } + /** 追加一条字段变更 */ public void addFieldChange(FieldChange change) { fieldChanges.add(change); diff --git a/.gitea/checker/src/main/java/com/codechecker/notify/WeComNotifier.java b/.gitea/checker/src/main/java/com/codechecker/notify/WeComNotifier.java index c023610..e5085fa 100644 --- a/.gitea/checker/src/main/java/com/codechecker/notify/WeComNotifier.java +++ b/.gitea/checker/src/main/java/com/codechecker/notify/WeComNotifier.java @@ -82,14 +82,17 @@ public class WeComNotifier { return truncate(sb.toString()); } - /** 变更对象行:类名(绿)+ 可选中文说明(灰,整行加粗) */ + /** 变更对象行:类名(绿)+ 可选中文说明 + 嵌套角色标签(灰,整行加粗) */ private String formatChangeTarget(ClassChangeReport report) { - String name = colorInfo(safe(report.getClassName())); + StringBuilder line = new StringBuilder(colorInfo(safe(report.getClassName()))); String description = report.getClassDescription(); - if (description == null || description.isBlank()) { - return name; + if (description != null && !description.isBlank()) { + line.append("(").append(colorComment(description)).append(")"); } - return name + "(" + colorComment(description) + ")"; + for (String role : report.getObjectRoleLabels()) { + line.append("(").append(colorComment(role)).append(")"); + } + return line.toString(); } /** 头部元信息,每项一行引用(加粗) */ @@ -139,14 +142,12 @@ public class WeComNotifier { appendImpactByType(sb, report); } - /** Dto/Vo/Entity/Model 各展示不同的 request/response/转换段落 */ + /** Dto/Vo 均展示 request + response(二者可能交叉);Entity/Model 仅类转换 */ private void appendImpactByType(StringBuilder sb, ClassChangeReport report) { switch (report.getClassType()) { case DTO: - appendSectionIfNeeded(sb, report, true, false, true); - break; case VO: - appendSectionIfNeeded(sb, report, false, true, true); + appendSectionIfNeeded(sb, report, true, true, true); break; case ENTITY: case MODEL: @@ -170,7 +171,7 @@ public class WeComNotifier { appendEndpointList(sb, report.getFrontendImpactEndpoints()); sb.append("\n"); } - if (showConversion) { + if (showConversion && report.isConversionCheckEnabled()) { sb.append("### 类转换影响").append("\n"); appendConversionList(sb, report); } @@ -178,10 +179,6 @@ public class WeComNotifier { /** 渲染关联 Entity,每项一行 */ private void appendConversionList(StringBuilder sb, ClassChangeReport report) { - if (!report.isConversionCheckEnabled()) { - sb.append(quoteLine(colorComment("未开启检测"))).append("\n"); - return; - } if (report.getConversionEntities().isEmpty()) { sb.append(quoteLine(colorComment("无"))).append("\n"); return;