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;
|
}
|
}
|
}
|