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; /** * 请验单字段的显式业务解析器;引用表和展示列均为固定白名单。 * *

{@code @Order} 不是装饰(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 BIZ_STATUS = labels( "created", "已创建", "confirmed", "已提交", "pending_fetch", "待取样", "deleted", "已废弃", "cancelled", "已撤回", "fetched", "已取样", "pending_receive", "待接样", "received", "已接样", "rejected", "已退回"); private static final Map BIZ_TYPE = labels("CP", "成品"); private static final Map 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 fields) { if (fields == null || fields.isEmpty()) { return; } applyBusinessEnums(fields); Set projectIds = new LinkedHashSet<>(); Set standardIds = new LinkedHashSet<>(); Set userIds = new LinkedHashSet<>(); Set 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 projects = projectIds.isEmpty() ? Collections.emptyMap() : lookup.projects(tenantId, projectIds); Map standards = standardIds.isEmpty() ? Collections.emptyMap() : lookup.standards(tenantId, standardIds); Map users = userIds.isEmpty() ? Collections.emptyMap() : lookup.users(tenantId, userIds); Map 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 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 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 labels) { if (blank(raw)) { return ""; } return labels.get(raw.trim()); } private static Map labels(String... values) { Map 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 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 names, boolean multiple) { if (blank(raw)) { return ""; } List ids = multiple ? parseIds(raw) : Collections.singletonList(raw.trim()); if (ids.isEmpty()) { return UNAVAILABLE; } List 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 target, String raw) { target.addAll(parseIds(raw)); } @SuppressWarnings("unchecked") private static List 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 ids = new ArrayList<>(); for (Object value : (Collection) 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 projects(String tenantId, Set ids); Map standards(String tenantId, Set ids); Map users(String tenantId, Set ids); Map products(String tenantId, Set 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 projects(String tenantId, Set 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 standards(String tenantId, Set 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 users(String tenantId, Set 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 products(String tenantId, Set 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 query(String resolverName, Set 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> rows = jdbc.queryForList(sql, params); Map result = new LinkedHashMap<>(); for (Map 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 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; } } }