package jnpf.bizcommon.audit.service.value; import jnpf.bizcommon.audit.entity.model.AuditFieldDiffViewVO; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** userSelect 用户 id → 「真实姓名/账号」的批量解析、控件类型判据与租户边界探针。 */ public final class JnpfUserFieldValueResolverProbe { private static int passed; private static int failed; public static void main(String[] args) { CountingLookup lookup = new CountingLookup(); JnpfUserFieldValueResolver resolver = new JnpfUserFieldValueResolver(lookup); List fields = Arrays.asList( field("tijiao_ren", "userSelect", "", "u-admin"), field("fuhe_ren", "UserSelect", "u-admin", "u-ys"), field("chouyang_ren", "userSelect", "", "[\"u-ys\",\"u-gone\"]"), // 两个非 userSelect 干扰项刻意用**独有**的值:若用 u-admin 这种已被真实用户 // 字段收集过的 id,即便解析器错误地把它们也收进去,Set 去重后条数纹丝不动, // 断言就成了永远转不红的摆设(L077 变体 5/8 同款陷阱)。 field("beizhu", "input", "", "u-only-in-beizhu"), field("zhuangtai", "select", "", "u-only-in-zhuangtai")); resolver.resolve(new AuditFieldValueContext("lims_ceshi_huichuan", "0", null), fields); check("整批只查一次库", lookup.calls == 1, "calls=" + lookup.calls); check("租户边界透传进查询", "0".equals(lookup.lastTenantId), String.valueOf(lookup.lastTenantId)); // 判据是控件类型不是字段名:非 userSelect 的列即便存着用户 id 也不碰, // 否则会把恰好等于某个用户 id 的普通文本篡改成人名。 check("非 userSelect 控件不被解析", fields.get(3).getNewDisplay() == null && fields.get(4).getNewDisplay() == null, fields.get(3).getNewDisplay() + " / " + fields.get(4).getNewDisplay()); check("非 userSelect 控件的值不进 ID 收集", lookup.requestedIds.equals(Arrays.asList("u-admin", "u-ys", "u-gone")), lookup.requestedIds.toString()); check("展示格式为 realName/account", "管理员/admin".equals(fields.get(0).getNewDisplay()), fields.get(0).getNewDisplay()); check("控件类型大小写不敏感", "yangsui/ys0001".equals(fields.get(1).getNewDisplay()), fields.get(1).getNewDisplay()); check("oldData 与 newData 两侧都解析", "管理员/admin".equals(fields.get(1).getOldDisplay()), fields.get(1).getOldDisplay()); check("空值解析成空串而不是「名称暂不可用」", "".equals(fields.get(0).getOldDisplay()), fields.get(0).getOldDisplay()); check("多选按请求顺序解析,单项查不到才回落", "yangsui/ys0001、名称暂不可用".equals(fields.get(2).getNewDisplay()), fields.get(2).getNewDisplay()); // 前序解析器(表专用/字典)已经填过 display 的字段不重复查库—— // @Order 建立的优先级靠这个 if 才落得下去。 CountingLookup skipLookup = new CountingLookup(); AuditFieldDiffViewVO resolved = field("qingyan_ren", "userSelect", "", "u-admin"); resolved.setNewDisplay("管理员"); new JnpfUserFieldValueResolver(skipLookup).resolve( new AuditFieldValueContext("lims_qingyandan", "0", null), Collections.singletonList(resolved)); check("前序解析器已填的 display 不被覆盖,也不触发查库", skipLookup.calls == 0 && "管理员".equals(resolved.getNewDisplay()), skipLookup.calls + " / " + resolved.getNewDisplay()); // 租户拿不到时宁可不解析,也不做一次无边界的全库查询。 CountingLookup blankTenant = new CountingLookup(); new JnpfUserFieldValueResolver(blankTenant).resolve( new AuditFieldValueContext("lims_ceshi_huichuan", "", null), Collections.singletonList(field("tijiao_ren", "userSelect", "", "u-admin"))); check("空租户 fail-closed(不查库)", blankTenant.calls == 0, String.valueOf(blankTenant.calls)); check("supports 对任意表恒为 true(userSelect 是平台控件)", resolver.supports("lims_ceshi_huichuan") && resolver.supports("any_table") && resolver.supports(null), "supports 收窄了适用表"); // 展示格式装配本身(验收标准「管理员/admin」的直接判据) check("userDisplay 拼 realName/account", "管理员/admin".equals(JnpfUserFieldValueResolver.userDisplay("管理员", "admin")), JnpfUserFieldValueResolver.userDisplay("管理员", "admin")); check("userDisplay 缺账号时不留孤零零斜杠", "管理员".equals(JnpfUserFieldValueResolver.userDisplay("管理员", null)) && "管理员".equals(JnpfUserFieldValueResolver.userDisplay("管理员", " ")), JnpfUserFieldValueResolver.userDisplay("管理员", null)); check("userDisplay 缺姓名时退回账号", "admin".equals(JnpfUserFieldValueResolver.userDisplay(null, "admin")), JnpfUserFieldValueResolver.userDisplay(null, "admin")); check("userDisplay 两边都空返回 null(调用方据此不入 map)", JnpfUserFieldValueResolver.userDisplay(null, null) == null, String.valueOf(JnpfUserFieldValueResolver.userDisplay(null, null))); // @Order 常量的相对次序即解析优先级;数值写反会让通用解析器抢在表专用之前。 check("解析器优先级 表专用 < 字典 < 用户兜底", LimsQingyandanFieldValueResolver.ORDER < JnpfDictionaryFieldValueResolver.ORDER && JnpfDictionaryFieldValueResolver.ORDER < JnpfUserFieldValueResolver.ORDER, LimsQingyandanFieldValueResolver.ORDER + " / " + JnpfDictionaryFieldValueResolver.ORDER + " / " + JnpfUserFieldValueResolver.ORDER); System.out.println("=== 用户字段解析断言:" + passed + " 通过 / " + failed + " 失败 ==="); System.exit(failed == 0 ? 0 : 1); } private static AuditFieldDiffViewVO field(String name, String componentType, String oldData, String newData) { AuditFieldDiffViewVO field = new AuditFieldDiffViewVO(); field.setField(name); field.setComponentType(componentType); field.setOldData(oldData); field.setNewData(newData); return field; } private static final class CountingLookup implements JnpfUserFieldValueResolver.UserLookup { private int calls; private String lastTenantId; private final List requestedIds = new ArrayList<>(); @Override public Map names(String tenantId, Set ids) { calls++; lastTenantId = tenantId; requestedIds.addAll(ids); Map result = new LinkedHashMap<>(); result.put("u-admin", "管理员/admin"); result.put("u-ys", "yangsui/ys0001"); return result; // u-gone 刻意缺席:模拟用户被删/查不到 } } 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 JnpfUserFieldValueResolverProbe() { } }