类中文说明补充优化
All checks were successful
类变更检测 / class-change-check (push) Successful in 14s

This commit is contained in:
2026-06-08 16:31:53 +08:00
parent 90afda6c3c
commit b9242a9f2b
9 changed files with 136 additions and 12 deletions

View File

@@ -18,7 +18,7 @@ Push 触发 CI 后,按变更类的后缀(`Dto` / `Vo` / `Entity` / `Model`
## 布局约定
1. **# 【类变更通知】** — 头部 4 项,每项一行 `>**标签: 值**`(加粗,冒号后两空格)
1. **# 【类变更通知】** — 头部 4 项,每项一行 `>**标签: 值**`(加粗,冒号后两空格);变更对象括号内展示类中文说明(@Schema / Javadoc无说明则仅类名
2. **## 【对象变更细节】** — 统计行 + 每条变更单行(标签/说明/类型合并)
3. **## 【影响范围】** — 各 ### 小节内,每项一行引用
@@ -27,7 +27,7 @@ Push 触发 CI 后,按变更类的后缀(`Dto` / `Vo` / `Entity` / `Model`
```
# 【类变更通知】
> **变更对象: <font color="info">ApplyAttendanceChangeDto</font>Dto**
> **变更对象: <font color="info">ApplyAttendanceChangeDto</font><font color="comment">流程表单 [出勤变更]</font>**
> **修改人: <font color="comment">dongzi</font>**
> **时间: <font color="comment">2026-06-07 20:14:35</font>**
> **路径: <font color="comment">jnpf-ftb/.../ApplyAttendanceChangeDto.java</font>**

View File

@@ -10,7 +10,7 @@
```
# 【类变更通知】
> **变更对象: <font color="info">ApplyAttendanceChangeDto</font>Dto**
> **变更对象: <font color="info">ApplyAttendanceChangeDto</font><font color="comment">流程表单 [出勤变更]</font>**
> **修改人: <font color="comment">dongzi</font>**
> **时间: <font color="comment">2026-06-07 20:14:35</font>**
> **路径: <font color="comment">jnpf-ftb/jnpf-ftb-entity/src/main/java/jnpf/model/workflow/dto/ApplyAttendanceChangeDto.java</font>**

View File

@@ -10,7 +10,7 @@
```
# 【类变更通知】
> **变更对象: <font color="info">TrainingPositionEntity</font>Entity**
> **变更对象: <font color="info">TrainingPositionEntity</font><font color="comment">培训岗位</font>**
> **修改人: <font color="comment">张三</font>**
> **时间: <font color="comment">2026-06-07 14:30:00</font>**
> **路径: <font color="comment">jnpf-ftb/jnpf-ftb-entity/src/main/java/jnpf/entity/training/TrainingPositionEntity.java</font>**

View File

@@ -10,7 +10,7 @@
```
# 【类变更通知】
> **变更对象: <font color="info">AttendanceRuleModel</font>Model**
> **变更对象: <font color="info">AttendanceRuleModel</font><font color="comment">考勤规则</font>**
> **修改人: <font color="comment">张三</font>**
> **时间: <font color="comment">2026-06-07 14:30:00</font>**
> **路径: <font color="comment">jnpf-ftb/jnpf-ftb-entity/src/main/java/jnpf/model/attendance/AttendanceRuleModel.java</font>**

View File

@@ -10,7 +10,7 @@
```
# 【类变更通知】
> **变更对象: <font color="info">AttendanceDetailVo</font>Vo**
> **变更对象: <font color="info">AttendanceDetailVo</font><font color="comment">考勤详情</font>**
> **修改人: <font color="comment">张三</font>**
> **时间: <font color="comment">2026-06-07 14:30:00</font>**
> **路径: <font color="comment">jnpf-ftb/jnpf-ftb-entity/src/main/java/jnpf/model/attendance/vo/AttendanceDetailVo.java</font>**

View File

@@ -56,13 +56,17 @@ public class ClassChangeAnalyzer {
String path = changedFile.getRelativePath();
String oldSource = gitScanner.readFileAtCommit(oldSha, path);
String classDescription = classDeclParser.extractClassDescription(
oldSource, changedFile.getClassName());
ClassChangeReport report = new ClassChangeReport(
changedFile.getClassName(),
null,
changedFile.getClassType(),
ClassChangeKind.DELETED,
path,
config.isDtoEntityConversionEnabled()
config.isDtoEntityConversionEnabled(),
classDescription
);
impactAnalyzer.analyze(report, endpointIndex, config, repoRoot, oldSource, oldSource);
return report;
@@ -105,13 +109,16 @@ public class ClassChangeAnalyzer {
return null;
}
String classDescription = classDeclParser.extractClassDescription(newSource, newClassName);
ClassChangeReport report = new ClassChangeReport(
newClassName,
renamed ? oldClassName : null,
changedFile.getClassType(),
changeKind,
newPath,
config.isDtoEntityConversionEnabled()
config.isDtoEntityConversionEnabled(),
classDescription
);
fieldChanges.forEach(report::addFieldChange);
impactAnalyzer.analyze(report, endpointIndex, config, repoRoot, newSource, oldSource);

View File

@@ -12,6 +12,7 @@ public class ClassChangeReport {
private final ClassType classType;
private final ClassChangeKind changeKind;
private final String sourceFile;
private final String classDescription;
private final List<FieldChange> fieldChanges = new ArrayList<>();
private final List<ApiEndpoint> inputImpactEndpoints = new ArrayList<>();
private final List<String> conversionEntities = new ArrayList<>();
@@ -20,13 +21,14 @@ public class ClassChangeReport {
public ClassChangeReport(String className, String oldClassName, ClassType classType,
ClassChangeKind changeKind, String sourceFile,
boolean conversionCheckEnabled) {
boolean conversionCheckEnabled, String classDescription) {
this.className = className;
this.oldClassName = oldClassName;
this.classType = classType;
this.changeKind = changeKind;
this.sourceFile = sourceFile;
this.conversionCheckEnabled = conversionCheckEnabled;
this.classDescription = classDescription == null ? "" : classDescription.trim();
}
/** 当前(新)简单类名 */
@@ -62,6 +64,11 @@ public class ClassChangeReport {
return sourceFile;
}
/** 类级中文说明(@Schema / 类 Javadoc无则空串 */
public String getClassDescription() {
return classDescription;
}
/** 是否整文件删除 */
public boolean isDeleted() {
return changeKind == ClassChangeKind.DELETED;

View File

@@ -82,11 +82,20 @@ public class WeComNotifier {
return truncate(sb.toString());
}
/** 变更对象行:类名(绿)+ 可选中文说明(灰,整行加粗) */
private String formatChangeTarget(ClassChangeReport report) {
String name = colorInfo(safe(report.getClassName()));
String description = report.getClassDescription();
if (description == null || description.isBlank()) {
return name;
}
return name + "" + colorComment(description) + "";
}
/** 头部元信息,每项一行引用(加粗) */
private void appendHeader(StringBuilder sb, ClassChangeReport report,
String modifier, String modifyTime) {
sb.append(quoteKvBold("变更对象", colorInfo(safe(report.getClassName()))
+ "" + report.getClassType().getLabel() + "")).append("\n");
sb.append(quoteKvBold("变更对象", formatChangeTarget(report))).append("\n");
sb.append(quoteKvBold("修改人", colorComment(modifier))).append("\n");
sb.append(quoteKvBold("时间", colorComment(modifyTime))).append("\n");
sb.append(quoteKvBold("路径", colorComment(report.getSourceFile()))).append("\n");

View File

@@ -4,9 +4,16 @@ import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.comments.JavadocComment;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
import java.util.Optional;
/**
* 从 Java 源文件路径或 AST 解析类名(简单名 / 全限定名)。
* 从 Java 源文件路径或 AST 解析类名(简单名 / 全限定名)及类级中文说明
*/
public class ClassDeclParser {
@@ -60,6 +67,100 @@ public class ClassDeclParser {
return inferQualifiedFromPath(relativePath, simpleName);
}
/**
* 提取类级中文说明:@Schema(description/title) &gt; 类 Javadoc 首段。
*/
public String extractClassDescription(String source, String expectedClassName) {
if (source == null || source.isBlank()) {
return "";
}
try {
CompilationUnit cu = StaticJavaParser.parse(source);
ClassOrInterfaceDeclaration classDecl = findClass(cu, expectedClassName);
if (classDecl == null) {
return "";
}
String fromSchema = readSchemaDescription(classDecl);
if (!fromSchema.isEmpty()) {
return fromSchema;
}
return extractClassJavadoc(classDecl);
} catch (Exception ignored) {
return "";
}
}
private ClassOrInterfaceDeclaration findClass(CompilationUnit cu, String expectedClassName) {
if (expectedClassName != null && !expectedClassName.isBlank()) {
for (TypeDeclaration<?> type : cu.getTypes()) {
if (type instanceof ClassOrInterfaceDeclaration) {
ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) type;
if (classDecl.getNameAsString().equals(expectedClassName)) {
return classDecl;
}
}
}
}
for (TypeDeclaration<?> type : cu.getTypes()) {
if (type instanceof ClassOrInterfaceDeclaration) {
return (ClassOrInterfaceDeclaration) type;
}
}
return null;
}
private String readSchemaDescription(ClassOrInterfaceDeclaration classDecl) {
for (AnnotationExpr annotation : classDecl.getAnnotations()) {
if (!"Schema".equals(annotation.getNameAsString())) {
continue;
}
String description = readAnnotationStringValue(annotation, "description");
if (!description.isEmpty()) {
return description;
}
String title = readAnnotationStringValue(annotation, "title");
if (!title.isEmpty()) {
return title;
}
}
return "";
}
private String readAnnotationStringValue(AnnotationExpr annotation, String attributeName) {
if (annotation.isNormalAnnotationExpr()) {
NormalAnnotationExpr normal = annotation.asNormalAnnotationExpr();
for (var pair : normal.getPairs()) {
if (pair.getNameAsString().equals(attributeName)) {
return literalString(pair.getValue());
}
}
return "";
}
if (annotation.isSingleMemberAnnotationExpr()) {
SingleMemberAnnotationExpr single = annotation.asSingleMemberAnnotationExpr();
if ("value".equals(attributeName) || "description".equals(attributeName)) {
return literalString(single.getMemberValue());
}
}
return "";
}
private String literalString(Expression expression) {
if (expression.isStringLiteralExpr()) {
return expression.asStringLiteralExpr().getValue().trim();
}
return "";
}
private String extractClassJavadoc(ClassOrInterfaceDeclaration classDecl) {
Optional<JavadocComment> javadoc = classDecl.getJavadocComment();
if (javadoc.isEmpty()) {
return "";
}
String text = javadoc.get().parse().getDescription().toText();
return text == null ? "" : text.trim().replaceAll("\\s+", " ");
}
/** 从 src/main/java/ 后的路径推断 package.className */
public static String inferQualifiedFromPath(String relativePath, String className) {
if (relativePath == null || relativePath.isBlank()) {