Nebula Dict 设计与实现
1. 分层设计
nebula-dict 采用 Nebula 一致的 api / core / local / remote / service 五段式分层。
1.1 nebula-dict-api
这一层负责稳定契约,当前可以确认包括:
IDictServiceCreateDictTypeCommand、UpdateDictTypeCommandCreateDictItemCommand、UpdateDictItemCommandPageDictTypeQuery、PageDictItemQueryDictTypeDto、DictTypeDetailDtoDictItemDto、DictItemDetailDto、DictItemTreeDto- 字典相关错误码与缓存名称常量
1.2 nebula-dict-core
这一层是真正的业务核心,当前可以确认包括:
DictServiceImplDictTypeDAO、DictItemDAODictTypeEntity、DictItemEntityDictCacheServiceDictCacheDefinitionConfiguration
1.3 nebula-dict-local
这一层对外暴露 REST 接口,当前核心入口是:
DictController
它负责:
- 请求对象到 Command / Query 的转换
- 响应 DTO 到 Resp 的转换
- 对外暴露
/api/dict/*接口
1.4 nebula-dict-remote
这一层为微服务消费者提供远程代理能力,当前关键入口包括:
DictFeignClientDictRemoteServiceImplDictRemoteProperties
1.5 nebula-dict-service
这一层是独立字典服务的启动模块:
- 默认端口
17779 - 服务内自身使用
nebula.dict.mode=local - 可被其他服务以 remote 模式调用
2. 核心数据模型
当前字典模块围绕两张核心业务表组织。
2.1 sys_dict_type
用于承载字典类型元数据,主要字段包括:
idcodenamestatusremarkcreate_timeupdate_time
它的业务意义是:
- 定义一个字典域,例如
order_status、user_status、region - 作为字典项的归属字典
- 作为对外查询时的稳定字典编码入口
2.2 sys_dict_item
用于承载字典项本身,主要字段包括:
iddict_codenameparent_idpathitem_valuesortstatusdefault_flagtag_colorextra_jsonremarkcreate_timeupdate_time
它的业务意义是:
dict_code用于归属到某个字典类型name用于前端展示名称item_value用于实际值表达parent_id + path用于构建树形字典default_flag / tag_color / extra_json用于承载富业务元数据
这里最关键的变化是:
- 字典类型不再使用
type_code / type_name,而是统一为code / name - 字典项不再保留
item_code,而是收敛到dict_code + name + item_value
3. 树形字典设计
这是当前 nebula-dict 相比纯平铺字典模块最关键的增强点。
3.1 层级字段
当前层级结构通过两个字段表达:
parentId:父级字典项 IDpath:树路径
其中:
- 根节点的
parentId为空 - 根节点的
path为自身 ID - 子节点的
path会拼成祖先ID,当前ID这样的链路
例如:
中国 path = cnId
上海 path = cnId,shId
浦东新区 path = cnId,shId,pdId
3.2 创建节点
创建字典项时,服务层会先做以下事情:
- 校验字典类型存在
- 如果带了
parentId,校验父节点存在 - 校验父节点与当前项属于同一个
dictCode - 保存当前节点
- 计算并回写
path - 清理该字典编码下的缓存
3.3 更新节点
更新字典项时,服务层不仅更新普通字段,还会处理层级关系:
- 查询当前节点
- 校验新父节点是否合法
- 校验不能形成环
- 更新
parentId - 重建当前节点
path - 递归刷新所有后代节点路径
- 清理该字典编码下缓存
3.4 删除节点
删除字典项时,服务层会先检查:
- 当前节点是否存在子节点
如果仍有子节点,则直接拒绝删除。
3.5 树查询
当前树查询接口为:
GET /api/dict/items/dict/{dictCode}/tree
返回模型为 DictItemTreeDto,其核心字段包括:
iddictCodenameparentIdpathitemValuechildren
当前树构建策略是:
- 先按
parentId对节点分组 - 再递归挂载子节点
- 若某节点的父节点不在当前加载结果里,则把它提升为根节点返回
这最后一点很重要,因为在 onlyEnabled=true 的场景下,如果父节点被过滤掉、子节点仍然启用,树接口也不会把这个子节点静默丢失。
4. 查询与分页设计
4.1 字典类型分页
字典类型分页基于 PageDictTypeQuery,支持按以下条件查询:
codenamestatus
4.2 字典项分页
字典项分页基于 PageDictItemQuery,支持按以下条件查询:
dictCodenamestatus
4.3 平铺列表查询
平铺查询接口:
GET /api/dict/items/dict/{dictCode}
它适合:
- 下拉选项
- 单选项列表
- 状态值显示
4.4 树列表查询
树查询接口:
GET /api/dict/items/dict/{dictCode}/tree
它适合:
- 分类树
- 区域树
- 多级联动选择器
- 树形筛选条件
5. 缓存设计
nebula-dict 当前使用单独的 DictCacheService 统一处理字典读取缓存。
5.1 缓存域
根据 DictCacheDefinitionConfiguration,当前存在两个缓存域:
dictItemByTypedictItemTreeByType
5.2 缓存键
缓存键由:
dictCodeonlyEnabled
共同组成,形如:
order_status::true
order_status::false
5.3 缓存读取
- 平铺查询通过
getOrLoadFlatList(...)获取缓存或回源 - 树查询通过
getOrLoadTree(...)获取缓存或回源
5.4 缓存失效
当以下操作发生时,会自动清理对应字典编码的平铺缓存和树缓存:
- 更新字典类型
- 删除字典类型
- 创建字典项
- 更新字典项
- 删除字典项
这意味着:
- 前端读取字典时可以享受缓存收益
- 后台维护字典时不需要自己额外处理缓存清理
6. local / remote 模式设计
6.1 local 模式
DictController 使用了:
@ConditionalOnNebulaModuleMode(module = "dict", value = "local", matchIfMissing = true)
因此在 nebula.dict.mode=local 时:
- 本地控制器生效
- 当前应用直接提供字典能力
6.2 remote 模式
业务应用切换到 nebula.dict.mode=remote 后:
- 不再直接启用本地控制器与实现
- 通过
DictFeignClient远程转调字典服务 - 业务代码仍然面向
IDictService编程
6.3 设计价值
这套设计的价值在于:
- 单体接入时,简单直接
- 微服务拆分时,不需要改业务代码接口层写法
- 字典能力可以从“内嵌模块”平滑升级为“共享字典中心”
7. 当前实现中的约束与边界
7.1 唯一性约束
- 字典类型
code唯一
当前字典项不再使用 itemCode 作为独立身份字段,因此服务与表结构不再保留 (dict_code, item_code) 唯一约束。
7.2 层级约束
- 父节点必须存在
- 父节点必须属于同一字典编码
- 不允许环形引用
- 有子节点时不允许删除
7.3 类型删除约束
- 字典类型下仍有字典项时,不允许删除
7.4 过滤语义
onlyEnabled 默认语义是:
- 传
null时,等价于true - 即默认只返回启用状态的字典项
这既适用于平铺列表,也适用于树查询。
8. 推荐继续阅读入口
如果要进一步看源码实现细节,建议重点阅读:
nebula-dict/nebula-dict-core/src/main/java/com/cludix/nebula/dict/service/impl/DictServiceImpl.javanebula-dict/nebula-dict-core/src/main/java/com/cludix/nebula/dict/service/DictCacheService.javanebula-dict/nebula-dict-local/src/main/java/com/cludix/nebula/dict/controller/DictController.javanebula-dict/nebula-dict-remote/src/main/java/com/cludix/nebula/dict/feign/DictFeignClient.javanebula-dict/nebula-dict-api/src/main/java/com/cludix/nebula/dict/model/dto/DictItemTreeDto.java