package jnpf.bizcommon.audit.service.sign; import jnpf.bizcommon.audit.service.title.AuditTitleSpec; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 签名字段候选挑选的纯 JVM 断言。 * *

判定规则为什么长这样:{@code biz_log_enabled} 在「操作记录标题」语境里是 * "这个字段进标题",在「多签名」语境里是"这是签名字段"——同一个开关两种语义, * 光看开关分不出来。于是判定拆成三层: *

    *
  1. {@code biz_sign} 及命名规则命中 {@code isCredentialReference} 的字段:为签名字段, * 不需要勾(这批字段本就已被强制脱敏);
  2. *
  3. 其余勾选字段:值拿去查签名表,命中的当签名字段;
  4. *
  5. 没命中的当标题字段。
  6. *
* 本探针只覆盖第 1、2 层的候选挑选(纯逻辑);"查表命中"那一跳需要 DB,放端到端。 * *

runner:{@code tasks/audit-sign-field-checks.sh} */ public final class AuditSignFieldProbe { private static int pass; private static int fail; public static void main(String[] args) { credentialNamesAreAlwaysCandidates(); enabledFieldsAreCandidates(); emptyValuesAreNotCandidates(); unmarkedNonCredentialFieldsAreNotCandidates(); candidatesAreDeduplicatedAndStable(); configuredFieldsTakePriority(); System.out.println("=== 签名字段候选断言:" + pass + " 通过 / " + fail + " 失败 ==="); if (fail > 0) { System.exit(1); } } /** S1 biz_sign 恒为候选,哪怕表单一个字段都没勾。 */ private static void credentialNamesAreAlwaysCandidates() { Map row = row("biz_sign", "852000000000000001"); List c = AuditSignEvidenceReader.candidateFields( row, AuditTitleSpec.EMPTY, Collections.emptyList()); check("S1 biz_sign 恒为候选", c.contains("biz_sign"), "实际 " + c); Map row2 = row("fuhe_sign", "852000000000000002"); List c2 = AuditSignEvidenceReader.candidateFields( row2, AuditTitleSpec.EMPTY, Collections.emptyList()); check("S1 *_sign 命名规则命中", c2.contains("fuhe_sign"), "实际 " + c2); } /** S2 勾选字段进候选(是不是签名由查表定,不由本方法定)。 */ private static void enabledFieldsAreCandidates() { Map row = row("pizhun_ren", "852000000000000003"); AuditTitleSpec spec = new AuditTitleSpec( Arrays.asList("pizhun_ren"), Collections.emptyList()); List c = AuditSignEvidenceReader.candidateFields(row, spec, Collections.emptyList()); check("S2 勾选字段进候选", c.contains("pizhun_ren"), "实际 " + c); } /** S3 值为空的不进候选——空值查签名表纯属浪费,还会把空串当 IN 参数。 */ private static void emptyValuesAreNotCandidates() { Map row = new LinkedHashMap(); row.put("biz_sign", ""); row.put("fuhe_sign", null); row.put("pizhun_ren", " "); AuditTitleSpec spec = new AuditTitleSpec( Arrays.asList("pizhun_ren"), Collections.emptyList()); List c = AuditSignEvidenceReader.candidateFields(row, spec, Collections.emptyList()); check("S3 空值不进候选", c.isEmpty(), "实际 " + c); } /** S4 既没勾、名字也不像签名的字段不进候选——否则每次保存都要拿一堆业务值去撞签名表。 */ private static void unmarkedNonCredentialFieldsAreNotCandidates() { Map row = row("chanpin_mingcheng", "青岩丹"); List c = AuditSignEvidenceReader.candidateFields( row, AuditTitleSpec.EMPTY, Collections.emptyList()); check("S4 未勾选的普通字段不进候选", c.isEmpty(), "实际 " + c); } /** S5 同一字段两条路径都命中时只出现一次,且顺序稳定(幂等键要靠它稳定)。 */ private static void candidatesAreDeduplicatedAndStable() { Map row = new LinkedHashMap(); row.put("biz_sign", "852000000000000001"); row.put("pizhun_ren", "852000000000000003"); AuditTitleSpec spec = new AuditTitleSpec( Arrays.asList("biz_sign", "pizhun_ren"), Collections.emptyList()); List a = AuditSignEvidenceReader.candidateFields(row, spec, Collections.emptyList()); List b = AuditSignEvidenceReader.candidateFields(row, spec, Collections.emptyList()); check("S5 去重", a.size() == 2, "实际 " + a); check("S5 顺序稳定", a.equals(b), a + " vs " + b); } /** * S6(评审修复轮次 1,D4):配置来源必须排在候选集合最前面——这是本任务(D4)新加的行为, * 前 5 条断言都是改动前就有的旧覆盖,全传空 {@code configuredFields},测不到这条。 * *

row 里故意先放一个命名规则命中的字段({@code fuhe_sign}),若没有配置来源它本应排最前; * 再放一个只靠配置来源才进候选的字段({@code pizhun_ren},不命中命名规则、未勾选)。 * 断言 {@code get(0)} 是配置字段,直接执行验证优先级,而不是像 * {@code tasks/audit-delete-coverage-checks.sh} 那样只看源码文本形状—— * 容器换成无序 Set 这类"文本还在、行为已变"的回归,源码级断言未必能稳定抓住,靠这条兜底。 */ private static void configuredFieldsTakePriority() { Map row = new LinkedHashMap(); row.put("fuhe_sign", "852000000000000010"); row.put("pizhun_ren", "852000000000000011"); List configured = Arrays.asList("pizhun_ren"); List c = AuditSignEvidenceReader.candidateFields(row, AuditTitleSpec.EMPTY, configured); check("S6 配置来源排在候选最前", !c.isEmpty() && "pizhun_ren".equals(c.get(0)), "实际 " + c); check("S6 命名规则命中的字段仍在候选里(兜底未失效)", c.contains("fuhe_sign"), "实际 " + c); } private static Map row(String field, Object value) { Map row = new LinkedHashMap(); row.put(field, value); return row; } private static void check(String name, boolean ok, String detail) { if (ok) { pass++; System.out.println(" ✅ " + name); } else { fail++; System.out.println(" ❌ " + name + " —— " + detail); } } }