feat: 修改字段类型修改不生效的问题
All checks were successful
序列化结构检查 / serialization-schema-check (push) Has been skipped

This commit is contained in:
2026-07-15 13:50:14 +08:00
parent fd5aab5e7f
commit 27dfeb8a4c
7 changed files with 285 additions and 35 deletions

View File

@@ -22,29 +22,41 @@ public class SchemaDiffer {
public List<SchemaChange> diff(TypeSchema oldSchema, TypeSchema newSchema) {
List<SchemaChange> changes = new ArrayList<>();
Map<String, JsonType> oldLeaves = leaves(oldSchema);
Map<String, JsonType> newLeaves = leaves(newSchema);
Map<String, FieldSchema> oldAll = fieldsByPath(oldSchema);
Map<String, FieldSchema> newAll = fieldsByPath(newSchema);
Map<String, FieldSchema> oldLeaves = leaves(oldSchema);
Map<String, FieldSchema> newLeaves = leaves(newSchema);
Set<String> typeChangedPaths = new LinkedHashSet<>();
// 类型变更:同路径下 JsonType 或 Java 类型任一变化(覆盖 BigDecimal→String 等同桶变更)
for (String path : oldAll.keySet()) {
FieldSchema oldField = oldAll.get(path);
FieldSchema newField = newAll.get(path);
if (newField == null) {
continue;
}
if (!isTypeChange(oldField, newField)) {
continue;
}
typeChangedPaths.add(path);
String oldLabel = typeLabel(oldField);
String newLabel = typeLabel(newField);
SchemaChange c = new SchemaChange(ChangeType.TYPE_CHANGED);
c.setFieldPath(path);
c.setOldValue(oldLabel);
c.setNewValue(newLabel);
c.setMessage("字段 " + path + " 类型由 " + oldLabel + " 变为 " + newLabel);
changes.add(c);
}
Set<String> removed = new LinkedHashSet<>(oldLeaves.keySet());
removed.removeAll(newLeaves.keySet());
removed.removeAll(typeChangedPaths);
Set<String> added = new LinkedHashSet<>(newLeaves.keySet());
added.removeAll(oldLeaves.keySet());
// 类型变更(同路径)
for (String path : oldLeaves.keySet()) {
if (newLeaves.containsKey(path)) {
JsonType oldType = oldLeaves.get(path);
JsonType newType = newLeaves.get(path);
if (oldType != newType) {
SchemaChange c = new SchemaChange(ChangeType.TYPE_CHANGED);
c.setFieldPath(path);
c.setOldValue(oldType.name());
c.setNewValue(newType.name());
c.setMessage("字段 " + path + " 类型由 " + oldType + " 变为 " + newType);
changes.add(c);
}
}
}
added.removeAll(typeChangedPaths);
// 路径迁移检测removed 的路径是某 added 路径的后缀)
List<String[]> moves = new ArrayList<>();
@@ -99,7 +111,7 @@ public class SchemaDiffer {
}
SchemaChange c = new SchemaChange(ChangeType.FIELD_REMOVED);
c.setFieldPath(r);
c.setOldValue(oldLeaves.get(r).name());
c.setOldValue(typeLabel(oldLeaves.get(r)));
c.setMessage("删除字段 " + r);
changes.add(c);
}
@@ -111,7 +123,7 @@ public class SchemaDiffer {
}
SchemaChange c = new SchemaChange(ChangeType.FIELD_ADDED);
c.setFieldPath(a);
c.setNewValue(newLeaves.get(a).name());
c.setNewValue(typeLabel(newLeaves.get(a)));
c.setMessage("新增字段 " + a);
changes.add(c);
}
@@ -119,11 +131,104 @@ public class SchemaDiffer {
return changes;
}
private Map<String, JsonType> leaves(TypeSchema schema) {
Map<String, JsonType> result = new LinkedHashMap<>();
private boolean isTypeChange(FieldSchema oldField, FieldSchema newField) {
if (oldField.getJsonType() != newField.getJsonType()) {
return true;
}
String oldJava = normalizeJavaType(oldField.getJavaType());
String newJava = normalizeJavaType(newField.getJavaType());
if (oldJava.isEmpty() || newJava.isEmpty()) {
return false;
}
return !oldJava.equals(newJava);
}
/**
* 展示用类型标签:优先 Java 简单名,并附带 JsonType便于识别同桶变更
*/
private String typeLabel(FieldSchema field) {
if (field == null) {
return "?";
}
String java = simpleJavaType(field.getJavaType());
JsonType json = field.getJsonType();
if (java.isEmpty() || "?".equals(java)) {
return json == null ? "?" : json.name();
}
if (json == null) {
return java;
}
return java + "/" + json.name();
}
/**
* 归一化后用于相等比较:去掉泛型与包名,基本类型与包装类型视为同一序列化类型。
*/
static String normalizeJavaType(String javaType) {
String simple = simpleJavaType(javaType);
if (simple.isEmpty() || "?".equals(simple)) {
return "";
}
switch (simple) {
case "int":
return "Integer";
case "long":
return "Long";
case "short":
return "Short";
case "byte":
return "Byte";
case "double":
return "Double";
case "float":
return "Float";
case "boolean":
return "Boolean";
case "char":
return "Character";
default:
return simple;
}
}
private static String simpleJavaType(String javaType) {
if (javaType == null) {
return "";
}
String s = javaType.trim();
if (s.isEmpty()) {
return "";
}
int lt = s.indexOf('<');
if (lt > 0) {
s = s.substring(0, lt).trim();
}
int dot = s.lastIndexOf('.');
if (dot >= 0 && dot < s.length() - 1) {
s = s.substring(dot + 1);
}
return s;
}
private Map<String, FieldSchema> fieldsByPath(TypeSchema schema) {
Map<String, FieldSchema> result = new LinkedHashMap<>();
if (schema == null) {
return result;
}
for (FieldSchema f : schema.getFields().values()) {
result.put(f.getPath(), f);
}
return result;
}
private Map<String, FieldSchema> leaves(TypeSchema schema) {
Map<String, FieldSchema> result = new LinkedHashMap<>();
if (schema == null) {
return result;
}
for (FieldSchema f : schema.getFields().values()) {
if (f.getJsonType() != JsonType.OBJECT && f.getJsonType() != JsonType.ARRAY) {
result.put(f.getPath(), f.getJsonType());
result.put(f.getPath(), f);
}
}
return result;

View File

@@ -128,11 +128,12 @@ public class ReportBuilder {
String oldJson = nvl(kc.getOldSkeletonJson());
String newJson = nvl(kc.getNewSkeletonJson());
Set<String> oldHighlight = SkeletonAnnotator.pathsForOldSkeleton(kc.getFieldDetails());
Set<String> newHighlight = SkeletonAnnotator.pathsForNewSkeleton(kc.getFieldDetails());
Set<String> newGreen = SkeletonAnnotator.pathsForNewSkeleton(kc.getFieldDetails());
Set<String> newOrange = SkeletonAnnotator.pathsForTypeChanged(kc.getFieldDetails());
String oldRendered = oldJson.isEmpty()
? "" : "" + SkeletonAnnotator.annotateOldForWecom(oldJson, oldHighlight) + "";
String newRendered = newJson.isEmpty()
? "" : "" + SkeletonAnnotator.annotateNewForWecom(newJson, newHighlight) + "";
? "" : "" + SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, newOrange) + "";
if (oldJson.isEmpty() && !newJson.isEmpty()) {
sb.append(" > **value 新增为:** ").append(newRendered).append("\n\n");
} else if (!oldJson.isEmpty() && newJson.isEmpty()) {

View File

@@ -15,13 +15,14 @@ import java.util.Set;
* <li>删除字段 → 旧骨架,橙色 {@code warning}</li>
* <li>新增字段 / 新增包装层 → 新骨架,绿色 {@code info}</li>
* <li>路径迁移 → 旧路径橙、新路径绿</li>
* <li>类型变更 → 新旧骨架均为橙色 {@code warning}(提高识别度)</li>
* </ul>
*/
final class SkeletonAnnotator {
/** 企微橙:删除 / 旧侧变更 */
/** 企微橙:删除 / 类型变更 / 旧侧变更 */
static final String COLOR_REMOVE = "warning";
/** 企微绿:新增 / 新侧变更 */
/** 企微绿:新增 / 新侧路径迁移 */
static final String COLOR_ADD = "info";
private static final String FONT_CLOSE = "</font>";
@@ -29,6 +30,7 @@ final class SkeletonAnnotator {
private SkeletonAnnotator() {
}
/** 旧骨架应橙色标注的路径:删除、路径迁移旧侧、类型变更。 */
static Set<String> pathsForOldSkeleton(List<SchemaChange> details) {
Set<String> paths = new LinkedHashSet<>();
if (details == null) {
@@ -38,7 +40,8 @@ final class SkeletonAnnotator {
if (c == null || c.getChangeType() == null) {
continue;
}
if (c.getChangeType() == ChangeType.FIELD_REMOVED) {
if (c.getChangeType() == ChangeType.FIELD_REMOVED
|| c.getChangeType() == ChangeType.TYPE_CHANGED) {
addIfPresent(paths, c.getFieldPath());
} else if (c.getChangeType() == ChangeType.FIELD_PATH_MOVED) {
addIfPresent(paths, c.getOldValue());
@@ -47,6 +50,7 @@ final class SkeletonAnnotator {
return paths;
}
/** 新骨架应绿色标注的路径:新增、包装层、路径迁移新侧(不含类型变更)。 */
static Set<String> pathsForNewSkeleton(List<SchemaChange> details) {
Set<String> paths = new LinkedHashSet<>();
if (details == null) {
@@ -69,18 +73,40 @@ final class SkeletonAnnotator {
return paths;
}
/** 新骨架应橙色标注的路径:类型变更(与旧侧同色,便于识别)。 */
static Set<String> pathsForTypeChanged(List<SchemaChange> details) {
Set<String> paths = new LinkedHashSet<>();
if (details == null) {
return paths;
}
for (SchemaChange c : details) {
if (c != null && c.getChangeType() == ChangeType.TYPE_CHANGED) {
addIfPresent(paths, c.getFieldPath());
}
}
return paths;
}
/**
* 标注旧骨架改动字段(删除 → 橙色)。
* 标注旧骨架改动字段(删除 / 类型变更 → 橙色)。
*/
static String annotateOldForWecom(String json, Set<String> highlightPaths) {
return annotateForWecom(json, highlightPaths, COLOR_REMOVE);
}
/**
* 标注新骨架改动字段(新增 → 绿色
* 标注新骨架:类型变更先着橙色,其余新增着绿色。
*/
static String annotateNewForWecom(String json, Set<String> highlightPaths) {
return annotateForWecom(json, highlightPaths, COLOR_ADD);
static String annotateNewForWecom(String json, Set<String> greenPaths) {
return annotateNewForWecom(json, greenPaths, null);
}
/**
* 标注新骨架改动字段(类型变更 → 橙色;新增 / 迁移新侧 → 绿色)。
*/
static String annotateNewForWecom(String json, Set<String> greenPaths, Set<String> orangePaths) {
String result = annotateForWecom(json, orangePaths, COLOR_REMOVE);
return annotateForWecom(result, greenPaths, COLOR_ADD);
}
/**

View File

@@ -21,12 +21,11 @@ public class JavaSchemaExtractor {
private static final Set<String> STRING_TYPES = new HashSet<>(Arrays.asList(
"String", "CharSequence", "char", "Character", "UUID",
"Date", "LocalDate", "LocalDateTime", "LocalTime", "Instant", "Timestamp",
"BigDecimal"));
"Date", "LocalDate", "LocalDateTime", "LocalTime", "Instant", "Timestamp"));
private static final Set<String> NUMBER_TYPES = new HashSet<>(Arrays.asList(
"int", "long", "short", "byte", "double", "float",
"Integer", "Long", "Short", "Byte", "Double", "Float",
"Number", "BigInteger", "AtomicInteger", "AtomicLong"));
"Number", "BigInteger", "BigDecimal", "AtomicInteger", "AtomicLong"));
private static final Set<String> BOOLEAN_TYPES = new HashSet<>(Arrays.asList(
"boolean", "Boolean"));
private static final Set<String> COLLECTION_TYPES = new HashSet<>(Arrays.asList(