刘光辉
昨天 bb638871a7fb692d80f1b7a758f991dc0879002c
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
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} 勾选。
 *
 * <p><b>纯静态、无依赖、永不抛异常</b>:解析失败一律返回 {@link AuditTitleSpec#EMPTY}。
 * 层 0 的 handler 外层虽有 {@code catch(Throwable)} 兜底(L002),但那会丢掉**整条事件**——
 * 标题解析失败只该让标题缺席,不该让"这次改过"缺席。
 *
 * <p><b>递归规则</b>:布局容器(栅格 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<String, Object> root = JsonUtil.stringToMap(formDataJson);
            if (root == null) {
                return AuditTitleSpec.EMPTY;
            }
            List<?> fields = asList(root.get("fields"));
            if (fields.isEmpty()) {
                return AuditTitleSpec.EMPTY;
            }
            List<String> main = new ArrayList<String>();
            List<AuditTitleSpec.SubTableSpec> subs = new ArrayList<AuditTitleSpec.SubTableSpec>();
            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<String> main,
                                List<AuditTitleSpec.SubTableSpec> 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<String> subFields = new ArrayList<String>();
                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<String> 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<Map> 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);
    }
}