package jnpf.bizcommon.audit.service;
|
|
import jnpf.bizcommon.audit.entity.AuditEventEntity;
|
import jnpf.audit.diff.AuditFieldDiff;
|
import jnpf.bizcommon.audit.entity.model.AuditFieldDiffViewVO;
|
import jnpf.bizcommon.audit.service.support.AuditQueryResponseSanitizer;
|
import jnpf.bizcommon.audit.service.value.AuditFieldValueResolver;
|
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
/** fieldDiffList 契约、日期/枚举展示、脱敏和无意义 diff 过滤探针。 */
|
public final class AuditFieldValueSemanticsProbe {
|
|
private static int passed;
|
private static int failed;
|
|
public static void main(String[] args) {
|
String formData = "{\"fields\":["
|
+ "{\"__vModel__\":\"biz_status\",\"__config__\":{\"label\":\"状态H\","
|
+ "\"noShow\":true,\"jnpfKey\":\"select\",\"dataType\":\"dictionary\"},"
|
+ "\"options\":[{\"enCode\":\"created\",\"fullName\":\"已创建\"}],"
|
+ "\"props\":{\"value\":\"enCode\",\"label\":\"fullName\"}},"
|
+ "{\"__vModel__\":\"shengchan_riqi\",\"__config__\":{\"label\":\"生产日期\","
|
+ "\"jnpfKey\":\"datePicker\"},\"format\":\"yyyy-MM-dd\"},"
|
+ "{\"__vModel__\":\"jiance_xiangmu\",\"__config__\":{\"label\":\"检测项目\","
|
+ "\"jnpfKey\":\"checkbox\",\"dataType\":\"dynamic\"},\"options\":[]},"
|
+ "{\"__vModel__\":\"external_status\",\"__config__\":{\"label\":\"外部状态\","
|
+ "\"jnpfKey\":\"select\",\"dataType\":\"dictionary\","
|
+ "\"dictionaryType\":\"bizStatus\"}},"
|
+ "{\"__vModel__\":\"approvalEvidence\",\"__config__\":{\"label\":\"审批凭证\","
|
+ "\"jnpfKey\":\"signature\"}}] }";
|
Map<String, AuditFieldDiff.FieldMeta> published =
|
AuditFormRegistry.parsePublishedFieldMetadata(formData);
|
check("真实 __vModel__/__config__ 可解析", published.containsKey("biz_status"), published.toString());
|
check("隐藏表单标签不覆盖 COMMENT", published.get("biz_status").label() == null,
|
String.valueOf(published.get("biz_status").label()));
|
check("字典字段类型为 enum", "enum".equals(published.get("biz_status").valueType()),
|
published.get("biz_status").valueType());
|
check("字典 options 固化展示快照", "已创建".equals(
|
published.get("biz_status").optionDisplay("created")), "未解析");
|
check("动态多选引用类型明确", "multiReference".equals(
|
published.get("jiance_xiangmu").valueType()), published.get("jiance_xiangmu").valueType());
|
check("无内联 options 的 dictionaryType 被保留",
|
"bizStatus".equals(published.get("external_status").dictionaryType()),
|
published.get("external_status").dictionaryType());
|
check("非约定字段名的签名控件由元数据标记脱敏",
|
AuditFieldDiff.isCredentialMetadata(published.get("approvalevidence")),
|
String.valueOf(published.get("approvalevidence").valueType()));
|
|
Map<String, AuditFieldDiff.FieldMeta> comments = new LinkedHashMap<>();
|
comments.put("biz_status", new AuditFieldDiff.FieldMeta(
|
"请验状态", null, null, "text", "input", null, Collections.emptyMap()));
|
Map<String, AuditFieldDiff.FieldMeta> metadata =
|
AuditFormRegistry.mergeFieldMetadata(comments, published);
|
check("COMMENT 名称与发布表单类型按属性合并", "请验状态".equals(
|
metadata.get("biz_status").label())
|
&& "enum".equals(metadata.get("biz_status").valueType()),
|
String.valueOf(metadata.get("biz_status").label()));
|
Map<String, AuditFieldDiff.FieldMeta> eventLabels = new LinkedHashMap<>();
|
eventLabels.put("biz_status", new AuditFieldDiff.FieldMeta("状态H", "select", true));
|
Map<String, AuditFieldDiff.FieldMeta> mergedEvent =
|
AuditFormRegistry.mergeFieldMetadata(metadata, eventLabels);
|
check("隐藏字段的 listLog 标签不覆盖 COMMENT",
|
"请验状态".equals(mergedEvent.get("biz_status").label()),
|
mergedEvent.get("biz_status").label());
|
|
Map<String, Object> createRow = new LinkedHashMap<>();
|
createRow.put("flowId", "");
|
createRow.put("biz_status", "created");
|
createRow.put("external_status", "created");
|
createRow.put("approvalEvidence", "credential-from-control");
|
String produced = AuditFieldDiff.diff(null, createRow, Collections.emptySet(), metadata);
|
check("CREATE 空值字段不生成 diff", !produced.contains("flowId"), produced);
|
check("未来事件固化 valueType/componentType", produced.contains("\"valueType\":\"enum\"")
|
&& produced.contains("\"componentType\":\"select\""), produced);
|
check("未来事件固化字典展示快照", produced.contains("\"newDisplay\":\"已创建\""), produced);
|
check("未来事件固化 dictionaryType",
|
produced.contains("\"dictionaryType\":\"bizStatus\""), produced);
|
check("元数据签名控件写入 fieldDiffs 前已脱敏",
|
!produced.contains("credential-from-control") && produced.contains(AuditFieldDiff.MASKED),
|
produced);
|
String snapshot = AuditFieldDiff.snapshot(createRow, Collections.emptySet(), metadata);
|
check("元数据签名控件写入 dataSnapshot 前已脱敏",
|
!snapshot.contains("credential-from-control") && snapshot.contains(AuditFieldDiff.MASKED),
|
snapshot);
|
|
String raw = "["
|
+ "{\"field\":\"biz_status\",\"fieldName\":\"biz_status\",\"type\":0,"
|
+ "\"oldData\":\"\",\"newData\":\"created\"},"
|
+ "{\"field\":\"shengchan_riqi\",\"fieldName\":\"shengchan_riqi\",\"type\":0,"
|
+ "\"oldData\":\"\",\"newData\":\"1782835200000\"},"
|
+ "{\"field\":\"jianyan_biaozhun\",\"fieldName\":\"检验标准\",\"type\":0,"
|
+ "\"oldData\":\"\",\"newData\":\"standard-1\"},"
|
+ "{\"field\":\"biz_sign\",\"fieldName\":\"biz_sign\",\"type\":0,"
|
+ "\"oldData\":\"\",\"newData\":\"credential-raw\"},"
|
+ "{\"field\":\"flowId\",\"fieldName\":\"flowId\",\"type\":0,"
|
+ "\"oldData\":\"\",\"newData\":\"\"}]";
|
metadata.put("shengchan_riqi", published.get("shengchan_riqi"));
|
metadata.put("jianyan_biaozhun", new AuditFieldDiff.FieldMeta(
|
"检验标准", "select", true, "reference", "select", null, Collections.emptyMap()));
|
String sanitized = AuditQueryResponseSanitizer.sanitizeFieldDiffs(raw, metadata);
|
check("兼容 fieldDiffs 过滤空到空", !sanitized.contains("flowId"), sanitized);
|
check("兼容 fieldDiffs 仍脱敏签名", !sanitized.contains("credential-raw")
|
&& sanitized.contains(AuditFieldDiff.MASKED), sanitized);
|
check("diffCount 使用有效项", AuditQueryResponseSanitizer.effectiveDiffCount(raw) == 4,
|
String.valueOf(AuditQueryResponseSanitizer.effectiveDiffCount(raw)));
|
String malformedItem = "[{\"oldData\":\"a\",\"newData\":\"b\"}]";
|
check("缺 field 的损坏项不计入 diffCount",
|
AuditQueryResponseSanitizer.effectiveDiffCount(malformedItem) == 0,
|
String.valueOf(AuditQueryResponseSanitizer.effectiveDiffCount(malformedItem)));
|
String whitespaceChange = "[{\"field\":\"remark\",\"oldData\":\" A \",\"newData\":\"A\"}]";
|
check("非空文本的空白变化仍保留",
|
AuditQueryResponseSanitizer.effectiveDiffCount(whitespaceChange) == 1,
|
String.valueOf(AuditQueryResponseSanitizer.effectiveDiffCount(whitespaceChange)));
|
|
AtomicInteger resolverCalls = new AtomicInteger();
|
AuditFieldValueResolver failingResolver = new AuditFieldValueResolver() {
|
@Override
|
public boolean supports(String targetTable) {
|
return true;
|
}
|
|
@Override
|
public void resolve(jnpf.bizcommon.audit.service.value.AuditFieldValueContext context,
|
List<AuditFieldDiffViewVO> fields) {
|
throw new IllegalStateException("expected-probe-failure");
|
}
|
};
|
AuditFieldValueResolver fakeResolver = new AuditFieldValueResolver() {
|
@Override
|
public boolean supports(String targetTable) {
|
return "lims_qingyandan".equals(targetTable);
|
}
|
|
@Override
|
public void resolve(jnpf.bizcommon.audit.service.value.AuditFieldValueContext context,
|
List<AuditFieldDiffViewVO> fields) {
|
resolverCalls.incrementAndGet();
|
for (AuditFieldDiffViewVO field : fields) {
|
if ("jianyan_biaozhun".equals(field.getField())) {
|
field.setNewDisplay("GB2 · 阿莫西林胶囊");
|
}
|
}
|
}
|
};
|
AuditEventEntity event = new AuditEventEntity();
|
event.setTargetTable("lims_qingyandan");
|
event.setTenantId("0");
|
event.setActionCode("CREATE");
|
AuditFieldDiffViewService viewService = new AuditFieldDiffViewService(
|
Arrays.asList(failingResolver, fakeResolver), "Asia/Shanghai");
|
List<AuditFieldDiffViewVO> views = viewService.build(event, sanitized, metadata);
|
check("单个 resolver 失败不影响详情", !views.isEmpty(), String.valueOf(views.size()));
|
check("结构化列表只调用一次批量 resolver", resolverCalls.get() == 1,
|
String.valueOf(resolverCalls.get()));
|
check("结构化列表项数与 diffCount 一致", views.size() == 4, String.valueOf(views.size()));
|
check("type=0 明确转换 created", "created".equals(find(views, "biz_status").getChangeType()),
|
find(views, "biz_status").getChangeType());
|
check("状态展示使用 options 快照", "已创建".equals(find(views, "biz_status").getNewDisplay()),
|
find(views, "biz_status").getNewDisplay());
|
check("epoch 毫秒按业务时区格式化", "2026-07-01".equals(
|
find(views, "shengchan_riqi").getNewDisplay()),
|
find(views, "shengchan_riqi").getNewDisplay());
|
check("显式引用解析器提供标准展示", "GB2 · 阿莫西林胶囊".equals(
|
find(views, "jianyan_biaozhun").getNewDisplay()),
|
find(views, "jianyan_biaozhun").getNewDisplay());
|
String enriched = viewService.enrichForPersistence(event, sanitized, metadata);
|
check("未来引用字段固化事件发生时展示快照",
|
enriched.contains("\"newDisplay\":\"GB2 · 阿莫西林胶囊\""), enriched);
|
check("展示快照增强保留旧 type 契约", enriched.contains("\"type\":0"), enriched);
|
AuditFieldDiffViewVO sign = find(views, "biz_sign");
|
check("masked 结构化项不含原始值", Boolean.TRUE.equals(sign.getMasked())
|
&& sign.getOldData() == null && sign.getNewData() == null
|
&& sign.getOldDisplay() == null && sign.getNewDisplay() == null, String.valueOf(sign));
|
|
String metadataSignRaw = "[{\"field\":\"approvalEvidence\",\"fieldName\":\"审批凭证\","
|
+ "\"jnpfKey\":\"signature\",\"type\":1,\"oldData\":\"old-secret\","
|
+ "\"newData\":\"new-secret\",\"oldDisplay\":\"old-secret\","
|
+ "\"newDisplay\":\"new-secret\"}]";
|
String metadataSignSanitized = AuditQueryResponseSanitizer.sanitizeFieldDiffs(
|
metadataSignRaw, metadata);
|
List<AuditFieldDiffViewVO> metadataSignViews = viewService.build(
|
event, metadataSignSanitized, metadata);
|
AuditFieldDiffViewVO metadataSign = find(metadataSignViews, "approvalEvidence");
|
check("查询侧按控件元数据脱敏非约定签名字段",
|
!metadataSignSanitized.contains("secret")
|
&& Boolean.TRUE.equals(metadataSign.getMasked())
|
&& metadataSign.getOldData() == null && metadataSign.getNewData() == null,
|
metadataSignSanitized);
|
String sanitizedSnapshot = AuditQueryResponseSanitizer.sanitizeJson(
|
"{\"approvalEvidence\":\"snapshot-secret\"}", metadata);
|
check("历史 dataSnapshot 按当前签名元数据响应级脱敏",
|
!sanitizedSnapshot.contains("snapshot-secret")
|
&& sanitizedSnapshot.contains(AuditFieldDiff.MASKED), sanitizedSnapshot);
|
|
String legacyTime = "["
|
+ "{\"field\":\"legacyDate\",\"fieldName\":\"日期\",\"jnpfKey\":\"datePicker\","
|
+ "\"type\":0,\"oldData\":\"\",\"newData\":\"1782835200000\"},"
|
+ "{\"field\":\"legacyTime\",\"fieldName\":\"时间\",\"jnpfKey\":\"timePicker\","
|
+ "\"type\":0,\"oldData\":\"\",\"newData\":\"1782835200000\"}]";
|
AuditFieldDiffViewService noResolverService = new AuditFieldDiffViewService(
|
Collections.emptyList(), "Asia/Shanghai");
|
List<AuditFieldDiffViewVO> legacyTimeViews = noResolverService.build(
|
event, legacyTime, Collections.emptyMap());
|
check("无当前元数据的 datePicker 仍格式化 epoch",
|
"2026-07-01".equals(find(legacyTimeViews, "legacyDate").getNewDisplay()),
|
find(legacyTimeViews, "legacyDate").getNewDisplay());
|
check("timePicker fallback 使用 datetime 而非 date",
|
"datetime".equals(find(legacyTimeViews, "legacyTime").getValueType())
|
&& "2026-07-01 00:00:00".equals(
|
find(legacyTimeViews, "legacyTime").getNewDisplay()),
|
find(legacyTimeViews, "legacyTime").getNewDisplay());
|
|
AuditFieldValueResolver partialFailure = new AuditFieldValueResolver() {
|
@Override
|
public boolean supports(String targetTable) {
|
return true;
|
}
|
|
@Override
|
public void resolve(jnpf.bizcommon.audit.service.value.AuditFieldValueContext context,
|
List<AuditFieldDiffViewVO> fields) {
|
fields.get(0).setNewDisplay("不应保留的半成品");
|
throw new IllegalStateException("expected-after-mutation");
|
}
|
};
|
AuditFieldDiffViewService atomicService = new AuditFieldDiffViewService(
|
Collections.singletonList(partialFailure), "Asia/Shanghai");
|
List<AuditFieldDiffViewVO> atomicViews = atomicService.build(event,
|
"[{\"field\":\"remark\",\"fieldName\":\"备注\",\"type\":0,"
|
+ "\"oldData\":\"\",\"newData\":\"原始值\"}]",
|
Collections.emptyMap());
|
check("resolver 修改后抛错会原子回滚展示结果",
|
"原始值".equals(find(atomicViews, "remark").getNewDisplay()),
|
find(atomicViews, "remark").getNewDisplay());
|
|
System.out.println("=== 字段值语义断言:" + passed + " 通过 / " + failed + " 失败 ===");
|
System.exit(failed == 0 ? 0 : 1);
|
}
|
|
private static AuditFieldDiffViewVO find(List<AuditFieldDiffViewVO> views, String field) {
|
for (AuditFieldDiffViewVO view : views) {
|
if (field.equals(view.getField())) {
|
return view;
|
}
|
}
|
throw new IllegalStateException("缺少字段:" + field);
|
}
|
|
private static void check(String name, boolean ok, String detail) {
|
if (ok) {
|
passed++;
|
System.out.println(" [PASS] " + name);
|
} else {
|
failed++;
|
System.out.println(" [FAIL] " + name + " -- " + detail);
|
}
|
}
|
|
private AuditFieldValueSemanticsProbe() {
|
}
|
}
|