feat: 静态字典和动态字典转换中文方法

This commit is contained in:
tangwei 2026-06-30 17:38:10 +08:00
parent dd0ce6a7d9
commit a37c7248ab
3 changed files with 475 additions and 0 deletions

View File

@ -16,6 +16,8 @@ import com.yfd.platform.qgc_base.mapper.SdHbrvDicMapper;
import com.yfd.platform.qgc_base.mapper.SdHydrobaseMapper;
import com.yfd.platform.qgc_base.service.ISdHbrvDicService;
import com.yfd.platform.system.service.IAdminAuthService;
import com.yfd.platform.utils.CodeToNameMetadataBo;
import com.yfd.platform.utils.DictCodeToNameConverter;
import com.yfd.platform.utils.QgcQueryWrapperUtil;
import com.yfd.platform.utils.SecurityUtils;
import jakarta.annotation.Resource;
@ -51,6 +53,35 @@ public class SdHbrvDicServiceImpl extends ServiceImpl<SdHbrvDicMapper, SdHbrvDic
private IAdminAuthService adminAuthService;
private SdHbrvDicMapper hbrvDicMapper;
@Resource
private DictCodeToNameConverter dictCodeToNameConverter;
/**
* 代码转名称元数据配置列表静态初始化
* <p>
* 每个模块的 ServiceImpl 中可按需定义自己的 codeToNameMetadataBoList
* 在查询方法返回前调用 dictCodeToNameConverter.convertCodeToName(voList, codeToNameMetadataBoList)
* 即可自动将 code 字段转为对应的 name 字段
* </p>
*
* <pre>
* 使用示例
* // 在查询方法末尾调用
* dictCodeToNameConverter.convertCodeToName(voList, CODE_TO_NAME_META_LIST);
* </pre>
*/
private static final List<CodeToNameMetadataBo> CODE_TO_NAME_META_LIST = new ArrayList<>();
// static {
//
// CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("grd").modifyProperty("grdName").dictType("STATIC").dictSource("TEST").build());
// CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("baseid").modifyProperty("basename").dictType("DYNAMIC").dictSource("SD_HYDROBASE").codeColumn("BASEID").nameColumn("BASENAME").filter("NVL(IS_DELETED, 0) = 0 ").build());
// }
@Override
public Page<SdHbrvDic> queryPageList(Page<SdHbrvDic> page, String hbrvnm, String baseid) {
return this.page(page, this.lambdaQuery()

View File

@ -0,0 +1,94 @@
package com.yfd.platform.utils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 代码转名称元数据配置
* <p>
* 用于配置 VO 中某个 code 字段如何转换为对应的 name 字段
* 支持静态字典SYS_DICTIONARY / SYS_DICTIONARY_ITEMS和动态字典业务表
* </p>
*
* <pre>
* 使用示例
* // 静态字典 vo.typeCode 转为 vo.typeName字典编码为 "PROJECT_TYPE"
* CodeToNameMetadataBo.builder()
* .codeProperty("typeCode")
* .modifyProperty("typeName")
* .dictType("STATIC")
* .dictSource("PROJECT_TYPE")
* .build();
*
* // 动态字典 vo.engCode 转为 vo.engName SD_ENGINFO_B_H
* CodeToNameMetadataBo.builder()
* .codeProperty("engCode")
* .modifyProperty("engName")
* .dictType("DYNAMIC")
* .dictSource("SD_ENGINFO_B_H")
* .codeColumn("STCD")
* .nameColumn("ENNM")
* .filter("NVL(IS_DELETED, 0) = 0")
* .build();
* </pre>
*
* @author Generated
* @since 2025-05-18
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CodeToNameMetadataBo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 编码属性名VO 中存放 code 的字段名
*/
private String codeProperty;
/**
* 名称属性名VO 中需要设置 name 的字段名
*/
private String modifyProperty;
/**
* 字典类型STATIC静态字典 DYNAMIC动态字典
*/
private String dictType;
/**
* 字典来源
* - 静态字典SYS_DICTIONARY 表中的 DICTCODE
* - 动态字典业务表名
*/
private String dictSource;
/**
* 动态字典表中编码列名静态字典不需要
*/
private String codeColumn;
/**
* 动态字典表中名称列名静态字典不需要
*/
private String nameColumn;
/**
* 额外过滤条件可选
* - 静态字典SYS_DICTIONARY_ITEMS 的额外 WHERE 条件
* - 动态字典业务表的额外 WHERE 条件
*/
private String filter;
/**
* 是否多选code 值为逗号分隔的多个编码
*/
@Builder.Default
private boolean isMulti = false;
}

View File

@ -0,0 +1,350 @@
package com.yfd.platform.utils;
import cn.hutool.core.util.StrUtil;
import com.yfd.platform.common.MicroservicDynamicSQLMapper;
import com.yfd.platform.system.domain.SysDictionary;
import com.yfd.platform.system.domain.SysDictionaryItems;
import com.yfd.platform.system.mapper.SysDictionaryItemsMapper;
import com.yfd.platform.system.mapper.SysDictionaryMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 字典代码转名称转换器
* <p>
* 支持静态字典SYS_DICTIONARY / SYS_DICTIONARY_ITEMS和动态字典业务表查询
* VO 中的 code 字段值转换为对应的 name 字段值
* </p>
*
* <h3>使用方式</h3>
* <pre>
* // 1. ServiceImpl 中注入
* &#64;Resource
* private DictCodeToNameConverter dictCodeToNameConverter;
*
* // 2. 定义元数据列表通常放在 static 静态代码块中
* private static final List&lt;CodeToNameMetadataBo&gt; CODE_TO_NAME_META_LIST = List.of(
* CodeToNameMetadataBo.builder()
* .codeProperty("stationType")
* .modifyProperty("stationTypeName")
* .dictType("STATIC")
* .dictSource("STATION_TYPE")
* .build(),
* CodeToNameMetadataBo.builder()
* .codeProperty("engCode")
* .modifyProperty("engName")
* .dictType("DYNAMIC")
* .dictSource("SD_ENGINFO_B_H")
* .codeColumn("STCD")
* .nameColumn("ENNM")
* .filter("NVL(IS_DELETED, 0) = 0")
* .build()
* );
*
* // 3. 在查询方法中调用转换
* public DataSourceResult&lt;MyVo&gt; queryList(DataSourceRequest request) {
* // ... 查询数据得到 voList ...
* dictCodeToNameConverter.convertCodeToName(voList, CODE_TO_NAME_META_LIST);
* // ... 返回结果 ...
* }
* </pre>
*
* @author Generated
* @since 2025-05-18
*/
@Slf4j
@Component
public class DictCodeToNameConverter {
@Resource
private SysDictionaryMapper sysDictionaryMapper;
@Resource
private SysDictionaryItemsMapper sysDictionaryItemsMapper;
@Resource
private MicroservicDynamicSQLMapper<?> microservicDynamicSQLMapper;
/**
* 静态字典缓存dictCode -> Map<itemCode, dictName>
*/
private final Map<String, Map<String, String>> staticDictCache = new ConcurrentHashMap<>();
/**
* 动态字典缓存缓存key -> Map<code, name>
*/
private final Map<String, Map<String, String>> dynamicDictCache = new ConcurrentHashMap<>();
/**
* 批量转换根据元数据配置 VO 列表中的 code 字段转换为对应的 name 字段
*
* @param voList VO 对象列表
* @param metadataList 元数据配置列表
* @param <T> VO 类型
*/
public <T> void convertCodeToName(List<T> voList, List<CodeToNameMetadataBo> metadataList) {
if (voList == null || voList.isEmpty() || metadataList == null || metadataList.isEmpty()) {
return;
}
for (CodeToNameMetadataBo metadata : metadataList) {
try {
Map<String, String> codeNameMap = loadCodeNameMap(metadata);
if (codeNameMap.isEmpty()) {
continue;
}
applyCodeToName(voList, metadata, codeNameMap);
} catch (Exception e) {
log.error("字典转换失败: codeProperty={}, dictSource={}", metadata.getCodeProperty(), metadata.getDictSource(), e);
}
}
}
/**
* 加载静态字典映射
*
* @param dictCode 字典编码
* @return Map<itemCode, dictName>
*/
public Map<String, String> loadStaticDictMap(String dictCode) {
return staticDictCache.computeIfAbsent(dictCode, key -> {
Map<String, String> map = new LinkedHashMap<>();
try {
SysDictionary dict = sysDictionaryMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysDictionary>()
.eq(SysDictionary::getDictCode, key)
);
if (dict == null) {
log.warn("静态字典未找到: dictCode={}", key);
return map;
}
List<SysDictionaryItems> items = sysDictionaryItemsMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysDictionaryItems>()
.eq(SysDictionaryItems::getDictId, dict.getId())
.orderByAsc(SysDictionaryItems::getOrderNo)
);
for (SysDictionaryItems item : items) {
if (StrUtil.isNotBlank(item.getItemCode())) {
map.put(item.getItemCode(), StrUtil.nullToDefault(item.getDictName(), item.getItemCode()));
}
}
} catch (Exception e) {
log.error("加载静态字典失败: dictCode={}", key, e);
}
return map;
});
}
/**
* 加载动态字典映射从业务表查询
*
* @param tableName 表名
* @param codeColumn 编码列名
* @param nameColumn 名称列名
* @param filter 额外过滤条件
* @return Map<code, name>
*/
public Map<String, String> loadDynamicDictMap(String tableName, String codeColumn, String nameColumn, String filter) {
String cacheKey = tableName + "|" + codeColumn + "|" + nameColumn + "|" + StrUtil.nullToDefault(filter, "");
return dynamicDictCache.computeIfAbsent(cacheKey, key -> {
Map<String, String> map = new LinkedHashMap<>();
try {
StringBuilder sql = new StringBuilder();
sql.append("SELECT ").append(codeColumn).append(" AS code, ")
.append(nameColumn).append(" AS name ")
.append("FROM ").append(tableName)
.append(" WHERE 1=1 ");
if (StrUtil.isNotBlank(filter)) {
sql.append("AND ").append(filter).append(" ");
}
Map<String, Object> params = new HashMap<>();
List<Map<String, Object>> rows = microservicDynamicSQLMapper.getAllList(sql.toString(), params);
for (Map<String, Object> row : rows) {
String code = Objects.toString(row.get("CODE"), null);
String name = Objects.toString(row.get("NAME"), null);
if (StrUtil.isNotBlank(code)) {
map.put(code, StrUtil.nullToDefault(name, code));
}
}
} catch (Exception e) {
log.error("加载动态字典失败: tableName={}", tableName, e);
}
return map;
});
}
/**
* 根据元数据加载 code->name 映射
*/
private Map<String, String> loadCodeNameMap(CodeToNameMetadataBo metadata) {
if ("STATIC".equalsIgnoreCase(metadata.getDictType())) {
return loadStaticDictMap(metadata.getDictSource());
} else if ("DYNAMIC".equalsIgnoreCase(metadata.getDictType())) {
return loadDynamicDictMap(
metadata.getDictSource(),
metadata.getCodeColumn(),
metadata.getNameColumn(),
metadata.getFilter()
);
}
log.warn("未知字典类型: dictType={}", metadata.getDictType());
return Collections.emptyMap();
}
/**
* code->name 映射应用到 VO 列表
*/
private <T> void applyCodeToName(List<T> voList, CodeToNameMetadataBo metadata, Map<String, String> codeNameMap) {
for (T vo : voList) {
try {
String codeValue = getFieldValue(vo, metadata.getCodeProperty());
if (StrUtil.isBlank(codeValue)) {
continue;
}
String nameValue;
if (metadata.isMulti()) {
// 多选逗号分隔的多个code
nameValue = Arrays.stream(codeValue.split(","))
.map(String::trim)
.filter(StrUtil::isNotBlank)
.map(c -> codeNameMap.getOrDefault(c, c))
.collect(Collectors.joining(","));
} else {
nameValue = codeNameMap.getOrDefault(codeValue.trim(), codeValue);
}
setFieldValue(vo, metadata.getModifyProperty(), nameValue);
} catch (Exception e) {
log.debug("字段转换跳过: vo={}, codeProperty={}", vo.getClass().getSimpleName(), metadata.getCodeProperty(), e);
}
}
}
/**
* 清除所有缓存
*/
public void clearCache() {
staticDictCache.clear();
dynamicDictCache.clear();
}
/**
* 清除指定静态字典缓存
*/
public void clearStaticDictCache(String dictCode) {
staticDictCache.remove(dictCode);
}
/**
* 清除指定动态字典缓存
*/
public void clearDynamicDictCache(String tableName, String codeColumn, String nameColumn, String filter) {
String cacheKey = tableName + "|" + codeColumn + "|" + nameColumn + "|" + StrUtil.nullToDefault(filter, "");
dynamicDictCache.remove(cacheKey);
}
// ==================== 反射工具方法 ====================
/**
* 通过反射获取对象的字段值转为字符串支持驼峰命名
*/
private <T> String getFieldValue(T obj, String fieldName) {
try {
Field field = findField(obj.getClass(), fieldName);
if (field == null) {
return null;
}
field.setAccessible(true);
Object value = field.get(obj);
return value != null ? value.toString() : null;
} catch (Exception e) {
return null;
}
}
/**
* 通过反射设置对象的字段值支持驼峰命名
*/
private <T> void setFieldValue(T obj, String fieldName, String value) {
try {
Field field = findField(obj.getClass(), fieldName);
if (field == null) {
return;
}
field.setAccessible(true);
field.set(obj, value);
} catch (Exception ignored) {
// 忽略设置失败
}
}
/**
* 查找字段忽略大小写支持驼峰和下划线变体递归查找父类
*/
private Field findField(Class<?> clazz, String fieldName) {
if (clazz == null || clazz == Object.class) {
return null;
}
// 先精确匹配
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().equals(fieldName)) {
return field;
}
}
// 忽略大小写匹配
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().equalsIgnoreCase(fieldName)) {
return field;
}
}
// 驼峰-下划线互转匹配
String camelName = underlineToCamel(fieldName);
String underlineName = camelToUnderline(fieldName);
for (Field field : clazz.getDeclaredFields()) {
String fName = field.getName();
if (fName.equalsIgnoreCase(camelName) || fName.equalsIgnoreCase(underlineName)) {
return field;
}
}
return findField(clazz.getSuperclass(), fieldName);
}
private String underlineToCamel(String str) {
if (StrUtil.isBlank(str) || !str.contains("_")) {
return str;
}
StringBuilder sb = new StringBuilder();
boolean upper = false;
for (char c : str.toCharArray()) {
if (c == '_') {
upper = true;
} else {
sb.append(upper ? Character.toUpperCase(c) : c);
upper = false;
}
}
return sb.toString();
}
private String camelToUnderline(String str) {
if (StrUtil.isBlank(str)) {
return str;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (Character.isUpperCase(c)) {
sb.append('_').append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
}