From 34981c30a78e8bbd7791131059a9210f9928b62c Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 09:24:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master

---
 jnpf-biz-common/jnpf-biz-common-audit/src/test/java/jnpf/bizcommon/audit/service/AuditFieldValueSemanticsProbe.java |  267 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 267 insertions(+), 0 deletions(-)

diff --git a/jnpf-biz-common/jnpf-biz-common-audit/src/test/java/jnpf/bizcommon/audit/service/AuditFieldValueSemanticsProbe.java b/jnpf-biz-common/jnpf-biz-common-audit/src/test/java/jnpf/bizcommon/audit/service/AuditFieldValueSemanticsProbe.java
new file mode 100644
index 0000000..6be9d29
--- /dev/null
+++ b/jnpf-biz-common/jnpf-biz-common-audit/src/test/java/jnpf/bizcommon/audit/service/AuditFieldValueSemanticsProbe.java
@@ -0,0 +1,267 @@
+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\":\"鐘舵�丠\","
+                + "\"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("鐘舵�丠", "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("鏄惧紡寮曠敤瑙f瀽鍣ㄦ彁渚涙爣鍑嗗睍绀�", "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() {
+    }
+}

--
Gitblit v1.8.0