This commit is contained in:
@@ -130,9 +130,9 @@ public class ReportBuilder {
|
|||||||
Set<String> oldHighlight = SkeletonAnnotator.pathsForOldSkeleton(kc.getFieldDetails());
|
Set<String> oldHighlight = SkeletonAnnotator.pathsForOldSkeleton(kc.getFieldDetails());
|
||||||
Set<String> newHighlight = SkeletonAnnotator.pathsForNewSkeleton(kc.getFieldDetails());
|
Set<String> newHighlight = SkeletonAnnotator.pathsForNewSkeleton(kc.getFieldDetails());
|
||||||
String oldRendered = oldJson.isEmpty()
|
String oldRendered = oldJson.isEmpty()
|
||||||
? "" : "“" + SkeletonAnnotator.annotateForWecom(oldJson, oldHighlight) + "”";
|
? "" : "“" + SkeletonAnnotator.annotateOldForWecom(oldJson, oldHighlight) + "”";
|
||||||
String newRendered = newJson.isEmpty()
|
String newRendered = newJson.isEmpty()
|
||||||
? "" : "“" + SkeletonAnnotator.annotateForWecom(newJson, newHighlight) + "”";
|
? "" : "“" + SkeletonAnnotator.annotateNewForWecom(newJson, newHighlight) + "”";
|
||||||
if (oldJson.isEmpty() && !newJson.isEmpty()) {
|
if (oldJson.isEmpty() && !newJson.isEmpty()) {
|
||||||
sb.append(" > **value 新增为:** ").append(newRendered).append("\n\n");
|
sb.append(" > **value 新增为:** ").append(newRendered).append("\n\n");
|
||||||
} else if (!oldJson.isEmpty() && newJson.isEmpty()) {
|
} else if (!oldJson.isEmpty() && newJson.isEmpty()) {
|
||||||
|
|||||||
@@ -10,16 +10,20 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在骨架 JSON 中为改动字段加企微 warning 标注(仅改动片段染色,其余明文)。
|
* 在骨架 JSON 中为改动字段加企微颜色标注(仅改动片段染色,其余明文)。
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>删除字段 → 标在旧骨架</li>
|
* <li>删除字段 → 旧骨架,橙色 {@code warning}</li>
|
||||||
* <li>新增字段 / 新增包装层 → 标在新骨架</li>
|
* <li>新增字段 / 新增包装层 → 新骨架,绿色 {@code info}</li>
|
||||||
* <li>路径迁移 → 旧路径标旧骨架、新路径标新骨架</li>
|
* <li>路径迁移 → 旧路径橙、新路径绿</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
final class SkeletonAnnotator {
|
final class SkeletonAnnotator {
|
||||||
|
|
||||||
private static final String FONT_OPEN = "<font color=\"warning\">";
|
/** 企微橙:删除 / 旧侧变更 */
|
||||||
|
static final String COLOR_REMOVE = "warning";
|
||||||
|
/** 企微绿:新增 / 新侧变更 */
|
||||||
|
static final String COLOR_ADD = "info";
|
||||||
|
|
||||||
private static final String FONT_CLOSE = "</font>";
|
private static final String FONT_CLOSE = "</font>";
|
||||||
|
|
||||||
private SkeletonAnnotator() {
|
private SkeletonAnnotator() {
|
||||||
@@ -66,14 +70,32 @@ final class SkeletonAnnotator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅标注改动字段:其余正文保持普通文本(避免整段包反引号导致企微整段变红)。
|
* 标注旧骨架改动字段(删除 → 橙色)。
|
||||||
* 改动片段格式:{@code <font color="warning">"field":value</font>}
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅标注改动字段:其余正文保持普通文本。
|
||||||
|
* 改动片段格式:{@code <font color="warning|info">"field":value</font>}
|
||||||
*/
|
*/
|
||||||
static String annotateForWecom(String json, Set<String> highlightPaths) {
|
static String annotateForWecom(String json, Set<String> highlightPaths) {
|
||||||
|
return annotateForWecom(json, highlightPaths, COLOR_REMOVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String annotateForWecom(String json, Set<String> highlightPaths, String color) {
|
||||||
if (json == null || json.isEmpty()) {
|
if (json == null || json.isEmpty()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return annotate(json, highlightPaths);
|
return annotate(json, highlightPaths, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated 使用 {@link #annotateForWecom},勿再整段包代码块 */
|
/** @deprecated 使用 {@link #annotateForWecom},勿再整段包代码块 */
|
||||||
@@ -82,18 +104,27 @@ final class SkeletonAnnotator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String annotate(String json, Set<String> highlightPaths) {
|
static String annotate(String json, Set<String> highlightPaths) {
|
||||||
|
return annotate(json, highlightPaths, COLOR_REMOVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String annotate(String json, Set<String> highlightPaths, String color) {
|
||||||
if (json == null || json.isEmpty() || highlightPaths == null || highlightPaths.isEmpty()) {
|
if (json == null || json.isEmpty() || highlightPaths == null || highlightPaths.isEmpty()) {
|
||||||
return json == null ? "" : json;
|
return json == null ? "" : json;
|
||||||
}
|
}
|
||||||
|
String fontOpen = fontOpen(color);
|
||||||
String result = json;
|
String result = json;
|
||||||
List<String> sorted = new ArrayList<>(highlightPaths);
|
List<String> sorted = new ArrayList<>(highlightPaths);
|
||||||
sorted.sort(Comparator.comparingInt(String::length).reversed());
|
sorted.sort(Comparator.comparingInt(String::length).reversed());
|
||||||
for (String path : sorted) {
|
for (String path : sorted) {
|
||||||
result = wrapOnePath(result, path);
|
result = wrapOnePath(result, path, fontOpen);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String fontOpen(String color) {
|
||||||
|
String c = color == null || color.isEmpty() ? COLOR_REMOVE : color;
|
||||||
|
return "<font color=\"" + c + "\">";
|
||||||
|
}
|
||||||
|
|
||||||
private static void addIfPresent(Set<String> paths, String path) {
|
private static void addIfPresent(Set<String> paths, String path) {
|
||||||
if (path != null && !path.trim().isEmpty()) {
|
if (path != null && !path.trim().isEmpty()) {
|
||||||
@@ -101,11 +132,10 @@ final class SkeletonAnnotator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String wrapOnePath(String json, String path) {
|
private static String wrapOnePath(String json, String path, String fontOpen) {
|
||||||
if (path == null || path.isEmpty()) {
|
if (path == null || path.isEmpty()) {
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
// 已标注过则跳过(避免重复)
|
|
||||||
String key = lastSegment(path);
|
String key = lastSegment(path);
|
||||||
if (key.isEmpty()) {
|
if (key.isEmpty()) {
|
||||||
return json;
|
return json;
|
||||||
@@ -114,8 +144,7 @@ final class SkeletonAnnotator {
|
|||||||
if (propStart < 0) {
|
if (propStart < 0) {
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
// 已在 font 内则跳过
|
int fontBefore = json.lastIndexOf("<font color=\"", propStart);
|
||||||
int fontBefore = json.lastIndexOf(FONT_OPEN, propStart);
|
|
||||||
int fontCloseBefore = json.lastIndexOf(FONT_CLOSE, propStart);
|
int fontCloseBefore = json.lastIndexOf(FONT_CLOSE, propStart);
|
||||||
if (fontBefore > fontCloseBefore) {
|
if (fontBefore > fontCloseBefore) {
|
||||||
return json;
|
return json;
|
||||||
@@ -127,11 +156,11 @@ final class SkeletonAnnotator {
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
String frag = json.substring(propStart, valueEnd);
|
String frag = json.substring(propStart, valueEnd);
|
||||||
if (frag.contains(FONT_OPEN)) {
|
if (frag.contains("<font color=\"")) {
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
return json.substring(0, propStart)
|
return json.substring(0, propStart)
|
||||||
+ FONT_OPEN + frag + FONT_CLOSE
|
+ fontOpen + frag + FONT_CLOSE
|
||||||
+ json.substring(valueEnd);
|
+ json.substring(valueEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +198,6 @@ final class SkeletonAnnotator {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} else if (c == '[') {
|
} else if (c == '[') {
|
||||||
// 数组:进入第一个元素对象
|
|
||||||
int arrEnd = findMatchingBracket(json, valueStart);
|
int arrEnd = findMatchingBracket(json, valueStart);
|
||||||
int elemObj = json.indexOf('{', valueStart);
|
int elemObj = json.indexOf('{', valueStart);
|
||||||
if (elemObj < 0 || elemObj >= arrEnd) {
|
if (elemObj < 0 || elemObj >= arrEnd) {
|
||||||
@@ -193,7 +221,6 @@ final class SkeletonAnnotator {
|
|||||||
if (idx < 0 || idx >= to) {
|
if (idx < 0 || idx >= to) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// 粗略校验:前面应是 { 或 ,
|
|
||||||
int p = idx - 1;
|
int p = idx - 1;
|
||||||
while (p >= from && Character.isWhitespace(json.charAt(p))) {
|
while (p >= from && Character.isWhitespace(json.charAt(p))) {
|
||||||
p--;
|
p--;
|
||||||
|
|||||||
@@ -71,23 +71,25 @@ class ReportBuilderTest {
|
|||||||
String oldPart = md.substring(oldSection, newSection);
|
String oldPart = md.substring(oldSection, newSection);
|
||||||
String newPart = md.substring(newSection);
|
String newPart = md.substring(newSection);
|
||||||
|
|
||||||
// 旧骨架:无 warning,且不能整段被反引号包裹
|
// 旧骨架:无染色,且不能整段被反引号包裹
|
||||||
assertFalse(oldPart.contains("<font color=\"warning\">"));
|
assertFalse(oldPart.contains("<font color=\"warning\">"));
|
||||||
|
assertFalse(oldPart.contains("<font color=\"info\">"));
|
||||||
assertFalse(oldPart.contains("`" + oldJson + "`"));
|
assertFalse(oldPart.contains("`" + oldJson + "`"));
|
||||||
assertTrue(oldPart.contains(oldJson));
|
assertTrue(oldPart.contains(oldJson));
|
||||||
|
|
||||||
// 新骨架:仅新增字段带 warning,其余明文
|
// 新骨架:新增字段绿色 info,其余明文
|
||||||
assertTrue(newPart.contains("<font color=\"warning\">\"message\":\"\"</font>"));
|
assertTrue(newPart.contains("<font color=\"info\">\"message\":\"\"</font>"));
|
||||||
assertTrue(newPart.contains("<font color=\"warning\">\"lastError\":\"\"</font>"));
|
assertTrue(newPart.contains("<font color=\"info\">\"lastError\":\"\"</font>"));
|
||||||
assertTrue(newPart.contains("\"taskId\":\"\""));
|
assertTrue(newPart.contains("\"taskId\":\"\""));
|
||||||
assertFalse(newPart.contains("<font color=\"warning\">\"taskId\":\"\"</font>"));
|
assertFalse(newPart.contains("<font color=\"info\">\"taskId\":\"\"</font>"));
|
||||||
|
assertFalse(newPart.contains("<font color=\"warning\">"));
|
||||||
// 禁止整段代码块
|
// 禁止整段代码块
|
||||||
assertFalse(newPart.contains("`" + newJson));
|
assertFalse(newPart.contains("`" + newJson));
|
||||||
assertFalse(md.contains("### P0"));
|
assertFalse(md.contains("### P0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void removedFieldsHighlightedOnlyInOldSkeleton() {
|
void removedFieldsHighlightedOrangeInOldSkeleton() {
|
||||||
SchemaChange removed = new SchemaChange(ChangeType.FIELD_REMOVED);
|
SchemaChange removed = new SchemaChange(ChangeType.FIELD_REMOVED);
|
||||||
removed.setFieldPath("lastError");
|
removed.setFieldPath("lastError");
|
||||||
removed.setMessage("删除字段 lastError");
|
removed.setMessage("删除字段 lastError");
|
||||||
@@ -100,10 +102,11 @@ class ReportBuilderTest {
|
|||||||
assertEquals(Collections.singleton("lastError"), oldPaths);
|
assertEquals(Collections.singleton("lastError"), oldPaths);
|
||||||
assertTrue(newPaths.isEmpty());
|
assertTrue(newPaths.isEmpty());
|
||||||
|
|
||||||
String oldMd = SkeletonAnnotator.annotateForWecom(oldJson, oldPaths);
|
String oldMd = SkeletonAnnotator.annotateOldForWecom(oldJson, oldPaths);
|
||||||
String newMd = SkeletonAnnotator.annotateForWecom(newJson, newPaths);
|
String newMd = SkeletonAnnotator.annotateNewForWecom(newJson, newPaths);
|
||||||
assertTrue(oldMd.contains("<font color=\"warning\">\"lastError\":\"\"</font>"));
|
assertTrue(oldMd.contains("<font color=\"warning\">\"lastError\":\"\"</font>"));
|
||||||
assertFalse(oldMd.contains("<font color=\"warning\">\"taskId\":\"\"</font>"));
|
assertFalse(oldMd.contains("<font color=\"warning\">\"taskId\":\"\"</font>"));
|
||||||
|
assertFalse(oldMd.contains("<font color=\"info\">"));
|
||||||
assertEquals(newJson, newMd);
|
assertEquals(newJson, newMd);
|
||||||
assertFalse(newMd.startsWith("`"));
|
assertFalse(newMd.startsWith("`"));
|
||||||
}
|
}
|
||||||
@@ -134,6 +137,7 @@ class ReportBuilderTest {
|
|||||||
assertTrue(console.contains("======== 字段明细 ========"));
|
assertTrue(console.contains("======== 字段明细 ========"));
|
||||||
assertTrue(console.contains("**删除字段**: x"));
|
assertTrue(console.contains("**删除字段**: x"));
|
||||||
assertTrue(console.contains("<font color=\"warning\">\"x\":\"\"</font>"));
|
assertTrue(console.contains("<font color=\"warning\">\"x\":\"\"</font>"));
|
||||||
|
assertFalse(console.contains("<font color=\"info\">\"x\":\"\"</font>"));
|
||||||
assertTrue(console.contains("> **位置**: `"));
|
assertTrue(console.contains("> **位置**: `"));
|
||||||
assertTrue(console.contains("> **类型**: `"));
|
assertTrue(console.contains("> **类型**: `"));
|
||||||
assertTrue(console.contains("> **value值由:**"));
|
assertTrue(console.contains("> **value值由:**"));
|
||||||
@@ -169,9 +173,10 @@ class ReportBuilderTest {
|
|||||||
String json = "{\"vo\":{\"dbName\":\"\",\"id\":\"\"},\"expiresAtMs\":0}";
|
String json = "{\"vo\":{\"dbName\":\"\",\"id\":\"\"},\"expiresAtMs\":0}";
|
||||||
Set<String> paths = new LinkedHashSet<>();
|
Set<String> paths = new LinkedHashSet<>();
|
||||||
paths.add("vo.dbName");
|
paths.add("vo.dbName");
|
||||||
String out = SkeletonAnnotator.annotate(json, paths);
|
String out = SkeletonAnnotator.annotate(json, paths, SkeletonAnnotator.COLOR_ADD);
|
||||||
assertTrue(out.contains("<font color=\"warning\">\"dbName\":\"\"</font>"));
|
assertTrue(out.contains("<font color=\"info\">\"dbName\":\"\"</font>"));
|
||||||
assertFalse(out.contains("<font color=\"warning\">\"id\":\"\"</font>"));
|
assertFalse(out.contains("<font color=\"info\">\"id\":\"\"</font>"));
|
||||||
|
assertFalse(out.contains("<font color=\"warning\">\"dbName\":\"\"</font>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user