• springboot 数据字典设计思路:字典表+字典枚举 两者兼故方案,查询返回结果在controller方法上添加注解进行字典翻译


    方式一:使用字典表存取 

            说明:适合于前端页面使用的下拉框数据值、或者字典数据不固定有变化调整的字典,建议放在数据表中维护。

           直接借鉴Ruoyi框架提供的2张字典表,sys_dict_type(字典类型定义表)、sys_dict_data(字典数据表)

    1. CREATE TABLE `sys_dict_type` (
    2. `dict_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '字典主键',
    3. `dict_name` varchar(100) DEFAULT '' COMMENT '字典名称',
    4. `dict_type` varchar(100) DEFAULT '' COMMENT '字典类型',
    5. `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
    6. `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
    7. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    8. `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
    9. `update_time` datetime DEFAULT NULL COMMENT '更新时间',
    10. `remark` varchar(500) DEFAULT NULL COMMENT '备注',
    11. PRIMARY KEY (`dict_id`),
    12. UNIQUE KEY `dict_type` (`dict_type`)
    13. ) COMMENT='字典类型表';
    14. CREATE TABLE `sys_dict_data` (
    15. `dict_code` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '字典编码',
    16. `dict_sort` int(4) DEFAULT '0' COMMENT '字典排序',
    17. `dict_label` varchar(100) DEFAULT '' COMMENT '字典标签',
    18. `dict_value` varchar(100) DEFAULT '' COMMENT '字典键值',
    19. `dict_type` varchar(100) DEFAULT '' COMMENT '字典类型',
    20. `css_class` varchar(100) DEFAULT NULL COMMENT '样式属性(其他样式扩展)',
    21. `list_class` varchar(100) DEFAULT NULL COMMENT '表格回显样式',
    22. `is_default` char(1) DEFAULT 'N' COMMENT '是否默认(Y是 N否)',
    23. `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
    24. `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
    25. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    26. `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
    27. `update_time` datetime DEFAULT NULL COMMENT '更新时间',
    28. `remark` varchar(500) DEFAULT NULL COMMENT '备注',
    29. PRIMARY KEY (`dict_code`)
    30. ) COMMENT='字典数据表';

    方式二:自定义枚举变量

            说明:适用于系统级别、固定不变的字典;可以考虑直接写在代码中,方便其它人在代码中使用。

    1. @DictType("sys_disable")
    2. public enum SysDisableEnum {
    3. ENABLE("0", "启用"),
    4. DISABLE("1", "停用");
    5. private String code;
    6. private String desc;
    7. public static List toList() {
    8. return (List)Stream.of(values()).map((row) -> {
    9. DictOptions options = new DictOptions();
    10. options.setDictLabel(row.getDesc());
    11. options.setDictValue(row.getCode());
    12. return options;
    13. }).collect(Collectors.toList());
    14. }
    15. private SysDisableEnum(final String code, final String desc) {
    16. this.code = code;
    17. this.desc = desc;
    18. }
    19. public String getCode() {
    20. return this.code;
    21. }
    22. public String getDesc() {
    23. return this.desc;
    24. }
    25. }

            对于字典表,在使用时很简单,无非是提供好添加、更新、删除及查询的API即可;但对于枚举字典,怎么能不重复配置到字典表中,同时又能融入到 查询字典的API中,这里就需要好好封装一下代码了!

            具体实现代码参考如下:  

    1. /**
    2. * 字典类型枚举:@DictType(value="sys_sex")
    3. */
    4. @Documented
    5. @Inherited
    6. @Retention(RetentionPolicy.RUNTIME)
    7. @Target(ElementType.TYPE)
    8. public @interface DictType {
    9. String value();//字典类型别名
    10. }
    11. /**
    12. * 字典翻译注解:@DictEnum(value="sex", dictType="sys_sex")
    13. */
    14. @Documented
    15. @Retention(RetentionPolicy.RUNTIME)
    16. @Target(ElementType.METHOD)
    17. public @interface DictEnum {
    18. String value();//实体类字段
    19. String dictType() default "";//字典code;指的是dict_type
    20. String target() default "";//返回目标属性,如 sexDesc
    21. }
    22. /**
    23. * 字典类型注解:用于多个字典翻译
    24. */
    25. @Documented
    26. @Retention(RetentionPolicy.RUNTIME)
    27. @Target(ElementType.METHOD)
    28. public @interface DictEnums {
    29. DictEnum[] value();//多个字典值
    30. }
    1. package com.geline.cloud.core.dict;
    2. import com.geline.cloud.core.domain.DictOptions;
    3. import java.util.HashMap;
    4. import java.util.List;
    5. import java.util.Map;
    6. /**
    7. * 字典枚举缓存
    8. * @author: mengxin
    9. * @date: 2022/9/25 10:14
    10. */
    11. public class DictTypeCache {
    12. private static Map> dictMapCache = new HashMap<>();
    13. public static void put(String dictType, List optionsList) {
    14. dictMapCache.put(dictType, optionsList);
    15. }
    16. public static Map> getAll(){
    17. return dictMapCache;
    18. }
    19. /**
    20. * 获取字典值对应的标签名
    21. * @param dictType
    22. * @param dictValue
    23. * @return
    24. */
    25. public static String getDictLabel(String dictType, String dictValue){
    26. List options = dictMapCache.get(dictType);
    27. if(options != null){
    28. for (DictOptions row : options){
    29. if(row.getDictValue().equals(dictValue)){
    30. return row.getDictLabel();
    31. }
    32. }
    33. }
    34. return null;
    35. }
    36. }
    1. package com.geline.cloud.core.dict;
    2. import com.geline.cloud.core.dict.annotation.DictType;
    3. import com.geline.cloud.core.domain.DictOptions;
    4. import org.springframework.boot.CommandLineRunner;
    5. import org.springframework.context.ResourceLoaderAware;
    6. import org.springframework.core.io.Resource;
    7. import org.springframework.core.io.ResourceLoader;
    8. import org.springframework.core.io.support.ResourcePatternResolver;
    9. import org.springframework.core.io.support.ResourcePatternUtils;
    10. import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
    11. import org.springframework.core.type.classreading.MetadataReader;
    12. import org.springframework.core.type.classreading.MetadataReaderFactory;
    13. import org.springframework.stereotype.Component;
    14. import org.springframework.util.ClassUtils;
    15. import java.lang.reflect.InvocationTargetException;
    16. import java.lang.reflect.Method;
    17. import java.util.List;
    18. import java.util.Map;
    19. /**
    20. * 扫描自定义字典枚举类 @DictType
    21. * @author: mengxin
    22. * @date: 2022/9/25 10:16
    23. */
    24. @Component
    25. public class DictTypeHandler implements CommandLineRunner, ResourceLoaderAware {
    26. private ResourceLoader resourceLoader;
    27. private ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
    28. private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    29. private static final String FULLTEXT_SACN_PACKAGE_PATH = "com.geline.cloud";
    30. @Override
    31. public void setResourceLoader(ResourceLoader resourceLoader) {
    32. this.resourceLoader = resourceLoader;
    33. }
    34. @Override
    35. public void run(String... args) throws Exception {
    36. String concat = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
    37. .concat(ClassUtils.convertClassNameToResourcePath(FULLTEXT_SACN_PACKAGE_PATH)
    38. .concat("/**/*Enum*.class")); //只扫描文件名中包含Enum的文件,会快点
    39. Resource[] resources = resolver.getResources(concat);
    40. MetadataReader metadataReader = null;
    41. for (Resource resource : resources) {
    42. if (resource.isReadable()) {
    43. metadataReader = metadataReaderFactory.getMetadataReader(resource);
    44. boolean flag = metadataReader.getAnnotationMetadata().hasAnnotation(DictType.class.getName());
    45. String className = metadataReader.getClassMetadata().getClassName();
    46. if(flag) {
    47. Map annotationAttributes = metadataReader.getAnnotationMetadata()
    48. .getAnnotationAttributes(DictType.class.getName());
    49. String dictType = annotationAttributes.get("value").toString();
    50. try {
    51. Class aClass = Class.forName(className);
    52. Method listDict = aClass.getDeclaredMethod("toList");
    53. Object[] oo = aClass.getEnumConstants();
    54. List list = (List) listDict.invoke(oo[0]);
    55. if(list != null){
    56. DictTypeCache.put(dictType, list);
    57. }
    58. } catch (ClassNotFoundException | NoSuchMethodException e) {
    59. e.printStackTrace();
    60. } catch (IllegalAccessException e) {
    61. e.printStackTrace();
    62. } catch (InvocationTargetException e) {
    63. e.printStackTrace();
    64. }
    65. }
    66. }
    67. }
    68. }
    69. }
    1. package com.geline.cloud.core.domain;
    2. import lombok.Getter;
    3. import lombok.Setter;
    4. /**
    5. * 查询字典列表
    6. * @author: mengx
    7. * @date: 2021/9/14 16:18
    8. */
    9. @Getter
    10. @Setter
    11. public class DictOptions {
    12. private String dictType;
    13. private String dictLabel;
    14. private String dictValue;
    15. private String isDefault;
    16. private String listClass;
    17. }
    1. package com.geline.cloud.core.dict;
    2. import cn.hutool.core.util.ObjectUtil;
    3. import cn.hutool.core.util.ReflectUtil;
    4. import cn.hutool.core.util.StrUtil;
    5. import com.baomidou.mybatisplus.core.metadata.IPage;
    6. import com.geline.cloud.core.dict.annotation.DictEnum;
    7. import com.geline.cloud.core.dict.annotation.DictEnums;
    8. import com.geline.cloud.core.domain.Result;
    9. import com.geline.cloud.core.domain.TableDataInfo;
    10. import lombok.extern.slf4j.Slf4j;
    11. import org.springframework.core.MethodParameter;
    12. import org.springframework.http.MediaType;
    13. import org.springframework.http.server.ServerHttpRequest;
    14. import org.springframework.http.server.ServerHttpResponse;
    15. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
    16. import java.lang.reflect.Field;
    17. import java.lang.reflect.Method;
    18. import java.util.Map;
    19. /**
    20. * 使用方式:子类继承后加 @ControllerAdvice
    21. * @author: mengxin
    22. * @date: 2022/9/17 10:05
    23. */
    24. @Slf4j
    25. public abstract class AbstractDictEnumHandler implements ResponseBodyAdvice {
    26. /**
    27. * 从DB中查询字典标签名
    28. * @param dictType 字典类型code
    29. * @param dictValue 字典值code
    30. * @return
    31. */
    32. public abstract String selectDictLabelByDatabase(String dictType, String dictValue);
    33. /**
    34. * 从枚举字典中取字典标签名
    35. * @param dictType
    36. * @param dictValue
    37. * @return
    38. */
    39. public String selectDictLabelByEnum(String dictType, String dictValue){
    40. return DictTypeCache.getDictLabel(dictType, dictValue);
    41. }
    42. @Override
    43. public boolean supports(MethodParameter methodParameter, Class aClass) {
    44. return methodParameter.hasMethodAnnotation(DictEnum.class) || methodParameter.hasMethodAnnotation(DictEnums.class);
    45. }
    46. @Override
    47. public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class clazz,
    48. ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
    49. if (ObjectUtil.isEmpty(o)) {
    50. return null;
    51. }
    52. // 获取方法 注解参数
    53. Method method = methodParameter.getMethod();
    54. final String methodName = method == null ? "" : (method.getDeclaringClass().getSimpleName() + "." + method.getName());
    55. DictEnum[] sensibles;
    56. if (methodParameter.hasMethodAnnotation(DictEnum.class)) {
    57. DictEnum sensible = methodParameter.getMethodAnnotation(DictEnum.class);
    58. sensibles = new DictEnum[]{sensible};
    59. } else {
    60. DictEnums typeSensibles = methodParameter.getMethodAnnotation(DictEnums.class);
    61. sensibles = typeSensibles == null ? new DictEnum[]{} : typeSensibles.value();
    62. }
    63. // 处理返回值
    64. this.dis(o, sensibles, methodName);
    65. return o;
    66. }
    67. /**
    68. * 处理返回数据
    69. *
    70. * @param obj 对象
    71. * @param sensibles 字典类型
    72. */
    73. private void dis(Object obj, DictEnum[] sensibles, String methodName) {
    74. // 处理返回类型
    75. if (obj instanceof TableDataInfo) {
    76. this.dis(((TableDataInfo) obj).getRows(), sensibles, methodName);
    77. return;
    78. }
    79. if (obj instanceof Result) {
    80. this.dis(((Result) obj).getData(), sensibles, methodName);
    81. return;
    82. }
    83. if (obj instanceof IPage) {
    84. ((IPage) obj).getRecords().forEach(e -> this.dis(e, sensibles, methodName));
    85. return;
    86. }
    87. if (obj instanceof Iterable) {
    88. ((Iterable) obj).forEach(e -> this.dis(e, sensibles, methodName));
    89. return;
    90. }
    91. if (obj instanceof Map) {
    92. Map map = (Map) obj;
    93. for (DictEnum sensible : sensibles) {
    94. if (!map.containsKey(sensible.value())) {
    95. log.warn("--- 方法 {},字典 {} 进行忽略,不存在 ------", methodName, sensible.value());
    96. continue;
    97. }
    98. Object value = map.get(sensible.value());
    99. if (value instanceof Iterable) {
    100. this.dis(value, sensibles, methodName);
    101. continue;
    102. }
    103. String dictType = StrUtil.isEmpty(sensible.dictType()) ? StrUtil.toUnderlineCase(sensible.value()) : sensible.dictType();
    104. String label = selectDictLabelByEnum(dictType, String.valueOf(value));
    105. if(label == null){
    106. label = selectDictLabelByDatabase(dictType, String.valueOf(value));
    107. }
    108. String target = sensible.target();
    109. map.put(StrUtil.isNotBlank(target) ? target : sensible.value(), label);
    110. }
    111. return;
    112. }
    113. Class clazz = obj.getClass();
    114. for (DictEnum sensible : sensibles) {
    115. try {
    116. Field field = ReflectUtil.getField(clazz, sensible.value());
    117. if (field == null || !field.getType().getSimpleName().contains("String")) {
    118. log.warn("--- 方法 {},字典 {} 进行忽略,不存在 || 非String类型 ------", methodName, sensible.value());
    119. continue;
    120. }
    121. Object val = ReflectUtil.getFieldValue(obj, field);
    122. if (ObjectUtil.isEmpty(val)) {
    123. continue;
    124. }
    125. String dictType = StrUtil.isEmpty(sensible.dictType()) ? StrUtil.toUnderlineCase(sensible.value()) : sensible.dictType();
    126. String label = selectDictLabelByEnum(dictType, String.valueOf(val));
    127. if(label == null){
    128. label = selectDictLabelByDatabase(dictType, String.valueOf(val));
    129. }
    130. ReflectUtil.setFieldValue(obj, field, label);
    131. } catch (Exception e) {
    132. e.printStackTrace();
    133. }
    134. }
    135. }
    136. }
      1. package com.geline.cloud.util.dict;
      2. import com.geline.cloud.core.dict.AbstractDictEnumHandler;
      3. import com.geline.cloud.modules.system.entity.SysDictData;
      4. import com.geline.cloud.modules.system.service.SysDictDataService;
      5. import lombok.extern.slf4j.Slf4j;
      6. import org.springframework.beans.factory.annotation.Autowired;
      7. import org.springframework.web.bind.annotation.ControllerAdvice;
      8. /**
      9. * 自定义字典处理
      10. * @author: mengxin
      11. * @date: 2022/9/17 10:43
      12. */
      13. @ControllerAdvice
      14. @Slf4j
      15. public class MyDictEnumHandler extends AbstractDictEnumHandler {
      16. @Autowired
      17. private SysDictDataService sysDictDataService;
      18. @Override
      19. public String selectDictLabelByDatabase(String dictType, String dictValue) {
      20. SysDictData dictData = sysDictDataService.getDictData(dictType, dictValue);
      21. if(dictData != null){
      22. return dictData.getDictLabel();
      23. }
      24. return null;
      25. }
      26. }

      具体使用方法:

      在controller方法上添加注解,扩展返回字典字段,如查询返回 sex, 增加一个sexDesc;

      举例:分页查询返回 Page处理字典,多个字典字段使用 @DictEnums,一个字典字段使用 @DictEnum 。

      1. /**
      2. * 分页查询
      3. * localhost:9501/reportData/pageMaps?pageNum=1&pageSize=10&orderByColumn=create_time&isAsc=desc&reportTitle=标题
      4. * @param pageNum
      5. * @param pageSize
      6. * @param orderByColumn
      7. * @param isAsc
      8. * @return
      9. */
      10. @DictEnums({
      11. @DictEnum(value = "reportType", dictType = "sys_report_type"),
      12. @DictEnum(value = "status", dictType = "sys_disable", target = "statusDesc")
      13. })
      14. @GetMapping({"/pageMaps"})
      15. public TableDataInfo pageMaps(String reportTitle,
      16. int pageNum, int pageSize, String orderByColumn, String isAsc) {
      17. ReportDataQueryVO queryVO = new ReportDataQueryVO();
      18. queryVO.setReportTitle(reportTitle);
      19. QueryWrapper query = QueryWrapperUtil.build(queryVO, orderByColumn, isAsc);
      20. Page page = this.getBaseService().pageMaps(new Page(pageNum, pageSize), query);
      21. return this.getTableDataInfo(page);
      22. }
      23. /**
      24. * 地图数据 - 分页查询
      25. * localhost:9501/visualMap/pageMaps?pageNum=1&pageSize=10&orderByColumn=create_time&isAsc=desc&name={名称}
      26. * @return
      27. */
      28. @DictEnum(value = "disable", dictType = "sys_disable")
      29. @GetMapping({"/pageMaps"})
      30. public TableDataInfo pageMaps(int pageNum, int pageSize, String orderByColumn, String isAsc, String name) {
      31. VisualMapQueryVO queryVO = new VisualMapQueryVO();
      32. queryVO.setName(name);
      33. QueryWrapper query = QueryWrapperUtil.build(queryVO, orderByColumn, isAsc);
      34. Page page = this.visualMapService.pageMaps(new Page(pageNum, pageSize), query);
      35. return this.getTableDataInfo(page);
      36. }

              至于 添加、更新相关代码,直接用mybatis生成的 Enitity.java,不用在实体类中额外添加 @Dict注解,前端添加、更新时传入字典code直接保存及更新,字典问题-只考虑查询返回的字典翻译问题。

              最后,再提供2个查询返回字典列表方法:

      1. package com.geline.cloud.system;
      2. import com.geline.cloud.core.dict.DictTypeCache;
      3. import com.geline.cloud.core.domain.DictOptions;
      4. import com.geline.cloud.core.domain.Result;
      5. import com.geline.cloud.modules.system.service.SysDictDataService;
      6. import org.springframework.beans.factory.annotation.Autowired;
      7. import org.springframework.web.bind.annotation.GetMapping;
      8. import org.springframework.web.bind.annotation.PathVariable;
      9. import org.springframework.web.bind.annotation.RequestMapping;
      10. import org.springframework.web.bind.annotation.RestController;
      11. import java.util.List;
      12. import java.util.Map;
      13. /**
      14. * 字典管理 Controller
      15. * @author mengx
      16. * @date 2020/7/26 21:45
      17. */
      18. @RestController
      19. @RequestMapping("/system/dict")
      20. public class SysDictController {
      21. @Autowired
      22. private SysDictDataService sysDictDataService;
      23. /**
      24. * 查询所有枚举字典Map
      25. * /system/dict/listEnums
      26. * @return
      27. */
      28. @GetMapping("/listEnums")
      29. public Result listEnums(){
      30. return Result.ok(DictTypeCache.getAll());
      31. }
      32. /**
      33. * 根据字典类型查询字典列表
      34. * /system/dict/list/{dictType}
      35. * @param dictType
      36. * @return
      37. */
      38. @GetMapping("/list/{dictType}")
      39. public Result list(@PathVariable String dictType){
      40. Map> all = DictTypeCache.getAll();
      41. List options = all.get(dictType);
      42. if(options != null){
      43. return Result.ok(options);
      44. }
      45. return Result.ok(sysDictDataService.getListByDictType(dictType));
      46. }
      47. }

       

       

    137. 相关阅读:
      超全,看完这份微服务架构与实践文档,微服务不再难
      iRDMA Flow Control Introduction
      计算点云每个点的高斯曲率(附open3d python代码)
      Java集合源码剖析——基于JDK1.8中ConcurrentHashMap的实现原理
      第2节 volatile 关键字内存可见性
      智能咖啡厅助手:人形机器人 +融合大模型,行为驱动的智能咖啡厅机器人
      计算机网络知识点总结——第四章网络层
      C++:map和set容器
      Character.AI:产品优势和商业壁垒在哪里?
      C语言——有一篇文章,共有 3 行文字,每行有 80 个字符。要求分别统计出其中英文大写字母、小写字母、数字、空格以及其他字符的个数
    138. 原文地址:https://blog.csdn.net/mxskymx/article/details/127107953