This commit is contained in:
@@ -37,10 +37,12 @@ Push 触发 CI 后,按变更类的后缀(`Dto` / `Vo` / `Entity` / `Model`
|
|||||||
|
|
||||||
| 类类型 | request | response | 类转换 |
|
| 类类型 | request | response | 类转换 |
|
||||||
|--------|:-------:|:--------:|:------:|
|
|--------|:-------:|:--------:|:------:|
|
||||||
| Dto | ✅ | ❌ | ✅ |
|
| Dto | ✅ | ✅ | ✅ |
|
||||||
| Vo | ❌ | ✅ | ✅ |
|
| Vo | ✅ | ✅ | ✅ |
|
||||||
| Entity / Model | ❌ | ❌ | ✅ |
|
| Entity / Model | ❌ | ❌ | ✅ |
|
||||||
|
|
||||||
|
Dto/Vo 均固定展示 request、response 两栏;无匹配接口时显示「无」。类转换栏仅在 `dto_entity_conversion.enabled: true` 时展示,关闭时不出现该小节。实际影响由接口索引 + 嵌套关系传播,不假定 Dto 仅 request、Vo 仅 response。
|
||||||
|
|
||||||
## 模版文件
|
## 模版文件
|
||||||
|
|
||||||
| 文件 | 场景 |
|
| 文件 | 场景 |
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Dto 类变更通知模版
|
# Dto 类变更通知模版
|
||||||
|
|
||||||
**识别规则**:类名以 `Dto` 结尾。
|
**识别规则**:类名以 `Dto` 结尾。
|
||||||
**影响范围**:request + 类转换。
|
**影响范围**:request + response + 类转换(无匹配时对应栏显示「无」)。
|
||||||
|
**嵌套标识**:被其他 Dto/Vo 嵌套时在「变更对象」行追加 `(嵌套对象)`;若同时直接作接口入参/返回值根类型,再追加 `(顶层对象)`。纯顶层不标注。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -39,6 +40,20 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 示例(嵌套对象)
|
||||||
|
|
||||||
|
```
|
||||||
|
> **变更对象: <font color="info">UserSelfDto</font>(<font color="comment">嵌套对象</font>)**
|
||||||
|
```
|
||||||
|
|
||||||
|
若该类同时直接出现在某接口 `@RequestBody` 或返回值类型中:
|
||||||
|
|
||||||
|
```
|
||||||
|
> **变更对象: <font color="info">SomeDto</font>(<font color="comment">说明</font>)(<font color="comment">嵌套对象</font>)(<font color="comment">顶层对象</font>)**
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 示例(类删除)
|
## 示例(类删除)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Vo 类变更通知模版
|
# Vo 类变更通知模版
|
||||||
|
|
||||||
**识别规则**:类名以 `Vo` 或 `VO` 结尾。
|
**识别规则**:类名以 `Vo` 或 `VO` 结尾。
|
||||||
**影响范围**:response + 类转换。
|
**影响范围**:request + response + 类转换(无匹配时对应栏显示「无」)。
|
||||||
|
**嵌套标识**:规则同 Dto——仅嵌套时标注 `(嵌套对象)`,嵌套且直接作接口根类型时追加 `(顶层对象)`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,12 @@ public class DtoNestIndex {
|
|||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 是否被其他 Dto/Vo 嵌套引用(存在至少一个祖先容器) */
|
||||||
|
public boolean hasAncestors(String className) {
|
||||||
|
Set<String> ancestors = ancestorsOf.get(className);
|
||||||
|
return ancestors != null && !ancestors.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
/** 嵌套类型的 @RequestBody 根 Dto 祖先(仅 Dto 后缀) */
|
/** 嵌套类型的 @RequestBody 根 Dto 祖先(仅 Dto 后缀) */
|
||||||
public Set<String> findRequestBodyRoots(String className) {
|
public Set<String> findRequestBodyRoots(String className) {
|
||||||
Set<String> roots = new LinkedHashSet<>();
|
Set<String> roots = new LinkedHashSet<>();
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ public class ImpactAnalyzer {
|
|||||||
matchEndpoints(report, endpointIndex, matchNames);
|
matchEndpoints(report, endpointIndex, matchNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
report.setObjectRoleLabels(NestedObjectRoleResolver.resolve(report, nestIndex, endpointIndex));
|
||||||
|
|
||||||
if (!config.isDtoEntityConversionEnabled()) {
|
if (!config.isDtoEntityConversionEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:嵌套 + 可选顶层)。
|
||||||
|
* <p>
|
||||||
|
* 仅当存在嵌套祖先时标注;纯顶层不标注;既嵌套又直接作接口根类型时同时标注。
|
||||||
|
*/
|
||||||
|
public final class NestedObjectRoleResolver {
|
||||||
|
|
||||||
|
private NestedObjectRoleResolver() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> resolve(ClassChangeReport report, DtoNestIndex nestIndex,
|
||||||
|
Map<String, ApiEndpoint> 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<String> labels = new ArrayList<>();
|
||||||
|
labels.add("嵌套对象");
|
||||||
|
if (isDirectEndpointType(className, endpointIndex)) {
|
||||||
|
labels.add("顶层对象");
|
||||||
|
}
|
||||||
|
return List.copyOf(labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 是否直接出现在接口入参或返回值类型(非仅经祖先传播) */
|
||||||
|
private static boolean isDirectEndpointType(String className, Map<String, ApiEndpoint> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ public class ClassChangeReport {
|
|||||||
private final List<String> conversionEntities = new ArrayList<>();
|
private final List<String> conversionEntities = new ArrayList<>();
|
||||||
private final List<ApiEndpoint> frontendImpactEndpoints = new ArrayList<>();
|
private final List<ApiEndpoint> frontendImpactEndpoints = new ArrayList<>();
|
||||||
private final boolean conversionCheckEnabled;
|
private final boolean conversionCheckEnabled;
|
||||||
|
private List<String> objectRoleLabels = List.of();
|
||||||
|
|
||||||
public ClassChangeReport(String className, String oldClassName, ClassType classType,
|
public ClassChangeReport(String className, String oldClassName, ClassType classType,
|
||||||
ClassChangeKind changeKind, String sourceFile,
|
ClassChangeKind changeKind, String sourceFile,
|
||||||
@@ -98,6 +99,15 @@ public class ClassChangeReport {
|
|||||||
return conversionCheckEnabled;
|
return conversionCheckEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 对象角色标签(如「嵌套对象」「顶层对象」),仅 Dto/Vo 且存在嵌套时非空 */
|
||||||
|
public List<String> getObjectRoleLabels() {
|
||||||
|
return objectRoleLabels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setObjectRoleLabels(List<String> labels) {
|
||||||
|
this.objectRoleLabels = labels == null || labels.isEmpty() ? List.of() : List.copyOf(labels);
|
||||||
|
}
|
||||||
|
|
||||||
/** 追加一条字段变更 */
|
/** 追加一条字段变更 */
|
||||||
public void addFieldChange(FieldChange change) {
|
public void addFieldChange(FieldChange change) {
|
||||||
fieldChanges.add(change);
|
fieldChanges.add(change);
|
||||||
|
|||||||
@@ -82,14 +82,17 @@ public class WeComNotifier {
|
|||||||
return truncate(sb.toString());
|
return truncate(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 变更对象行:类名(绿)+ 可选中文说明(灰,整行加粗) */
|
/** 变更对象行:类名(绿)+ 可选中文说明 + 嵌套角色标签(灰,整行加粗) */
|
||||||
private String formatChangeTarget(ClassChangeReport report) {
|
private String formatChangeTarget(ClassChangeReport report) {
|
||||||
String name = colorInfo(safe(report.getClassName()));
|
StringBuilder line = new StringBuilder(colorInfo(safe(report.getClassName())));
|
||||||
String description = report.getClassDescription();
|
String description = report.getClassDescription();
|
||||||
if (description == null || description.isBlank()) {
|
if (description != null && !description.isBlank()) {
|
||||||
return name;
|
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);
|
appendImpactByType(sb, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Dto/Vo/Entity/Model 各展示不同的 request/response/转换段落 */
|
/** Dto/Vo 均展示 request + response(二者可能交叉);Entity/Model 仅类转换 */
|
||||||
private void appendImpactByType(StringBuilder sb, ClassChangeReport report) {
|
private void appendImpactByType(StringBuilder sb, ClassChangeReport report) {
|
||||||
switch (report.getClassType()) {
|
switch (report.getClassType()) {
|
||||||
case DTO:
|
case DTO:
|
||||||
appendSectionIfNeeded(sb, report, true, false, true);
|
|
||||||
break;
|
|
||||||
case VO:
|
case VO:
|
||||||
appendSectionIfNeeded(sb, report, false, true, true);
|
appendSectionIfNeeded(sb, report, true, true, true);
|
||||||
break;
|
break;
|
||||||
case ENTITY:
|
case ENTITY:
|
||||||
case MODEL:
|
case MODEL:
|
||||||
@@ -170,7 +171,7 @@ public class WeComNotifier {
|
|||||||
appendEndpointList(sb, report.getFrontendImpactEndpoints());
|
appendEndpointList(sb, report.getFrontendImpactEndpoints());
|
||||||
sb.append("\n");
|
sb.append("\n");
|
||||||
}
|
}
|
||||||
if (showConversion) {
|
if (showConversion && report.isConversionCheckEnabled()) {
|
||||||
sb.append("### 类转换影响").append("\n");
|
sb.append("### 类转换影响").append("\n");
|
||||||
appendConversionList(sb, report);
|
appendConversionList(sb, report);
|
||||||
}
|
}
|
||||||
@@ -178,10 +179,6 @@ public class WeComNotifier {
|
|||||||
|
|
||||||
/** 渲染关联 Entity,每项一行 */
|
/** 渲染关联 Entity,每项一行 */
|
||||||
private void appendConversionList(StringBuilder sb, ClassChangeReport report) {
|
private void appendConversionList(StringBuilder sb, ClassChangeReport report) {
|
||||||
if (!report.isConversionCheckEnabled()) {
|
|
||||||
sb.append(quoteLine(colorComment("未开启检测"))).append("\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (report.getConversionEntities().isEmpty()) {
|
if (report.getConversionEntities().isEmpty()) {
|
||||||
sb.append(quoteLine(colorComment("无"))).append("\n");
|
sb.append(quoteLine(colorComment("无"))).append("\n");
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user