feat(plugin): 新增字典翻译插件模块

- 新增coder-common-thin-dict插件模块
- 实现CoderDictAspect AOP切面自动字典翻译
- 支持@CoderDict和@CoderDictClass注解
- 自动翻译List、Page和单对象返回结果
- 集成Redis缓存提升翻译性能
- 提供@EnableCoderDict启用注解
This commit is contained in:
Leo 2025-09-26 16:37:26 +08:00
parent 1dc2300763
commit f74779d0ad
4 changed files with 284 additions and 0 deletions

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.leocoder.thin</groupId>
<artifactId>coder-common-thin-plugins</artifactId>
<version>${revision}</version>
</parent>
<name>coder-common-thin-dict</name>
<artifactId>coder-common-thin-dict</artifactId>
<description>字典翻译插件</description>
<dependencies>
<!-- 通用公共模块 -->
<dependency>
<groupId>org.leocoder.thin</groupId>
<artifactId>coder-common-thin-common</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据模型模块 -->
<dependency>
<groupId>org.leocoder.thin</groupId>
<artifactId>coder-common-thin-model</artifactId>
<version>${revision}</version>
</dependency>
<!-- Aop依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,16 @@
package org.leocoder.thin.dict.anno;
import org.leocoder.thin.common.utils.cache.RedisUtil;
import org.leocoder.thin.dict.aspect.CoderDictAspect;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({CoderDictAspect.class, RedisUtil.class})
public @interface EnableCoderDict {
}

View File

@ -0,0 +1,212 @@
package org.leocoder.thin.dict.aspect;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
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.leocoder.thin.common.anno.CoderDict;
import org.leocoder.thin.common.anno.CoderDictClass;
import org.leocoder.thin.common.constants.CoderCacheConstants;
import org.leocoder.thin.common.utils.cache.RedisUtil;
import org.leocoder.thin.domain.pojo.system.SysDictData;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Leocoder
* @description [CoderDictAspect]
*/
@Aspect
@Order(4)
@Component
@Slf4j
public class CoderDictAspect {
private Map<String, String> dictMap = new HashMap<>();
@Resource
private RedisUtil redisUtil;
@Pointcut("@annotation(org.leocoder.thin.common.anno.CoderDictClass)")
public void logPointCut() {
}
/**
* @description [翻译数据]
* 需要处理的数据类型
* 1分页数据R<IPage<T>>
* 2普通列表R<List<T>>
* 3普通数据R<T>
* @author Leocoder
*/
@Around("@annotation(CoderDictClass)")
public Object CoderTypeDictTranslation(final ProceedingJoinPoint proceedingJoinPoint, CoderDictClass CoderDictClass) throws Throwable {
Object proceed = proceedingJoinPoint.proceed();
if (ObjectUtils.isEmpty(proceed)) {
return proceed;
}
if (proceed instanceof List) {
// 数据字典翻译
List<Object> list = (List<Object>) proceed;
CoderDictTranslate(list);
return list;
} else if (proceed instanceof Page) {
// Page 类型处理逻辑
IPage page = (IPage) proceed;
List records = page.getRecords();
// 数据字典翻译
CoderDictTranslate(records);
page.setRecords(records);
// 返回修改后的分页对象
return page;
} else {
// 其他类型处理逻辑或错误处理
return proceed;
}
}
/**
* @description [获取不同类型返回结果数据进行翻译]
* @author Leocoder
*/
private void CoderDictTranslate(Object resultData) {
Object objectData;
// 检查输入的result是否是List或者ArrayList的实例
if (resultData instanceof List) {
// 如果是列表则获取第一个元素
List CoderList = ((List) resultData);
// 如果列表为空则直接返回原始结果
if (CoderList.isEmpty()) {
return;
}
// 获取集合的第一条数据
objectData = CoderList.get(0);
} else {
// 否则直接使用result对象
objectData = resultData;
}
// 获取数据字典key 实体类字段名称
List<CoderDictModel> dictModelList = getCoderDict(objectData.getClass());
// dictModelList.forEach(System.out::println);
// CoderDictModel(dictKey=sys_user_sex, dictValue=sex)
// 如果没有字典映射则直接返回原始结果
if (dictModelList.isEmpty()) {
return;
}
// 获取所有的字典数据
List<SysDictData> dictDataList = listDictCache(dictModelList);
// 如果字典数据是空则直接返回
if (CollectionUtils.isEmpty(dictDataList)) {
return;
}
// 将字典值转换成map形式
for (SysDictData dictData : dictDataList) {
dictMap.put(dictData.getDictType() + "_" + dictData.getDictValue(), dictData.getDictLabel());
}
// 根据对象类型为返回数据的每一个对象赋予字典值
if (resultData instanceof List) {
for (Object entity : (List) resultData) {
assignDictValue(entity, dictModelList, dictMap);
}
} else {
assignDictValue(resultData, dictModelList, dictMap);
}
}
/**
* @description [获取字典数据缓存]
* @author Leocoder
*/
private List<SysDictData> listDictCache(List<CoderDictModel> dictModelList) {
List<SysDictData> dictDataList = new ArrayList<>();
List<String> dictKeyList = getDictKey(dictModelList);
for (String dictKey : dictKeyList) {
Boolean hasKey = redisUtil.hasKey(CoderCacheConstants.DICT_REDIS_KEY + dictKey);
if(hasKey) {
List<SysDictData> dictDataKeyList = redisUtil.getKey(CoderCacheConstants.DICT_REDIS_KEY + dictKey);
dictDataList.addAll(dictDataKeyList);
}
}
return dictDataList;
}
/**
* @param entity 返回List集合数据循环出来的实体类对象转换字典的核心代码
* @param dictModelList CoderDictModel(dictKey=sys_user_sex, dictValue=sex) 获取实体类的@CoderDict注解值
* @param dictMap 所有字典数据缓存
*/
public void assignDictValue(Object entity, List<CoderDictModel> dictModelList, Map<String, String> dictMap) {
try {
// 遍历每个CoderDictModel对象
for (CoderDictModel dictModel : dictModelList) {
String dictKey = dictModel.getDictKey(); // 获取字典类型
String fieldName = dictModel.getDictValue(); // 获取需要赋值的字段名
Class<?> classData = entity.getClass(); // 获取实体类的类
Field fieldData = classData.getDeclaredField(fieldName); // 获取需要赋值的字段
fieldData.setAccessible(true); // 设置字段可访问性
Object originalValue = fieldData.get(entity); // 获取字段的原始值
if (ObjectUtils.isNotEmpty(originalValue)) {
// 如果原始值不为空则将字典值赋给字段
String dictValue = dictMap.getOrDefault(dictKey + "_" + originalValue, originalValue.toString());
fieldData.set(entity, dictValue);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取实体中所有需要翻译的dictKey返回需要翻译的dictKey列表
*/
private List<String> getDictKey(List<CoderDictModel> CoderDictBos) {
// 创建一个字符串列表用于存储dictKey
List<String> dictKeyList = new ArrayList<>();
// 如果传入的CoderDictBo为空则直接返回空列表
if (CollectionUtils.isEmpty(CoderDictBos)) {
return dictKeyList;
}
for (CoderDictModel dictBo : CoderDictBos) {
// 将每个CoderDictBo对象的key添加到列表中
dictKeyList.add(dictBo.getDictKey());
}
return dictKeyList;
}
/**
* 获取实体类中配置的dictKey 对应的字段名称
*/
private List<CoderDictModel> getCoderDict(Class classData) {
Field[] fields = classData.getDeclaredFields(); // 获取类classData中声明的所有字段
List<CoderDictModel> list = new ArrayList<>(); // 创建一个CoderDictBo列表
CoderDictModel CoderDictBo;
CoderDict CoderDict;
for (Field field : fields) {
if (field.isAnnotationPresent(CoderDict.class)) { // 检查字段是否带有CoderDict注解
CoderDict = field.getAnnotation(CoderDict.class); // 获取CoderDict注解
CoderDictBo = new CoderDictModel(CoderDict.dictKey(), field.getName()); // 创建CoderDictBo对象存储dictKey和字段名
list.add(CoderDictBo);
}
}
// 返回包含配置的dictKey和对应字段内容的列表
return list;
}
}

View File

@ -0,0 +1,21 @@
package org.leocoder.thin.dict.aspect;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Leocoder
* @description [CoderDictModel-@CoderDict注解字段]
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class CoderDictModel {
/** 字典Key值 */
private String dictKey;
/** 实体类字段名称 */
private String dictValue;
}