基于AOP的数据字典实现:实现前端下拉框的可配置更新

news2024/11/25 16:46:19

作者:后端小肥肠

创作不易,未经允许严禁转载。

目录

1. 前言

2. 数据字典

2.1. 数据字典简介

2.2. 数据字典如何管理各模块的下拉框

3. 数据字典核心内容解读

3.1. 表结构

3.2. 核心代码 

3.2.1. 根据实体类名称获取下属数据字典

3.2.2. 数据字典AOP切面

3.2.2.1. 场景模拟

3.2.2.2. 数据字典交互流程

3.2.2.3. AOP代码

4. 数据字典使用

4.1. 新增Student类对应数据字典值

4.2. 新增学生数据

4.3. 根据id查询学生数据详细信息

5. 结语

6. 参考链接


1. 前言

在现代软件开发中,数据字典作为管理系统常量和配置项的重要工具,其灵活性和可维护性对系统的健壮性起着至关重要的作用。然而,传统的数据字典与业务模块的整合方式往往存在着严重的耦合问题。通常情况下,为了在业务模块中使用数据字典的标签(label),我们不得不在VO类中添加字段,并通过查询数据字典来获取对应的标签值,这种做法不仅增加了代码的复杂性,还使得业务模块与数据字典的耦合度过高,不利于系统的模块化和扩展。

本文将探讨如何利用面向切面编程(AOP)的思想,通过注解的方式实现数据字典与其他业务模块的无侵入性整合。我们将重点关注如何通过AOP技术,使数据字典的值(value)在业务模块中自动转换为其对应的标签(label),从而实现业务逻辑与数据字典的松耦合,为系统的可维护性和拓展性提供新的解决方案。

2. 数据字典

2.1. 数据字典简介

数据字典是软件系统中用于管理常量、配置项或者枚举值的集合。它通常包括标签(label)和值(value)两部分,标签用于展示给用户或者其他系统模块,而值则是实际的业务逻辑中使用的数据标识。我举个例子吧,比如前端下拉框的渲染:

我们来看一下前端代码:

<template>
  <el-select v-model="value" placeholder="请选择">
    <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value">
    </el-option>
  </el-select>
</template>

<script>
  export default {
    data() {
      return {
        options: [{
          value: '选项1',
          label: '黄金糕'
        }, {
          value: '选项2',
          label: '双皮奶'
        }, {
          value: '选项3',
          label: '蚵仔煎'
        }, {
          value: '选项4',
          label: '龙须面'
        }, {
          value: '选项5',
          label: '北京烤鸭'
        }],
        value: ''
      }
    }
  }
</script>

从前端代码可看出 下拉框的渲染主要依靠valuelabel,常规的做法有枚举,或者后端建表后从表中获取,这两种方法都有许多弊端,枚举的话需要开发人员写死在代码中,再来看建表,如果每个下拉框都建表,那就会浪费大量后端资源,采用数据字典,统一管理各个功能模块的下拉框是较优的选择。

2.2. 数据字典如何管理各模块的下拉框

数据字典中是如何把各模块的下拉框管理起来的,在数据字典中一共管理三块内容,分别是实体类(表),属性字段,属性字段值(数据字典value和label);以前端的视角来看就是表单,下拉框,下拉框的值(数据字典label和value)。

3. 数据字典核心内容解读

3.1. 表结构

数据字典一共涵盖两张表,分别为dictionary_type和dictionary_value,下面将分别对这两张表进行解释。

dictionary_type

CREATE TABLE "public"."dictionary_type" (
  "id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
  "type_name" varchar(50) COLLATE "pg_catalog"."default",
  "type_description" varchar(100) COLLATE "pg_catalog"."default",
  "parent_id" varchar(32) COLLATE "pg_catalog"."default",
  "create_time" timestamp(6),
  "update_time" timestamp(6),
  "version" int4 DEFAULT 1,
  "type_label" varchar(50) COLLATE "pg_catalog"."default",
  "is_deleted" int2 DEFAULT 0,
  CONSTRAINT "dictionary_type_pkey" PRIMARY KEY ("id")
)
;

ALTER TABLE "public"."dictionary_type" 
  OWNER TO "postgres";

COMMENT ON COLUMN "public"."dictionary_type"."id" IS '主键ID';

COMMENT ON COLUMN "public"."dictionary_type"."type_name" IS '字典类型名称';

COMMENT ON COLUMN "public"."dictionary_type"."type_description" IS '字典类型描述';

COMMENT ON COLUMN "public"."dictionary_type"."parent_id" IS '父节点id';

COMMENT ON COLUMN "public"."dictionary_type"."create_time" IS '创建时间';

COMMENT ON COLUMN "public"."dictionary_type"."update_time" IS '更新时间';

COMMENT ON COLUMN "public"."dictionary_type"."version" IS '乐观锁';

COMMENT ON COLUMN "public"."dictionary_type"."type_label" IS '字典类型标签';

COMMENT ON TABLE "public"."dictionary_type" IS '字典类型表';

 dictionary_type表管理实体类和属性字段,当parent_id为null时则该数据为实体类,否则为归属某实体类下的属性字段。

dictionary_value

CREATE TABLE "public"."dictionary_value" (
  "id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
  "value_name" varchar(50) COLLATE "pg_catalog"."default",
  "type_id" varchar(32) COLLATE "pg_catalog"."default",
  "create_time" timestamp(6),
  "update_time" timestamp(6),
  "version" int4 DEFAULT 1,
  "value_label" varchar(50) COLLATE "pg_catalog"."default",
  "value_sort" int4,
  "is_deleted" int2,
  CONSTRAINT "dictionary_value_pkey" PRIMARY KEY ("id")
)
;

ALTER TABLE "public"."dictionary_value" 
  OWNER TO "postgres";

COMMENT ON COLUMN "public"."dictionary_value"."id" IS '主键ID';

COMMENT ON COLUMN "public"."dictionary_value"."value_name" IS '字典值名称';

COMMENT ON COLUMN "public"."dictionary_value"."type_id" IS '字典类型id';

COMMENT ON COLUMN "public"."dictionary_value"."create_time" IS '创建时间';

COMMENT ON COLUMN "public"."dictionary_value"."update_time" IS '更新时间';

COMMENT ON COLUMN "public"."dictionary_value"."version" IS '乐观锁';

COMMENT ON COLUMN "public"."dictionary_value"."value_label" IS '字典值标签';

COMMENT ON COLUMN "public"."dictionary_value"."value_sort" IS '字典值排序';

COMMENT ON TABLE "public"."dictionary_value" IS '字典值表';

dictionary_value 中管理某实体类下属性字段多对应的数据字典(label和value)。dictionary_value dictionary_type为多对一的关系(一个属性字段下对应多个数据字典值)。

3.2. 核心代码 

3.2.1. 根据实体类名称获取下属数据字典

controller层

    /**
     * 获取模块数据字典
     * @param typeName
     * @return
     */
    @ApiOperation("获取某个模块下的数据字典")
    @GetMapping("/parameter/{typeName}")
    Map<String, Object> getCompleteParameter(@PathVariable("typeName") String typeName){
        return iDictionaryValueService.getParameters(typeName);
    }

在上述代码中typeName为实体类名称。 

service层

    public Map<String, Object> getParameters(String typeName) {
        List<Map<String, Object>> dictParameters=baseMapper.getDictParameters(typeName);
        Set<Object> typeSet= new HashSet<>();
        Map<String,Object>resParam=new HashMap<>();
        for (Map<String, Object> dictParameter : dictParameters) {
            typeSet.add(dictParameter.get("type_name").toString());
        }
        for (Object o : typeSet) {
            List<ParameterVO> parameterVoList = new ArrayList<>();
            for (Map<String, Object> dictParameter : dictParameters) {
                if(dictParameter.get("type_name").toString().equals(o.toString())){
                    ParameterVO parameterVO=new ParameterVO(dictParameter.get("value_name").toString(),dictParameter.get("value_label").toString());
                    parameterVoList.add(parameterVO);
                }
            }
            resParam.put(o.toString(),parameterVoList);
        }
        return resParam;
    }

mapper层

@Select("select a.value_name,a.value_label,a.type_name from  dictionary_type d JOIN  (select v.value_name,v.value_label,t.type_name,t.parent_id from dictionary_value v,dictionary_type t where v.type_id=t.id and  v.is_deleted = 0 and t.is_deleted = 0)a on a.parent_id=d.id where d.type_name =#{typeName} AND d.is_deleted = 0")
    List<Map<String, Object>> getDictParameters(@Param("typeName") String typeName);
3.2.2. 数据字典AOP切面
3.2.2.1. 场景模拟

先预设一个场景,假设有一张学生表需要整合数据字典,表结构如下:

CREATE TABLE "public"."student" (
  "id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
  "name" varchar(50) COLLATE "pg_catalog"."default",
  "blood_type" varchar(10) COLLATE "pg_catalog"."default",
  "constellation_type" varchar(10) COLLATE "pg_catalog"."default",
  "create_time" timestamp(6),
  "update_time" timestamp(6),
  "version" int4 DEFAULT 1,
  "is_deleted" int2 DEFAULT 0,
  CONSTRAINT "student_pkey" PRIMARY KEY ("id")
)
;

ALTER TABLE "public"."student" 
  OWNER TO "postgres";

COMMENT ON COLUMN "public"."student"."blood_type" IS '血型';

COMMENT ON COLUMN "public"."student"."constellation_type" IS '星座类型';

在上表中星座和血型为需要和数据字典集成的字段。 

3.2.2.2. 数据字典交互流程

AOP切面主要使用在分页查询和查询详情时。与数据字典有交集的实体类(Student)在分页或查询详情时技术流程图如下:

在上图中可看出与数据字典有交集的模块要进行分页或查询详情时,需要远程调用数据字典模块的相关接口,通过数据表中的value查询数据字典对应的label,最后封装为vo类返回给前端,如果把这个逻辑以硬编码的形式内嵌到查询详情代码中的话,有个比较致命的缺点就是代码的耦合性太高了,不利于模块的迁移复用。

 上述代码为查看详情的部分代码,在封装VO类时进行了硬编码,可以看出,在耦合性极高的同时,代码的可读性也较差,故引入AOP切面,将远程调用label和将label值更新至VO类写入AOP切面。 

3.2.2.3. AOP代码

数据字典AOP注解,它的作用是用于标记类的字段,指示字段的字典类型,并且在序列化过程中使用自定义的序列化器进行处理。

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DictSerializer.class)
public @interface Dict {
    /** 字典类型 */
    String type();
}

通过 @JsonSerialize(using = DictSerializer.class),我们告诉 Jackson 在对带有 @Dict 注解的字段进行序列化时,使用 DictSerializer 类来处理序列化过程。

数据字典序列化类: 

@Component
public class DictSerializer extends StdSerializer<Object> implements ContextualSerializer {
    private IDictionaryValueService dictionaryValueService;
    private String type;

    @Autowired
    public DictSerializer(IDictionaryValueService dictionaryValueService) {
        super(Object.class);
        this.dictionaryValueService = dictionaryValueService;
    }

    public DictSerializer(String type, IDictionaryValueService dictionaryValueService) {
        super(Object.class);
        this.type = type;
        this.dictionaryValueService = dictionaryValueService;
    }

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        if (Objects.isNull(value)) {
            gen.writeObject(value);
            return;
        }

        String label = null;
        if (dictionaryValueService != null && type != null) {
            try {
                String response = dictionaryValueService.getLabelByValue(value.toString());
                label = response; // 设置为空时返回 "null"
            } catch (RuntimeException e) {
                label = null;
            }
        }
        gen.writeObject(value);
        gen.writeFieldName(gen.getOutputContext().getCurrentName() + "Label");
        gen.writeObject(label);
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        if (property != null) {
            Dict dict = property.getAnnotation(Dict.class);
            if (dict != null) {
                return new DictSerializer(dict.type(), dictionaryValueService);
            }
        }
        return this;
    }


}

DictSerializer 是一个用于处理带有 @Dict 注解字段的自定义 Jackson 序列化器。它利用注入的 IDictionaryValueService 接口,根据字段值获取对应的标签,并将原始值与标签作为新字段输出,实现了动态字典值的序列化处理。

我写的示例代码把AOP相关代码写到了数据字典模块,但是实际项目中应当放到common模块,方便所有和数据字典有交集的业务模块调用。

4. 数据字典使用

基于第3章预设的场景,我们这章直接实操来看一下如何使用数据字典(ps,我将Student类相关代码写到了数据字典中,实际应该是在别的模块,这里为了方便我就写到了一个模块)。

4.1. 新增Student类对应数据字典值

新增dictionary_type表数据:

新增dictionary_value 表数据:

根据实体类名获取该实体类对应的数据字典,返回至前端进行下拉框动态渲染:

4.2. 新增学生数据

这里新增和平时操作无异:

    @PostMapping("")
    public boolean saveStudent(@RequestBody Student student){
      return  studentService.save(student);
    }

在传数据字典值时只需要传入value值即可:

 

4.3. 根据id查询学生数据详细信息

编写VO类:

@Data
public class StudentVO {
    private String id;

    private String name;

    @Dict(type = "bloodType")
    private String bloodType;
    
    @Dict(type = "constellationType")
    private String constellationType;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date createTime;
}

查看详情方法:

   public StudentVO getStudentInfoById(String id) {
        Student student = baseMapper.selectById(id);
        StudentVO studentVO= BeanCopyUtils.copyBean(student,StudentVO.class);
        return studentVO;
    }

运行结果:

5. 结语

本文探讨了如何通过面向切面编程(AOP)实现数据字典与业务模块的无侵入整合。通过自定义注解和序列化器,我们有效地降低了系统中业务模块与数据字典的耦合度,提升了系统的灵活性和可维护性。希望本文能为读者在实际项目中应用这些技术提供启发,进一步提升软件开发的效率和质量。若本文对你有帮助,别忘记三连哦~

6. 参考链接

基于Springboot,一个注解搞定数据字典问题 - 掘金 (juejin.cn)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1904035.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【QT】显示类控件

显示类控件 显示类控件1. label - 标签2. LCD Number - 显示数字的控件3. ProgressBar - 进度条4. Calendar Widget - 日历5. Line Edit - 输入框6. Text Edit - 多行输入框7. Combo Box - 下拉框8. Spin Box - 微调框9. Date Edit & Time Edit - 日期微调框10. Dial - 旋钮…

3-4 优化器和学习率

3-4 优化器和学习率 主目录点这里 优化器是机器学习和深度学习模型训练过程中用于调整模型参数的方法。它的主要目标是通过最小化损失函数来找到模型参数的最优值&#xff0c;从而提升模型的性能。 在深度学习中&#xff0c;优化器使用反向传播算法计算损失函数相对于模型参数…

pycharm远程连接和conda环境参考博客自用整理

pycharm远程连接 pycharm的连接需要先用xftp把项目上传上去&#xff08;包括venv&#xff09;&#xff0c;似乎才能连 https://blog.csdn.net/weixin_41174300/article/details/134420981 注意要上传一份一模一样的&#xff0c;然后在deployment里面添加mapping 注意传输文件…

【C语言】操作符--百科全书

目录 一、操作符的分类 二、 ⼆进制和进制转换 三、 原码、反码、补码 四、 移位操作符 五、位操作符&#xff1a;&、|、^、~ 六、单⽬操作符 七、逗号表达式 八、 下标访问[]、函数调⽤() 九、结构体 十、操作符的属性&#xff1a;优先级、结合性 十一、表达式…

P1392 取数

传送门&#xff1a;取数 如若你看完题解后&#xff0c;仍有问题&#xff0c;欢迎评论 首先说一下 我首先想到的思路 &#xff08; 20%通过率 &#xff09;&#xff1a;通过dfs , 将所有的情况放入priority_queue中&#xff08;greater<int>&#xff09;&#xff0c;维持…

【ARMv8/v9 GIC 系列 1.7 -- GIC PPI | SPI | SGI | LPI 中断使能配置介绍】

文章目录 GIC 各种中断使能配置PPIs(每个处理器私有中断)SPIs(共享外设中断)SGIs(软件生成的中断)LPIs(局部中断)GIC 各种中断使能配置 在ARM GICv3和GICv4架构中,不同类型的中断(如PPIs、SPIs、SGIs和LPIs)可以通过不同的方式进行启用和禁用。 下面详细介绍这些中…

java项目总结6

目录 1.双列集合 2.map的三种遍历方式&#xff1a; 1.键找值 2.键值对 3.lambda遍历map 3.HashMap 例子&#xff1a;统计字符出现次数 4.LinkedHashMap 5.TreeMap 6.可变参数 7.Collections: 1.双列集合 双列集合特点&#xff1a; 定义Map<String&#xff0c;St…

【Python】已解决:(paddleocr导包报错)ModuleNotFoundError: No module named ‘paddle’

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;&#xff08;paddleocr导包报错&#xff09;ModuleNotFoundError: No module named ‘paddle’ 一、分析问题背景 近日&#xff0c;一些使用PaddleOCR库进行文字…

移动校园(3):处理全校课程数据excel文档,实现空闲教室查询与课程表查询

首先打开教学平台 然后导出为excel文档 import mathimport pandas as pd import pymssql serverName 127.0.0.1 userName sa passWord 123456 databaseuniSchool conn pymssql.connect(serverserverName,useruserName,passwordpassWord,databasedatabase) cursor conn.cur…

vue3项目 前端blocked:mixed-content问题解决方案

一、问题分析 blocked:mixed-content其实浏览器不允许在https页面里嵌入http的请求&#xff0c;现在高版本的浏览器为了用户体验&#xff0c;都不会弹窗报错&#xff0c;只会在控制台上打印一条错误信息。一般出现这个问题就是在https协议里嵌入了http请求&#xff0c;解决方法…

拉曼光谱入门:3.拉曼光谱的特征参数与定量定性分析策略

1.特征参数 1.1 退偏振率 退偏振率&#xff08;p&#xff09;是一个衡量拉曼散射光偏振状态的参数&#xff0c;它描述了拉曼散射光的偏振方向与入射光偏振方向之间的关系。退偏振率定义为垂直偏振方向的拉曼散射强度与平行偏振方向的拉曼散射强度之比。退偏振率&#xff08;p&…

逆变器学习笔记(二)

用正点原子示波器看交流220V波形的时候&#xff0c;一定注意先把探头调到X10档位&#xff01;&#xff01;!!!!!!!!!!!!!!!!!!!!!!!!!!! 全桥LLC电路&#xff1a; 1.电感的两种模式——DCM和CCM的区别&#xff1a; DCM&#xff08;Discontinuous Conduction Mode&#xff0c;…

【数据结构】05.双向链表

一、双向链表的结构 注意&#xff1a;这里的“带头”跟前面我们说的“头节点”是两个概念&#xff0c;带头链表里的头节点&#xff0c;实际为“哨兵位”&#xff0c;哨兵位节点不存储任何有效元素&#xff0c;只是站在这里“放哨的”。 “哨兵位”存在的意义&#xff1a;遍历循…

Go语言如何入门,有哪些书推荐?

Go 语言之所以如此受欢迎&#xff0c;其编译器功不可没。Go 语言的发展也得益于其编译速度够快。 对开发者来说&#xff0c;更快的编译速度意味着更短的反馈周期。大型的 Go 应用程序总是能在几秒钟之 内完成编译。而当使用 go run编译和执行小型的 Go 应用程序时&#xff0c;其…

Facebook数据仓库的变迁与启示

❃博主首页 &#xff1a; <码到三十五> ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a; <搬的每块砖&#xff0c;皆为峰峦之基&#xff1b;公众号搜索(码到…

昇思学习打卡-10-ShuffleNet图像分类

文章目录 网络介绍网络结构部分实现对应网络结构 模型训练shuffleNet的优缺点总结优点不足 网络介绍 ShuffleNet主要应用在移动端&#xff0c;所以模型的设计目标就是利用有限的计算资源来达到最好的模型精度。ShuffleNetV1的设计核心是引入了两种操作&#xff1a;Pointwise G…

20、matlab信号波形生成:狄利克雷函数、高斯脉冲和高斯脉冲序列

1、名词说明 狄利克雷函数&#xff08;Dirac Delta Function&#xff09; 狄利克雷函数&#xff0c;也称为单位冲激函数或δ函数&#xff0c;是一个在数学和信号处理中常用的特殊函数。狄利克雷函数通常用符号δ(t)表示&#xff0c;其定义为&#xff1a; δ(t) { ∞, t 0{…

美股交易相关知识点 持续完善中

美股交易时间 美东时间&#xff1a;除了凌晨 03:50 ~ 04:00 这10分钟时间不可交易以外&#xff0c;其他时间都是可以交易的。 如果是在香港或者北京时间下交易要区分两种: 美东夏令时&#xff1a;除了下午 15:50 ~ 16:00 这10分钟时间不可交易以外&#xff0c;其他时间都是可…

springboot公寓租赁系统-计算机毕业设计源码03822

摘要 1 绪论 1.1 研究背景与意义 1.2选题背景 1.3论文结构与章节安排 2 公寓租赁系统系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例分析 2.4 系…

GRPC使用之ProtoBuf

1. 入门指导 1. 基本定义 Protocol Buffers提供一种跨语言的结构化数据的序列化能力&#xff0c;类似于JSON&#xff0c;不过更小、更快&#xff0c;除此以外它还能用用接口定义(IDL interface define language)&#xff0c;通protoc编译Protocol Buffer定义文件&#xff0c;…