Files
huachuang/docs/superpowers/specs/2026-07-15-codegen-design.md
2026-07-15 16:38:35 +08:00

360 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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