Files
schemaCheck/src/main/java/com/codechecker/cache/diff/SchemaDiffer.java
dongzi 110beb79c0
All checks were successful
缓存序列化结构检查 / cache-schema-check (push) Has been skipped
feat: 项目整体命名修改cache-schema-checker
2026-07-14 11:03:34 +08:00

152 lines
5.4 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.codechecker.cache.diff;
import com.codechecker.cache.schema.FieldSchema;
import com.codechecker.cache.schema.JsonType;
import com.codechecker.cache.schema.TypeSchema;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 对比两个版本的 {@link TypeSchema},输出结构变更列表。基于叶子字段路径集合进行差异分析。
*/
public class SchemaDiffer {
/**
* @return 结构变更列表(不含 keyPattern/location 上下文,由调用方补充)
*/
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);
Set<String> removed = new LinkedHashSet<>(oldLeaves.keySet());
removed.removeAll(newLeaves.keySet());
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);
}
}
}
// 路径迁移检测removed 的路径是某 added 路径的后缀)
List<String[]> moves = new ArrayList<>();
Set<String> matchedRemoved = new LinkedHashSet<>();
Set<String> matchedAdded = new LinkedHashSet<>();
for (String r : removed) {
for (String a : added) {
if (matchedAdded.contains(a)) {
continue;
}
if (isSuffix(a, r)) {
moves.add(new String[]{r, a});
matchedRemoved.add(r);
matchedAdded.add(a);
break;
}
}
}
// 包装层检测:多个迁移共享同一新前缀
Map<String, Integer> prefixCount = new LinkedHashMap<>();
for (String[] move : moves) {
String a = move[1];
if (a.contains(".")) {
String prefix = a.substring(0, a.indexOf('.'));
prefixCount.merge(prefix, 1, Integer::sum);
}
}
for (Map.Entry<String, Integer> e : prefixCount.entrySet()) {
if (e.getValue() >= 2) {
SchemaChange c = new SchemaChange(ChangeType.WRAPPER_ADDED);
c.setFieldPath(e.getKey());
c.setNewValue(e.getKey());
c.setMessage("新增包装层 " + e.getKey() + ",原顶层字段被下移至该层(影响 " + e.getValue() + " 个字段)");
changes.add(c);
}
}
for (String[] move : moves) {
SchemaChange c = new SchemaChange(ChangeType.FIELD_PATH_MOVED);
c.setFieldPath(move[1]);
c.setOldValue(move[0]);
c.setNewValue(move[1]);
c.setMessage("字段路径迁移:" + move[0] + "" + move[1]);
changes.add(c);
}
// 剩余删除
for (String r : removed) {
if (matchedRemoved.contains(r)) {
continue;
}
SchemaChange c = new SchemaChange(ChangeType.FIELD_REMOVED);
c.setFieldPath(r);
c.setOldValue(oldLeaves.get(r).name());
c.setMessage("删除字段 " + r);
changes.add(c);
}
// 剩余新增
for (String a : added) {
if (matchedAdded.contains(a)) {
continue;
}
SchemaChange c = new SchemaChange(ChangeType.FIELD_ADDED);
c.setFieldPath(a);
c.setNewValue(newLeaves.get(a).name());
c.setMessage("新增字段 " + a);
changes.add(c);
}
return changes;
}
private Map<String, JsonType> leaves(TypeSchema schema) {
Map<String, JsonType> result = new LinkedHashMap<>();
for (FieldSchema f : schema.getFields().values()) {
if (f.getJsonType() != JsonType.OBJECT && f.getJsonType() != JsonType.ARRAY) {
result.put(f.getPath(), f.getJsonType());
}
}
return result;
}
/**
* 判断 full 的按段后缀是否等于 suffix如 vo.dbName 的后缀是 dbName
*/
private boolean isSuffix(String full, String suffix) {
if (full.equals(suffix)) {
return false;
}
String[] fullSeg = full.split("\\.");
String[] sufSeg = suffix.split("\\.");
if (sufSeg.length >= fullSeg.length) {
return false;
}
for (int i = 1; i <= sufSeg.length; i++) {
if (!fullSeg[fullSeg.length - i].equals(sufSeg[sufSeg.length - i])) {
return false;
}
}
return true;
}
}