Files
huachuang/docs/superpowers/specs/2026-07-15-codegen-design.md

360 lines
14 KiB
Markdown
Raw Normal View History

# 编码生成功能设计规格
> 日期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 + stepstep=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 错误码(追加到 ErrorCodeConstantscode 从 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` 发钉钉通知,配置项控制 |