刘光辉
13 小时以前 34981c30a78e8bbd7791131059a9210f9928b62c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
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\":\"状态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() {
    }
}