背景:
后端数据库中多数存放的是字典编码,如果要做字典转换,通常有两种方式,第一种:直接让前端做字典的转换,但是这样如果后期字典值修改了,需要前端调整字典文件。第二种:后端使用sql 关联转换,这样会导致查询关联表,影响查询效率。通过借鉴几位大神的方式,归纳如下方法,实现字典转换。
一、自定义注解类
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Dict {
/**
* 字典编码
*/
String dictName();
/**
* 实体类内对应的中文字段名,默认为“当前字段+Text”
* <p>
* 例如当前字段为“type”,则对应中文字段默认为“typeText”
*/
String dictField() default "";
}
二、AOP切面类
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @Author:zjm
* @Date:2023/4/149:53
* @Description: 配置切面,为各个查询方法调用时
*/
@Slf4j
@Aspect
@Component
public class DictAspect {
@Pointcut("execution(* cn.stylefeng.guns.sys.modular.mapper.*.query*(..))") //指定mapper下所有查询方法
public void doPointcut() {
}
@AfterReturning(pointcut = "doPointcut()", returning = "result")
public void doAfterReturning(JoinPoint pjp, Object result) {
try {
DictUtils.convertDict(result);
} catch (Exception e) {
log.error("查询结果字典转换失败", e);
}
}
}
三、字典转换工具类
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.SneakyThrows;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class DictUtils {
private static DictTestMapper staticMapper;
@Autowired
public DictUtils(DictTestMapper mapper){
this.staticMapper = mapper;
}
private static final String DICT_FIELD_SUFFIX = "Text";
public static void convertDict(Object target) {
if (target instanceof List) {
List<?> objectList = ((List<?>) target);
if (CollectionUtils.isNotEmpty(objectList)) {
List<DictDefinition> dictDefinitions = getMetadata(objectList.get(0));
if (CollectionUtils.isEmpty(dictDefinitions)) return;
List<String> dictNames = dictDefinitions.stream().map(d -> d.getDict().dictName()).collect(Collectors.toList());
Map<String, Map<String, String>> dictMapMap = getDictMap(dictNames);
objectList.forEach(t -> doConvertDict(t, dictDefinitions, dictMapMap));
}
} else {
List<DictDefinition> dictDefinitions = getMetadata(target);
if (CollectionUtils.isEmpty(dictDefinitions)) return;
List<String> dictNames = dictDefinitions.stream().map(d -> d.getDict().dictName()).collect(Collectors.toList());
Map<String, Map<String, String>> dictMapMap = getDictMap(dictNames);
doConvertDict(target, dictDefinitions, dictMapMap);
}
}
/**
* 仅获取一次Dict元数据,降低多次反射造成的性能消耗
* @param target 目标实体类
* @return Dict元数据
*/
private static List<DictDefinition> getMetadata(Object target) {
List<DictDefinition> dictDefinitions = new ArrayList<>();
if (ClassUtils.isPrimitiveOrWrapper(target.getClass())
|| target instanceof Map || target instanceof String) {
return dictDefinitions;
}
List<Field> fields = FieldUtils.getAllFieldsList(target.getClass());
for (Field field : fields) {
Dict dict = AnnotationUtils.getAnnotation(field, Dict.class);
if (dict != null) {
DictDefinition dictDefinition = new DictDefinition();
dictDefinition.setDict(dict);
dictDefinition.setField(field);
dictDefinitions.add(dictDefinition);
}
}
return dictDefinitions;
}
/**
* 统一获取当前实体类涉及到的字典表数据,避免多次查询数据库造成的性能消耗
* @param dictNames 字典表Key值
* @return 字典表数据
*/
@SneakyThrows
private static Map<String, Map<String, String>> getDictMap(List<String> dictNames) {
QueryWrapper<SysDict> wrapper = new QueryWrapper();
wrapper.lambda().in(SysDict::getName,dictNames);
List<SysDict> dictList = staticMapper.selectList(wrapper);
return dictList.stream().collect(Collectors.groupingBy(
SysDict::getName, Collectors.toMap(SysDict::getCode, SysDict::getText, (v1, v2) -> v2)));
}
@SneakyThrows
private static void doConvertDict(Object target, List<DictDefinition> dictDefinitions,
Map<String, Map<String, String>> dictMapMap) {
for (DictDefinition dictDefinition : dictDefinitions) {
Dict dict = dictDefinition.getDict();
Field field = dictDefinition.getField();
Map<String, String> dictMap = dictMapMap.get(dict.dictName());
String dictCode = String.valueOf(FieldUtils.readField(target, field.getName(), true));
String dictField = StringUtils.isEmpty(dict.dictField()) ? field.getName() + DICT_FIELD_SUFFIX : dict.dictField();
FieldUtils.writeField(target, dictField, dictMap.get(dictCode), true);
}
}
@Data
public static class DictDefinition {
private Dict dict;
private Field field;
}
@Data
@TableName("t_dict") //配置本地字典表
public static class SysDict {
private String code;
private String text;
private String name;
}
}
四、VO中使用注解确定需要字典转换的字段
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TestVo {
private Integer id;
private String name;
private String idCard;
/**
* 性别(0、未知的性别 1、男性 2、女性)
*/
@Dict(dictName = "sex_type")
private String sex;
//字典翻译后的中文会赋值到该字段上面
private String sexText;
}