diff --git a/nl-module-base/nl-module-base-server/pom.xml b/nl-module-base/nl-module-base-server/pom.xml
index ef5da7d2..01eee04e 100644
--- a/nl-module-base/nl-module-base-server/pom.xml
+++ b/nl-module-base/nl-module-base-server/pom.xml
@@ -46,6 +46,10 @@
cn.nl.cloud
nl-spring-boot-starter-biz-ip
+
+ cn.nl.cloud
+ nl-spring-boot-starter-protection
+
diff --git a/nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/SequenceAllocator.java b/nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/SequenceAllocator.java
new file mode 100644
index 00000000..852dab90
--- /dev/null
+++ b/nl-module-base/nl-module-base-server/src/main/java/cn/code/nl/module/base/service/codegen/SequenceAllocator.java
@@ -0,0 +1,192 @@
+package cn.code.nl.module.base.service.codegen;
+
+import cn.code.nl.module.base.dal.dataobject.codegen.CodeSequenceDO;
+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.context.annotation.Lazy;
+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 localSegmentCache = new ConcurrentHashMap<>();
+
+ @Resource
+ private CodeSequenceMapper codeSequenceMapper;
+
+ /**
+ * 自注入,用于 Spring AOP 代理调用带 @Lock4j 的方法
+ */
+ @Resource
+ @Lazy
+ private SequenceAllocator self;
+
+ /**
+ * 分配一个序号(号段预分配策略)
+ *
+ * @param ruleId 规则ID
+ * @param segment 序号段配置
+ * @return 格式化后的序号字符串
+ */
+ public String allocateBySegment(Long ruleId, SequenceSegment segment) {
+ String resetKey = buildResetKey(segment.getResetBy());
+ String cacheKey = ruleId + ":" + resetKey;
+
+ // 从本地缓存取号段
+ AtomicLong current = localSegmentCache.get(cacheKey);
+ if (current != null) {
+ long seqValue = current.incrementAndGet();
+ // 检查号段是否还有效 - 从 DB 查当前号段末尾
+ CodeSequenceDO seqRecord = codeSequenceMapper.selectByRuleIdAndResetKey(ruleId, resetKey);
+ if (seqRecord != null && seqValue <= seqRecord.getCurrentValue()) {
+ return formatSequence(seqValue, segment);
+ }
+ // 号段耗尽,清理缓存
+ localSegmentCache.remove(cacheKey);
+ }
+
+ // 号段耗尽或不存在,通过代理调用带 @Lock4j 的方法从 DB 预取
+ String lockKey = "codegen:seq:segment:" + ruleId + ":" + resetKey;
+ long seqValue = self.fetchSegmentWithLock(lockKey, cacheKey, ruleId, resetKey, segment);
+ 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:lock:" + ruleId + ":" + resetKey;
+ return self.allocateWithLock(lockKey, ruleId, resetKey, segment);
+ }
+
+ /**
+ * Lock4j 保护下从 DB 预取号段
+ */
+ @Lock4j(keys = "#lockKey", acquireTimeout = 3000, expire = 5000)
+ public long fetchSegmentWithLock(String lockKey, String cacheKey, Long ruleId,
+ String resetKey, SequenceSegment segment) {
+ // double-check:其他线程可能已经预取了
+ AtomicLong existing = localSegmentCache.get(cacheKey);
+ if (existing != null) {
+ return existing.incrementAndGet();
+ }
+
+ Long startAt = segment.getStartAt() != null ? segment.getStartAt() : 1L;
+ Long maxValue = segment.getMaxValue() != null ? segment.getMaxValue() : 9999L;
+
+ // DB 原子更新:current_value = current_value + step
+ codeSequenceMapper.updateCurrentValueBySegment(ruleId, resetKey, DEFAULT_STEP, startAt, maxValue);
+
+ // 查回最新 current_value
+ CodeSequenceDO seq = codeSequenceMapper.selectByRuleIdAndResetKey(ruleId, resetKey);
+ Long newCurrentValue = (seq != null) ? seq.getCurrentValue() : (startAt + DEFAULT_STEP - 1);
+ long segmentStart = newCurrentValue - DEFAULT_STEP + 1;
+
+ // 检查上限
+ if (newCurrentValue > maxValue) {
+ log.error("序号超出最大值上限,ruleId={}, resetKey={}, currentValue={}, maxValue={}",
+ ruleId, resetKey, newCurrentValue, maxValue);
+ 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();
+ }
+
+ /**
+ * 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());
+ // 查回当前值
+ CodeSequenceDO seq = codeSequenceMapper.selectByRuleIdAndResetKey(ruleId, resetKey);
+ Long value = (seq != null) ? seq.getCurrentValue() : startAt;
+ // 检查上限
+ if (segment.getMaxValue() != null && value > segment.getMaxValue()) {
+ log.error("序号超出最大值上限,ruleId={}, resetKey={}, currentValue={}, maxValue={}",
+ ruleId, resetKey, value, segment.getMaxValue());
+ throw exception(CODE_GEN_SEQ_EXCEED_MAX);
+ }
+ return formatSequence(value, segment);
+ }
+
+ /**
+ * 根据重置维度构建 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;
+ }
+
+}