From e367d4257f7a80694646f2141e5543765e4efa15 Mon Sep 17 00:00:00 2001 From: dongzi Date: Mon, 8 Jun 2026 16:16:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AD=97=E6=AE=B5=E4=BF=AE=E6=94=B9=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E8=A7=84=E5=88=99=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notify-templates/field-description.md | 16 ++- .../autoCheck/analyzer/FieldDiffEngine.java | 133 ++++++++++++++++-- .../java/com/autoCheck/model/FieldChange.java | 31 +++- .../com/autoCheck/notify/WeComNotifier.java | 15 ++ 4 files changed, 172 insertions(+), 23 deletions(-) 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` 说明: 流程主键 类型: IntegerString +> [重命名] taskIdtaskIds 说明: 流程主键 -> [新增] `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;