Aop+自定义注解实现数据字典映射

ops/2024/11/8 17:34:10/

数据字典

    Web项目开发中,字典表的一般都会存在,主要用来给整个系统提供基础服务。

  比如男女性别的类型可以使用0和1来进行表示,在存储数据和查询数据的时候,就可以使用字典表中的数据进行翻译处理。

   再比如之前做的一个项目中宠物类型包含老虎-1、海豚-2、大象-3、长颈鹿-4等等;做答题处理时的答题类型,比如单选题、多选题、填空题等等;

   比较常用的有用户类型,普通用户还是VIP用户等等;这些类型的数据也可以存储在数据字典中进行统一处理。

数据结构以及业务

举例:

在业务表中使用的是业务目录,而其中的“数据结构”,“数据目录类型”,“数据来源”使用的是字典表中的id,但我们在页面显示的时候,想用字典表中的value值,即我们在库里相应的业务表的外键存字典表的ID,查询的时候返回给前端在字典表中的value字段。

如以下表以及内容的展示:

目录表:

内容: 外键,字典的ID

如图所示:(字典表)需要我们在表中拿出外键所对应的Label

列举方法

   那有什么办法可以让我们查询出key的同时,将value值也查询出来,当时处理问题的时候想了三种解决方法:

1.要么就写sql 匹配。(在SQL查询的时候做字段的匹配,但是增加了SQL的复杂性不易维护)

2.要么就业务处理。得写个工具类(针对当时的情况感觉可以实现,然后查找了一些资料比如,自定义注解啥的)

3.再就是前端调两次接口,查询一次数据,再查询一次字典表做转译。(所涉及到的字段有四五个甚至更多,这是我问的别的同学,他们公司采用的就是这种方法)

根据查找资料以及实际考虑,最终采用了使用自定义注解的方式去做字段映射。

实现步骤:

首先展示项目的目录结构

总共三个,自定义注解,切面,具体实现

一 ,创建自定义注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Dict {/*** 字典类型,对应数据库的类型,下方展示*/String dictType();/*** 实体类内对应的中文字段名,默认为“当前字段+Text”* <p>*  例如当前字段为“type”,则对应中文字段默认为“typeText”* 就是你在实体中想要给某个字段赋值,这个字段就要写成那个字段*/String dictField() default "";}

二,创建切面


@Slf4j
@Aspect
@Component
public class BydDaoDictAspect {
//  * `@Pointcut` 注解用于定义一个切点,切点是决定哪些方法应该被通知(advice)的表达式。
//	* `execution(* com.wiseom.asset.manage.service..*.*(..))` 是一个切点表达式,它表示匹配 `com.wiseom.asset.manage.service` 包及其子包下的所有类的所有方法。
//	* `doPointcut()` 方法本身没有执行任何操作,它只是为切点提供了一个标识符。@Pointcut("execution(* com.wiseom.asset.manage.service..*.*(..))")public void doPointcut() {}
//  * `@AfterReturning` 注解表示在匹配的方法成功执行并返回结果后执行通知。
//  * `pointcut = "doPointcut()"` 表示这个通知应用于之前定义的 `doPointcut` 切点。
//	* `returning = "result"` 捕获返回的结果,并将其作为 `result` 参数传递给通知方法。
//	* 在 `doAfterReturning` 方法中,`DictUtils.convertDict(result)` 是将返回的 `result` 对象转换为字典格式。如果在转换过程中发生异常,它会被捕获并记录到日志中。@AfterReturning(pointcut = "doPointcut()", returning = "result")public void doAfterReturning(JoinPoint pjp, Object result) {try {DictUtils.convertDict(result);} catch (Exception e) {log.error(String.valueOf(e));}}
}

备注:

1:切点在com.wiseom.asset.manage.service的原因,因为我们使用了mybitsplus ,在我们处理查询,或者执行SQL的时候大多都在service层就被处理了,所以切点设在了service层。

2:如果切点设置在Controller,则需要修改下方实现三中的 方法 convertDict,因为 Controller的返回值拿到的是 R 包裹的内容,返回值对象是R,而不是实体,具体问题查看下方的扩展四

三,创建实现


@Component
public class DictUtils {private static final String DICT_FIELD_SUFFIX = "Text";/*** 统一获取当前实体类涉及到的字典表数据,避免多次查询数据库造成的性能消耗* 这个工具类主要的改动就是这个,所以我就把这一段放在上面了* @param dictNames 字典表type值* @return 字典表数据*/@SneakyThrowsprivate static  Map<String, Map<Long, String>> getDictMap(List<String> dictNames) {//创建fegin的实例,调用系统的sys_dict_item表,需要新写一个接口final 	RemoteDictService remoteDictService = SpringContextHolder.getBean(RemoteDictService.class);//把注解里的所有字典的类型查出来,具体根据需求做查询List<SysDictItem> dictList = remoteDictService.getDictItemByType(dictNames);//根据类型做一个分组,这里对应的是 Map<DictType, Map<ID, Label>>return dictList.stream().collect(Collectors.groupingBy(SysDictItem::getDictType, Collectors.toMap(SysDictItem::getId, SysDictItem::getLabel, (v1, v2) -> v2)));}/*** 这是主方法主要是判断AOP后返回的内容,并根据判断条件拿到里面的内容*  然后先判断有哪些带有注解,根据注解拿到对应的dictType去库里查找对应的内容 *  根据拿到内容在对指定的字段dictField进行赋值*/public static void convertDict(Object target) {//判断对象是不是分页的if (target instanceof Page) {//拿到分页中的records对象里边包含实体的内容for(Object object : ((Page<?>)target).getRecords()){//拿到实体List<DictDefinition> dictDefinitions = getMetadata(object);// 如果没有注解,则直接返回if (CollectionUtils.isEmpty(dictDefinitions)) return;// 从字典定义中提取所有的注解的 dictType(数据库字典的类型)List<String> dictNames = dictDefinitions.stream().map(d -> d.getDict().dictType()).collect(Collectors.toList());// 根据字典类型获取字典映射Map<String, Map<Long, String>> dictMapMap = getDictMap(dictNames);// 转换target对象的字典字段doConvertDict(object, dictDefinitions, dictMapMap);}} elseif (target instanceof List) {// 将target强制转换为List<?>类型List<?> objectList = ((List<?>) target);// 使用CollectionUtils.isNotEmpty来检查列表是否非空if (CollectionUtils.isNotEmpty(objectList)) {// 获取列表中第一个元素的字典定义List<DictDefinition> dictDefinitions = getMetadata(objectList.get(0));// 如果没有注解,则直接返回if (CollectionUtils.isEmpty(dictDefinitions)) return;// 从字典定义中提取所有的注解的 dictType(数据库字典的类型)List<String> dictNames = dictDefinitions.stream().map(d -> d.getDict().dictType()).collect(Collectors.toList());// 根据字典类型获取字典映射Map<String, Map<Long, String>> dictMapMap = getDictMap(dictNames);// 遍历列表中的每个元素,并转换其字典字段objectList.forEach(t -> doConvertDict(t, dictDefinitions, dictMapMap));}} else {// 如果target不是List,则直接获取其字典定义List<DictDefinition> dictDefinitions = getMetadata(target);// 如果没有注解,则直接返回if (CollectionUtils.isEmpty(dictDefinitions)) return;// 从字典定义中提取所有的注解的 dictType(数据库字典的类型)List<String> dictNames = dictDefinitions.stream().map(d -> d.getDict().dictType()).collect(Collectors.toList());// 根据字典类型获取字典映射Map<String, Map<Long, String>> dictMapMap = getDictMap(dictNames);// 转换target对象的字典字段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;}@SneakyThrowsprivate static void doConvertDict(Object target, List<DictDefinition> dictDefinitions,Map<String, Map<Long, String>> dictMapMap) {for (DictDefinition dictDefinition : dictDefinitions) {//获取Dict注解和字段信息Dict dict = dictDefinition.getDict();Field field = dictDefinition.getField();//获取字典映射Map<Long, String> dictMap = dictMapMap.get(dict.dictType());//读取实体中带有注解的字段原有的值String dictCode = String.valueOf(FieldUtils.readField(target, field.getName(), true));//类型转换,我在库中用的存储的字段是String,所以需要进行类型的转换,如果你的库设置的不是,或者其他的类型,需要注意到这个地方Long longDictCode=Long.valueOf(dictCode);//拿到字典注解的labelString dictField = StringUtils.isEmpty(dict.dictField()) ? field.getName() + DICT_FIELD_SUFFIX : dict.dictField();//设置字段的字典文本值FieldUtils.writeField(target, dictField, dictMap.get(longDictCode), true);}}@Datapublic static class DictDefinition {private Dict dict;private Field field;}}

系统表的查询接口:

//这个我是在SysDictController下添加的接口,目的是实现查找多个类型的内容,同时还需要写个fegin接口以供别的模块使用
/*** 通过字典类型查找字典* @param types 类型* @return 同类型字典*/@PostMapping("/types")public List<SysDictItem> getDictByTypes(@RequestBody List<String> types) {return sysDictItemService.list(Wrappers.<SysDictItem>query().lambda().in(SysDictItem::getDictType, types));}

使用方法

直接在实体字段上方添加注解

1.dictType:数据库中的字典的类型

2.dictField:实体中赋值的字段

如上方使用方法所示,如果你想将字典中的label值给实体中的 catalogStructure,则标注

如果前端需要,一个原来的ID,一个label,那就需要创建一个DTO,如图所示,将dictField 后边写上你想要的字段名称就行了

四,备注以及扩展

这个是公司用的pigx框架,没用到的同学,可以跳过了,仅供参考,依据具体项目实际来定

参数的入值:

@AfterReturning(pointcut = "doPointcut()", returning = "result")public void doAfterReturning(JoinPoint pjp, Object result) {try {DictUtils.convertDict(result);} catch (Exception e) {log.error(String.valueOf(e));}}

这个代码中的result需要着重注意,他可能会存在判断的问题

举例:这个是正常的,只拿到这个返回的实体就行,在我们实现三中的方法convertDict所有if()的目的都是为了拿到返回值中的实体

比如底下这个图,分页返回的就需要做一个判断(实现三我已经加上了,这个做一个参考):

判断:

五:具体实现 优化1,多个字段的匹配,用“,”隔开代码优化


@Component
public class DictUtils {private static final String DICT_FIELD_SUFFIX = "Text";/*** 执行字典转换的公共方法* @param target 待转换的对象*/public static void convertDict(Object target) {//判断是否是分页if (target instanceof Page) {for (Object object : ((Page<?>) target).getRecords()) {convertDictInternal(object);}//是否是list} else if (target instanceof List) {if (CollectionUtils.isNotEmpty((List<?>) target)) {for (Object object : (List<?>) target) {convertDictInternal(object);}}} else {convertDictInternal(target);}}/*** 内部的字典转换实现方法* @param target 待转换的对象*/@SneakyThrowsprivate static void convertDictInternal(Object target) {// 获取目标对象的字典定义List<DictDefinition> dictDefinitions = getMetadata(target);// 如果没有字典定义则直接返回if (CollectionUtils.isEmpty(dictDefinitions)) {return;}// 提取字典类型并获取字典映射String commaSeparatedString = dictDefinitions.stream().map(d -> d.getDict().dictType()).collect(Collectors.joining(","));List<String> dictNames = Arrays.asList(commaSeparatedString.split(","));Map<String, Map<Long, String>> dictMapMap = getDictMap(dictNames);// 执行具体的字典转换操作doConvertDict(target, dictDefinitions, dictMapMap);}/*** 获取目标对象的字典定义* @param target 目标对象* @return 字典定义列表*/private  static List<DictDefinition> getMetadata(Object target) {List<DictDefinition> dictDefinitions = new ArrayList<>();// 排除基本类型、包装类型、Map 和 String 类型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 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 字典名称列表* @return 字典映射*/@SneakyThrowsprivate static  Map<String, Map<Long, String>> getDictMap(List<String> dictNames) {final RemoteDictService remoteDictService = SpringContextHolder.getBean(RemoteDictService.class);List<SysDictItem> dictList = remoteDictService.getDictItemByType(dictNames);return new HashMap<>(dictList.stream().collect(Collectors.groupingBy(SysDictItem::getDictType, Collectors.toMap(SysDictItem::getId, SysDictItem::getLabel, (v1, v2) -> v2))));}/*** 执行具体的字典字段转换* @param target 目标对象* @param dictDefinitions 字典定义列表* @param dictMapMap 字典映射*/@SneakyThrowsprivate static  void doConvertDict(Object target, List<DictDefinition> dictDefinitions,Map<String, Map<Long, String>> dictMapMap) {for (DictDefinition dictDefinition : dictDefinitions) {Dict dict = dictDefinition.getDict();Field field = dictDefinition.getField();String[] dictTypes = dict.dictType().split(",");for (String dictType : dictTypes) {Map<Long, String> dictMap = dictMapMap.get(dictType.trim());// 读取字段的字典编码String dictCode = String.valueOf(FieldUtils.readField(target, field.getName(), true));// 类型转换Long longDictCode = Long.valueOf(dictCode);// 确定字典字段名String dictField = StringUtils.isEmpty(dict.dictField())? field.getName() + DICT_FIELD_SUFFIX : dict.dictField();// 设置字段的字典文本值if (dictMap!= null && dictMap.containsKey(longDictCode)) {FieldUtils.writeField(target, dictField, dictMap.get(longDictCode), true);}}}}@Datapublic static class DictDefinition {private Dict dict;private Field field;}
}

使用方式:

参考:

主要内容参考:

java自定义注解实现数据字典映射_字典映射 java-CSDN博客

分页扩展参考:

字典翻译@Dict - 莫大人 - 博客园


http://www.ppmy.cn/ops/131988.html

相关文章

Unity图形学之Shader结构

Unity - Manual: ShaderLab: Legacy Lighting 1.Shader 语言&#xff1a; OpenGL&#xff1a;SGL 跨平台性能非常好 GLSL语言 OpenGL Shader LanguageDX&#xff1a;微软 非跨平台 性能非常好 HLSL语言 High Level Shader LanguageCG&#xff1a;微软和英伟达 联合开发CG …

如何为 Redis 设置密码

前言 Redis 是一个高性能的键值对数据库&#xff0c;广泛应用于缓存、消息队列等场景。为了保障 Redis 服务的安全性&#xff0c;设置密码认证是非常重要的一步。 方法一&#xff1a;通过编辑配置文件设置密码 1. 找到 redis.conf 配置文件 通常情况下&#xff0c;redis.co…

国产光耦在现代电子中的多功能性和性能

近几年&#xff0c;随着国内电子行业的快速发展&#xff0c;国产光耦合器取得了显著的进步&#xff0c;其创新旨在满足现代电子行业的严格需求。这些光耦合器提供了包括隔离、信号传输和抗噪声等基本功能&#xff0c;使其适用于工业、汽车和消费电子等多个领域。本文将探讨这些…

金融市场中的量化分析:正大科技如何赋能投资者决策

在当前金融市场中&#xff0c;量化分析技术已成为许多投资者提升决策效率的重要工具。正大科技致力于通过量化分析&#xff0c;帮助投资者基于数据和算法做出更为科学的市场判断。本文将探讨量化分析在金融市场中的应用&#xff0c;并展示正大科技如何利用量化手段优化投资策略…

微服务实战系列之玩转Docker(十八)

导览 前言Q&#xff1a;如何保障容器云环境下etcd集群的数据安全一、安全机制身份认证必学必看1. 启动参数2. 授权命令3. 开启认证 二、应用实践1. 访问容器2. 查看认证是否开启3. 查看是否已创建用户4. 创建用户5. 开启认证6. 验证是否开启7. 验证数据 结语系列回顾 前言 etc…

WebSocket 及时通信 - 2024最新版前端秋招面试短期突击面试题【100道】

WebSocket 及时通信 - 2024最新版前端秋招面试短期突击面试题【100道】 &#x1f310; 1. 你对 WebSocket 的理解是什么&#xff1f; WebSocket 是一种通讯协议&#xff0c;提供了在单个 TCP 连接上进行全双工&#xff08;双向&#xff09;通信的能力。与传统的 HTTP 请求-响…

杨辉三角,洗牌算法

杨辉三角 给定一个非负整数numRows&#xff0c;生成杨辉三角的前numRows行。 在杨辉三角中&#xff0c;每个数是它的左上方和右上方的数的和。 public List<List<Integer>> generate(int numRows){List<List<Integer>> ret new ArrayList<>();…

符号回归概念

一、符号回归概念 符号回归是一种有监督的机器学习方法&#xff0c;用于发现某种隐藏的数学表达式或函数&#xff0c;以最佳地拟合给定数据集。与传统的回归方法不同&#xff0c;符号回归不仅仅是找到一个数学模型的参数&#xff0c;而是通过搜索和组合基本数学运算符和函数&a…