# 编码生成功能设计规格 > 日期:2026-07-15 > 分支:feature/20260713/base-module > 模块:nl-module-base(基础模块) ## 一、需求概述 在 base 模块下实现通用编码生成引擎,支持固定前缀、日期、流水序号三段组合,通过规则配置化管理,满足订单编码(`ORD202607150001`)、入库单编码(`CK202607150001`)等业务场景。 ### 1.1 范围 **第一期实现:** - 三种编码段类型:固定前缀(PREFIX)、日期(DATE)、流水序号(SEQUENCE) - 序号控制:按天/按月/按年/全局重置、补位字符可自定义、最大值上限 - 格式控制:分隔符配置、字母大小写转换、固定总长度约束 - 并发唯一性:号段预分配 + 分布式锁实时两种策略混合 - 规则管理:CRUD + 缓存 **本期不实现(预留扩展点):** - 固定后缀段(SUFFIX)→ 与 PREFIX 逻辑相同,二期加 type 即可 - 业务字段映射段(BIZ_FIELD) - 随机段(RANDOM) - 可视化拖拽配置 - 多租户/数据隔离 - 溢出告警(LOG 打点即可,不做通知) ### 1.2 决策记录 | 决策项 | 选择 | 理由 | |--------|------|------| | 前端范围 | 仅后端 API | 聚焦核心引擎质量,前端可后续对接 | | 并发方案 | 混合方案(号段预分配 + Lock4j 实时) | 高性能 + 灵活切换 | | 段类型 | 核心三种(PREFIX/DATE/SEQUENCE) | 覆盖 80% 场景,其他预留扩展 | | 存储模型 | 主表字段 + JSON 分段 | 基本属性可查询,分段配置灵活 | | 重置维度 | 全部(天/月/年/全局) | 实现成本差异不大,一次到位 | --- ## 二、数据库设计 ### 2.1 编码规则表 `base_code_rule` ```sql CREATE TABLE base_code_rule ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', rule_name VARCHAR(64) NOT NULL COMMENT '规则名称', rule_code VARCHAR(32) NOT NULL COMMENT '规则编码(唯一标识,调用时传入)', segment_config JSON NOT NULL COMMENT '分段配置(JSON数组)', separator VARCHAR(8) DEFAULT '' COMMENT '分隔符(- / _ . 或空)', letter_case TINYINT DEFAULT 0 COMMENT '字母大小写:0=原样 1=全大写 2=全小写', total_length INT DEFAULT 0 COMMENT '固定总长度约束(0=不限制)', seq_strategy VARCHAR(16) DEFAULT 'segment' COMMENT '序号策略:segment=号段预分配 lock=分布式锁实时', status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=启用 1=禁用', remark VARCHAR(256) DEFAULT '' COMMENT '备注', creator VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updater VARCHAR(64) DEFAULT '' COMMENT '更新者', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', deleted TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除', PRIMARY KEY (id), UNIQUE KEY uk_rule_code (rule_code) ) COMMENT '编码规则定义表'; ``` ### 2.2 流水号段表 `base_code_sequence` ```sql CREATE TABLE base_code_sequence ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', rule_id BIGINT NOT NULL COMMENT '规则ID,关联 base_code_rule.id', reset_key VARCHAR(64) NOT NULL DEFAULT '' COMMENT '重置维度键(如按天:20260715,全局:GLOBAL)', current_value BIGINT NOT NULL DEFAULT 0 COMMENT '当前已分配的最大序号', max_value BIGINT NOT NULL DEFAULT 9999 COMMENT '序号最大值上限', creator VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updater VARCHAR(64) DEFAULT '' COMMENT '更新者', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', deleted TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除', PRIMARY KEY (id), KEY idx_rule_reset (rule_id, reset_key) ) COMMENT '编码流水号段记录表'; ``` ### 2.3 `segment_config` JSON 结构 ```json [ { "type": "PREFIX", "value": "ORD" }, { "type": "DATE", "format": "yyyyMMdd" }, { "type": "SEQUENCE", "resetBy": "DAY", "startAt": 1, "paddingLen": 4, "paddingChar": "0", "maxValue": 9999 } ] ``` ### 2.4 段类型字段定义 | 段类型 | 字段 | 说明 | |--------|------|------| | PREFIX | `value` | 固定前缀字符串 | | DATE | `format` | Java 日期格式,如 `yyyyMMdd`、`yyyyMM`、`yyyy` | | SEQUENCE | `resetBy` | 重置维度:`DAY`/`MONTH`/`YEAR`/`GLOBAL` | | SEQUENCE | `startAt` | 起始值,默认 1 | | SEQUENCE | `paddingLen` | 补位后总长度,如 4 → 0001 | | SEQUENCE | `paddingChar` | 补位字符,默认 `"0"`,可任意字符串 | | SEQUENCE | `maxValue` | 序号最大值上限 | --- ## 三、包结构与模块划分 ### 3.1 API 模块(`nl-module-base-api`) ``` cn.code.nl.module.base ├── enums/ │ └── ErrorCodeConstants.java # 追加 CODE_RULE_NOT_EXISTS 等 └── api/codegen/ ├── CodeGenApi.java # Feign 接口,供其他模块调用 └── dto/ └── CodeGenerateReqDTO.java # 编码生成请求(ruleCode + bizParams) ``` ### 3.2 Server 模块(`nl-module-base-server`) ``` cn.code.nl.module.base ├── controller/admin/codegen/ │ ├── CodeRuleController.java # 规则 CRUD 接口 │ └── vo/ │ ├── CodeRuleSaveReqVO.java # 新增/编辑请求 │ ├── CodeRulePageReqVO.java # 分页查询请求 │ ├── CodeRuleRespVO.java # 规则响应 │ └── CodeRuleSimpleRespVO.java # 精简响应(下拉选项) ├── service/codegen/ │ ├── CodeRuleService.java # 规则管理接口 │ ├── CodeRuleServiceImpl.java # 规则管理实现 │ ├── CodeGenService.java # 编码生成接口 │ ├── CodeGenServiceImpl.java # 编码生成核心引擎 │ └── SequenceAllocator.java # 号段预分配器 ├── dal/dataobject/codegen/ │ ├── CodeRuleDO.java # 编码规则 DO │ └── CodeSequenceDO.java # 流水号段 DO └── dal/mysql/codegen/ ├── CodeRuleMapper.java ├── CodeSequenceMapper.java └── CodeSequenceMapper.xml # 流水号段的 UPDATE/DELETE SQL ``` --- ## 四、核心流程设计 ### 4.1 编码生成流程 ``` 外部调用 generate(ruleCode) │ ▼ ┌─ 查询规则缓存(Redis)──► 未命中时从 DB 加载并写缓存 │ ▼ ┌─ 遍历分段配置,逐段生成: │ PREFIX 段 ──► 直接返回配置的固定值 │ DATE 段 ──► 按 format 格式化当前日期 │ SEQUENCE 段──► 调用 SequenceAllocator.allocate() │ ▼ ┌─ 拼装:段值之间插入 separator │ ▼ ┌─ 后处理:letterCase 转换 / totalLength 校验 │ ▼ 返回编码字符串 ``` ### 4.2 SequenceAllocator 号段分配流程 ``` allocate(ruleId, resetKey, segment, strategy) │ ├── strategy = "lock" ──► Lock4j 加锁 → DB SELECT + UPDATE current_value+1 → 解锁 → 返回 │ └── strategy = "segment" ──► 先从内存 ConcurrentHashMap 取号段 │ ├── 命中且未用完 ──► 从当前号段取一个 → 返回 │ └── 未命中或耗尽 ──► ├── Lock4j 加锁 ├── DB UPDATE current_value = current_value + step(step=100) ├── 解锁 ├── 号段 [current_value-step+1, current_value] 放入本地缓存 └── 从缓存取一个返回 ``` ### 4.3 重置维度键计算 ```java public String buildResetKey(ResetByEnum resetBy) { return switch (resetBy) { case DAY -> LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); // "20260715" case MONTH -> LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM")); // "202607" case YEAR -> String.valueOf(Year.now().getValue()); // "2026" case GLOBAL -> "GLOBAL"; }; } ``` 不同 `resetKey` 在 `base_code_sequence` 表中为不同行,各周期流水自然隔离。 ### 4.4 并发保障总结 | 策略 | 实现 | 性能 | 连续性 | 适用场景 | |------|------|------|--------|----------| | `segment`(默认) | Lock4j + DB UPDATE 预取 + 内存 CAS | 高 | 允许断号 | 高并发订单编码 | | `lock` | Lock4j + DB SELECT FOR UPDATE 实时 | 低 | 严格连续 | 低频关键编码 | --- ## 五、API 设计 ### 5.1 规则管理(Controller) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/base/code-rule/create` | 创建规则 | | PUT | `/base/code-rule/update` | 更新规则 | | DELETE | `/base/code-rule/delete?id=` | 删除规则 | | GET | `/base/code-rule/get?id=` | 获取规则详情 | | GET | `/base/code-rule/page` | 分页查询规则列表 | | GET | `/base/code-rule/simple-list` | 精简列表(下拉选项) | ### 5.2 编码生成(Feign API) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/api/base/code-gen/generate` | 生成编码 | 请求体: ```json { "ruleCode": "ORDER_CODE" } ``` 响应:纯字符串,如 `"ORD202607150001"` ### 5.3 规则 Save 请求体示例 ```json { "ruleName": "订单编码", "ruleCode": "ORDER_CODE", "segments": [ { "type": "PREFIX", "value": "ORD" }, { "type": "DATE", "format": "yyyyMMdd" }, { "type": "SEQUENCE", "resetBy": "DAY", "startAt": 1, "paddingLen": 4, "paddingChar": "0", "maxValue": 9999 } ], "separator": "", "letterCase": 1, "totalLength": 0, "seqStrategy": "segment", "status": 0, "remark": "订单编号生成规则" } ``` --- ## 六、核心类与方法清单 ### 6.1 CodeRuleServiceImpl | 方法 | 职责 | |------|------| | `createRule(CodeRuleSaveReqVO)` | 创建规则,校验 ruleCode 唯一性 | | `updateRule(CodeRuleSaveReqVO)` | 更新规则,清除 Redis 缓存 | | `deleteRule(Long id)` | 删除规则 | | `getRule(Long id)` | 获取规则详情 | | `getRuleByCode(String ruleCode)` | 根据 ruleCode 查规则(带 Redis 缓存) | | `getRulePage(CodeRulePageReqVO)` | 分页查询规则列表 | ### 6.2 CodeGenServiceImpl | 方法 | 职责 | |------|------| | `generate(String ruleCode)` | 编码生成入口 | | `buildSegment(CodeRuleDO, SegmentConfig)` | 分发到具体段类型处理 | | `buildPrefixSegment(PrefixSegment)` | 返回固定前缀值 | | `buildDateSegment(DateSegment)` | 按 format 格式化当前日期 | | `buildSequenceSegment(CodeRuleDO, SequenceSegment)` | 委托 SequenceAllocator 分配序号 | | `postProcess(String code, CodeRuleDO)` | 大小写转换 + 总长度校验 | ### 6.3 SequenceAllocator | 方法 | 职责 | |------|------| | `allocate(Long ruleId, SequenceSegment, String strategy)` | 序号分配入口 | | `allocateByLock(Long ruleId, String resetKey, SequenceSegment)` | Lock4j 锁 + DB 实时分配 | | `allocateBySegment(Long ruleId, String resetKey, SequenceSegment)` | 号段预分配(内存 CAS + Lock4j + DB) | | `buildResetKey(String resetBy)` | 根据重置维度计算 resetKey | | `formatSequence(long value, SequenceSegment)` | 补位格式化 | ### 6.4 数据对象 | 类 | 映射表 | 说明 | |----|--------|------| | `CodeRuleDO` | `base_code_rule` | segmentConfig 用 JacksonTypeHandler | | `CodeSequenceDO` | `base_code_sequence` | ruleId + resetKey 联合定位 | ### 6.5 枚举 | 枚举 | 值 | 说明 | |------|-----|------| | `SegmentTypeEnum` | `PREFIX` / `DATE` / `SEQUENCE` | 段类型 | | `ResetByEnum` | `DAY` / `MONTH` / `YEAR` / `GLOBAL` | 重置维度 | | `LetterCaseEnum` | `NONE(0)` / `UPPER(1)` / `LOWER(2)` | 大小写 | | `SeqStrategyEnum` | `SEGMENT` / `LOCK` | 序号策略 | ### 6.6 错误码(追加到 ErrorCodeConstants,code 从 9 开始) | 常量名 | code | 说明 | |--------|------|------| | `CODE_RULE_NOT_EXISTS` | 9 | 编码规则不存在 | | `CODE_RULE_CODE_DUPLICATE` | 10 | 规则编码已存在 | | `CODE_GEN_SEQ_EXCEED_MAX` | 11 | 序号超出最大值上限 | | `CODE_GEN_LENGTH_EXCEED` | 12 | 生成的编码超出总长度限制 | --- ## 七、缓存设计 | 缓存 Key | 内容 | TTL | 失效时机 | |----------|------|-----|----------| | `codegen:rule:{ruleCode}` | 规则 DO(含分段配置) | 30 分钟 | 规则更新/删除时主动清除 | | 内存号段 `{ruleId}:{resetKey}` | 预分配号段区间 | 用完即弃 | 号段耗尽后自动刷新 | --- ## 八、边界与异常处理 | 场景 | 处理方式 | |------|----------| | 规则不存在 | 抛出 `CODE_RULE_NOT_EXISTS` 业务异常 | | 规则被禁用 | 抛出业务异常 | | 序号达上限 | Lock4j 锁内检测,达上限抛 `CODE_GEN_SEQ_EXCEED_MAX`,日志记录 | | 生成后总长度超限 | 校验后抛 `CODE_GEN_LENGTH_EXCEED` | | 号段预分配时并发冲突 | Lock4j 保证串行,不冲突 | | 服务重启 | 内存号段丢失,从 DB 重新预取新的 range(少量断号可接受) | | segment 策略下 DB 不可用 | 如已缓存号段则继续分配,耗尽后失败;全局不可用则抛异常 | --- ## 九、二期扩展预留 | 扩展项 | 实现方式 | |--------|----------| | SUFFIX 段 | 在 `SegmentTypeEnum` 加 `SUFFIX`,逻辑同 PREFIX | | BIz_FIELD 段 | 在 `CodeGenerateReqDTO.bizParams` 中传入字段值,`buildBizFieldSegment` 读取 | | RANDOM 段 | 加 `RANDOM` 类型,配置位数和字符集(数字/字母/混合),用 `SecureRandom` 生成 | | 溢出告警 | 加 `AlertService` 发钉钉通知,配置项控制 |