package jnpf.bizcommon.audit.service.title; import jnpf.util.JsonUtil; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; /** * 从已发布表单 JSON({@code base_visual_release.F_FORM_DATA})解析 * {@code __config__.biz_log_enabled} 勾选。 * *

纯静态、无依赖、永不抛异常:解析失败一律返回 {@link AuditTitleSpec#EMPTY}。 * 层 0 的 handler 外层虽有 {@code catch(Throwable)} 兜底(L002),但那会丢掉**整条事件**—— * 标题解析失败只该让标题缺席,不该让"这次改过"缺席。 * *

递归规则:布局容器(栅格 row / 标签页 tab / 卡片 card / 表格布局 tableGridTr…)的 * children 是主表字段,必须继续往下收;只有 {@code jnpfKey=table}(子表控件)的 children * 才是另一张表的字段,独立成组且**不再往主表收**。 */ public final class AuditTitleSpecParser { /** 子表控件的 jnpfKey,与 {@code AuditFieldDiff.JNPF_KEY_TABLE} 同值。 */ private static final String JNPF_KEY_TABLE = "table"; private AuditTitleSpecParser() { } @SuppressWarnings({"rawtypes", "unchecked"}) public static AuditTitleSpec parse(String formDataJson) { if (formDataJson == null || formDataJson.trim().isEmpty()) { return AuditTitleSpec.EMPTY; } try { Map root = JsonUtil.stringToMap(formDataJson); if (root == null) { return AuditTitleSpec.EMPTY; } List fields = asList(root.get("fields")); if (fields.isEmpty()) { return AuditTitleSpec.EMPTY; } List main = new ArrayList(); List subs = new ArrayList(); collect(fields, main, subs); if (main.isEmpty() && subs.isEmpty()) { return AuditTitleSpec.EMPTY; } return new AuditTitleSpec(main, subs); } catch (Throwable t) { // 这段文本解析不了就是解析不了,返回 EMPTY = 不产出标题。不记日志: // 同一个表单每次保存都会走到这里,报错会把日志刷爆;调用方按 EMPTY 写 extra 留痕。 return AuditTitleSpec.EMPTY; } } @SuppressWarnings("rawtypes") private static void collect(List fields, List main, List subs) { for (Object raw : fields) { if (!(raw instanceof Map)) { continue; } Map item = (Map) raw; Map config = config(item); if (config == null) { continue; } // 子表 vModel 保留原始大小写(如 tableField6da682):Task 4 要拿它去 newData 里 // 按键取子表明细,newData 的键就是表单设计器写的原样,lower() 过就永远查不到。 // 主表字段则要小写——见 AuditTitleSpec.mainFields() 的口径。 String rawVModel = str(item.containsKey("vModel") ? item.get("vModel") : item.get("__vModel__")); String vModel = lower(rawVModel); String jnpfKey = str(config.get("jnpfKey")); List children = asList(config.get("children")); if (JNPF_KEY_TABLE.equals(jnpfKey)) { List subFields = new ArrayList(); collectEnabledFlat(children, subFields); if (rawVModel != null && !subFields.isEmpty()) { subs.add(new AuditTitleSpec.SubTableSpec( rawVModel, lower(str(config.get("tableName"))), subFields)); } continue; // 子表 children 已单独处理,绝不能再往主表收 } if (vModel != null && enabled(config)) { main.add(vModel); } if (!children.isEmpty()) { collect(children, main, subs); // 布局容器:children 仍是主表字段 } } } /** 子表内部不再嵌套子表(平台不支持),故只收一层勾选字段。 */ @SuppressWarnings("rawtypes") private static void collectEnabledFlat(List fields, List out) { for (Object raw : fields) { if (!(raw instanceof Map)) { continue; } Map item = (Map) raw; Map config = config(item); if (config == null) { continue; } String vModel = lower(str(item.containsKey("vModel") ? item.get("vModel") : item.get("__vModel__"))); if (vModel != null && enabled(config)) { out.add(vModel); } List children = asList(config.get("children")); if (!children.isEmpty()) { collectEnabledFlat(children, out); // 子表里的布局容器 } } } @SuppressWarnings("rawtypes") private static Map config(Map item) { Object raw = item.containsKey("config") ? item.get("config") : item.get("__config__"); return raw instanceof Map ? (Map) raw : null; } @SuppressWarnings("rawtypes") private static boolean enabled(Map config) { Object raw = config.get("biz_log_enabled"); if (raw instanceof Boolean) { return (Boolean) raw; } // 跨 JVM / 存量表单里可能是字符串 "true";认不出就当没勾——**不猜**, // 猜错的方向是把没勾的字段写进明文标题列。 return raw != null && "true".equalsIgnoreCase(String.valueOf(raw).trim()); } @SuppressWarnings({"rawtypes", "unchecked"}) private static List asList(Object raw) { if (raw instanceof List) { return (List) raw; } if (raw == null || String.valueOf(raw).trim().isEmpty()) { return Collections.emptyList(); } List parsed = JsonUtil.getJsonToList(String.valueOf(raw), Map.class); return parsed == null ? Collections.emptyList() : parsed; } private static String str(Object value) { if (value == null) { return null; } String s = value.toString().trim(); return s.isEmpty() ? null : s; } private static String lower(String value) { return value == null ? null : value.toLowerCase(Locale.ROOT); } }