fix: 放弃Jackson多态,改用普通DTO避免type序列化冲突

- SegmentConfig改为普通POJO,移除@JsonTypeInfo和子类
- CodeGenServiceImpl/SequenceAllocator直接用SegmentConfig
- 彻底消除type字段序列化/反序列化的冲突问题
This commit is contained in:
zhouz
2026-07-15 18:08:50 +08:00
parent 98dca0edb1
commit f3ef75f990
6 changed files with 65 additions and 112 deletions

View File

@@ -1,10 +1,7 @@
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;
@@ -61,27 +58,25 @@ public class CodeGenServiceImpl implements CodeGenService {
* 根据段类型分发处理,生成段值
*/
private String buildSegment(CodeRuleDO rule, SegmentConfig segment) {
if (segment instanceof PrefixSegment) {
return buildPrefixSegment((PrefixSegment) segment);
} else if (segment instanceof DateSegment) {
return buildDateSegment((DateSegment) segment);
} else if (segment instanceof SequenceSegment) {
return buildSequenceSegment(rule, (SequenceSegment) segment);
}
throw new IllegalArgumentException("不支持的段类型: " + segment.getClass().getSimpleName());
return switch (segment.getType()) {
case "PREFIX" -> buildPrefixSegment(segment);
case "DATE" -> buildDateSegment(segment);
case "SEQUENCE" -> buildSequenceSegment(rule, segment);
default -> throw new IllegalArgumentException("不支持的段类型: " + segment.getType());
};
}
/**
* 生成固定前缀段
*/
private String buildPrefixSegment(PrefixSegment segment) {
private String buildPrefixSegment(SegmentConfig segment) {
return segment.getValue() != null ? segment.getValue() : "";
}
/**
* 生成日期段
*/
private String buildDateSegment(DateSegment segment) {
private String buildDateSegment(SegmentConfig segment) {
String format = StrUtil.isNotBlank(segment.getFormat())
? segment.getFormat() : "yyyyMMdd";
return LocalDate.now().format(DateTimeFormatter.ofPattern(format));
@@ -90,7 +85,7 @@ public class CodeGenServiceImpl implements CodeGenService {
/**
* 生成流水序号段
*/
private String buildSequenceSegment(CodeRuleDO rule, SequenceSegment segment) {
private String buildSequenceSegment(CodeRuleDO rule, SegmentConfig segment) {
String strategy = rule.getSeqStrategy();
if (SeqStrategyEnum.LOCK.getCode().equals(strategy)) {
return sequenceAllocator.allocateByLock(rule.getId(), segment);

View File

@@ -2,7 +2,7 @@ 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.dto.SegmentConfig;
import cn.code.nl.module.base.service.codegen.enums.ResetByEnum;
import cn.hutool.core.util.StrUtil;
import com.baomidou.lock.annotation.Lock4j;
@@ -57,7 +57,7 @@ public class SequenceAllocator {
* @param segment 序号段配置
* @return 格式化后的序号字符串
*/
public String allocateBySegment(Long ruleId, SequenceSegment segment) {
public String allocateBySegment(Long ruleId, SegmentConfig segment) {
String resetKey = buildResetKey(segment.getResetBy());
String cacheKey = ruleId + ":" + resetKey;
@@ -87,7 +87,7 @@ public class SequenceAllocator {
* @param segment 序号段配置
* @return 格式化后的序号字符串
*/
public String allocateByLock(Long ruleId, SequenceSegment segment) {
public String allocateByLock(Long ruleId, SegmentConfig segment) {
String resetKey = buildResetKey(segment.getResetBy());
String lockKey = "codegen:seq:lock:" + ruleId + ":" + resetKey;
return self.allocateWithLock(lockKey, ruleId, resetKey, segment);
@@ -98,7 +98,7 @@ public class SequenceAllocator {
*/
@Lock4j(keys = "#lockKey", acquireTimeout = 3000, expire = 5000)
public long fetchSegmentWithLock(String lockKey, String cacheKey, Long ruleId,
String resetKey, SequenceSegment segment) {
String resetKey, SegmentConfig segment) {
// double-check其他线程可能已经预取了
AtomicLong existing = localSegmentCache.get(cacheKey);
if (existing != null) {
@@ -136,7 +136,7 @@ public class SequenceAllocator {
* Lock4j 保护下实时分配序号
*/
@Lock4j(keys = "#lockKey", acquireTimeout = 3000, expire = 5000)
public String allocateWithLock(String lockKey, Long ruleId, String resetKey, SequenceSegment segment) {
public String allocateWithLock(String lockKey, Long ruleId, String resetKey, SegmentConfig segment) {
Long startAt = segment.getStartAt() != null ? segment.getStartAt() : 1L;
// step=1 每次只取一个号
codeSequenceMapper.updateCurrentValueBySegment(ruleId, resetKey, 1, startAt, segment.getMaxValue());
@@ -178,7 +178,7 @@ public class SequenceAllocator {
* @param segment 序号段配置
* @return 格式化后的字符串
*/
public String formatSequence(long value, SequenceSegment segment) {
public String formatSequence(long value, SegmentConfig segment) {
String raw = String.valueOf(value);
int paddingLen = segment.getPaddingLen() != null ? segment.getPaddingLen() : 0;
if (paddingLen <= 0 || raw.length() >= paddingLen) {

View File

@@ -1,20 +0,0 @@
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;
}

View File

@@ -1,20 +0,0 @@
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;
}

View File

@@ -1,22 +1,60 @@
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 多态序列化/反序列化
* 注意:type 字段由 @JsonTypeInfo 自动管理,不要在子类中重复定义,
* 否则会导致序列化时 type 被覆盖为 null反序列化时找不到子类型。
* 分段配置 —— 通用 DTO不做 Jackson 多态。
* type 决定了哪些字段有效PREFIX→value, DATE→format, SEQUENCE→resetBy/startAt/paddingLen/paddingChar/maxValue
*
* @author zhouz
*/
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
@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 {
public class SegmentConfig {
/**
* 段类型PREFIX / DATE / SEQUENCE
*/
private String type;
// ========== PREFIX 段 ==========
/**
* 固定值,如 "ORD"
*/
private String value;
// ========== DATE 段 ==========
/**
* 日期格式,如 "yyyyMMdd"
*/
private String format;
// ========== SEQUENCE 段 ==========
/**
* 重置维度DAY / MONTH / YEAR / GLOBAL
*/
private String resetBy;
/**
* 起始值,默认 1
*/
private Long startAt;
/**
* 补位后的总长度,如 4 → 0001
*/
private Integer paddingLen;
/**
* 补位字符,默认 "0"
*/
private String paddingChar;
/**
* 序号最大值上限
*/
private Long maxValue;
}

View File

@@ -1,40 +0,0 @@
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;
}