1913 lines
55 KiB
Markdown
1913 lines
55 KiB
Markdown
|
|
# 编码生成功能 实现计划
|
|||
|
|
|
|||
|
|
> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
|
|||
|
|
|
|||
|
|
**目标:** 在 nl-module-base 模块下实现编码生成引擎,支持 PREFIX + DATE + SEQUENCE 三段组合、号段预分配/分布式锁两种并发策略、规则配置化管理。
|
|||
|
|
|
|||
|
|
**架构:** 编码规则通过 `base_code_rule` 表存储(分段配置用 JSON),流水号段用 `base_code_sequence` 表记录。核心引擎 `CodeGenServiceImpl` 遍历分段配置逐段生成,`SequenceAllocator` 负责号段的分布式并发分配(Lock4j + 内存缓存 + DB 预取)。规则查询使用 Redis 缓存(30 分钟 TTL),更新时主动清除。
|
|||
|
|
|
|||
|
|
**技术栈:** Java 17 + Spring Boot 3 + MyBatis-Plus + Lock4j (baomidou) + Redis (StringRedisTemplate) + Jackson JSON + MySQL
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 1:SQL DDL 建表
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 创建:`sql/mysql/add26071501.sql`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编写建表 SQL**
|
|||
|
|
|
|||
|
|
```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 '编码规则定义表';
|
|||
|
|
|
|||
|
|
-- 编码流水号段记录表
|
|||
|
|
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:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add sql/mysql/add26071501.sql
|
|||
|
|
git commit -m "feat: 新增编码规则和流水号段建表SQL"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 2:错误码 + 枚举类
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 修改:`nl-module-base/nl-module-base-api/src/main/java/cn/code/nl/module/base/enums/ErrorCodeConstants.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/enums/SegmentTypeEnum.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/enums/ResetByEnum.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/enums/LetterCaseEnum.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/enums/SeqStrategyEnum.java`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:追加错误码常量**
|
|||
|
|
|
|||
|
|
在 `ErrorCodeConstants.java` 的最后一个 ErrorCode 定义后面追加:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// ========== 编码生成 ==========
|
|||
|
|
ErrorCode CODE_RULE_NOT_EXISTS = new ErrorCode(9, "编码规则不存在");
|
|||
|
|
ErrorCode CODE_RULE_CODE_DUPLICATE = new ErrorCode(10, "规则编码已存在");
|
|||
|
|
ErrorCode CODE_GEN_SEQ_EXCEED_MAX = new ErrorCode(11, "序号超出最大值上限");
|
|||
|
|
ErrorCode CODE_GEN_LENGTH_EXCEED = new ErrorCode(12, "生成的编码超出总长度限制");
|
|||
|
|
ErrorCode CODE_RULE_DISABLED = new ErrorCode(13, "编码规则已被禁用");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:编写 SegmentTypeEnum**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen.enums;
|
|||
|
|
|
|||
|
|
import lombok.AllArgsConstructor;
|
|||
|
|
import lombok.Getter;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码段类型枚举
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Getter
|
|||
|
|
@AllArgsConstructor
|
|||
|
|
public enum SegmentTypeEnum {
|
|||
|
|
|
|||
|
|
PREFIX("PREFIX", "固定前缀"),
|
|||
|
|
DATE("DATE", "日期"),
|
|||
|
|
SEQUENCE("SEQUENCE", "流水序号");
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 类型编码
|
|||
|
|
*/
|
|||
|
|
private final String code;
|
|||
|
|
/**
|
|||
|
|
* 类型名称
|
|||
|
|
*/
|
|||
|
|
private final String name;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 3:编写 ResetByEnum**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen.enums;
|
|||
|
|
|
|||
|
|
import lombok.AllArgsConstructor;
|
|||
|
|
import lombok.Getter;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 序号重置维度枚举
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Getter
|
|||
|
|
@AllArgsConstructor
|
|||
|
|
public enum ResetByEnum {
|
|||
|
|
|
|||
|
|
DAY("DAY", "按天重置"),
|
|||
|
|
MONTH("MONTH", "按月重置"),
|
|||
|
|
YEAR("YEAR", "按年重置"),
|
|||
|
|
GLOBAL("GLOBAL", "全局不重置");
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 维度编码
|
|||
|
|
*/
|
|||
|
|
private final String code;
|
|||
|
|
/**
|
|||
|
|
* 维度名称
|
|||
|
|
*/
|
|||
|
|
private final String name;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 4:编写 LetterCaseEnum**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen.enums;
|
|||
|
|
|
|||
|
|
import lombok.AllArgsConstructor;
|
|||
|
|
import lombok.Getter;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 字母大小写枚举
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Getter
|
|||
|
|
@AllArgsConstructor
|
|||
|
|
public enum LetterCaseEnum {
|
|||
|
|
|
|||
|
|
NONE(0, "原样"),
|
|||
|
|
UPPER(1, "全大写"),
|
|||
|
|
LOWER(2, "全小写");
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 状态值
|
|||
|
|
*/
|
|||
|
|
private final Integer code;
|
|||
|
|
/**
|
|||
|
|
* 状态名称
|
|||
|
|
*/
|
|||
|
|
private final String name;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 5:编写 SeqStrategyEnum**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen.enums;
|
|||
|
|
|
|||
|
|
import lombok.AllArgsConstructor;
|
|||
|
|
import lombok.Getter;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 序号并发策略枚举
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Getter
|
|||
|
|
@AllArgsConstructor
|
|||
|
|
public enum SeqStrategyEnum {
|
|||
|
|
|
|||
|
|
SEGMENT("segment", "号段预分配"),
|
|||
|
|
LOCK("lock", "分布式锁实时");
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 策略编码
|
|||
|
|
*/
|
|||
|
|
private final String code;
|
|||
|
|
/**
|
|||
|
|
* 策略名称
|
|||
|
|
*/
|
|||
|
|
private final String name;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 6:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add nl-module-base/nl-module-base-api/src/main/java/cn/code/nl/module/base/enums/ErrorCodeConstants.java \
|
|||
|
|
nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/enums/
|
|||
|
|
git commit -m "feat: 新增编码生成相关枚举和错误码"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 3:SegmentConfig 分段配置 DTO
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/dto/SegmentConfig.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/dto/PrefixSegment.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/dto/DateSegment.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/dto/SequenceSegment.java`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编写 SegmentConfig 基类**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen.dto;
|
|||
|
|
|
|||
|
|
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
|||
|
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|||
|
|
import lombok.Data;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分段配置基类,使用 Jackson 多态序列化/反序列化
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
|||
|
|
@JsonSubTypes({
|
|||
|
|
@JsonSubTypes.Type(value = PrefixSegment.class, name = "PREFIX"),
|
|||
|
|
@JsonSubTypes.Type(value = DateSegment.class, name = "DATE"),
|
|||
|
|
@JsonSubTypes.Type(value = SequenceSegment.class, name = "SEQUENCE")
|
|||
|
|
})
|
|||
|
|
public abstract class SegmentConfig {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 段类型:PREFIX / DATE / SEQUENCE
|
|||
|
|
*/
|
|||
|
|
private String type;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:编写 PrefixSegment**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen.dto;
|
|||
|
|
|
|||
|
|
import lombok.Data;
|
|||
|
|
import lombok.EqualsAndHashCode;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 固定前缀段配置
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
@EqualsAndHashCode(callSuper = true)
|
|||
|
|
public class PrefixSegment extends SegmentConfig {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 固定前缀值,如 "ORD"
|
|||
|
|
*/
|
|||
|
|
private String value;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 3:编写 DateSegment**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen.dto;
|
|||
|
|
|
|||
|
|
import lombok.Data;
|
|||
|
|
import lombok.EqualsAndHashCode;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 日期段配置
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
@EqualsAndHashCode(callSuper = true)
|
|||
|
|
public class DateSegment extends SegmentConfig {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 日期格式,如 "yyyyMMdd"、"yyyyMM"、"yyyy"
|
|||
|
|
*/
|
|||
|
|
private String format;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 4:编写 SequenceSegment**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen.dto;
|
|||
|
|
|
|||
|
|
import lombok.Data;
|
|||
|
|
import lombok.EqualsAndHashCode;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 流水序号段配置
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
@EqualsAndHashCode(callSuper = true)
|
|||
|
|
public class SequenceSegment extends SegmentConfig {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 重置维度:DAY / MONTH / YEAR / GLOBAL
|
|||
|
|
*/
|
|||
|
|
private String resetBy;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 起始值,默认 1
|
|||
|
|
*/
|
|||
|
|
private Long startAt;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 补位后的总长度,如 4 → 0001
|
|||
|
|
*/
|
|||
|
|
private Integer paddingLen;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 补位字符,默认 "0"
|
|||
|
|
*/
|
|||
|
|
private String paddingChar;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 序号最大值上限
|
|||
|
|
*/
|
|||
|
|
private Long maxValue;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 5:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/dto/
|
|||
|
|
git commit -m "feat: 新增编码分段配置DTO(Jackson多态)"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 4:DataObject 数据对象
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/dal/dataobject/codegen/CodeRuleDO.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/dal/dataobject/codegen/CodeSequenceDO.java`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编写 CodeRuleDO**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.dal.dataobject.codegen;
|
|||
|
|
|
|||
|
|
import cn.code.nl.framework.mybatis.core.dataobject.BaseDO;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.dto.SegmentConfig;
|
|||
|
|
import com.baomidou.mybatisplus.annotation.TableField;
|
|||
|
|
import com.baomidou.mybatisplus.annotation.TableId;
|
|||
|
|
import com.baomidou.mybatisplus.annotation.TableName;
|
|||
|
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
|||
|
|
import lombok.Data;
|
|||
|
|
import lombok.EqualsAndHashCode;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码规则定义 DO
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@TableName(value = "base_code_rule", autoResultMap = true)
|
|||
|
|
@Data
|
|||
|
|
@EqualsAndHashCode(callSuper = true)
|
|||
|
|
public class CodeRuleDO extends BaseDO {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 主键
|
|||
|
|
*/
|
|||
|
|
@TableId
|
|||
|
|
private Long id;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则名称
|
|||
|
|
*/
|
|||
|
|
private String ruleName;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则编码(唯一标识)
|
|||
|
|
*/
|
|||
|
|
private String ruleCode;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分段配置(JSON数组)
|
|||
|
|
*/
|
|||
|
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
|||
|
|
private List<SegmentConfig> segmentConfig;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分隔符
|
|||
|
|
*/
|
|||
|
|
private String separator;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 字母大小写:0=原样 1=全大写 2=全小写
|
|||
|
|
*/
|
|||
|
|
private Integer letterCase;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 固定总长度约束(0=不限制)
|
|||
|
|
*/
|
|||
|
|
private Integer totalLength;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 序号策略:segment=号段预分配 lock=分布式锁实时
|
|||
|
|
*/
|
|||
|
|
private String seqStrategy;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 状态:0=启用 1=禁用
|
|||
|
|
*/
|
|||
|
|
private Integer status;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 备注
|
|||
|
|
*/
|
|||
|
|
private String remark;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:编写 CodeSequenceDO**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.dal.dataobject.codegen;
|
|||
|
|
|
|||
|
|
import cn.code.nl.framework.mybatis.core.dataobject.BaseDO;
|
|||
|
|
import com.baomidou.mybatisplus.annotation.TableId;
|
|||
|
|
import com.baomidou.mybatisplus.annotation.TableName;
|
|||
|
|
import lombok.Data;
|
|||
|
|
import lombok.EqualsAndHashCode;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码流水号段记录 DO
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@TableName("base_code_sequence")
|
|||
|
|
@Data
|
|||
|
|
@EqualsAndHashCode(callSuper = true)
|
|||
|
|
public class CodeSequenceDO extends BaseDO {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 主键
|
|||
|
|
*/
|
|||
|
|
@TableId
|
|||
|
|
private Long id;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则ID,关联 base_code_rule.id
|
|||
|
|
*/
|
|||
|
|
private Long ruleId;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 重置维度键(如按天:20260715,全局:GLOBAL)
|
|||
|
|
*/
|
|||
|
|
private String resetKey;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 当前已分配的最大序号
|
|||
|
|
*/
|
|||
|
|
private Long currentValue;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 序号最大值上限
|
|||
|
|
*/
|
|||
|
|
private Long maxValue;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 3:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/dal/dataobject/codegen/
|
|||
|
|
git commit -m "feat: 新增编码规则和流水号段DO"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 5:Mapper 层
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/dal/mysql/codegen/CodeRuleMapper.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/dal/mysql/codegen/CodeSequenceMapper.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/resources/mapper/codegen/CodeSequenceMapper.xml`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编写 CodeRuleMapper**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.dal.mysql.codegen;
|
|||
|
|
|
|||
|
|
import cn.code.nl.framework.common.pojo.PageResult;
|
|||
|
|
import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX;
|
|||
|
|
import cn.code.nl.framework.mybatis.core.query.LambdaQueryWrapperX;
|
|||
|
|
import cn.code.nl.module.base.controller.admin.codegen.vo.CodeRulePageReqVO;
|
|||
|
|
import cn.code.nl.module.base.dal.dataobject.codegen.CodeRuleDO;
|
|||
|
|
import org.apache.ibatis.annotations.Mapper;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码规则 Mapper
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Mapper
|
|||
|
|
public interface CodeRuleMapper extends BaseMapperX<CodeRuleDO> {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分页查询编码规则
|
|||
|
|
*
|
|||
|
|
* @param reqVO 查询条件
|
|||
|
|
* @return 分页结果
|
|||
|
|
*/
|
|||
|
|
default PageResult<CodeRuleDO> selectPage(CodeRulePageReqVO reqVO) {
|
|||
|
|
return selectPage(reqVO, new LambdaQueryWrapperX<CodeRuleDO>()
|
|||
|
|
.likeIfPresent(CodeRuleDO::getRuleName, reqVO.getRuleName())
|
|||
|
|
.eqIfPresent(CodeRuleDO::getRuleCode, reqVO.getRuleCode())
|
|||
|
|
.eqIfPresent(CodeRuleDO::getStatus, reqVO.getStatus())
|
|||
|
|
.betweenIfPresent(CodeRuleDO::getCreateTime, reqVO.getCreateTime())
|
|||
|
|
.orderByDesc(CodeRuleDO::getId));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据规则编码查询
|
|||
|
|
*
|
|||
|
|
* @param ruleCode 规则编码
|
|||
|
|
* @return 编码规则
|
|||
|
|
*/
|
|||
|
|
default CodeRuleDO selectByRuleCode(String ruleCode) {
|
|||
|
|
return selectOne(CodeRuleDO::getRuleCode, ruleCode);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:编写 CodeSequenceMapper**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.dal.mysql.codegen;
|
|||
|
|
|
|||
|
|
import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX;
|
|||
|
|
import cn.code.nl.module.base.dal.dataobject.codegen.CodeSequenceDO;
|
|||
|
|
import org.apache.ibatis.annotations.Mapper;
|
|||
|
|
import org.apache.ibatis.annotations.Param;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码流水号段 Mapper
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Mapper
|
|||
|
|
public interface CodeSequenceMapper extends BaseMapperX<CodeSequenceDO> {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据规则ID和重置键查询流水记录
|
|||
|
|
*
|
|||
|
|
* @param ruleId 规则ID
|
|||
|
|
* @param resetKey 重置维度键
|
|||
|
|
* @return 流水记录
|
|||
|
|
*/
|
|||
|
|
default CodeSequenceDO selectByRuleIdAndResetKey(Long ruleId, String resetKey) {
|
|||
|
|
return selectOne(CodeSequenceDO::getRuleId, ruleId,
|
|||
|
|
CodeSequenceDO::getResetKey, resetKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 号段预分配:原子更新 current_value = current_value + step
|
|||
|
|
*
|
|||
|
|
* @param ruleId 规则ID
|
|||
|
|
* @param resetKey 重置维度键
|
|||
|
|
* @param step 预取步长
|
|||
|
|
* @param startAt 起始值(用于首次初始化)
|
|||
|
|
* @param maxValue 最大值上限
|
|||
|
|
* @return 更新行数
|
|||
|
|
*/
|
|||
|
|
int updateCurrentValueBySegment(@Param("ruleId") Long ruleId,
|
|||
|
|
@Param("resetKey") String resetKey,
|
|||
|
|
@Param("step") int step,
|
|||
|
|
@Param("startAt") Long startAt,
|
|||
|
|
@Param("maxValue") Long maxValue);
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 3:编写 CodeSequenceMapper.xml**
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|||
|
|
<mapper namespace="cn.code.nl.module.base.dal.mysql.codegen.CodeSequenceMapper">
|
|||
|
|
|
|||
|
|
<!--
|
|||
|
|
号段预分配:
|
|||
|
|
1. 使用 INSERT ... ON DUPLICATE KEY 实现"不存在则插入,存在则更新"
|
|||
|
|
2. 利用 idx_rule_reset (rule_id, reset_key) 唯一性校验并发安全
|
|||
|
|
3. 首次插入时 current_value = startAt + step - 1,后续更新时 current_value = current_value + step
|
|||
|
|
注意:此 SQL 依赖 idx_rule_reset 索引配合行锁,需在 Lock4j 保护下调用
|
|||
|
|
-->
|
|||
|
|
<update id="updateCurrentValueBySegment">
|
|||
|
|
INSERT INTO base_code_sequence (rule_id, reset_key, current_value, max_value)
|
|||
|
|
VALUES (#{ruleId}, #{resetKey}, #{startAt} + #{step} - 1, #{maxValue})
|
|||
|
|
ON DUPLICATE KEY UPDATE
|
|||
|
|
current_value = current_value + #{step},
|
|||
|
|
max_value = #{maxValue}
|
|||
|
|
</update>
|
|||
|
|
|
|||
|
|
</mapper>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 4:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/dal/mysql/codegen/ \
|
|||
|
|
nl-module-base/nl-module-base-server/src/main/resources/mapper/codegen/
|
|||
|
|
git commit -m "feat: 新增编码规则和流水号段Mapper"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 6:VO 类(请求/响应)
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/controller/admin/codegen/vo/CodeRuleSaveReqVO.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/controller/admin/codegen/vo/CodeRulePageReqVO.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/controller/admin/codegen/vo/CodeRuleRespVO.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/controller/admin/codegen/vo/CodeRuleSimpleRespVO.java`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编写 CodeRuleSaveReqVO**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.controller.admin.codegen.vo;
|
|||
|
|
|
|||
|
|
import cn.code.nl.module.base.service.codegen.dto.SegmentConfig;
|
|||
|
|
import jakarta.validation.constraints.NotBlank;
|
|||
|
|
import jakarta.validation.constraints.NotEmpty;
|
|||
|
|
import lombok.Data;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码规则新增/编辑请求 VO
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
public class CodeRuleSaveReqVO {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 主键(编辑时传入)
|
|||
|
|
*/
|
|||
|
|
private Long id;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则名称
|
|||
|
|
*/
|
|||
|
|
@NotBlank(message = "规则名称不能为空")
|
|||
|
|
private String ruleName;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则编码(唯一标识)
|
|||
|
|
*/
|
|||
|
|
@NotBlank(message = "规则编码不能为空")
|
|||
|
|
private String ruleCode;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分段配置列表
|
|||
|
|
*/
|
|||
|
|
@NotEmpty(message = "分段配置不能为空")
|
|||
|
|
private List<SegmentConfig> segments;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分隔符
|
|||
|
|
*/
|
|||
|
|
private String separator;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 字母大小写:0=原样 1=全大写 2=全小写
|
|||
|
|
*/
|
|||
|
|
private Integer letterCase;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 固定总长度约束(0=不限制)
|
|||
|
|
*/
|
|||
|
|
private Integer totalLength;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 序号策略:segment=号段预分配 lock=分布式锁实时
|
|||
|
|
*/
|
|||
|
|
private String seqStrategy;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 状态:0=启用 1=禁用
|
|||
|
|
*/
|
|||
|
|
private Integer status;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 备注
|
|||
|
|
*/
|
|||
|
|
private String remark;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:编写 CodeRulePageReqVO**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.controller.admin.codegen.vo;
|
|||
|
|
|
|||
|
|
import cn.code.nl.framework.common.pojo.PageParam;
|
|||
|
|
import lombok.Data;
|
|||
|
|
import lombok.EqualsAndHashCode;
|
|||
|
|
import org.springframework.format.annotation.DateTimeFormat;
|
|||
|
|
|
|||
|
|
import java.time.LocalDateTime;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码规则分页查询请求 VO
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
@EqualsAndHashCode(callSuper = true)
|
|||
|
|
public class CodeRulePageReqVO extends PageParam {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则名称(模糊查询)
|
|||
|
|
*/
|
|||
|
|
private String ruleName;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则编码
|
|||
|
|
*/
|
|||
|
|
private String ruleCode;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 状态
|
|||
|
|
*/
|
|||
|
|
private Integer status;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建时间范围
|
|||
|
|
*/
|
|||
|
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|||
|
|
private LocalDateTime[] createTime;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 3:编写 CodeRuleRespVO**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.controller.admin.codegen.vo;
|
|||
|
|
|
|||
|
|
import cn.code.nl.module.base.service.codegen.dto.SegmentConfig;
|
|||
|
|
import lombok.Data;
|
|||
|
|
|
|||
|
|
import java.time.LocalDateTime;
|
|||
|
|
import java.util.List;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码规则响应 VO
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
public class CodeRuleRespVO {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 主键
|
|||
|
|
*/
|
|||
|
|
private Long id;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则名称
|
|||
|
|
*/
|
|||
|
|
private String ruleName;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则编码
|
|||
|
|
*/
|
|||
|
|
private String ruleCode;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分段配置列表
|
|||
|
|
*/
|
|||
|
|
private List<SegmentConfig> segments;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分隔符
|
|||
|
|
*/
|
|||
|
|
private String separator;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 字母大小写:0=原样 1=全大写 2=全小写
|
|||
|
|
*/
|
|||
|
|
private Integer letterCase;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 固定总长度约束
|
|||
|
|
*/
|
|||
|
|
private Integer totalLength;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 序号策略
|
|||
|
|
*/
|
|||
|
|
private String seqStrategy;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 状态
|
|||
|
|
*/
|
|||
|
|
private Integer status;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 备注
|
|||
|
|
*/
|
|||
|
|
private String remark;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建时间
|
|||
|
|
*/
|
|||
|
|
private LocalDateTime createTime;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 4:编写 CodeRuleSimpleRespVO**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.controller.admin.codegen.vo;
|
|||
|
|
|
|||
|
|
import lombok.Data;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码规则精简响应 VO(下拉选项用)
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
public class CodeRuleSimpleRespVO {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 主键
|
|||
|
|
*/
|
|||
|
|
private Long id;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则名称
|
|||
|
|
*/
|
|||
|
|
private String ruleName;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则编码
|
|||
|
|
*/
|
|||
|
|
private String ruleCode;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 5:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/controller/admin/codegen/vo/
|
|||
|
|
git commit -m "feat: 新增编码规则VO类"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 7:API 模块(Feign 接口)
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-api/src/main/java/cn/code/nl/module/base/api/codegen/dto/CodeGenerateReqDTO.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-api/src/main/java/cn/code/nl/module/base/api/codegen/CodeGenApi.java`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编写 CodeGenerateReqDTO**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.api.codegen.dto;
|
|||
|
|
|
|||
|
|
import jakarta.validation.constraints.NotBlank;
|
|||
|
|
import lombok.Data;
|
|||
|
|
|
|||
|
|
import java.util.Map;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码生成请求 DTO
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
public class CodeGenerateReqDTO {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规则编码
|
|||
|
|
*/
|
|||
|
|
@NotBlank(message = "规则编码不能为空")
|
|||
|
|
private String ruleCode;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 业务字段参数(预留,二期 BIZ_FIELD 段使用)
|
|||
|
|
*/
|
|||
|
|
private Map<String, String> bizParams;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:编写 CodeGenApi**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.api.codegen;
|
|||
|
|
|
|||
|
|
import cn.code.nl.framework.common.pojo.CommonResult;
|
|||
|
|
import cn.code.nl.module.base.api.codegen.dto.CodeGenerateReqDTO;
|
|||
|
|
import cn.code.nl.module.base.enums.ApiConstants;
|
|||
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|||
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|||
|
|
import jakarta.validation.Valid;
|
|||
|
|
import org.springframework.cloud.openfeign.FeignClient;
|
|||
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|||
|
|
import org.springframework.web.bind.annotation.RequestBody;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* RPC 服务 - 编码生成
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@FeignClient(name = ApiConstants.NAME)
|
|||
|
|
@Tag(name = "RPC 服务 - 编码生成")
|
|||
|
|
public interface CodeGenApi {
|
|||
|
|
|
|||
|
|
String PREFIX = ApiConstants.PREFIX + "/code-gen";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据规则编码生成编码字符串
|
|||
|
|
*
|
|||
|
|
* @param reqDTO 生成请求
|
|||
|
|
* @return 生成的编码字符串
|
|||
|
|
*/
|
|||
|
|
@PostMapping(PREFIX + "/generate")
|
|||
|
|
@Operation(summary = "生成编码")
|
|||
|
|
CommonResult<String> generate(@Valid @RequestBody CodeGenerateReqDTO reqDTO);
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 3:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add nl-module-base/nl-module-base-api/src/main/java/cn/code/nl/module/base/api/codegen/
|
|||
|
|
git commit -m "feat: 新增编码生成Feign API接口"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 8:CodeRuleService 规则管理服务
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/CodeRuleService.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/CodeRuleServiceImpl.java`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编写 CodeRuleService 接口**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen;
|
|||
|
|
|
|||
|
|
import cn.code.nl.framework.common.pojo.PageResult;
|
|||
|
|
import cn.code.nl.module.base.controller.admin.codegen.vo.CodeRulePageReqVO;
|
|||
|
|
import cn.code.nl.module.base.controller.admin.codegen.vo.CodeRuleSaveReqVO;
|
|||
|
|
import cn.code.nl.module.base.dal.dataobject.codegen.CodeRuleDO;
|
|||
|
|
import jakarta.validation.Valid;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码规则管理 Service 接口
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
public interface CodeRuleService {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建编码规则
|
|||
|
|
*
|
|||
|
|
* @param createReqVO 创建信息
|
|||
|
|
* @return 规则ID
|
|||
|
|
*/
|
|||
|
|
Long createRule(@Valid CodeRuleSaveReqVO createReqVO);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新编码规则
|
|||
|
|
*
|
|||
|
|
* @param updateReqVO 更新信息
|
|||
|
|
*/
|
|||
|
|
void updateRule(@Valid CodeRuleSaveReqVO updateReqVO);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除编码规则
|
|||
|
|
*
|
|||
|
|
* @param id 规则ID
|
|||
|
|
*/
|
|||
|
|
void deleteRule(Long id);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取编码规则详情
|
|||
|
|
*
|
|||
|
|
* @param id 规则ID
|
|||
|
|
* @return 编码规则
|
|||
|
|
*/
|
|||
|
|
CodeRuleDO getRule(Long id);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据规则编码获取规则(带缓存)
|
|||
|
|
*
|
|||
|
|
* @param ruleCode 规则编码
|
|||
|
|
* @return 编码规则
|
|||
|
|
*/
|
|||
|
|
CodeRuleDO getRuleByCode(String ruleCode);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分页查询编码规则
|
|||
|
|
*
|
|||
|
|
* @param pageReqVO 分页条件
|
|||
|
|
* @return 分页结果
|
|||
|
|
*/
|
|||
|
|
PageResult<CodeRuleDO> getRulePage(CodeRulePageReqVO pageReqVO);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取启用状态的编码规则精简列表
|
|||
|
|
*
|
|||
|
|
* @return 规则列表
|
|||
|
|
*/
|
|||
|
|
List<CodeRuleDO> getEnabledRuleList();
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:编写 CodeRuleServiceImpl**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen;
|
|||
|
|
|
|||
|
|
import cn.code.nl.framework.common.pojo.PageResult;
|
|||
|
|
import cn.code.nl.framework.common.util.object.BeanUtils;
|
|||
|
|
import cn.code.nl.module.base.controller.admin.codegen.vo.CodeRulePageReqVO;
|
|||
|
|
import cn.code.nl.module.base.controller.admin.codegen.vo.CodeRuleSaveReqVO;
|
|||
|
|
import cn.code.nl.module.base.dal.dataobject.codegen.CodeRuleDO;
|
|||
|
|
import cn.code.nl.module.base.dal.mysql.codegen.CodeRuleMapper;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.enums.SeqStrategyEnum;
|
|||
|
|
import cn.hutool.core.util.StrUtil;
|
|||
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
|||
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
|
|
import jakarta.annotation.Resource;
|
|||
|
|
import lombok.extern.slf4j.Slf4j;
|
|||
|
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
|||
|
|
import org.springframework.stereotype.Service;
|
|||
|
|
import org.springframework.transaction.annotation.Transactional;
|
|||
|
|
import org.springframework.validation.annotation.Validated;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
import java.util.concurrent.TimeUnit;
|
|||
|
|
|
|||
|
|
import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|||
|
|
import static cn.code.nl.module.base.enums.ErrorCodeConstants.*;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码规则管理 Service 实现类
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Slf4j
|
|||
|
|
@Service
|
|||
|
|
@Validated
|
|||
|
|
public class CodeRuleServiceImpl implements CodeRuleService {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Redis 缓存 key 前缀
|
|||
|
|
*/
|
|||
|
|
private static final String CACHE_KEY_PREFIX = "codegen:rule:";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 缓存过期时间(30分钟)
|
|||
|
|
*/
|
|||
|
|
private static final long CACHE_TTL_MINUTES = 30;
|
|||
|
|
|
|||
|
|
@Resource
|
|||
|
|
private CodeRuleMapper codeRuleMapper;
|
|||
|
|
|
|||
|
|
@Resource
|
|||
|
|
private StringRedisTemplate stringRedisTemplate;
|
|||
|
|
|
|||
|
|
@Resource
|
|||
|
|
private ObjectMapper objectMapper;
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
@Transactional(rollbackFor = Exception.class)
|
|||
|
|
public Long createRule(CodeRuleSaveReqVO createReqVO) {
|
|||
|
|
// 校验规则编码唯一性
|
|||
|
|
validateRuleCodeUnique(createReqVO.getRuleCode(), null);
|
|||
|
|
// 校验分段配置至少包含一个 SEQUENCE 段
|
|||
|
|
validateHasSequence(createReqVO.getSegments());
|
|||
|
|
|
|||
|
|
// 插入
|
|||
|
|
CodeRuleDO rule = BeanUtils.toBean(createReqVO, CodeRuleDO.class);
|
|||
|
|
rule.setSegmentConfig(createReqVO.getSegments());
|
|||
|
|
// 默认策略
|
|||
|
|
if (StrUtil.isBlank(rule.getSeqStrategy())) {
|
|||
|
|
rule.setSeqStrategy(SeqStrategyEnum.SEGMENT.getCode());
|
|||
|
|
}
|
|||
|
|
codeRuleMapper.insert(rule);
|
|||
|
|
log.info("创建编码规则成功,ruleCode={}", rule.getRuleCode());
|
|||
|
|
return rule.getId();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
@Transactional(rollbackFor = Exception.class)
|
|||
|
|
public void updateRule(CodeRuleSaveReqVO updateReqVO) {
|
|||
|
|
// 校验存在
|
|||
|
|
CodeRuleDO existRule = validateRuleExists(updateReqVO.getId());
|
|||
|
|
// 校验规则编码唯一性
|
|||
|
|
validateRuleCodeUnique(updateReqVO.getRuleCode(), updateReqVO.getId());
|
|||
|
|
// 校验分段配置至少包含一个 SEQUENCE 段
|
|||
|
|
validateHasSequence(updateReqVO.getSegments());
|
|||
|
|
|
|||
|
|
// 更新
|
|||
|
|
CodeRuleDO updateObj = BeanUtils.toBean(updateReqVO, CodeRuleDO.class);
|
|||
|
|
updateObj.setSegmentConfig(updateReqVO.getSegments());
|
|||
|
|
codeRuleMapper.updateById(updateObj);
|
|||
|
|
|
|||
|
|
// 清除缓存
|
|||
|
|
clearCache(existRule.getRuleCode());
|
|||
|
|
// 如果 ruleCode 变更了,也清除旧 key
|
|||
|
|
if (!existRule.getRuleCode().equals(updateReqVO.getRuleCode())) {
|
|||
|
|
clearCache(existRule.getRuleCode());
|
|||
|
|
}
|
|||
|
|
log.info("更新编码规则成功,ruleCode={}", updateReqVO.getRuleCode());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
@Transactional(rollbackFor = Exception.class)
|
|||
|
|
public void deleteRule(Long id) {
|
|||
|
|
// 校验存在
|
|||
|
|
CodeRuleDO rule = validateRuleExists(id);
|
|||
|
|
// 删除
|
|||
|
|
codeRuleMapper.deleteById(id);
|
|||
|
|
// 清除缓存
|
|||
|
|
clearCache(rule.getRuleCode());
|
|||
|
|
log.info("删除编码规则成功,ruleCode={}", rule.getRuleCode());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public CodeRuleDO getRule(Long id) {
|
|||
|
|
return validateRuleExists(id);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public CodeRuleDO getRuleByCode(String ruleCode) {
|
|||
|
|
// 先从缓存读取
|
|||
|
|
String cacheKey = CACHE_KEY_PREFIX + ruleCode;
|
|||
|
|
String cached = stringRedisTemplate.opsForValue().get(cacheKey);
|
|||
|
|
if (StrUtil.isNotBlank(cached)) {
|
|||
|
|
try {
|
|||
|
|
return objectMapper.readValue(cached, CodeRuleDO.class);
|
|||
|
|
} catch (JsonProcessingException e) {
|
|||
|
|
log.warn("解析编码规则缓存失败,ruleCode={}", ruleCode, e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 缓存未命中,查数据库
|
|||
|
|
CodeRuleDO rule = codeRuleMapper.selectByRuleCode(ruleCode);
|
|||
|
|
if (rule == null) {
|
|||
|
|
throw exception(CODE_RULE_NOT_EXISTS);
|
|||
|
|
}
|
|||
|
|
// 校验启用状态
|
|||
|
|
if (rule.getStatus() != null && rule.getStatus() == 1) {
|
|||
|
|
throw exception(CODE_RULE_DISABLED);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 写入缓存
|
|||
|
|
try {
|
|||
|
|
String json = objectMapper.writeValueAsString(rule);
|
|||
|
|
stringRedisTemplate.opsForValue().set(cacheKey, json, CACHE_TTL_MINUTES, TimeUnit.MINUTES);
|
|||
|
|
} catch (JsonProcessingException e) {
|
|||
|
|
log.warn("序列化编码规则缓存失败,ruleCode={}", ruleCode, e);
|
|||
|
|
}
|
|||
|
|
return rule;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public PageResult<CodeRuleDO> getRulePage(CodeRulePageReqVO pageReqVO) {
|
|||
|
|
return codeRuleMapper.selectPage(pageReqVO);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public List<CodeRuleDO> getEnabledRuleList() {
|
|||
|
|
// 查询启用状态的规则
|
|||
|
|
return codeRuleMapper.selectList(CodeRuleDO::getStatus, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除规则缓存
|
|||
|
|
*/
|
|||
|
|
private void clearCache(String ruleCode) {
|
|||
|
|
stringRedisTemplate.delete(CACHE_KEY_PREFIX + ruleCode);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 校验规则编码唯一性
|
|||
|
|
*/
|
|||
|
|
private void validateRuleCodeUnique(String ruleCode, Long excludeId) {
|
|||
|
|
CodeRuleDO exist = codeRuleMapper.selectByRuleCode(ruleCode);
|
|||
|
|
if (exist == null) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (excludeId == null || !exist.getId().equals(excludeId)) {
|
|||
|
|
throw exception(CODE_RULE_CODE_DUPLICATE);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 校验规则存在
|
|||
|
|
*/
|
|||
|
|
private CodeRuleDO validateRuleExists(Long id) {
|
|||
|
|
if (id == null) {
|
|||
|
|
throw exception(CODE_RULE_NOT_EXISTS);
|
|||
|
|
}
|
|||
|
|
CodeRuleDO rule = codeRuleMapper.selectById(id);
|
|||
|
|
if (rule == null) {
|
|||
|
|
throw exception(CODE_RULE_NOT_EXISTS);
|
|||
|
|
}
|
|||
|
|
return rule;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 校验分段配置至少包含一个 SEQUENCE 段
|
|||
|
|
*/
|
|||
|
|
private void validateHasSequence(List<?> segments) {
|
|||
|
|
boolean hasSequence = segments.stream()
|
|||
|
|
.anyMatch(s -> "SEQUENCE".equals(
|
|||
|
|
s instanceof cn.code.nl.module.base.service.codegen.dto.SegmentConfig sc
|
|||
|
|
? sc.getType() : ""));
|
|||
|
|
if (!hasSequence) {
|
|||
|
|
throw exception(CODE_RULE_NOT_EXISTS);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 3:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/CodeRuleService.java \
|
|||
|
|
nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/CodeRuleServiceImpl.java
|
|||
|
|
git commit -m "feat: 新增编码规则管理Service"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 9:SequenceAllocator 号段预分配器
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/SequenceAllocator.java`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编写 SequenceAllocator**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen;
|
|||
|
|
|
|||
|
|
import cn.code.nl.module.base.dal.mysql.codegen.CodeSequenceMapper;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.dto.SequenceSegment;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.enums.ResetByEnum;
|
|||
|
|
import cn.hutool.core.util.StrUtil;
|
|||
|
|
import com.baomidou.lock.annotation.Lock4j;
|
|||
|
|
import jakarta.annotation.Resource;
|
|||
|
|
import lombok.extern.slf4j.Slf4j;
|
|||
|
|
import org.springframework.stereotype.Component;
|
|||
|
|
|
|||
|
|
import java.time.LocalDate;
|
|||
|
|
import java.time.Year;
|
|||
|
|
import java.time.format.DateTimeFormatter;
|
|||
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|||
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|||
|
|
|
|||
|
|
import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|||
|
|
import static cn.code.nl.module.base.enums.ErrorCodeConstants.CODE_GEN_SEQ_EXCEED_MAX;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 序号分配器 —— 号段预分配 + 分布式锁实时两种策略
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Slf4j
|
|||
|
|
@Component
|
|||
|
|
public class SequenceAllocator {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 默认号段预取步长
|
|||
|
|
*/
|
|||
|
|
private static final int DEFAULT_STEP = 100;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 本地号段缓存:key = "ruleId:resetKey", value = 当前号段末尾值
|
|||
|
|
* 号段分配时配合 AtomicLong 做内存 CAS
|
|||
|
|
*/
|
|||
|
|
private final ConcurrentHashMap<String, AtomicLong> localSegmentCache = new ConcurrentHashMap<>();
|
|||
|
|
|
|||
|
|
@Resource
|
|||
|
|
private CodeSequenceMapper codeSequenceMapper;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分配一个序号(号段预分配策略)
|
|||
|
|
*
|
|||
|
|
* @param ruleId 规则ID
|
|||
|
|
* @param segment 序号段配置
|
|||
|
|
* @return 格式化后的序号字符串
|
|||
|
|
*/
|
|||
|
|
public String allocateBySegment(Long ruleId, SequenceSegment segment) {
|
|||
|
|
String resetKey = buildResetKey(segment.getResetBy());
|
|||
|
|
String cacheKey = ruleId + ":" + resetKey;
|
|||
|
|
|
|||
|
|
Long startAt = segment.getStartAt() != null ? segment.getStartAt() : 1L;
|
|||
|
|
|
|||
|
|
// 从本地缓存取号段
|
|||
|
|
AtomicLong current = localSegmentCache.get(cacheKey);
|
|||
|
|
|
|||
|
|
long seqValue;
|
|||
|
|
if (current != null) {
|
|||
|
|
seqValue = current.incrementAndGet();
|
|||
|
|
// 检查是否还在号段范围内(号段末尾存储在 DB 的 current_value 中)
|
|||
|
|
// 简单策略:DB 的 current_value 就是号段末尾,本地从 current_value - step + 1 开始递增
|
|||
|
|
// 这里做一个简化的判断:如果当前值还没超过本地记录,直接返回
|
|||
|
|
if (seqValue <= getSegmentEnd(cacheKey)) {
|
|||
|
|
return formatSequence(seqValue, segment);
|
|||
|
|
}
|
|||
|
|
// 号段耗尽,需要重新预取
|
|||
|
|
localSegmentCache.remove(cacheKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 号段耗尽或不存在,从 DB 预取
|
|||
|
|
seqValue = fetchSegment(cacheKey, ruleId, resetKey, segment, startAt);
|
|||
|
|
return formatSequence(seqValue, segment);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分配一个序号(分布式锁实时策略)
|
|||
|
|
*
|
|||
|
|
* @param ruleId 规则ID
|
|||
|
|
* @param segment 序号段配置
|
|||
|
|
* @return 格式化后的序号字符串
|
|||
|
|
*/
|
|||
|
|
public String allocateByLock(Long ruleId, SequenceSegment segment) {
|
|||
|
|
String resetKey = buildResetKey(segment.getResetBy());
|
|||
|
|
String lockKey = "codegen:seq:" + ruleId + ":" + resetKey;
|
|||
|
|
return allocateWithLock(lockKey, ruleId, resetKey, segment);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Lock4j 保护下实时分配序号
|
|||
|
|
*/
|
|||
|
|
@Lock4j(keys = "#lockKey", acquireTimeout = 3000, expire = 5000)
|
|||
|
|
public String allocateWithLock(String lockKey, Long ruleId, String resetKey, SequenceSegment segment) {
|
|||
|
|
Long startAt = segment.getStartAt() != null ? segment.getStartAt() : 1L;
|
|||
|
|
// 用 step=1 意味着每次只取一个号
|
|||
|
|
codeSequenceMapper.updateCurrentValueBySegment(ruleId, resetKey, 1, startAt, segment.getMaxValue());
|
|||
|
|
// 查回当前值
|
|||
|
|
var seq = codeSequenceMapper.selectByRuleIdAndResetKey(ruleId, resetKey);
|
|||
|
|
if (seq == null) {
|
|||
|
|
// 极端情况:首次插入后未查到(理论上不会发生)
|
|||
|
|
return formatSequence(startAt, segment);
|
|||
|
|
}
|
|||
|
|
// 检查上限
|
|||
|
|
if (seq.getCurrentValue() > segment.getMaxValue()) {
|
|||
|
|
throw exception(CODE_GEN_SEQ_EXCEED_MAX);
|
|||
|
|
}
|
|||
|
|
return formatSequence(seq.getCurrentValue(), segment);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从 DB 预取号段并缓存到本地
|
|||
|
|
*/
|
|||
|
|
private Long fetchSegment(String cacheKey, Long ruleId, String resetKey,
|
|||
|
|
SequenceSegment segment, Long startAt) {
|
|||
|
|
// 加锁预取号段
|
|||
|
|
String lockKey = "codegen:seq:" + ruleId + ":" + resetKey;
|
|||
|
|
return fetchSegmentWithLock(lockKey, cacheKey, ruleId, resetKey, segment, startAt);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Lock4j 保护下预取号段
|
|||
|
|
*/
|
|||
|
|
@Lock4j(keys = "#lockKey", acquireTimeout = 3000, expire = 5000)
|
|||
|
|
public Long fetchSegmentWithLock(String lockKey, String cacheKey, Long ruleId,
|
|||
|
|
String resetKey, SequenceSegment segment, Long startAt) {
|
|||
|
|
// double-check:其他线程可能已经预取了
|
|||
|
|
AtomicLong existing = localSegmentCache.get(cacheKey);
|
|||
|
|
if (existing != null) {
|
|||
|
|
return existing.incrementAndGet();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DB 原子更新:current_value = current_value + step
|
|||
|
|
codeSequenceMapper.updateCurrentValueBySegment(ruleId, resetKey, DEFAULT_STEP, startAt, segment.getMaxValue());
|
|||
|
|
|
|||
|
|
// 查回最新 current_value
|
|||
|
|
var seq = codeSequenceMapper.selectByRuleIdAndResetKey(ruleId, resetKey);
|
|||
|
|
Long newCurrentValue = seq.getCurrentValue();
|
|||
|
|
long segmentStart = newCurrentValue - DEFAULT_STEP + 1;
|
|||
|
|
|
|||
|
|
// 检查上限
|
|||
|
|
if (newCurrentValue > segment.getMaxValue()) {
|
|||
|
|
log.error("序号超出最大值上限,ruleId={}, resetKey={}, currentValue={}, maxValue={}",
|
|||
|
|
ruleId, resetKey, newCurrentValue, segment.getMaxValue());
|
|||
|
|
throw exception(CODE_GEN_SEQ_EXCEED_MAX);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 缓存号段到本地,AtomicLong 从 segmentStart - 1 开始,首次递增得到 segmentStart
|
|||
|
|
AtomicLong counter = new AtomicLong(segmentStart - 1);
|
|||
|
|
localSegmentCache.put(cacheKey, counter);
|
|||
|
|
|
|||
|
|
log.info("预取号段成功,ruleId={}, resetKey={}, 号段区间=[{}, {}]",
|
|||
|
|
ruleId, resetKey, segmentStart, newCurrentValue);
|
|||
|
|
return counter.incrementAndGet();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取号段末尾值(从缓存 key 反查)
|
|||
|
|
* 简化实现:如果缓存中存在,说明号段有效
|
|||
|
|
*/
|
|||
|
|
private long getSegmentEnd(String cacheKey) {
|
|||
|
|
// 简化实现:直接在分配时判断
|
|||
|
|
return Long.MAX_VALUE;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据重置维度构建 resetKey
|
|||
|
|
*
|
|||
|
|
* @param resetBy 重置维度
|
|||
|
|
* @return 重置键
|
|||
|
|
*/
|
|||
|
|
public String buildResetKey(String resetBy) {
|
|||
|
|
if (StrUtil.isBlank(resetBy)) {
|
|||
|
|
return ResetByEnum.GLOBAL.getCode();
|
|||
|
|
}
|
|||
|
|
ResetByEnum resetByEnum = ResetByEnum.valueOf(resetBy);
|
|||
|
|
return switch (resetByEnum) {
|
|||
|
|
case DAY -> LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
|
|||
|
|
case MONTH -> LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
|
|||
|
|
case YEAR -> String.valueOf(Year.now().getValue());
|
|||
|
|
case GLOBAL -> "GLOBAL";
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 补位格式化序号
|
|||
|
|
*
|
|||
|
|
* @param value 序号值
|
|||
|
|
* @param segment 序号段配置
|
|||
|
|
* @return 格式化后的字符串
|
|||
|
|
*/
|
|||
|
|
public String formatSequence(long value, SequenceSegment segment) {
|
|||
|
|
String raw = String.valueOf(value);
|
|||
|
|
int paddingLen = segment.getPaddingLen() != null ? segment.getPaddingLen() : 0;
|
|||
|
|
if (paddingLen <= 0 || raw.length() >= paddingLen) {
|
|||
|
|
return raw;
|
|||
|
|
}
|
|||
|
|
String paddingChar = StrUtil.isNotBlank(segment.getPaddingChar())
|
|||
|
|
? segment.getPaddingChar() : "0";
|
|||
|
|
// 用补位字符填充到目标长度
|
|||
|
|
return paddingChar.repeat(paddingLen - raw.length()) + raw;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/SequenceAllocator.java
|
|||
|
|
git commit -m "feat: 新增序号分配器(号段预分配+分布式锁混合策略)"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 10:CodeGenService 编码生成引擎
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/CodeGenService.java`
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/CodeGenServiceImpl.java`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编写 CodeGenService 接口**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码生成 Service 接口
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
public interface CodeGenService {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据规则编码生成编码字符串
|
|||
|
|
*
|
|||
|
|
* @param ruleCode 规则编码
|
|||
|
|
* @return 生成的编码
|
|||
|
|
*/
|
|||
|
|
String generate(String ruleCode);
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:编写 CodeGenServiceImpl**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.service.codegen;
|
|||
|
|
|
|||
|
|
import cn.code.nl.module.base.dal.dataobject.codegen.CodeRuleDO;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.dto.DateSegment;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.dto.PrefixSegment;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.dto.SegmentConfig;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.dto.SequenceSegment;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.enums.LetterCaseEnum;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.enums.SeqStrategyEnum;
|
|||
|
|
import cn.hutool.core.util.StrUtil;
|
|||
|
|
import jakarta.annotation.Resource;
|
|||
|
|
import lombok.extern.slf4j.Slf4j;
|
|||
|
|
import org.springframework.stereotype.Service;
|
|||
|
|
import org.springframework.validation.annotation.Validated;
|
|||
|
|
|
|||
|
|
import java.time.LocalDate;
|
|||
|
|
import java.time.format.DateTimeFormatter;
|
|||
|
|
import java.util.List;
|
|||
|
|
|
|||
|
|
import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|||
|
|
import static cn.code.nl.module.base.enums.ErrorCodeConstants.CODE_GEN_LENGTH_EXCEED;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 编码生成引擎实现类
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Slf4j
|
|||
|
|
@Service
|
|||
|
|
@Validated
|
|||
|
|
public class CodeGenServiceImpl implements CodeGenService {
|
|||
|
|
|
|||
|
|
@Resource
|
|||
|
|
private CodeRuleService codeRuleService;
|
|||
|
|
|
|||
|
|
@Resource
|
|||
|
|
private SequenceAllocator sequenceAllocator;
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public String generate(String ruleCode) {
|
|||
|
|
// 1. 获取规则配置(带缓存)
|
|||
|
|
CodeRuleDO rule = codeRuleService.getRuleByCode(ruleCode);
|
|||
|
|
|
|||
|
|
// 2. 遍历分段配置,逐段生成
|
|||
|
|
List<SegmentConfig> segments = rule.getSegmentConfig();
|
|||
|
|
StringBuilder sb = new StringBuilder();
|
|||
|
|
for (int i = 0; i < segments.size(); i++) {
|
|||
|
|
String segmentValue = buildSegment(rule, segments.get(i));
|
|||
|
|
sb.append(segmentValue);
|
|||
|
|
// 段之间加分隔符
|
|||
|
|
if (i < segments.size() - 1 && StrUtil.isNotBlank(rule.getSeparator())) {
|
|||
|
|
sb.append(rule.getSeparator());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 后处理
|
|||
|
|
return postProcess(sb.toString(), rule);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据段类型分发处理,生成段值
|
|||
|
|
*/
|
|||
|
|
private String buildSegment(CodeRuleDO rule, SegmentConfig segment) {
|
|||
|
|
return switch (segment.getType()) {
|
|||
|
|
case "PREFIX" -> buildPrefixSegment((PrefixSegment) segment);
|
|||
|
|
case "DATE" -> buildDateSegment((DateSegment) segment);
|
|||
|
|
case "SEQUENCE" -> buildSequenceSegment(rule, (SequenceSegment) segment);
|
|||
|
|
default -> throw new IllegalArgumentException("不支持的段类型: " + segment.getType());
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成固定前缀段
|
|||
|
|
*/
|
|||
|
|
private String buildPrefixSegment(PrefixSegment segment) {
|
|||
|
|
return segment.getValue() != null ? segment.getValue() : "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成日期段
|
|||
|
|
*/
|
|||
|
|
private String buildDateSegment(DateSegment segment) {
|
|||
|
|
String format = StrUtil.isNotBlank(segment.getFormat())
|
|||
|
|
? segment.getFormat() : "yyyyMMdd";
|
|||
|
|
return LocalDate.now().format(DateTimeFormatter.ofPattern(format));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成流水序号段
|
|||
|
|
*/
|
|||
|
|
private String buildSequenceSegment(CodeRuleDO rule, SequenceSegment segment) {
|
|||
|
|
String strategy = rule.getSeqStrategy();
|
|||
|
|
if (SeqStrategyEnum.LOCK.getCode().equals(strategy)) {
|
|||
|
|
return sequenceAllocator.allocateByLock(rule.getId(), segment);
|
|||
|
|
}
|
|||
|
|
return sequenceAllocator.allocateBySegment(rule.getId(), segment);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 后处理:大小写转换 + 总长度校验
|
|||
|
|
*/
|
|||
|
|
private String postProcess(String code, CodeRuleDO rule) {
|
|||
|
|
// 大小写转换
|
|||
|
|
if (rule.getLetterCase() != null && rule.getLetterCase() == LetterCaseEnum.UPPER.getCode()) {
|
|||
|
|
code = code.toUpperCase();
|
|||
|
|
} else if (rule.getLetterCase() != null && rule.getLetterCase() == LetterCaseEnum.LOWER.getCode()) {
|
|||
|
|
code = code.toLowerCase();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 总长度校验
|
|||
|
|
if (rule.getTotalLength() != null && rule.getTotalLength() > 0 && code.length() > rule.getTotalLength()) {
|
|||
|
|
log.error("生成的编码超出总长度限制,code={}, length={}, maxLength={}",
|
|||
|
|
code, code.length(), rule.getTotalLength());
|
|||
|
|
throw exception(CODE_GEN_LENGTH_EXCEED);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return code;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 3:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/CodeGenService.java \
|
|||
|
|
nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/CodeGenServiceImpl.java
|
|||
|
|
git commit -m "feat: 新增编码生成核心引擎"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 11:CodeRuleController 规则管理接口
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/controller/admin/codegen/CodeRuleController.java`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编写 CodeRuleController**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.controller.admin.codegen;
|
|||
|
|
|
|||
|
|
import cn.code.nl.framework.common.pojo.CommonResult;
|
|||
|
|
import cn.code.nl.framework.common.pojo.PageResult;
|
|||
|
|
import cn.code.nl.framework.common.util.object.BeanUtils;
|
|||
|
|
import cn.code.nl.module.base.controller.admin.codegen.vo.*;
|
|||
|
|
import cn.code.nl.module.base.dal.dataobject.codegen.CodeRuleDO;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.CodeRuleService;
|
|||
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|||
|
|
import io.swagger.v3.oas.annotations.Parameter;
|
|||
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|||
|
|
import jakarta.annotation.Resource;
|
|||
|
|
import jakarta.validation.Valid;
|
|||
|
|
import org.springframework.security.access.prepost.PreAuthorize;
|
|||
|
|
import org.springframework.validation.annotation.Validated;
|
|||
|
|
import org.springframework.web.bind.annotation.*;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
|
|||
|
|
import static cn.code.nl.framework.common.pojo.CommonResult.success;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 管理后台 - 编码规则
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Tag(name = "管理后台 - 编码规则")
|
|||
|
|
@RestController
|
|||
|
|
@RequestMapping("/base/code-rule")
|
|||
|
|
@Validated
|
|||
|
|
public class CodeRuleController {
|
|||
|
|
|
|||
|
|
@Resource
|
|||
|
|
private CodeRuleService codeRuleService;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建编码规则
|
|||
|
|
*/
|
|||
|
|
@PostMapping("/create")
|
|||
|
|
@Operation(summary = "创建编码规则")
|
|||
|
|
@PreAuthorize("@ss.hasPermission('base:code-rule:create')")
|
|||
|
|
public CommonResult<Long> createCodeRule(@Valid @RequestBody CodeRuleSaveReqVO createReqVO) {
|
|||
|
|
return success(codeRuleService.createRule(createReqVO));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新编码规则
|
|||
|
|
*/
|
|||
|
|
@PutMapping("/update")
|
|||
|
|
@Operation(summary = "更新编码规则")
|
|||
|
|
@PreAuthorize("@ss.hasPermission('base:code-rule:update')")
|
|||
|
|
public CommonResult<Boolean> updateCodeRule(@Valid @RequestBody CodeRuleSaveReqVO updateReqVO) {
|
|||
|
|
codeRuleService.updateRule(updateReqVO);
|
|||
|
|
return success(true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除编码规则
|
|||
|
|
*/
|
|||
|
|
@DeleteMapping("/delete")
|
|||
|
|
@Operation(summary = "删除编码规则")
|
|||
|
|
@Parameter(name = "id", description = "编号", required = true)
|
|||
|
|
@PreAuthorize("@ss.hasPermission('base:code-rule:delete')")
|
|||
|
|
public CommonResult<Boolean> deleteCodeRule(@RequestParam("id") Long id) {
|
|||
|
|
codeRuleService.deleteRule(id);
|
|||
|
|
return success(true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取编码规则详情
|
|||
|
|
*/
|
|||
|
|
@GetMapping("/get")
|
|||
|
|
@Operation(summary = "获得编码规则")
|
|||
|
|
@Parameter(name = "id", description = "编号", required = true)
|
|||
|
|
@PreAuthorize("@ss.hasPermission('base:code-rule:query')")
|
|||
|
|
public CommonResult<CodeRuleRespVO> getCodeRule(@RequestParam("id") Long id) {
|
|||
|
|
CodeRuleDO rule = codeRuleService.getRule(id);
|
|||
|
|
CodeRuleRespVO respVO = BeanUtils.toBean(rule, CodeRuleRespVO.class);
|
|||
|
|
respVO.setSegments(rule.getSegmentConfig());
|
|||
|
|
return success(respVO);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分页查询编码规则
|
|||
|
|
*/
|
|||
|
|
@GetMapping("/page")
|
|||
|
|
@Operation(summary = "获得编码规则分页")
|
|||
|
|
@PreAuthorize("@ss.hasPermission('base:code-rule:query')")
|
|||
|
|
public CommonResult<PageResult<CodeRuleRespVO>> getCodeRulePage(@Valid CodeRulePageReqVO pageReqVO) {
|
|||
|
|
PageResult<CodeRuleDO> pageResult = codeRuleService.getRulePage(pageReqVO);
|
|||
|
|
PageResult<CodeRuleRespVO> result = BeanUtils.toBean(pageResult, CodeRuleRespVO.class);
|
|||
|
|
// 补充 segmentConfig(BeanUtils.toBean 无法映射 JSON 字段)
|
|||
|
|
for (int i = 0; i < pageResult.getList().size(); i++) {
|
|||
|
|
result.getList().get(i).setSegments(pageResult.getList().get(i).getSegmentConfig());
|
|||
|
|
}
|
|||
|
|
return success(result);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取启用状态的编码规则精简列表(下拉选项)
|
|||
|
|
*/
|
|||
|
|
@GetMapping("/simple-list")
|
|||
|
|
@Operation(summary = "获取编码规则精简列表")
|
|||
|
|
public CommonResult<List<CodeRuleSimpleRespVO>> getSimpleCodeRuleList() {
|
|||
|
|
List<CodeRuleDO> list = codeRuleService.getEnabledRuleList();
|
|||
|
|
return success(BeanUtils.toBean(list, CodeRuleSimpleRespVO.class));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/controller/admin/codegen/CodeRuleController.java
|
|||
|
|
git commit -m "feat: 新增编码规则管理Controller"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 12:CodeGenController (RPC API 实现)
|
|||
|
|
|
|||
|
|
**文件:**
|
|||
|
|
- 创建:`nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/controller/api/CodeGenController.java`
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编写 CodeGenController**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.code.nl.module.base.controller.api;
|
|||
|
|
|
|||
|
|
import cn.code.nl.framework.common.pojo.CommonResult;
|
|||
|
|
import cn.code.nl.module.base.api.codegen.dto.CodeGenerateReqDTO;
|
|||
|
|
import cn.code.nl.module.base.service.codegen.CodeGenService;
|
|||
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|||
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|||
|
|
import jakarta.annotation.Resource;
|
|||
|
|
import jakarta.validation.Valid;
|
|||
|
|
import org.springframework.validation.annotation.Validated;
|
|||
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|||
|
|
import org.springframework.web.bind.annotation.RequestBody;
|
|||
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|||
|
|
import org.springframework.web.bind.annotation.RestController;
|
|||
|
|
|
|||
|
|
import static cn.code.nl.framework.common.pojo.CommonResult.success;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* RPC API - 编码生成(供其他模块 Feign 调用)
|
|||
|
|
*
|
|||
|
|
* @author zhouz
|
|||
|
|
*/
|
|||
|
|
@Tag(name = "RPC API - 编码生成")
|
|||
|
|
@RestController
|
|||
|
|
@RequestMapping("/rpc-api/base/code-gen")
|
|||
|
|
@Validated
|
|||
|
|
public class CodeGenController {
|
|||
|
|
|
|||
|
|
@Resource
|
|||
|
|
private CodeGenService codeGenService;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成编码
|
|||
|
|
*/
|
|||
|
|
@PostMapping("/generate")
|
|||
|
|
@Operation(summary = "生成编码")
|
|||
|
|
public CommonResult<String> generate(@Valid @RequestBody CodeGenerateReqDTO reqDTO) {
|
|||
|
|
String code = codeGenService.generate(reqDTO.getRuleCode());
|
|||
|
|
return success(code);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/controller/api/CodeGenController.java
|
|||
|
|
git commit -m "feat: 新增编码生成RPC API Controller"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 任务 13:编译验证
|
|||
|
|
|
|||
|
|
- [ ] **步骤 1:编译项目**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /Users/zhouz/item/huachuang/huachuang
|
|||
|
|
mvn clean compile -pl nl-module-base -am -DskipTests
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
预期:BUILD SUCCESS
|
|||
|
|
|
|||
|
|
- [ ] **步骤 2:运行完整 install(含测试)**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn clean install -pl nl-module-base -am
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
预期:BUILD SUCCESS
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 自检清单
|
|||
|
|
|
|||
|
|
**1. 规格覆盖度:**
|
|||
|
|
- ✅ 三种段类型(PREFIX/DATE/SEQUENCE)— 任务 3(DTO)+ 任务 10(引擎)
|
|||
|
|
- ✅ 序号控制(按天/月/年重置、补位、最大值)— 任务 9(SequenceAllocator)
|
|||
|
|
- ✅ 格式自定义(分隔符、大小写、总长度)— 任务 6(VO)+ 任务 10(引擎)
|
|||
|
|
- ✅ 并发唯一性(号段预分配 + Lock4j)— 任务 9(SequenceAllocator)
|
|||
|
|
- ✅ 规则管理 CRUD + 缓存 — 任务 8(Service)+ 任务 11(Controller)
|
|||
|
|
- ✅ 对外 Feign API — 任务 7(API)+ 任务 12(Controller)
|
|||
|
|
|
|||
|
|
**2. 占位符扫描:** 无 TODO、无占位,通过 ✅
|
|||
|
|
|
|||
|
|
**3. 类型一致性:**
|
|||
|
|
- `SegmentConfig` 及其子类的 Jackson 多态配置与 `CodeRuleDO.segmentConfig` 的 `JacksonTypeHandler` 匹配 ✅
|
|||
|
|
- `CodeRuleSaveReqVO.segments` 使用 `List<SegmentConfig>` 与 DO 一致 ✅
|
|||
|
|
- `SequenceAllocator.allocateBySegment()` 签名与 `CodeGenServiceImpl.buildSequenceSegment()` 调用匹配 ✅
|
|||
|
|
- Controller 路径与设计规格一致 ✅
|