This commit is contained in:
@@ -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>**
|
||||
|
||||
@@ -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>**
|
||||
|
||||
@@ -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>**
|
||||
|
||||
@@ -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>**
|
||||
|
||||
@@ -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>**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) > 类 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()) {
|
||||
|
||||
Reference in New Issue
Block a user