package jnpf.bizcommon.audit.service.impl;
|
|
import com.alibaba.fastjson.JSON;
|
import jnpf.audit.AuditConsts;
|
import jnpf.bizcommon.audit.service.support.AuditDiffKey;
|
import jnpf.bizcommon.audit.service.support.AuditEventCategories;
|
|
import java.util.Arrays;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
|
/** 写入侧三分类与 field_diffs 端点组合纯 JVM 探针。 */
|
public final class AuditEventIngestMergeProbe {
|
private static int passed;
|
private static int failed;
|
|
public static void main(String[] args) {
|
check("业务事件分类", AuditEventCategories.BUSINESS.equals(
|
AuditEventCategories.resolve(AuditConsts.TYPE_DATA_CHANGE, null)));
|
check("显式复审签名分类", AuditEventCategories.REVIEW_SIGN.equals(
|
AuditEventCategories.resolve(AuditConsts.TYPE_E_SIGNATURE,
|
"{\"signatureRole\":\"REVIEW\"}")));
|
check("历史 fuhe_sign 分类", AuditEventCategories.REVIEW_SIGN.equals(
|
AuditEventCategories.resolve(AuditConsts.TYPE_E_SIGNATURE,
|
"{\"signField\":\"fuhe_sign\"}")));
|
check("普通签名默认主签名", AuditEventCategories.PRIMARY_SIGN.equals(
|
AuditEventCategories.resolve(AuditConsts.TYPE_E_SIGNATURE, "{}")));
|
|
String existing = "[{\"field\":\"status\",\"fieldName\":\"状态\","
|
+ "\"oldData\":\"draft\",\"newData\":\"submitted\"},"
|
+ "{\"field\":\"amount\",\"oldData\":1,\"newData\":2}]";
|
String later = "[{\"field\":\"status\",\"fieldName\":\"业务状态\","
|
+ "\"oldData\":\"submitted\",\"newData\":\"approved\"}]";
|
String mergedLater = AuditEventServiceImpl.mergeFieldDiffs(existing, later, false, true);
|
check("同字段保留最早旧值", mergedLater.contains("\"oldData\":\"draft\""));
|
check("同字段使用最晚新值", mergedLater.contains("\"newData\":\"approved\""));
|
check("不同字段组成并集", mergedLater.contains("\"field\":\"amount\""));
|
check("较晚元数据覆盖展示名", mergedLater.contains("\"fieldName\":\"业务状态\""));
|
|
String earlier = "[{\"field\":\"status\",\"oldData\":\"created\","
|
+ "\"newData\":\"draft\"}]";
|
String mergedEarlier = AuditEventServiceImpl.mergeFieldDiffs(existing, earlier, true, false);
|
check("乱序到达仍使用真实最早旧值", mergedEarlier.contains("\"oldData\":\"created\""));
|
check("乱序到达不覆盖当前最晚新值", mergedEarlier.contains("\"newData\":\"submitted\""));
|
|
String formCreate = "[{\"field\":\"chanpin_mingcheng\",\"fieldName\":\"产品名称\","
|
+ "\"oldData\":\"\",\"newData\":\"红细胞\"},"
|
+ "{\"field\":\"biz_status\",\"fieldName\":\"业务状态\","
|
+ "\"oldData\":\"\",\"newData\":\"created\",\"newDisplay\":\"已创建\"}]";
|
String apiSubmit = "[{\"field\":\"biz_status\",\"fieldName\":\"biz_status\","
|
+ "\"oldData\":\"created\",\"newData\":\"confirmed\"},"
|
+ "{\"field\":\"quyang_status\",\"fieldName\":\"quyang_status\","
|
+ "\"oldData\":\"\",\"newData\":\"pending_fetch\"}]";
|
String formThenApi = AuditEventServiceImpl.mergeFieldDiffs(
|
formCreate, apiSubmit, false, true, true, false,
|
protectedFields("chanpin_mingcheng", "biz_status"));
|
check("表单先到时保留表单初始值",
|
hasDiff(formThenApi, "biz_status", "业务状态", "", "created"));
|
check("表单先到时补入接口字段", formThenApi.contains("\"field\":\"quyang_status\""));
|
check("表单有效值不被接口覆盖",
|
diffHasValue(formThenApi, "biz_status", "newDisplay", "已创建"));
|
check("表单先到时字段顺序以表单为准",
|
formThenApi.indexOf("chanpin_mingcheng") < formThenApi.indexOf("quyang_status"));
|
|
String apiThenForm = AuditEventServiceImpl.mergeFieldDiffs(
|
apiSubmit, formCreate, true, false, false, true,
|
protectedFields("chanpin_mingcheng", "biz_status"));
|
check("表单后到时仍保留表单初始值",
|
hasDiff(apiThenForm, "biz_status", "业务状态", "", "created"));
|
check("表单后到时仍补入接口字段", apiThenForm.contains("\"field\":\"quyang_status\""));
|
check("表单后到时重新按表单字段排序",
|
apiThenForm.indexOf("chanpin_mingcheng") < apiThenForm.indexOf("quyang_status"));
|
|
String formEmpty = "[{\"field\":\"unit\",\"fieldName\":\"单位\","
|
+ "\"oldData\":\"\",\"newData\":\"\"},"
|
+ "{\"field\":\"amount\",\"fieldName\":\"数量\","
|
+ "\"oldData\":\"\",\"newData\":\"0\"}]";
|
String apiFilled = "[{\"field\":\"unit\",\"oldData\":\"\",\"newData\":\"粒\"},"
|
+ "{\"field\":\"amount\",\"oldData\":\"0\",\"newData\":\"2\"}]";
|
String filled = AuditEventServiceImpl.mergeFieldDiffs(
|
formEmpty, apiFilled, true, false, true, false, protectedFields());
|
check("表单空值允许接口补入", hasDiff(filled, "unit", "单位", "", "粒"));
|
check("表单零值允许接口补入", hasDiff(filled, "amount", "数量", "", "2"));
|
String olderApiIgnored = AuditEventServiceImpl.mergeFieldDiffs(
|
filled, apiFilled.replace("\"newData\":\"2\"", "\"newData\":\"1\""),
|
false, false, true, false, protectedFields());
|
check("乱序到达的旧接口不覆盖较新字段补充值",
|
hasDiff(olderApiIgnored, "amount", "数量", "", "2"));
|
|
String childForm = "[{\"field\":\"tableField1\",\"jnpfKey\":\"table\","
|
+ "\"chidField\":[{\"prop\":\"name\",\"label\":\"名称\"}],"
|
+ "\"chidData\":[{\"name\":\"新值\",\"jnpf_old_name\":\"旧值\",\"jnpf_type\":1}]}]";
|
String childApi = "[{\"field\":\"tableField1\",\"oldData\":\"api-old\",\"newData\":\"api-new\"}]";
|
String childMerged = AuditEventServiceImpl.mergeFieldDiffs(
|
childForm, childApi, false, true, true, false,
|
protectedFields("tableField1"));
|
check("表单子表明细不被接口同名字段覆盖", childMerged.contains("\"chidData\"")
|
&& !childMerged.contains("api-new"));
|
|
String formSnapshot = "{\"name\":\"陈皮\",\"amount\":\"0\",\"unit\":\"\","
|
+ "\"biz_status\":\"created\"}";
|
String apiSnapshot1 = "{\"name\":\"接口名称\",\"amount\":\"2\",\"unit\":\"粒\","
|
+ "\"biz_status\":\"confirmed\",\"api_field\":\"first\"}";
|
String apiSnapshot2 = "{\"amount\":\"3\",\"api_field\":\"latest\"}";
|
String mergedSnapshot = AuditEventServiceImpl.mergeFormFirstSnapshots(
|
formSnapshot, apiSnapshot1, apiSnapshot2);
|
check("快照保留表单有效值", snapshotHasValue(mergedSnapshot, "name", "陈皮")
|
&& snapshotHasValue(mergedSnapshot, "biz_status", "created"));
|
check("快照空值和零值允许接口补入", snapshotHasValue(mergedSnapshot, "amount", "3")
|
&& snapshotHasValue(mergedSnapshot, "unit", "粒"));
|
check("快照补入表单缺失字段并使用接口最新值",
|
snapshotHasValue(mergedSnapshot, "api_field", "latest"));
|
check("业务展示来源层0表单优先于层2接口",
|
AuditEventServiceImpl.sourcePriority(AuditEventCategories.BUSINESS, 0)
|
> AuditEventServiceImpl.sourcePriority(AuditEventCategories.BUSINESS, 2));
|
|
String apiThenFormSnapshot = AuditEventServiceImpl.mergeSnapshots(
|
apiSnapshot1, formSnapshot, false, false, true,
|
false, true, formSnapshot);
|
String formThenApiSnapshot = AuditEventServiceImpl.mergeSnapshots(
|
formSnapshot, apiSnapshot1, true, true, true,
|
true, false, formSnapshot);
|
check("表单和接口请求先后不影响快照基准",
|
JSON.parseObject(apiThenFormSnapshot).equals(JSON.parseObject(formThenApiSnapshot)));
|
String newerApiKept = AuditEventServiceImpl.mergeSnapshots(
|
mergedSnapshot, apiSnapshot1, false, false, true,
|
true, false, formSnapshot);
|
check("乱序到达的旧接口不覆盖较新补充值",
|
snapshotHasValue(newerApiKept, "amount", "3")
|
&& snapshotHasValue(newerApiKept, "api_field", "latest"));
|
|
String noFormSnapshot = AuditEventServiceImpl.mergeSnapshots(
|
apiSnapshot2, apiSnapshot1, false, false, false,
|
false, false, null);
|
check("无表单场景继续按较新事件选择快照",
|
JSON.parseObject(apiSnapshot2).equals(JSON.parseObject(noFormSnapshot)));
|
|
String scopedDiffs = "[{\"field\":\"name\",\"newData\":\"主表\"},"
|
+ "{\"field\":\"ghost\",\"newData\":\"不存在\"},"
|
+ "{\"field\":\"tableField1\",\"jnpfKey\":\"table\","
|
+ "\"targetTable\":\"child_table\","
|
+ "\"chidField\":[{\"prop\":\"item_name\",\"label\":\"名称\"},"
|
+ "{\"prop\":\"ghost_child\",\"label\":\"非法\"}],"
|
+ "\"chidData\":[{\"item_name\":\"A\",\"jnpf_old_item_name\":\"\","
|
+ "\"ghost_child\":\"X\",\"jnpf_old_ghost_child\":\"Y\",\"jnpf_type\":0}]}]";
|
String filteredDiffs = AuditEventServiceImpl.filterFieldDiffs(
|
scopedDiffs, "main_table", table -> columns(table));
|
check("过滤主表不存在字段", diffCount(filteredDiffs, "ghost") == 0
|
&& diffCount(filteredDiffs, "name") == 1);
|
check("子表字段只按对应子表列过滤", filteredDiffs.contains("item_name")
|
&& !filteredDiffs.contains("ghost_child")
|
&& filteredDiffs.contains("\"targetTable\":\"child_table\""));
|
|
String sameNameDifferentTables = AuditEventServiceImpl.mergeFieldDiffs(
|
"[{\"field\":\"name\",\"targetTable\":\"main_table\",\"newData\":\"主\"}]",
|
"[{\"field\":\"name\",\"targetTable\":\"child_table\",\"newData\":\"子\"}]",
|
false, true);
|
check("主表和子表同名字段不互相覆盖", diffCount(sameNameDifferentTables, "name") == 2);
|
|
String firstBatchRow = batchRowDiff("row-1", "draft", "done");
|
String batchMerged = firstBatchRow;
|
for (int i = 2; i <= 10; i++) {
|
batchMerged = AuditEventServiceImpl.mergeFieldDiffs(
|
batchMerged, batchRowDiff("row-" + i, "pending-" + i, "done"), false, true);
|
}
|
boolean allTargetIdsRetained = true;
|
for (int i = 1; i <= 10; i++) {
|
allTargetIdsRetained &= batchMerged.contains("\"targetId\":\"row-" + i + "\"");
|
}
|
check("批量操作10条记录的同字段按 targetId 全部保留",
|
diffCount(batchMerged, "status") == 10 && allTargetIdsRetained);
|
|
String sameBatchRowLater = "[{\"field\":\"status\",\"targetTable\":\"lims_task\","
|
+ "\"targetId\":\"row-1\",\"oldData\":\"done\",\"newData\":\"archived\"}]";
|
String sameRowMerged = AuditEventServiceImpl.mergeFieldDiffs(
|
firstBatchRow, sameBatchRowLater, false, true);
|
check("同一 targetId 的同字段仍合并时间端点",
|
diffCount(sameRowMerged, "status") == 1
|
&& sameRowMerged.contains("\"oldData\":\"draft\"")
|
&& sameRowMerged.contains("\"newData\":\"archived\""));
|
|
String legacyBatchMerged = AuditEventServiceImpl.mergeFieldDiffs(
|
"[{\"field\":\"status\",\"targetTable\":\"lims_task\","
|
+ "\"oldData\":\"draft\",\"newData\":\"done\"}]",
|
"[{\"field\":\"status\",\"targetTable\":\"lims_task\","
|
+ "\"oldData\":\"done\",\"newData\":\"archived\"}]",
|
false, true);
|
check("旧日志缺少 targetId 时保持原合并规则",
|
diffCount(legacyBatchMerged, "status") == 1);
|
|
String upgradedLegacy = AuditEventServiceImpl.scopeFieldDiffs(
|
"[{\"field\":\"status\",\"targetTable\":\"lims_task\","
|
+ "\"oldData\":\"draft\",\"newData\":\"done\"}]",
|
"lims_task", "row-1");
|
String legacyRetryMerged = AuditEventServiceImpl.mergeFieldDiffs(
|
upgradedLegacy, sameBatchRowLater, false, true);
|
check("旧日志与带 targetId 的迟到事件不会生成重复字段",
|
diffCount(legacyRetryMerged, "status") == 1
|
&& legacyRetryMerged.contains("\"newData\":\"archived\""));
|
|
String scopedSingle = AuditEventServiceImpl.scopeFieldDiffs(
|
"[{\"field\":\"status\",\"oldData\":\"a\",\"newData\":\"b\"}]",
|
"lims_task", "row-1");
|
check("单记录事件自动补充 targetTable 和 targetId",
|
scopedSingle.contains("\"targetTable\":\"lims_task\"")
|
&& scopedSingle.contains("\"targetId\":\"row-1\""));
|
String explicitlyScoped = AuditEventServiceImpl.scopeFieldDiffs(
|
batchRowDiff("row-2", "pending", "done"), "other_table", "other-row");
|
check("批量差异显式 targetId 不被顶层首条记录覆盖",
|
explicitlyScoped.contains("\"targetTable\":\"lims_task\"")
|
&& explicitlyScoped.contains("\"targetId\":\"row-2\"")
|
&& !explicitlyScoped.contains("other-row"));
|
|
String formScopedProtection = AuditEventServiceImpl.mergeFieldDiffs(
|
"[{\"field\":\"name\",\"targetTable\":\"main_table\",\"newData\":\"表单主表\"},"
|
+ "{\"field\":\"name\",\"targetTable\":\"child_table\",\"newData\":\"\"}]",
|
"[{\"field\":\"name\",\"targetTable\":\"main_table\",\"newData\":\"接口主表\"},"
|
+ "{\"field\":\"name\",\"targetTable\":\"child_table\",\"newData\":\"接口子表\"}]",
|
false, true, true, false,
|
protectedFields(AuditDiffKey.of("main_table", "name")));
|
check("表单保护字段按物理表隔离",
|
diffHasScopedValue(formScopedProtection, "main_table", "name", "表单主表")
|
&& diffHasScopedValue(formScopedProtection, "child_table", "name", "接口子表"));
|
|
Set<String> normalizedLegacyKeys = AuditEventServiceImpl.normalizedProtectedFields(
|
Arrays.asList("main_table" + Character.MIN_VALUE + "name"));
|
String persistedKeys = JSON.toJSONString(normalizedLegacyKeys);
|
check("旧 NUL 复合键写回前转换为可打印格式",
|
normalizedLegacyKeys.contains(AuditDiffKey.of("main_table", "name"))
|
&& AuditDiffKey.isScoped(AuditDiffKey.of("main_table", "name"))
|
&& !AuditDiffKey.isScoped("@table:2147483647:x:name")
|
&& persistedKeys.indexOf(Character.MIN_VALUE) < 0
|
&& !persistedKeys.contains("\\u0000"));
|
|
System.out.println("=== 写入合并断言:" + passed + " 通过 / " + failed + " 失败 ===");
|
System.exit(failed == 0 ? 0 : 1);
|
}
|
|
private static void check(String name, boolean ok) {
|
if (ok) {
|
passed++;
|
System.out.println(" [PASS] " + name);
|
} else {
|
failed++;
|
System.out.println(" [FAIL] " + name);
|
}
|
}
|
|
@SuppressWarnings("unchecked")
|
private static boolean hasDiff(String json, String field, String fieldName,
|
String oldData, String newData) {
|
List<Map> diffs = JSON.parseArray(json, Map.class);
|
for (Map<String, Object> diff : diffs) {
|
if (field.equals(String.valueOf(diff.get("field")))
|
&& fieldName.equals(String.valueOf(diff.get("fieldName")))
|
&& oldData.equals(String.valueOf(diff.get("oldData")))
|
&& newData.equals(String.valueOf(diff.get("newData")))) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
private static boolean diffHasValue(String json, String field, String key, String expected) {
|
List<Map> diffs = JSON.parseArray(json, Map.class);
|
for (Map<?, ?> diff : diffs) {
|
if (field.equals(String.valueOf(diff.get("field")))) {
|
return expected.equals(String.valueOf(diff.get(key)));
|
}
|
}
|
return false;
|
}
|
|
private static boolean snapshotHasValue(String json, String field, String expected) {
|
Map<String, Object> snapshot = JSON.parseObject(json, Map.class);
|
return expected.equals(String.valueOf(snapshot.get(field)));
|
}
|
|
private static Set<String> columns(String table) {
|
if ("main_table".equals(table)) {
|
return protectedFields("id", "name");
|
}
|
if ("child_table".equals(table)) {
|
return protectedFields("f_id", "item_name");
|
}
|
return protectedFields();
|
}
|
|
private static int diffCount(String json, String field) {
|
int count = 0;
|
for (Map<?, ?> diff : JSON.parseArray(json, Map.class)) {
|
if (field.equals(String.valueOf(diff.get("field")))) {
|
count++;
|
}
|
}
|
return count;
|
}
|
|
private static String batchRowDiff(String targetId, String oldData, String newData) {
|
return "[{\"field\":\"status\",\"targetTable\":\"lims_task\",\"targetId\":\""
|
+ targetId + "\",\"oldData\":\"" + oldData + "\",\"newData\":\""
|
+ newData + "\"}]";
|
}
|
|
private static boolean diffHasScopedValue(String json, String table, String field, String expected) {
|
for (Map<?, ?> diff : JSON.parseArray(json, Map.class)) {
|
if (table.equals(String.valueOf(diff.get("targetTable")))
|
&& field.equals(String.valueOf(diff.get("field")))) {
|
return expected.equals(String.valueOf(diff.get("newData")));
|
}
|
}
|
return false;
|
}
|
|
private static LinkedHashSet<String> protectedFields(String... fields) {
|
LinkedHashSet<String> result = new LinkedHashSet<>();
|
java.util.Collections.addAll(result, fields);
|
return result;
|
}
|
|
private AuditEventIngestMergeProbe() {
|
}
|
}
|