360 lines
14 KiB
Markdown
360 lines
14 KiB
Markdown
|
|
# 编码生成功能设计规格
|
|||
|
|
|
|||
|
|
> 日期: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` 发钉钉通知,配置项控制 |
|