14 KiB
14 KiB
编码生成功能设计规格
日期: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
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
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 结构
[
{ "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 重置维度键计算
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 |
生成编码 |
请求体:
{ "ruleCode": "ORDER_CODE" }
响应:纯字符串,如 "ORD202607150001"
5.3 规则 Save 请求体示例
{
"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 发钉钉通知,配置项控制 |