JAVA字典 自动翻译
(1) MySQL8 表结构
CREATE TABLE `sys_dict` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`type_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '字典类型编码',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '字典项名称',
`value` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '字典项值'
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=417 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='字典数据表';
(2) Mypper.xml
<select id="translateDictByKeys" resultType="com.wangyao.common.database.model.DictModel">
select
${name} as "value",
${value} as "code"
from sys_dict
where ${value}
IN (
<foreach item="key" collection="keys" separator=",">
#{key}
</foreach>
)
</select>
<select id="queryManyDictByKeys" resultType="com.wangyao.common.database.model.DictModelMany">
SELECT
item.type_code AS dictCode,
item.name AS "value",
item.value AS "code"
FROM
sys_dict item
WHERE item.type_code IN (
<foreach item="dictCode" collection="dictCodeList" separator=",">
#{dictCode}
</foreach>
)
AND item.value IN (
<foreach item="key" collection="keys" separator=",">
#{key}
</foreach>
)
</select>
<select id="translateAllDict" resultType="com.wangyao.common.database.model.DictModelMany">
select
type_code as "dictCode",
name as "value",
`value` as "code"
from sys_dict
</select>
(3) mapper 接口
/***
* Desc:
* @param name 字段名
* @param value 值
* @param keys keys
* @return {@link List<DictModel>}
* @author 01传说
*/
List<DictModel> translateDictByKeys(@Param("name") String name, @Param("value") String value, @Param("keys") List<String> keys);
/***
* Desc:
* @param dictCodeList 字典集合
* @param keys keys
* @return {@link List<DictModelMany>}
* @author 01传说
*/
List<DictModelMany> queryManyDictByKeys(@Param("dictCodeList") List<String> dictCodeList, @Param("keys") List<String> keys);
/***
* Desc:
* @return {@link List<DictModelMany>}
* @author 01传说
*/
List<DictModelMany> translateAllDict();
(4)service实现接口
public interface BaseCommonService {
/***
* Desc:
* @param text text
* @param code code
* @param values values
* @return {@link List<DictModel>}
* @author 01传说
*/
List<DictModel> translateDictByKeys(String text, String code, String values);
/***
* Desc:
* @param dictCodes dictCodes
* @param values values
* @return {@link Map<String,List<DictModel>>}
* @author 01传说
*/
Map<String, List<DictModel>> translateManyDict(List<String> dictCodes, List<String> values);
/***
* Desc:
* @return {@link List<DictModelMany>}
* @author 01传说
*/
List<DictModelMany> queryManyDictByKeys();
(5)serviceImpl
@Service
@Slf4j
public class BaseCommonServiceImpl implements BaseCommonService {
@Resource
private BaseCommonMapper baseCommonMapper;
@Override
public List<DictModel> translateDictByKeys(String text, String code, String values) {
return baseCommonMapper.translateDictByKeys(text,code, Collections.singletonList(values));
}
@Override
public Map<String, List<DictModel>> translateManyDict(List<String> dictCodes, List<String> values) {
List<DictModelMany> list = baseCommonMapper.queryManyDictByKeys(dictCodes, values);
Map<String, List<DictModel>> dictMap = new HashMap<>(16);
for (DictModelMany dict : list) {
List<DictModel> dictItemList = dictMap.computeIfAbsent(dict.getDictCode(), i -> new ArrayList<>());
dictItemList.add(new DictModel(dict.getCode(), dict.getValue()));
}
return dictMap;
}
@Override
public List<DictModelMany> queryManyDictByKeys() {
return baseCommonMapper.translateAllDict();
}
}
(6)封装工具类
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.collect.Lists;
import com.wangyao.common.annotation.Dict;
import com.wangyao.common.cache.util.RedisUtil;
import com.wangyao.common.constant.ConstantNumeral;
import com.wangyao.common.database.model.DictModel;
import com.wangyao.common.database.service.BaseCommonService;
import com.wangyao.common.database.vo.PageVO;
import com.wangyao.common.response.ServerResponseEntity;
import com.wangyao.common.util.ConvertUtils;
import com.wangyao.common.vo.EsPageVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static com.alibaba.fastjson.JSON.toJavaObject;
@Aspect
@Component
@Slf4j
public class DictAspect {
@Resource
private BaseCommonService baseCommonService;
/**
* 定义切点Pointcut
*/
@Pointcut("execution(public * com.wangyao..*.*Controller.*(..))")
public void executionController() {
}
@Around("executionController()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long time1=System.currentTimeMillis();
Object result = pjp.proceed();
long time2=System.currentTimeMillis();
log.debug("获取JSON数据 耗时:"+(time2-time1)+"ms");
long start=System.currentTimeMillis();
this.translateDictFromTable(result);
long end=System.currentTimeMillis();
log.debug("解析注入JSON数据 耗时"+(end-start)+"ms");
return result;
}
/**
* 读取数据库翻译字典
* @param result 返回结果
*/
private void translateDictFromTable(Object result) throws IllegalAccessException {
if (result instanceof ServerResponseEntity) {
// 分页处理
handle(result);
}
}
@SuppressWarnings("all")
private void handle(Object result) throws IllegalAccessException {
if (((ServerResponseEntity<?>) result).getData() instanceof PageVO) {
List<JSONObject> items = handleList(((PageVO<?>) ((ServerResponseEntity<?>) result).getData()).getList());
if (CollUtil.isNotEmpty(items)) {
((PageVO) ((ServerResponseEntity<?>) result).getData()).setList(items);
}
} else if (((ServerResponseEntity<?>) result).getData() instanceof List) {
List<JSONObject> items = handleList(((List<?>) ((ServerResponseEntity<?>) result).getData()));
if (CollUtil.isNotEmpty(items)) {
((ServerResponseEntity<List>) result).setData(items);
}
}else if (((ServerResponseEntity<?>) result).getData() instanceof EsPageVO
&& ObjectUtil.isNotEmpty(((EsPageVO) ((ServerResponseEntity<?>) result).getData()).getList())) {
List<JSONObject> items = handleList(((EsPageVO<?>) ((ServerResponseEntity<?>) result).getData()).getList());
if (CollUtil.isNotEmpty(items)) {
((EsPageVO) ((ServerResponseEntity<List>) result).getData()).setList(items);
}
}else if(((ServerResponseEntity<?>) result).getData() instanceof String
|| ((ServerResponseEntity<?>) result).getData() instanceof Integer
||((ServerResponseEntity<?>) result).getData() instanceof Long){
} else if (((ServerResponseEntity<?>) result).getData() != null) {
List<JSONObject> items = handleList(List.of((Object)((ServerResponseEntity<?>) result).getData()));
if (CollUtil.isNotEmpty(items)) {
((ServerResponseEntity) result).setData(items.get(ConstantNumeral.ZERO.value()));
}
}
}
/**
* 判断object是否为基本类型
* @param object 对象
*/
public static boolean isBaseType(Object object) {
Class<?> className = object.getClass();
return className.equals(Integer.class) ||
className.equals(Byte.class) ||
className.equals(Long.class) ||
className.equals(Double.class) ||
className.equals(Float.class) ||
className.equals(Character.class) ||
className.equals(Short.class) ||
className.equals(Boolean.class);
}
@NotNull
private List<JSONObject> handleList(List<?> result){
List<JSONObject> items = Lists.newArrayList();
//step.1 筛选出加了 Dict 注解的字段列表
List<Field> dictFieldList = new ArrayList<>();
// 字典数据列表, key = 字典code,value=数据列表
Map<String, List<String>> dataListMap = new HashMap<>(16);
for (Object record : result) {
try {
if(!isBaseType(record)){
items.add(handleObject(record, dictFieldList, dataListMap));
}
} catch (JSONException | IllegalAccessException e) {
log.error("can not cast to JSONObject:{}", record.getClass(), e);
}
}
//step.2 调用翻译方法,一次性翻译
Map<String, List<DictModel>> translText = this.translateAllDict(dataListMap);
//step.3 将翻译结果填充到返回结果里
for (JSONObject record : items) {
translationField(record, dictFieldList, translText);
}
return items;
}
private JSONObject handleObject(Object record, List<Field> dictFieldList, Map<String, List<String>> dataListMap) throws IllegalAccessException {
JSONObject item = JSONObject.parseObject(JSON.toJSONString(record, SerializerFeature.WriteMapNullValue));
// 遍历所有字段,把字典Code取出来,放到 map 里
Field[] fields = ConvertUtils.getAllFields(record);
for (Field field : fields) {
String value = item.getString(field.getName());
if (ConvertUtils.isEmpty(value)) {
continue;
}
if (field.getType().getName().contains("wangyao")) {
List<JSONObject> items = handleList(List.of(toJavaObject(JSONObject.parseObject(value), field.getType())));
item.put(field.getName(), items.get(0));
}
if(field.getType().equals(List.class)){
// 获取List泛型中的对象的Class
Type genericType = field.getGenericType();
// 如果是泛型参数的类型
if(genericType instanceof ParameterizedType pt){
//得到泛型里的class类型对象
Type actualTypeArgument = pt.getActualTypeArguments()[0];
//List中的泛型类型不为T
if(!"T".equals(actualTypeArgument.getTypeName())){
Class<?> genericClazz = (Class<?>)actualTypeArgument;
Field[] declaredFields = genericClazz.getDeclaredFields();
handleListField(dictFieldList, dataListMap, declaredFields, value);
}else{
log.info("List泛型类型为T");
Class<? extends Type> aClass = actualTypeArgument.getClass();
System.out.println(Arrays.toString(aClass.getDeclaredFields()));
}
}
}
if (field.getAnnotation(Dict.class) != null) {
if (!dictFieldList.contains(field)) {
dictFieldList.add(field);
}
String code = field.getAnnotation(Dict.class).dicCode();
List<String> dataList;
dataList = dataListMap.computeIfAbsent(code, k -> new ArrayList<>());
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
}
}
return item;
}
/***
* Desc:
* @param dictFieldList 字典字段列表
* @param dataListMap 字典数据列表
* @param declaredFields 声明的字段
* @param value 值
* @author 01传说
*/
private void handleListField(List<Field> dictFieldList, Map<String, List<String>> dataListMap, Field[] declaredFields, String value) {
for (Field declaredField : declaredFields) {
if (declaredField.getAnnotation(Dict.class) != null) {
if (!dictFieldList.contains(declaredField)) {
dictFieldList.add(declaredField);
}
JSONArray jsonArray = JSONArray.parseArray(value);
if(CollectionUtil.isNotEmpty(jsonArray)){
for(Object object:jsonArray){
JSONObject listItem = (JSONObject) object;
String listValue = listItem.getString(declaredField.getName());
String declaredFieldDictCode = declaredField.getAnnotation(Dict.class).dicCode();
if (ConvertUtils.isNotEmpty(listValue)) {
List<String> dataList;
dataList = dataListMap.computeIfAbsent(declaredFieldDictCode, k -> new ArrayList<>());
this.listAddAllDeduplicate(dataList, Arrays.asList(listValue.split(",")));
}
}
}
}
}
}
private void translationField(JSONObject record, List<Field> dictFieldList, Map<String, List<DictModel>> translText) {
for (Map.Entry<String, Object> next : record.entrySet()) {
Object value = next.getValue();
if (value instanceof List) {
JSONArray array = JSONArray.parseArray(JSON.toJSONString(value));
for (Object o : array) {
if(!o.getClass().getName().contains("wangyao")){
continue;
}
JSONObject listItem = (JSONObject) o;
handleTranslate(listItem, dictFieldList, translText);
}
next.setValue(array);
}
}
handleTranslate(record, dictFieldList, translText);
}
/***
* Desc:
* @param record 记录
* @param dictFieldList 字典字段列表
* @param translText 翻译字段
* @author 01传说
*/
private void handleTranslate(JSONObject record, List<Field> dictFieldList, Map<String, List<DictModel>> translText) {
for (Field field : dictFieldList) {
String fieldDictCode = field.getAnnotation(Dict.class).dicCode();
String value = record.getString(field.getName());
if (ConvertUtils.isNotEmpty(value)) {
List<DictModel> dictModels = translText.get(fieldDictCode);
if(CollUtil.isEmpty(dictModels)){
continue;
}
String textValue = this.translDictText(dictModels, value);
log.debug(" 字典Val : " + textValue);
log.debug(" __翻译字典字段__ " + field.getName() + "DictText: " + textValue);
log.debug(" ---- dictCode: " + fieldDictCode);
log.debug(" ---- value: " + value);
log.debug(" ----- text: " + textValue);
log.debug(" ---- dictModels: " + JSON.toJSONString(dictModels));
record.put(field.getName() + "DictText", textValue);
}
}
}
/**
* list 去重添加
*/
private void listAddAllDeduplicate(List<String> dataList, List<String> addList) {
// 筛选出dataList中没有的数据
List<String> filterList = addList.stream().filter(i -> !dataList.contains(i)).toList();
dataList.addAll(filterList);
}
/**
* 一次性把所有的字典都翻译了
* 1. 所有的普通数据字典的所有数据只执行一次SQL
* 2. 表字典相同的所有数据只执行一次SQL
* @param dataListMap 字典信息
* @return Map
*/
private Map<String, List<DictModel>> translateAllDict(Map<String, List<String>> dataListMap) {
// 翻译后的字典文本,key=dictCode
Map<String, List<DictModel>> translText = new HashMap<>();
// 需要翻译的数据(有些可以从redis缓存中获取,就不走数据库查询)
List<String> needTranslData = new ArrayList<>();
//step.1 先通过redis中获取缓存字典数据
for (String dictCode : dataListMap.keySet()) {
List<String> dataList = dataListMap.get(dictCode);
if (dataList.isEmpty()) {
continue;
}
// 表字典需要翻译的数据
List<String> needTranslDataTable = new ArrayList<>();
for (String s : dataList) {
String data = s.trim();
if (data.isEmpty()) {
continue; //跳过循环
}
if (dictCode.contains(",")) {
String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s]", dictCode, data);
existKey(translText, dictCode, needTranslDataTable, data, keyString);
} else {
String keyString = String.format("sys:cache:dict::%s:%s", dictCode, data);
existKey(translText, dictCode, needTranslData, data, keyString);
}
}
//step.2 调用数据库翻译表字典
if (!needTranslDataTable.isEmpty()) {
String[] arr = dictCode.split(",");
String text = arr[0], code = arr[1];
String values = String.join(",", needTranslDataTable);
log.info("translateDictFromTableByKeys.dictCode:" + dictCode);
log.info("translateDictFromTableByKeys.values:" + values);
List<DictModel> texts = baseCommonService.translateDictByKeys(text, code, values);
log.info("translateDictFromTableByKeys.result:" + texts);
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.addAll(texts);
// 做 redis 缓存
for (DictModel dict : texts) {
String redisKey = String.format("sys:cache:dictTable::SimpleKey [%s,%s]", dictCode, dict.getValue());
try {
RedisUtil.set(redisKey, dict.getCode(),60);
} catch (Exception e) {
log.warn(e.getMessage(), e);
}
}
}
}
//step.3 调用数据库进行翻译普通字典
if (!needTranslData.isEmpty()) {
List<String> dictCodeList = Arrays.asList(dataListMap.keySet().toArray(new String[]{}));
// 将不包含逗号的字典code筛选出来,因为带逗号的是表字典,而不是普通的数据字典
List<String> filterDictCodes = dictCodeList.stream().filter(key -> !key.contains(",")).collect(Collectors.toList());
String dictCodes = String.join(",", filterDictCodes);
String values = String.join(",", needTranslData);
log.info("translateManyDict.dictCodes:" + dictCodes);
log.info("translateManyDict.values:" + values);
Map<String, List<DictModel>> manyDict = baseCommonService.translateManyDict(filterDictCodes, needTranslData);
log.info("translateManyDict.result:" + manyDict);
for (String dictCode : manyDict.keySet()) {
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
List<DictModel> newList = manyDict.get(dictCode);
list.addAll(newList);
// 做 redis 缓存
for (DictModel dict : newList) {
String redisKey = String.format("sys:cache:dict::%s:%s", dictCode, dict.getCode());
try {
RedisUtil.set(redisKey, dict.getValue(),60);
} catch (Exception e) {
log.warn(e.getMessage(), e);
}
}
}
}
return translText;
}
private void existKey(Map<String, List<DictModel>> translText, String dictCode, List<String> needTranslDataTable, String data, String keyString) {
if (RedisUtil.hasKey(keyString)) {
try {
String text = ConvertUtils.getString(RedisUtil.get(keyString));
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.add(new DictModel(data, text));
} catch (Exception e) {
log.warn(e.getMessage());
}
} else if (!needTranslDataTable.contains(data)) {
// 去重添加
needTranslDataTable.add(data);
}
}
/**
* 字典值替换文本
* @param dictModels 字典值
* @param values 值
* @return String
*/
private String translDictText(List<DictModel> dictModels, String values) {
List<String> result = new ArrayList<>();
// 允许多个逗号分隔,允许传数组对象
String[] splitVal = values.split(",");
for (String val : splitVal) {
String dictText = val;
for (DictModel dict : dictModels) {
if (val.equals(dict.getCode())) {
dictText = dict.getValue();
break;
}
}
result.add(dictText);
}
return String.join(",", result);
}
}
(7)Dict 实体类
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {
/**
* 方法描述: 数据code
* @return 返回类型: String
*/
String dicCode();
/**
* 方法描述: 数据Text
* @return 返回类型: String
*/
String dicText() default "";
}
(8)DictModel实体类
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class DictModel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
public DictModel() {
}
public DictModel(String code, String value) {
this.code = code;
this.value = value;
}
/**
* 字典value
*/
private String code;
/**
* 字典文本
*/
private String value;
}
(9)实际应用
@Dict(dicCode = "up_price_flag")
private Integer upPriceFlag;
up_price_flag对应数据库中 type_code值