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;
|
|
/**
|
* 按 JNPF dictionaryType 批量解析字典项;一次详情至多执行一条字典查询。
|
*
|
* <p>{@code @Order} 的含义见 {@link LimsQingyandanFieldValueResolver} 的类注释。
|
*/
|
@Slf4j
|
@Component
|
@Order(JnpfDictionaryFieldValueResolver.ORDER)
|
public class JnpfDictionaryFieldValueResolver implements AuditFieldValueResolver {
|
|
/** 平台通用解析器,排在表专用(100)之后、用户兜底(300)之前。 */
|
public static final int ORDER = 200;
|
|
private static final String UNAVAILABLE = "名称暂不可用";
|
|
private final DictionaryLookup lookup;
|
|
@Autowired
|
public JnpfDictionaryFieldValueResolver(JdbcTemplate jdbcTemplate) {
|
this(new JdbcDictionaryLookup(jdbcTemplate));
|
}
|
|
JnpfDictionaryFieldValueResolver(DictionaryLookup lookup) {
|
this.lookup = lookup;
|
}
|
|
@Override
|
public boolean supports(String targetTable) {
|
return true;
|
}
|
|
@Override
|
public void resolve(AuditFieldValueContext context, List<AuditFieldDiffViewVO> fields) {
|
if (context == null || blank(context.getTenantId()) || fields == null || fields.isEmpty()
|
|| context.getDictionaryTypes().isEmpty()) {
|
return;
|
}
|
Set<String> dictionaryTypes = new LinkedHashSet<>();
|
Set<String> values = new LinkedHashSet<>();
|
for (AuditFieldDiffViewVO field : fields) {
|
String dictionaryType = dictionaryType(context, field);
|
if (dictionaryType == null || displaysResolved(field)) {
|
continue;
|
}
|
dictionaryTypes.add(dictionaryType);
|
values.addAll(parseValues(field.getOldData()));
|
values.addAll(parseValues(field.getNewData()));
|
}
|
if (dictionaryTypes.isEmpty() || values.isEmpty()) {
|
return;
|
}
|
|
Map<String, Map<String, String>> labels = lookup.labels(
|
context.getTenantId().trim(), dictionaryTypes, values);
|
for (AuditFieldDiffViewVO field : fields) {
|
String dictionaryType = dictionaryType(context, field);
|
Map<String, String> dictionary = dictionaryType == null
|
? null : labels.get(dictionaryType);
|
if (dictionary == null) {
|
continue;
|
}
|
boolean multiple = "multiEnum".equals(field.getValueType())
|
|| startsWithArray(field.getOldData()) || startsWithArray(field.getNewData());
|
if (blank(field.getValueType())) {
|
field.setValueType(multiple ? "multiEnum" : "enum");
|
}
|
if (field.getOldDisplay() == null) {
|
field.setOldDisplay(display(field.getOldData(), dictionary));
|
}
|
if (field.getNewDisplay() == null) {
|
field.setNewDisplay(display(field.getNewData(), dictionary));
|
}
|
}
|
}
|
|
private static String dictionaryType(AuditFieldValueContext context, AuditFieldDiffViewVO field) {
|
if (field == null || blank(field.getField())) {
|
return null;
|
}
|
return context.getDictionaryTypes().get(field.getField().trim().toLowerCase(Locale.ROOT));
|
}
|
|
private static boolean displaysResolved(AuditFieldDiffViewVO field) {
|
return (blank(field.getOldData()) || field.getOldDisplay() != null)
|
&& (blank(field.getNewData()) || field.getNewDisplay() != null);
|
}
|
|
private static String display(String raw, Map<String, String> labels) {
|
if (blank(raw)) {
|
return "";
|
}
|
List<String> values = parseValues(raw);
|
if (values.isEmpty()) {
|
return UNAVAILABLE;
|
}
|
List<String> result = new ArrayList<>(values.size());
|
for (String value : values) {
|
String label = labels.get(value);
|
result.add(blank(label) ? UNAVAILABLE : label);
|
}
|
return String.join("、", result);
|
}
|
|
@SuppressWarnings("unchecked")
|
private static List<String> parseValues(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> result = new ArrayList<>();
|
for (Object value : (Collection<Object>) parsed) {
|
if (value != null && !String.valueOf(value).trim().isEmpty()) {
|
result.add(String.valueOf(value).trim());
|
}
|
}
|
return result;
|
} catch (RuntimeException ignored) {
|
return Collections.emptyList();
|
}
|
}
|
|
private static boolean startsWithArray(String raw) {
|
return raw != null && raw.trim().startsWith("[");
|
}
|
|
private static boolean blank(String value) {
|
return value == null || value.trim().isEmpty();
|
}
|
|
interface DictionaryLookup {
|
Map<String, Map<String, String>> labels(String tenantId, Set<String> dictionaryTypes,
|
Set<String> values);
|
}
|
|
/** 表名和列名固定;dictionaryType、值和租户全部参数化。 */
|
private static final class JdbcDictionaryLookup implements DictionaryLookup {
|
private final NamedParameterJdbcTemplate jdbc;
|
|
private JdbcDictionaryLookup(JdbcTemplate jdbcTemplate) {
|
this.jdbc = new NamedParameterJdbcTemplate(jdbcTemplate);
|
}
|
|
@Override
|
public Map<String, Map<String, String>> labels(
|
String tenantId, Set<String> dictionaryTypes, Set<String> values) {
|
try {
|
MapSqlParameterSource params = new MapSqlParameterSource()
|
.addValue("dictionaryTypes", dictionaryTypes)
|
.addValue("values", values)
|
.addValue("tenantId", tenantId);
|
List<Map<String, Object>> rows = jdbc.queryForList(
|
"SELECT t.f_id AS type_id, t.f_en_code AS type_code, "
|
+ "d.f_id AS data_id, d.f_en_code AS data_code, d.f_full_name AS data_name "
|
+ "FROM base_dictionary_data d "
|
+ "JOIN base_dictionary_type t ON t.f_id = d.f_dictionary_type_id "
|
+ "WHERE (t.f_id IN (:dictionaryTypes) OR t.f_en_code IN (:dictionaryTypes)) "
|
+ "AND (d.f_id IN (:values) OR d.f_en_code IN (:values)) "
|
+ "AND COALESCE(NULLIF(t.f_tenant_id, ''), '0') = :tenantId "
|
+ "AND COALESCE(NULLIF(d.f_tenant_id, ''), '0') = :tenantId "
|
+ "AND COALESCE(t.f_delete_mark, 0) = 0 "
|
+ "AND COALESCE(d.f_delete_mark, 0) = 0",
|
params);
|
Map<String, Map<String, String>> result = new LinkedHashMap<>();
|
for (Map<String, Object> row : rows) {
|
String typeId = text(row.get("type_id"));
|
String typeCode = text(row.get("type_code"));
|
String dataId = text(row.get("data_id"));
|
String dataCode = text(row.get("data_code"));
|
String label = text(row.get("data_name"));
|
put(result, typeId, dataId, dataCode, label);
|
put(result, typeCode, dataId, dataCode, label);
|
}
|
return result;
|
} catch (RuntimeException ex) {
|
log.warn("[AUDIT-QUERY] JNPF 字典批量解析失败 typeCount={} valueCount={} err={}",
|
dictionaryTypes.size(), values.size(), ex.getClass().getSimpleName());
|
return Collections.emptyMap();
|
}
|
}
|
|
private static void put(Map<String, Map<String, String>> result, String type,
|
String dataId, String dataCode, String label) {
|
if (type == null || label == null) {
|
return;
|
}
|
Map<String, String> values = result.computeIfAbsent(type, key -> new LinkedHashMap<>());
|
if (dataId != null) {
|
values.put(dataId, label);
|
}
|
if (dataCode != null) {
|
values.put(dataCode, label);
|
}
|
}
|
|
private static String text(Object value) {
|
if (value == null) {
|
return null;
|
}
|
String result = String.valueOf(value).trim();
|
return result.isEmpty() ? null : result;
|
}
|
}
|
}
|