刘光辉
昨天 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
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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
package jnpf.bizcommon.audit.service.value;
 
import com.alibaba.fastjson.JSON;
import jnpf.bizcommon.audit.entity.model.AuditFieldDiffViewVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Component;
 
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
 
/**
 * 请验单字段的显式业务解析器;引用表和展示列均为固定白名单。
 *
 * <p><b>{@code @Order} 不是装饰</b>(2026-08-02 加):解析器按注入顺序串行执行,先写上
 * display 的那个说了算(后续解析器一律 {@code if (display == null)} 才动手)。表专用解析器
 * 必须排在通用解析器之前,否则 {@code qingyan_ren} 会被
 * {@link JnpfUserFieldValueResolver} 按通用格式(realName/account)抢先解析掉,
 * 本类为它定的格式(只显示姓名)就永远不生效。Spring 对没标注的 bean 一律按
 * {@code LOWEST_PRECEDENCE} 处理、保留扫描序——那是类名字母序的副产品,不该拿它当契约。
 */
@Slf4j
@Component
@Order(LimsQingyandanFieldValueResolver.ORDER)
public class LimsQingyandanFieldValueResolver implements AuditFieldValueResolver {
 
    /** 表专用解析器,最先跑。 */
    public static final int ORDER = 100;
 
    private static final String TABLE = "lims_qingyandan";
    private static final String UNAVAILABLE = "名称暂不可用";
    private static final Map<String, String> BIZ_STATUS = labels(
            "created", "已创建", "confirmed", "已提交", "pending_fetch", "待取样",
            "deleted", "已废弃", "cancelled", "已撤回", "fetched", "已取样",
            "pending_receive", "待接样", "received", "已接样", "rejected", "已退回");
    private static final Map<String, String> BIZ_TYPE = labels("CP", "成品");
    private static final Map<String, String> JIANCE_FANGSHI = labels(
            "quanjian", "全检", "bufenjian", "部分检", "mianjian", "免检");
 
    private final ReferenceLookup lookup;
 
    @Autowired
    public LimsQingyandanFieldValueResolver(JdbcTemplate jdbcTemplate) {
        this(new JdbcReferenceLookup(jdbcTemplate));
    }
 
    LimsQingyandanFieldValueResolver(ReferenceLookup lookup) {
        this.lookup = lookup;
    }
 
    @Override
    public boolean supports(String targetTable) {
        return targetTable != null && TABLE.equals(targetTable.trim().toLowerCase(Locale.ROOT));
    }
 
    @Override
    public void resolve(AuditFieldValueContext context, List<AuditFieldDiffViewVO> fields) {
        if (fields == null || fields.isEmpty()) {
            return;
        }
        applyBusinessEnums(fields);
        Set<String> projectIds = new LinkedHashSet<>();
        Set<String> standardIds = new LinkedHashSet<>();
        Set<String> userIds = new LinkedHashSet<>();
        Set<String> productIds = new LinkedHashSet<>();
 
        for (AuditFieldDiffViewVO field : fields) {
            String name = normalized(field.getField());
            if ("jiance_xiangmu".equals(name)) {
                setMissingSemantics(field, "multiReference", "checkbox");
                collect(projectIds, field.getOldData());
                collect(projectIds, field.getNewData());
            } else if ("jianyan_biaozhun".equals(name)) {
                setMissingSemantics(field, "reference", "select");
                collect(standardIds, field.getOldData());
                collect(standardIds, field.getNewData());
            } else if ("qingyan_ren".equals(name)) {
                setMissingSemantics(field, "reference", "userSelect");
                collect(userIds, field.getOldData());
                collect(userIds, field.getNewData());
            } else if ("chanpin_id".equals(name)) {
                setMissingSemantics(field, "reference", "popupSelect");
                field.setTechnical(Boolean.TRUE);
                collect(productIds, field.getOldData());
                collect(productIds, field.getNewData());
            }
        }
 
        if (context == null || blank(context.getTenantId())) {
            return;
        }
        String tenantId = context.getTenantId().trim();
        Map<String, String> projects = projectIds.isEmpty()
                ? Collections.emptyMap() : lookup.projects(tenantId, projectIds);
        Map<String, String> standards = standardIds.isEmpty()
                ? Collections.emptyMap() : lookup.standards(tenantId, standardIds);
        Map<String, String> users = userIds.isEmpty()
                ? Collections.emptyMap() : lookup.users(tenantId, userIds);
        Map<String, String> products = productIds.isEmpty()
                ? Collections.emptyMap() : lookup.products(tenantId, productIds);
 
        for (AuditFieldDiffViewVO field : fields) {
            String name = normalized(field.getField());
            if ("jiance_xiangmu".equals(name)) {
                apply(field, projects, true);
            } else if ("jianyan_biaozhun".equals(name)) {
                apply(field, standards, false);
            } else if ("qingyan_ren".equals(name)) {
                apply(field, users, false);
            } else if ("chanpin_id".equals(name)) {
                apply(field, products, false);
            }
        }
    }
 
    private static void applyBusinessEnums(List<AuditFieldDiffViewVO> fields) {
        for (AuditFieldDiffViewVO field : fields) {
            String name = normalized(field.getField());
            if ("biz_status".equals(name)) {
                applyEnum(field, BIZ_STATUS, "select");
            } else if ("biz_type".equals(name)) {
                applyEnum(field, BIZ_TYPE, "select");
            } else if ("jiance_fangshi".equals(name)) {
                applyEnum(field, JIANCE_FANGSHI, "radio");
            }
        }
    }
 
    private static void applyEnum(AuditFieldDiffViewVO field, Map<String, String> labels,
                                  String componentType) {
        setMissingSemantics(field, "enum", componentType);
        if (field.getOldDisplay() == null) {
            field.setOldDisplay(enumDisplay(field.getOldData(), labels));
        }
        if (field.getNewDisplay() == null) {
            field.setNewDisplay(enumDisplay(field.getNewData(), labels));
        }
    }
 
    private static String enumDisplay(String raw, Map<String, String> labels) {
        if (blank(raw)) {
            return "";
        }
        return labels.get(raw.trim());
    }
 
    private static Map<String, String> labels(String... values) {
        Map<String, String> labels = new LinkedHashMap<>();
        for (int i = 0; i + 1 < values.length; i += 2) {
            labels.put(values[i], values[i + 1]);
        }
        return Collections.unmodifiableMap(labels);
    }
 
    private static void setMissingSemantics(AuditFieldDiffViewVO field,
                                            String valueType, String componentType) {
        if (blank(field.getValueType())) {
            field.setValueType(valueType);
        }
        if (blank(field.getComponentType())) {
            field.setComponentType(componentType);
        }
    }
 
    private static void apply(AuditFieldDiffViewVO field, Map<String, String> names, boolean multiple) {
        if (field.getOldDisplay() == null) {
            field.setOldDisplay(display(field.getOldData(), names, multiple));
        }
        if (field.getNewDisplay() == null) {
            field.setNewDisplay(display(field.getNewData(), names, multiple));
        }
    }
 
    private static String display(String raw, Map<String, String> names, boolean multiple) {
        if (blank(raw)) {
            return "";
        }
        List<String> ids = multiple ? parseIds(raw) : Collections.singletonList(raw.trim());
        if (ids.isEmpty()) {
            return UNAVAILABLE;
        }
        List<String> display = new ArrayList<>(ids.size());
        for (String id : ids) {
            String name = names.get(id);
            display.add(blank(name) ? UNAVAILABLE : name);
        }
        return String.join("、", display);
    }
 
    private static void collect(Set<String> target, String raw) {
        target.addAll(parseIds(raw));
    }
 
    @SuppressWarnings("unchecked")
    private static List<String> parseIds(String raw) {
        if (blank(raw)) {
            return Collections.emptyList();
        }
        String trimmed = raw.trim();
        if (!trimmed.startsWith("[")) {
            return Collections.singletonList(trimmed);
        }
        try {
            Object parsed = JSON.parse(trimmed);
            if (!(parsed instanceof Collection)) {
                return Collections.emptyList();
            }
            List<String> ids = new ArrayList<>();
            for (Object value : (Collection<Object>) parsed) {
                if (value != null && !String.valueOf(value).trim().isEmpty()) {
                    ids.add(String.valueOf(value).trim());
                }
            }
            return ids;
        } catch (Throwable ignored) {
            return Collections.emptyList();
        }
    }
 
    private static String normalized(String field) {
        return field == null ? null : field.trim().toLowerCase(Locale.ROOT);
    }
 
    private static boolean blank(String value) {
        return value == null || value.trim().isEmpty();
    }
 
    interface ReferenceLookup {
        Map<String, String> projects(String tenantId, Set<String> ids);
 
        Map<String, String> standards(String tenantId, Set<String> ids);
 
        Map<String, String> users(String tenantId, Set<String> ids);
 
        Map<String, String> products(String tenantId, Set<String> ids);
    }
 
    /** 每种引用固定一条 IN 查询;SQL 表名和列名不接受外部输入。 */
    private static final class JdbcReferenceLookup implements ReferenceLookup {
        private final NamedParameterJdbcTemplate jdbc;
 
        private JdbcReferenceLookup(JdbcTemplate jdbcTemplate) {
            this.jdbc = new NamedParameterJdbcTemplate(jdbcTemplate);
        }
 
        @Override
        public Map<String, String> projects(String tenantId, Set<String> ids) {
            return query("projects", ids, tenantId,
                    "SELECT f_id, jianyan_xiangmu_mingcheng AS display_name "
                            + "FROM zhiliang_biaozhun_guanli_zixiang "
                            + "WHERE f_id IN (:ids) "
                            + "AND COALESCE(NULLIF(f_tenant_id, ''), '0') = :tenantId",
                    row -> text(row.get("display_name")));
        }
 
        @Override
        public Map<String, String> standards(String tenantId, Set<String> ids) {
            return query("standards", ids, tenantId,
                    "SELECT f_id, biaozhun_bianhao, mingcheng FROM zhiliang_biaozhun_guanli "
                            + "WHERE f_id IN (:ids) "
                            + "AND COALESCE(NULLIF(f_tenant_id, ''), '0') = :tenantId",
                    row -> join(text(row.get("biaozhun_bianhao")), text(row.get("mingcheng"))));
        }
 
        @Override
        public Map<String, String> users(String tenantId, Set<String> ids) {
            return query("users", ids, tenantId,
                    "SELECT f_id, f_real_name, f_account FROM base_user "
                            + "WHERE f_id IN (:ids) "
                            + "AND COALESCE(NULLIF(f_tenant_id, ''), '0') = :tenantId",
                    row -> first(text(row.get("f_real_name")), text(row.get("f_account"))));
        }
 
        @Override
        public Map<String, String> products(String tenantId, Set<String> ids) {
            return query("products", ids, tenantId,
                    "SELECT f_id, mingcheng, bianma FROM yangpin_guanli "
                            + "WHERE f_id IN (:ids) "
                            + "AND COALESCE(NULLIF(f_tenant_id, ''), '0') = :tenantId",
                    row -> product(text(row.get("mingcheng")), text(row.get("bianma"))));
        }
 
        private Map<String, String> query(String resolverName, Set<String> ids, String tenantId,
                                          String sql, DisplayFactory factory) {
            if (ids.isEmpty()) {
                return Collections.emptyMap();
            }
            try {
                MapSqlParameterSource params = new MapSqlParameterSource()
                        .addValue("ids", ids)
                        .addValue("tenantId", tenantId);
                List<Map<String, Object>> rows = jdbc.queryForList(sql, params);
                Map<String, String> result = new LinkedHashMap<>();
                for (Map<String, Object> row : rows) {
                    String id = text(row.get("f_id"));
                    String display = factory.display(row);
                    if (id != null && display != null) {
                        result.put(id, display);
                    }
                }
                return result;
            } catch (RuntimeException ex) {
                log.warn("[AUDIT-QUERY] 字段引用批量解析失败 resolver={} count={} err={}",
                        resolverName, ids.size(), ex.getClass().getSimpleName());
                return Collections.emptyMap();
            }
        }
 
        private interface DisplayFactory {
            String display(Map<String, Object> row);
        }
 
        private static String join(String code, String name) {
            if (code == null) {
                return name;
            }
            return name == null ? code : code + " · " + name;
        }
 
        private static String product(String name, String code) {
            if (name == null) {
                return code;
            }
            return code == null ? name : name + "(" + code + ")";
        }
 
        private static String first(String first, String second) {
            return first == null ? second : first;
        }
 
        private static String text(Object value) {
            if (value == null) {
                return null;
            }
            String text = String.valueOf(value).trim();
            return text.isEmpty() ? null : text;
        }
    }
}