mldong 快速开发框架字典模块设计与实现

本文带你深入解析基于枚举 + 动态配置构建的通用字典系统,适用于中后台管理系统的数据字典需求。

一、字典模块的核心目标

统一字典管理 :支持枚举类字典、数据库字典、自定义字典。

动态扩展能力 :允许第三方业务模块注册自定义字典。

标签/值双向转换 :提供 dictType -> value <-> label 转换接口。

缓存优化 :提升高频访问性能,同时支持缓存清理。

导入导出功能:支持 Excel 导入导出,便于维护。


二、字典模型结构详解

核心实体类说明

实体 描述
Dict 字典主表,存储字典编码、名称、数据类型等元信息
DictItem 字典项明细,每个字典可包含多个字典项

数据库结构图解(ER 图)


三、字典分类与来源

支持的字典类型

类型 来源 示例
枚举类字典 Java 枚举类注解标记 SexEnum
自定义字典 业务模块实现 CustomDictService 接口 如订单状态、支付方式
数据库字典 存储在 sys_dictsys_dict_item 表中 用户手动配置的业务字典

示例:枚举类字典

java 复制代码
@DictEnum(key = "sex", name = "性别")
public enum SexEnum implements CodedEnum {
    MAN(1, "男"),
    WOMAN(2, "女"),
    UN_KNOW(3, "未知");
}

四、核心服务接口设计

DictService 核心接口方法

java 复制代码
public interface DictService extends IService<Dict> {
    boolean save(DictParam param);
    boolean update(DictParam param);
    CommonPage<DictVO> page(DictPageParam param);
    DictVO findById(Long id);
    List<cn.hutool.core.lang.Dict> getByDictType(String dictType);
    String toLabel(String dictType, Object value);
    Object toValue(String dictType, String label);
    void clearCache();
    void importTo(InputStream inputStream);
}

🔗 源码地址:DictService.java


五、缓存机制详解

使用的缓存工具

示例代码:

java 复制代码
@Override
public boolean update(DictParam param) {
    LowCodeServiceUtil.checkUnique(baseMapper,"code",param.getCode(),param.getId(),"唯一编码已存在,请检查code参数");
    Dict dict = new Dict();
    BeanUtil.copyProperties(param, dict);
    return RedisUtil.delayedDoubleRemove(dictCache,(Dict object)->{
        boolean success = super.updateById(object);
        if(success && CollectionUtil.isNotEmpty(param.getDictItemList())){
            List<Long> ids = new ArrayList<>();
            param.getDictItemList().forEach(item->{
                item.setDictId(dict.getId());
                try{
                    dictItemService.save(item);
                } catch (ServiceException e){
                    DictItem dictItem = dictItemService.getOne(Wrappers.lambdaQuery(DictItem.class).eq(DictItem::getCode,item.getCode()));
                    item.setId(dictItem.getId());
                    dictItemService.update(item);
                }
                ids.add(item.getId());
            });
            dictItemService.remove(
                    Wrappers.lambdaQuery(DictItem.class)
                            .eq(DictItem::getDictId,dict.getId())
                            .notIn(DictItem::getId,ids));

        }
        return success;
    },dict,"code");
}

六、缓存工具类 RedisUtil.delayedDoubleRemove 详解

作用:延时双删策略防止缓存穿透 & 防止并发更新导致脏读

实现逻辑如下:

  1. 第一次删除缓存;
  2. 执行数据库更新操作;
  3. 延迟一段时间后再次删除缓存。

源码结构(示意):

java 复制代码
public static <T> boolean delayedDoubleRemove(AbstractRedisCacheOperator<T> cacheOperator,
                                              Function<T, Boolean> operation,
                                              T data, String keySuffix) {
    // 第一次删除缓存
    cacheOperator.delete(keySuffix);

    // 执行数据库更新操作
    boolean result = operation.apply(data);

    // 延迟 500ms 后再次删除缓存
    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            cacheOperator.delete(keySuffix);
        }
    }, 500);

    return result;
}

优势:

🧠 避免缓存和数据库不一致问题

🔒 防止并发更新造成缓存污染

⏱️ 合理利用时间窗口,降低缓存击穿风险


七、DictCache 缓存实现细节

缓存 key 构建规则

java 复制代码
@Override
public String getCommonKeyPrefix() {
    return "sys_dict:";
}

示例缓存 key:

plain 复制代码
sys_dict:sex
sys_dict:wf_assignment_handler

缓存有效期

默认永久生效,有变更后会自动更新。


八、字典下拉接口 /sys/dict/getByDictType 详解

接口路径

java 复制代码
@PostMapping("/sys/dict/getByDictType")
@ApiOperation(value = "字典下拉")
public CommonResult<List<Dict>> getByDictType(@RequestBody @Validated({DictParam.DictType.class}) DictParam param) {
    return CommonResult.data(dictService.getByDictType(param.getDictType()));
}

🔗 接口文件地址:DictController.java

对应服务方法

java 复制代码
@Override
public List<cn.hutool.core.lang.Dict> getByDictType(String dictType) {
    DictModel res = dictCache.get(dictType);
    if(ObjectUtil.isNotNull(res)) return dictModelToDictList(res);
    DictModel dictModel;
    DictDataType dictDataType;
    Dict dict = this.getOne(Wrappers.lambdaQuery(Dict.class).eq(Dict::getCode, dictType));
    if (ObjectUtil.isNull(dict)) {
        // 空,则使用枚举类
        dictModel = dictScanner.getDictMap().get(dictType);
        if (dictModel == null) {
            String[] customDictServiceArr = SpringUtil.getBeanNamesForType(CustomDictService.class);
            for (String name : customDictServiceArr) {
                CustomDictService customDictService = SpringUtil.getBean(name);
                Map<String, Object> args = new HashMap<>();
                args.put("dictType", dictType);
                dictModel = customDictService.getByDictKey(args);
                if(dictModel != null) {
                    break;
                }
            }
        }
        if (dictModel == null) {
            return new ArrayList<>();
        } else {
            return dictModelToDictList(dictModel);
        }
    } else {
        dictDataType = CodedEnum.codeOf(DictDataType.class,dict.getDataType()).orElse(DictDataType.STRING);
    }
    return dictItemService.getDictItemListByDictId(dict.getId(), dictDataType);
}

🔗 服务实现地址:DictServiceImpl.java

该方法用于根据字典类型 dictType 获取对应的字典项列表。支持多种数据来源:

  1. 缓存命中;
  2. 数据库字典匹配;
  3. 枚举类字典匹配;
  4. 自定义字典匹配;
  5. 默认返回空列表。

九、字典标签和值互转的 API 详解

在 mldong 项目中,为了方便前端与后端之间的字典数据转换,提供了两个实用的 API 方法:

  • toLabel(String dictType, Object value):将字典值转换为对应的标签(label);
  • toValue(String dictType, String label):将字典标签转换为对应的值(value);

此外,在 LowCodeServiceUtil 中还封装了静态方法,支持全局调用这些功能:

  • dictValueToLabel(String dictType, Object value)
  • dictLabelToValue(String dictType, String label)

这两个方法广泛应用于表单展示、数据回显、日志显示等场景,能够提升系统的可读性和易用性。

方法定义

toLabel(...)

java 复制代码
@Override
public String toLabel(String dictType, Object value) {
    if(value == null) return null;
    List<cn.hutool.core.lang.Dict> dicts = getByDictType(dictType);
    for (cn.hutool.core.lang.Dict dict : dicts) {
        if (dict.getStr(CommonConstant.VALUE).equals(StrUtil.toString(value))) {
            return dict.getStr(CommonConstant.LABEL);
        }
    }
    return StrUtil.toString(value);
}

🔗 文件路径:DictServiceImpl.java

toValue(...)

java 复制代码
@Override
public Object toValue(String dictType, String label) {
    if(StrUtil.isEmpty(label)) return null;
    List<cn.hutool.core.lang.Dict> dicts = getByDictType(dictType);
    for (cn.hutool.core.lang.Dict dict : dicts) {
        if (dict.getStr(CommonConstant.LABEL).equals(label)) {
            return dict.get(CommonConstant.VALUE);
        }
    }
    return label;
}

🔗 文件路径:DictServiceImpl.java


十、总结与建议

✅ 已有优势:

  • 支持枚举类、数据库、自定义三种字典来源;
  • 提供完善的标签/值转换 API;
  • 内置缓存机制,提升高频访问性能;
  • 支持 Excel 导入导出,便于维护;
  • 接口权限控制粒度细,支持 OR/AND 组合;
  • 可扩展性强,支持业务模块自定义字典。

💡 可优化方向:

  • 字段级字典绑定;
  • 字典版本管理;
  • 多语言支持;
  • 可视化字典配置界面;
  • 字典审计日志;
  • 异步刷新缓存;
  • 引入本地缓存 + Redis 二级缓存架构。

十一、结语

欢迎关注【mldong】开源项目,后续将持续分享字典扩展、低代码平台构建等内容。

如果你也想打造一个灵活可扩展的字典系统,欢迎 Star 我们的开源项目 mldong

更多关于字典、低代码平台构建的内容,欢迎关注【mldong】公众号,第一时间获取更新通知!

📌 如需获取完整源码,请访问 Gitee 地址:

🔗 gitee.com/mldong/mldo...

相关推荐
饕餮争锋8 分钟前
设计模式笔记_创建型_单例模式
java·笔记·设计模式
why15122 分钟前
6.15 操作系统面试题 锁 内存管理
后端·性能优化
Y1_again_0_again38 分钟前
Java 包装类详解
java·开发语言
丘山子1 小时前
如何确保 Go 系统在面临超时或客户端主动取消时,能够优雅地释放资源?
后端·面试·go
武子康1 小时前
Java-52 深入浅出 Tomcat SSL工作原理 性能优化 参数配置 JVM优化
java·jvm·后端·servlet·性能优化·tomcat·ssl
别骂我h1 小时前
容器技术技术入门与Docker环境部署
java·spring cloud·docker
OnlyLowG1 小时前
SpringSecurity导致redis压力大问题解决
后端
深栈解码1 小时前
OpenIM 源码深度解析系列(十四):事件增量同步机制解析
后端
想用offer打牌1 小时前
一站式了解CDN😈
后端·架构·cdn
用户30742971671581 小时前
Spring AI Chain工作流模式完整指南
java·架构