diff --git a/.gitea/checker/notify-templates/field-description.md b/.gitea/checker/notify-templates/field-description.md
index 88fe4de..adde17f 100644
--- a/.gitea/checker/notify-templates/field-description.md
+++ b/.gitea/checker/notify-templates/field-description.md
@@ -16,11 +16,11 @@
每条变更占**一行**,标签、说明、类型横向排列,冒号后两空格:
```
-> **共 4 项变更**
+> **共 2 项变更**
-> [修改] `taskId` 说明: 流程主键 类型: Integer → String
+> [重命名] taskId → taskIds 说明: 流程主键
-> [新增] `storeId` 说明: 门店ID
+> [删除] `changeUserNickName` 说明: 变更人员别名
```
| 操作 | 标签 | 类型段 |
@@ -28,6 +28,14 @@
| 新增 | info `[新增]` | 无 |
| 删除 | warning `[删除]` | 无 |
| 修改 | warning `[修改]` | 仅类型变化时出现 |
+| 重命名 | warning `[重命名]` | 说明匹配时合并删除+新增;类型变化时附带类型行 |
+
+### 重命名配对规则
+
+- 删除+新增且**类型相同、说明相同**(非空)→ `[重命名]`
+- 删除+新增且**说明相同但类型不同** → `[重命名]` + 类型行
+- 说明均为空时也可配对
+- 说明不同则不配对,保持删除+新增
- 统计行加粗,数字用 warning(橙色),文案为「共 N 项变更」(不含「字段」)
- 多条变更之间用**空行**分隔
@@ -43,5 +51,5 @@
## 实现
- `ClassFieldParser.extractFieldLabel()`
-- `FieldDiffEngine` — 仅类型变化产生 `[修改]`
+- `FieldDiffEngine` — 类型变化产生 `[修改]`,说明匹配的删除+新增合并为 `[重命名]`
- `WeComNotifier.formatFieldChange()`
diff --git a/.gitea/checker/src/main/java/com/autoCheck/analyzer/FieldDiffEngine.java b/.gitea/checker/src/main/java/com/autoCheck/analyzer/FieldDiffEngine.java
index 07cb541..5d34456 100644
--- a/.gitea/checker/src/main/java/com/autoCheck/analyzer/FieldDiffEngine.java
+++ b/.gitea/checker/src/main/java/com/autoCheck/analyzer/FieldDiffEngine.java
@@ -5,37 +5,146 @@ import com.aicheck.model.FieldInfo;
import java.util.ArrayList;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
- * 对比新旧字段列表,产出新增/删除/类型修改(纯注释变更忽略)。
+ * 对比新旧字段列表,产出新增/删除/类型修改/重命名(纯注释变更忽略)。
*/
public class FieldDiffEngine {
- /** 按字段名对比,仅类型变化记为 MODIFIED */
+ /**
+ * 按字段名对比;删除+新增且说明匹配时合并为重命名。
+ * 输出顺序:按新字段声明顺序,未配对的删除字段置于末尾。
+ */
public List diff(List oldFields, List newFields) {
Map oldMap = toMap(oldFields);
Map newMap = toMap(newFields);
- List changes = new ArrayList<>();
- for (Map.Entry entry : newMap.entrySet()) {
- FieldInfo oldField = oldMap.get(entry.getKey());
- FieldInfo newField = entry.getValue();
+ List modified = new ArrayList<>();
+ List added = new ArrayList<>();
+ List removed = new ArrayList<>();
+
+ for (FieldInfo newField : newFields) {
+ FieldInfo oldField = oldMap.get(newField.getName());
if (oldField == null) {
- changes.add(FieldChange.added(newField));
+ added.add(newField);
} else if (!oldField.getType().equals(newField.getType())) {
- changes.add(FieldChange.modified(oldField, newField, buildTypeDetail(oldField, newField)));
+ modified.add(FieldChange.modified(oldField, newField, buildTypeDetail(oldField, newField)));
}
// 仅 @Schema / 注释文案变化:不纳入字段变更
}
- for (Map.Entry entry : oldMap.entrySet()) {
- if (!newMap.containsKey(entry.getKey())) {
- changes.add(FieldChange.removed(entry.getValue()));
+ for (FieldInfo oldField : oldFields) {
+ if (!newMap.containsKey(oldField.getName())) {
+ removed.add(oldField);
}
}
- return changes;
+
+ List renamed = pairRenames(removed, added);
+ return mergeInOrder(newFields, renamed, modified, added, removed);
+ }
+
+ /**
+ * 将删除+新增配对为字段重命名。
+ * 优先:说明相同且类型相同;其次:说明相同但类型不同(重命名+改类型)。
+ */
+ private List pairRenames(List removed, List added) {
+ List renames = new ArrayList<>();
+ Set matchedRemoved = new LinkedHashSet<>();
+ Set matchedAdded = new LinkedHashSet<>();
+
+ for (FieldInfo oldField : removed) {
+ FieldInfo pair = findRenamePair(oldField, added, matchedAdded, true);
+ if (pair == null) {
+ pair = findRenamePair(oldField, added, matchedAdded, false);
+ }
+ if (pair != null) {
+ renames.add(FieldChange.renamed(oldField, pair));
+ matchedRemoved.add(oldField);
+ matchedAdded.add(pair);
+ }
+ }
+
+ removed.removeIf(matchedRemoved::contains);
+ added.removeIf(matchedAdded::contains);
+ return renames;
+ }
+
+ private FieldInfo findRenamePair(FieldInfo removed, List added,
+ Set excluded, boolean requireSameType) {
+ for (FieldInfo candidate : added) {
+ if (excluded.contains(candidate)) {
+ continue;
+ }
+ if (!descriptionsMatch(removed, candidate)) {
+ continue;
+ }
+ if (requireSameType && !removed.getType().equals(candidate.getType())) {
+ continue;
+ }
+ if (!requireSameType && removed.getType().equals(candidate.getType())) {
+ continue;
+ }
+ return candidate;
+ }
+ return null;
+ }
+
+ /** 说明相同(非空)或双方均为空时视为匹配 */
+ private boolean descriptionsMatch(FieldInfo oldField, FieldInfo newField) {
+ String oldDesc = normalizeDescription(oldField.getDescription());
+ String newDesc = normalizeDescription(newField.getDescription());
+ if (oldDesc.isEmpty() && newDesc.isEmpty()) {
+ return true;
+ }
+ if (oldDesc.isEmpty() || newDesc.isEmpty()) {
+ return false;
+ }
+ return oldDesc.equals(newDesc);
+ }
+
+ private String normalizeDescription(String description) {
+ return description == null ? "" : description.trim();
+ }
+
+ /** 按新字段声明顺序合并各变更类型 */
+ private List mergeInOrder(List newFields, List renamed,
+ List modified, List added,
+ List removed) {
+ Map renamedByNewName = new LinkedHashMap<>();
+ for (FieldChange change : renamed) {
+ renamedByNewName.put(change.getFieldName(), change);
+ }
+
+ Map modifiedByName = new LinkedHashMap<>();
+ for (FieldChange change : modified) {
+ modifiedByName.put(change.getFieldName(), change);
+ }
+
+ Set emitted = new LinkedHashSet<>();
+ List result = new ArrayList<>();
+
+ for (FieldInfo newField : newFields) {
+ String name = newField.getName();
+ if (renamedByNewName.containsKey(name)) {
+ result.add(renamedByNewName.get(name));
+ emitted.add(name);
+ } else if (modifiedByName.containsKey(name)) {
+ result.add(modifiedByName.get(name));
+ emitted.add(name);
+ } else if (added.stream().anyMatch(f -> f.getName().equals(name))) {
+ result.add(FieldChange.added(newField));
+ emitted.add(name);
+ }
+ }
+
+ for (FieldInfo oldField : removed) {
+ result.add(FieldChange.removed(oldField));
+ }
+ return result;
}
/** 字段列表转 LinkedHashMap,保持声明顺序 */
diff --git a/.gitea/checker/src/main/java/com/autoCheck/model/FieldChange.java b/.gitea/checker/src/main/java/com/autoCheck/model/FieldChange.java
index 929102d..ad03a6a 100644
--- a/.gitea/checker/src/main/java/com/autoCheck/model/FieldChange.java
+++ b/.gitea/checker/src/main/java/com/autoCheck/model/FieldChange.java
@@ -1,26 +1,28 @@
package com.aicheck.model;
/**
- * 字段级 diff 结果,用于通知中的 [新增]/[删除]/[修改] 行。
+ * 字段级 diff 结果,用于通知中的 [新增]/[删除]/[修改]/[重命名] 行。
*/
public class FieldChange {
/** 字段变更种类 */
public enum ChangeKind {
- ADDED, REMOVED, MODIFIED
+ ADDED, REMOVED, MODIFIED, RENAMED
}
private final ChangeKind kind;
private final String fieldName;
+ private final String oldFieldName;
private final String description;
private final String oldType;
private final String newType;
private final String oldDescription;
private final String detail;
- private FieldChange(ChangeKind kind, String fieldName, String description,
+ private FieldChange(ChangeKind kind, String fieldName, String oldFieldName, String description,
String oldType, String newType, String oldDescription, String detail) {
this.kind = kind;
this.fieldName = fieldName;
+ this.oldFieldName = oldFieldName;
this.description = description;
this.oldType = oldType;
this.newType = newType;
@@ -30,22 +32,32 @@ public class FieldChange {
/** 构造新增字段变更 */
public static FieldChange added(FieldInfo field) {
- return new FieldChange(ChangeKind.ADDED, field.getName(), field.getDescription(),
+ return new FieldChange(ChangeKind.ADDED, field.getName(), null, field.getDescription(),
null, field.getType(), null, null);
}
/** 构造删除字段变更 */
public static FieldChange removed(FieldInfo field) {
- return new FieldChange(ChangeKind.REMOVED, field.getName(), field.getDescription(),
+ return new FieldChange(ChangeKind.REMOVED, field.getName(), null, field.getDescription(),
field.getType(), null, field.getDescription(), null);
}
/** 构造修改字段变更,detail 通常为类型变化描述 */
public static FieldChange modified(FieldInfo oldField, FieldInfo newField, String detail) {
- return new FieldChange(ChangeKind.MODIFIED, newField.getName(), newField.getDescription(),
+ return new FieldChange(ChangeKind.MODIFIED, newField.getName(), null, newField.getDescription(),
oldField.getType(), newField.getType(), oldField.getDescription(), detail);
}
+ /** 构造字段重命名;类型变化时 detail 为 oldType → newType */
+ public static FieldChange renamed(FieldInfo oldField, FieldInfo newField) {
+ String typeDetail = oldField.getType().equals(newField.getType())
+ ? null
+ : oldField.getType() + " → " + newField.getType();
+ return new FieldChange(ChangeKind.RENAMED, newField.getName(), oldField.getName(),
+ newField.getDescription(), oldField.getType(), newField.getType(),
+ oldField.getDescription(), typeDetail);
+ }
+
public ChangeKind getKind() {
return kind;
}
@@ -54,6 +66,11 @@ public class FieldChange {
return fieldName;
}
+ /** 重命名前的字段名,仅 RENAMED 时有值 */
+ public String getOldFieldName() {
+ return oldFieldName;
+ }
+
/** 变更后的字段说明(通知「说明」段) */
public String getDescription() {
return description;
@@ -71,7 +88,7 @@ public class FieldChange {
return oldDescription;
}
- /** 结构性变更详情,如 Integer → String */
+ /** 结构性变更详情,重命名时为类型变化描述 */
public String getDetail() {
return detail;
}
diff --git a/.gitea/checker/src/main/java/com/autoCheck/notify/WeComNotifier.java b/.gitea/checker/src/main/java/com/autoCheck/notify/WeComNotifier.java
index 0962cb3..f68d9d0 100644
--- a/.gitea/checker/src/main/java/com/autoCheck/notify/WeComNotifier.java
+++ b/.gitea/checker/src/main/java/com/autoCheck/notify/WeComNotifier.java
@@ -218,6 +218,17 @@ public class WeComNotifier {
return quoteLine(tagAdded() + " " + fieldName + " 说明: " + descPart);
case REMOVED:
return quoteLine(tagRemoved() + " " + fieldName + " 说明: " + descPart);
+ case RENAMED:
+ StringBuilder renameLine = new StringBuilder();
+ renameLine.append(tagRenamed()).append(" ")
+ .append(colorComment(safe(change.getOldFieldName()))).append(" → ")
+ .append(colorInfo(safe(change.getFieldName())))
+ .append(" 说明: ").append(descPart);
+ String renameTypeDetail = change.getDetail();
+ if (renameTypeDetail != null && !renameTypeDetail.isBlank()) {
+ renameLine.append(" 类型: ").append(formatTypeChange(renameTypeDetail));
+ }
+ return quoteLine(renameLine.toString());
case MODIFIED:
default:
StringBuilder line = new StringBuilder();
@@ -254,6 +265,10 @@ public class WeComNotifier {
return colorWarning("[修改]");
}
+ private String tagRenamed() {
+ return colorWarning("[重命名]");
+ }
+
/** 引用行:{@code >标签: 值}(冒号后两空格) */
private String quoteKv(String key, String value) {
return "> " + key + ": " + value;