Commit 6734be4f8a1092c5bd68dc97275e4a4508186fa7

Authored by YunaiV
1 parent f938ee11

refactor: 统一日期解析、优惠券和 MES 修复代码风格

yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java
@@ -11,6 +11,7 @@ import java.io.IOException; @@ -11,6 +11,7 @@ import java.io.IOException;
11 import java.time.Instant; 11 import java.time.Instant;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
13 import java.time.format.DateTimeFormatter; 13 import java.time.format.DateTimeFormatter;
  14 +import java.util.regex.Pattern;
14 15
15 /** 16 /**
16 * LocalDateTime 反序列化器,支持毫秒时间戳、常见日期时间字符串和 ISO-8601 日期。 17 * LocalDateTime 反序列化器,支持毫秒时间戳、常见日期时间字符串和 ISO-8601 日期。
@@ -21,6 +22,8 @@ public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDa @@ -21,6 +22,8 @@ public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDa
21 22
22 public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer(); 23 public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
23 24
  25 + private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("-?\\d+");
  26 +
24 @Override 27 @Override
25 public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { 28 public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
26 String text = p.getText(); 29 String text = p.getText();
@@ -28,14 +31,16 @@ public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDa @@ -28,14 +31,16 @@ public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDa
28 return null; 31 return null;
29 } 32 }
30 text = text.trim(); 33 text = text.trim();
31 - if (text.matches("-?\\d+")) { 34 + // 情况一:毫秒时间戳
  35 + if (TIMESTAMP_PATTERN.matcher(text).matches()) {
32 return LocalDateTimeUtil.of(Long.parseLong(text)); 36 return LocalDateTimeUtil.of(Long.parseLong(text));
33 } 37 }
34 - // Hutool 5.x 的自动解析会忽略部分负时区偏移,并截断纳秒。 38 + // 情况二:ISO-8601 日期;标准解析保留时区偏移与纳秒精度
35 if (text.contains("T")) { 39 if (text.contains("T")) {
36 return LocalDateTimeUtil.of(DateTimeFormatter.ISO_DATE_TIME.parseBest( 40 return LocalDateTimeUtil.of(DateTimeFormatter.ISO_DATE_TIME.parseBest(
37 text, Instant::from, LocalDateTime::from)); 41 text, Instant::from, LocalDateTime::from));
38 } 42 }
  43 + // 情况三:常见日期时间字符串
39 return LocalDateTimeUtil.of(DateUtil.parse(text).toInstant()); 44 return LocalDateTimeUtil.of(DateUtil.parse(text).toInstant());
40 } 45 }
41 46
yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializerTest.java
@@ -4,30 +4,28 @@ import com.fasterxml.jackson.databind.ObjectMapper; @@ -4,30 +4,28 @@ import com.fasterxml.jackson.databind.ObjectMapper;
4 import com.fasterxml.jackson.databind.module.SimpleModule; 4 import com.fasterxml.jackson.databind.module.SimpleModule;
5 import org.junit.jupiter.api.Test; 5 import org.junit.jupiter.api.Test;
6 6
  7 +import java.time.Instant;
7 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
  9 +import java.time.OffsetDateTime;
  10 +import java.time.ZoneId;
8 11
9 import static org.junit.jupiter.api.Assertions.assertEquals; 12 import static org.junit.jupiter.api.Assertions.assertEquals;
10 -import static org.junit.jupiter.api.Assertions.assertThrows;  
11 import static org.junit.jupiter.api.Assertions.assertNull; 13 import static org.junit.jupiter.api.Assertions.assertNull;
  14 +import static org.junit.jupiter.api.Assertions.assertThrows;
12 15
13 /** 16 /**
14 * {@link TimestampLocalDateTimeDeserializer} 的单元测试 17 * {@link TimestampLocalDateTimeDeserializer} 的单元测试
15 * 18 *
16 - * 覆盖全部在用格式(epoch 毫秒 / 常见字符串 / ISO / 纯日期),  
17 - * 以及非法输入抛清晰错误(不再静默落 1970-01-01)。  
18 - *  
19 * @author 芋道源码 19 * @author 芋道源码
20 */ 20 */
21 public class TimestampLocalDateTimeDeserializerTest { 21 public class TimestampLocalDateTimeDeserializerTest {
22 22
23 - private static final ObjectMapper MAPPER; 23 + private static final ObjectMapper MAPPER = new ObjectMapper();
24 24
25 static { 25 static {
26 - ObjectMapper mapper = new ObjectMapper();  
27 SimpleModule module = new SimpleModule(); 26 SimpleModule module = new SimpleModule();
28 module.addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE); 27 module.addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);
29 - mapper.registerModule(module);  
30 - MAPPER = mapper; 28 + MAPPER.registerModule(module);
31 } 29 }
32 30
33 private LocalDateTime parse(String json) throws Exception { 31 private LocalDateTime parse(String json) throws Exception {
@@ -37,13 +35,15 @@ public class TimestampLocalDateTimeDeserializerTest { @@ -37,13 +35,15 @@ public class TimestampLocalDateTimeDeserializerTest {
37 @Test 35 @Test
38 public void testEpochMilliNumber() throws Exception { 36 public void testEpochMilliNumber() throws Exception {
39 // 1700000000000 = 2023-11-14T22:13:20Z(本地时区换算后) 37 // 1700000000000 = 2023-11-14T22:13:20Z(本地时区换算后)
40 - LocalDateTime expected = LocalDateTime.ofInstant(java.time.Instant.ofEpochMilli(1700000000000L), java.time.ZoneId.systemDefault()); 38 + LocalDateTime expected = LocalDateTime.ofInstant(Instant.ofEpochMilli(1700000000000L),
  39 + ZoneId.systemDefault());
41 assertEquals(expected, parse("1700000000000")); 40 assertEquals(expected, parse("1700000000000"));
42 } 41 }
43 42
44 @Test 43 @Test
45 public void testEpochMilliString() throws Exception { 44 public void testEpochMilliString() throws Exception {
46 - LocalDateTime expected = LocalDateTime.ofInstant(java.time.Instant.ofEpochMilli(1700000000000L), java.time.ZoneId.systemDefault()); 45 + LocalDateTime expected = LocalDateTime.ofInstant(Instant.ofEpochMilli(1700000000000L),
  46 + ZoneId.systemDefault());
47 assertEquals(expected, parse("\"1700000000000\"")); 47 assertEquals(expected, parse("\"1700000000000\""));
48 } 48 }
49 49
@@ -71,24 +71,24 @@ public class TimestampLocalDateTimeDeserializerTest { @@ -71,24 +71,24 @@ public class TimestampLocalDateTimeDeserializerTest {
71 @Test 71 @Test
72 public void testIsoWithOffset() throws Exception { 72 public void testIsoWithOffset() throws Exception {
73 // 前端 Date 对象序列化:带 Z(UTC)。2026-09-11T00:00:00Z 换算到系统时区 73 // 前端 Date 对象序列化:带 Z(UTC)。2026-09-11T00:00:00Z 换算到系统时区
74 - LocalDateTime expected = java.time.OffsetDateTime.parse("2026-09-11T00:00:00Z")  
75 - .atZoneSameInstant(java.time.ZoneId.systemDefault()).toLocalDateTime(); 74 + LocalDateTime expected = OffsetDateTime.parse("2026-09-11T00:00:00Z")
  75 + .atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
76 assertEquals(expected, parse("\"2026-09-11T00:00:00Z\"")); 76 assertEquals(expected, parse("\"2026-09-11T00:00:00Z\""));
77 } 77 }
78 78
79 @Test 79 @Test
80 public void testIsoWithOffsetMillis() throws Exception { 80 public void testIsoWithOffsetMillis() throws Exception {
81 // JSON.stringify(Date) 实际产物:带毫秒 + Z(无 value-format 的 Date 对象控件提交格式) 81 // JSON.stringify(Date) 实际产物:带毫秒 + Z(无 value-format 的 Date 对象控件提交格式)
82 - LocalDateTime expected = java.time.OffsetDateTime.parse("2026-09-11T00:00:00.000Z")  
83 - .atZoneSameInstant(java.time.ZoneId.systemDefault()).toLocalDateTime(); 82 + LocalDateTime expected = OffsetDateTime.parse("2026-09-11T00:00:00.000Z")
  83 + .atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
84 assertEquals(expected, parse("\"2026-09-11T00:00:00.000Z\"")); 84 assertEquals(expected, parse("\"2026-09-11T00:00:00.000Z\""));
85 } 85 }
86 86
87 @Test 87 @Test
88 public void testIsoWithOffsetPlus() throws Exception { 88 public void testIsoWithOffsetPlus() throws Exception {
89 // 带 +08:00 时区偏移的形式 89 // 带 +08:00 时区偏移的形式
90 - LocalDateTime expected = java.time.OffsetDateTime.parse("2026-09-11T08:00:00.000+08:00")  
91 - .atZoneSameInstant(java.time.ZoneId.systemDefault()).toLocalDateTime(); 90 + LocalDateTime expected = OffsetDateTime.parse("2026-09-11T08:00:00.000+08:00")
  91 + .atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
92 assertEquals(expected, parse("\"2026-09-11T08:00:00.000+08:00\"")); 92 assertEquals(expected, parse("\"2026-09-11T08:00:00.000+08:00\""));
93 } 93 }
94 94
@@ -99,16 +99,16 @@ public class TimestampLocalDateTimeDeserializerTest { @@ -99,16 +99,16 @@ public class TimestampLocalDateTimeDeserializerTest {
99 99
100 @Test 100 @Test
101 public void testIsoWithNegativeOffsetAndMillis() throws Exception { 101 public void testIsoWithNegativeOffsetAndMillis() throws Exception {
102 - LocalDateTime expected = java.time.OffsetDateTime.parse("2026-09-11T23:30:00.123-05:30")  
103 - .atZoneSameInstant(java.time.ZoneId.systemDefault()).toLocalDateTime(); 102 + LocalDateTime expected = OffsetDateTime.parse("2026-09-11T23:30:00.123-05:30")
  103 + .atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
104 assertEquals(expected, parse("\"2026-09-11T23:30:00.123-05:30\"")); 104 assertEquals(expected, parse("\"2026-09-11T23:30:00.123-05:30\""));
105 } 105 }
106 106
107 @Test 107 @Test
108 public void testZeroAndNegativeEpochMilli() throws Exception { 108 public void testZeroAndNegativeEpochMilli() throws Exception {
109 for (long millis : new long[]{0L, -1L}) { 109 for (long millis : new long[]{0L, -1L}) {
110 - LocalDateTime expected = LocalDateTime.ofInstant(java.time.Instant.ofEpochMilli(millis),  
111 - java.time.ZoneId.systemDefault()); 110 + LocalDateTime expected = LocalDateTime.ofInstant(Instant.ofEpochMilli(millis),
  111 + ZoneId.systemDefault());
112 assertEquals(expected, parse(Long.toString(millis))); 112 assertEquals(expected, parse(Long.toString(millis)));
113 assertEquals(expected, parse("\"" + millis + "\"")); 113 assertEquals(expected, parse("\"" + millis + "\""));
114 } 114 }
@@ -134,9 +134,8 @@ public class TimestampLocalDateTimeDeserializerTest { @@ -134,9 +134,8 @@ public class TimestampLocalDateTimeDeserializerTest {
134 134
135 @Test 135 @Test
136 public void testInvalidThrows() { 136 public void testInvalidThrows() {
137 - // 非法输入抛清晰错误,不再静默落 1970 137 + // 调用,并断言异常
138 assertThrows(Exception.class, () -> parse("\"not-a-date\"")); 138 assertThrows(Exception.class, () -> parse("\"not-a-date\""));
139 -  
140 } 139 }
141 140
142 } 141 }
yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
@@ -216,13 +216,16 @@ public class CouponServiceImpl implements CouponService { @@ -216,13 +216,16 @@ public class CouponServiceImpl implements CouponService {
216 public void takeCouponByRegister(Long userId) { 216 public void takeCouponByRegister(Long userId) {
217 List<CouponTemplateDO> templates = couponTemplateService.getCouponTemplateListByTakeType(CouponTakeTypeEnum.REGISTER); 217 List<CouponTemplateDO> templates = couponTemplateService.getCouponTemplateListByTakeType(CouponTakeTypeEnum.REGISTER);
218 // 1. 过滤不可领取(如已过期)的模板,避免单张异常中断整批 218 // 1. 过滤不可领取(如已过期)的模板,避免单张异常中断整批
219 - List<CouponTemplateDO> takeableTemplates = templates.stream()  
220 - .filter(this::isCouponTemplateNotExpired)  
221 - .collect(Collectors.toList());  
222 - if (takeableTemplates.size() < templates.size()) {  
223 - log.info("[takeCouponByRegister][用户({}) 过滤 {} 张不可领取的模板]", userId, templates.size() - takeableTemplates.size());  
224 - }  
225 - // 2. 逐个发放,单张失败不影响其余 219 + List<CouponTemplateDO> takeableTemplates = filterList(templates, template -> {
  220 + boolean expired = CouponTemplateValidityTypeEnum.DATE.getType().equals(template.getValidityType())
  221 + && LocalDateTimeUtils.beforeNow(template.getValidEndTime());
  222 + if (expired) {
  223 + log.warn("[takeCouponByRegister][用户({}) 模板({}) 已过期,跳过发放]", userId, template.getId());
  224 + }
  225 + return !expired;
  226 + });
  227 +
  228 + // 2.1 逐个发放,单张失败不影响其余
226 int successCount = 0; 229 int successCount = 0;
227 for (CouponTemplateDO template : takeableTemplates) { 230 for (CouponTemplateDO template : takeableTemplates) {
228 try { 231 try {
@@ -232,17 +235,12 @@ public class CouponServiceImpl implements CouponService { @@ -232,17 +235,12 @@ public class CouponServiceImpl implements CouponService {
232 log.error("[takeCouponByRegister][用户({}) 模板({}) 发放失败]", userId, template.getId(), e); 235 log.error("[takeCouponByRegister][用户({}) 模板({}) 发放失败]", userId, template.getId(), e);
233 } 236 }
234 } 237 }
235 - // 3. 应发全失败时留一条 ERROR 标记,供监控告警 238 + // 2.2 应发全失败时留一条 ERROR 标记,供监控告警
236 if (CollUtil.isNotEmpty(takeableTemplates) && successCount == 0) { 239 if (CollUtil.isNotEmpty(takeableTemplates) && successCount == 0) {
237 log.error("[takeCouponByRegister][用户({}) 全部 {} 张新人券发放失败]", userId, takeableTemplates.size()); 240 log.error("[takeCouponByRegister][用户({}) 全部 {} 张新人券发放失败]", userId, takeableTemplates.size());
238 } 241 }
239 } 242 }
240 243
241 - private boolean isCouponTemplateNotExpired(CouponTemplateDO template) {  
242 - return !CouponTemplateValidityTypeEnum.DATE.getType().equals(template.getValidityType())  
243 - || !LocalDateTimeUtils.beforeNow(template.getValidEndTime());  
244 - }  
245 -  
246 @Override 244 @Override
247 public int expireCoupon() { 245 public int expireCoupon() {
248 // 1. 查询待过期的优惠券 246 // 1. 查询待过期的优惠券
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/autocode/MesMdAutoCodePartMapper.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.mes.dal.mysql.md.autocode; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.mes.dal.mysql.md.autocode;
3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO; 5 import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO;
6 -import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodePartTypeEnum;  
7 import org.apache.ibatis.annotations.Mapper; 6 import org.apache.ibatis.annotations.Mapper;
8 7
9 import java.util.List; 8 import java.util.List;
@@ -22,8 +21,8 @@ public interface MesMdAutoCodePartMapper extends BaseMapperX&lt;MesMdAutoCodePartDO @@ -22,8 +21,8 @@ public interface MesMdAutoCodePartMapper extends BaseMapperX&lt;MesMdAutoCodePartDO
22 .orderByAsc(MesMdAutoCodePartDO::getSort)); 21 .orderByAsc(MesMdAutoCodePartDO::getSort));
23 } 22 }
24 23
25 - default List<MesMdAutoCodePartDO> selectFixedCharPartList() {  
26 - return selectList(MesMdAutoCodePartDO::getType, MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType()); 24 + default List<MesMdAutoCodePartDO> selectByType(Integer type) {
  25 + return selectList(MesMdAutoCodePartDO::getType, type);
27 } 26 }
28 27
29 default void deleteByRuleId(Long ruleId) { 28 default void deleteByRuleId(Long ruleId) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodePartServiceImpl.java
@@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.mes.controller.admin.md.autocode.vo.part.MesMdAut @@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.mes.controller.admin.md.autocode.vo.part.MesMdAut
7 import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO;
8 import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodeRuleDO; 8 import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodeRuleDO;
9 import cn.iocoder.yudao.module.mes.dal.mysql.md.autocode.MesMdAutoCodePartMapper; 9 import cn.iocoder.yudao.module.mes.dal.mysql.md.autocode.MesMdAutoCodePartMapper;
10 -import cn.iocoder.yudao.module.mes.dal.mysql.md.autocode.MesMdAutoCodeRuleMapper;  
11 import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodePartTypeEnum; 10 import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodePartTypeEnum;
12 import jakarta.annotation.Resource; 11 import jakarta.annotation.Resource;
13 import org.springframework.stereotype.Service; 12 import org.springframework.stereotype.Service;
@@ -21,6 +20,7 @@ import java.util.Map; @@ -21,6 +20,7 @@ import java.util.Map;
21 import java.util.stream.Collectors; 20 import java.util.stream.Collectors;
22 21
23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  23 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
24 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.AUTO_CODE_PART_FIXED_CHAR_DUPLICATE; 24 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.AUTO_CODE_PART_FIXED_CHAR_DUPLICATE;
25 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.AUTO_CODE_PART_NOT_EXISTS; 25 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.AUTO_CODE_PART_NOT_EXISTS;
26 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.AUTO_CODE_PART_SERIAL_NUMBER_DUPLICATE; 26 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.AUTO_CODE_PART_SERIAL_NUMBER_DUPLICATE;
@@ -36,10 +36,6 @@ public class MesMdAutoCodePartServiceImpl implements MesMdAutoCodePartService { @@ -36,10 +36,6 @@ public class MesMdAutoCodePartServiceImpl implements MesMdAutoCodePartService {
36 36
37 @Resource 37 @Resource
38 private MesMdAutoCodePartMapper partMapper; 38 private MesMdAutoCodePartMapper partMapper;
39 -  
40 - @Resource  
41 - private MesMdAutoCodeRuleMapper ruleMapper;  
42 -  
43 @Resource 39 @Resource
44 private MesMdAutoCodeRuleService ruleService; 40 private MesMdAutoCodeRuleService ruleService;
45 41
@@ -125,10 +121,10 @@ public class MesMdAutoCodePartServiceImpl implements MesMdAutoCodePartService { @@ -125,10 +121,10 @@ public class MesMdAutoCodePartServiceImpl implements MesMdAutoCodePartService {
125 * 从源头避免不同规则生成相同编码;生成编码时的全局查重仍保留,兜底存量配置的冲突。 121 * 从源头避免不同规则生成相同编码;生成编码时的全局查重仍保留,兜底存量配置的冲突。
126 */ 122 */
127 private void validateFixedCharUnique(Long id, MesMdAutoCodePartSaveReqVO reqVO) { 123 private void validateFixedCharUnique(Long id, MesMdAutoCodePartSaveReqVO reqVO) {
128 - // 1. 全租户固定字符分段,按规则分组  
129 - Map<Long, List<MesMdAutoCodePartDO>> fixedPartsByRuleId = partMapper.selectFixedCharPartList().stream()  
130 - .collect(Collectors.groupingBy(MesMdAutoCodePartDO::getRuleId));  
131 - // 2. 本次保存后,本规则的前缀指纹(排除正在修改的分段,加入本次保存的分段) 124 + // 1.1 所有固定字符分段,按规则分组
  125 + Map<Long, List<MesMdAutoCodePartDO>> fixedPartsByRuleId = convertMultiMap(
  126 + partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType()), MesMdAutoCodePartDO::getRuleId);
  127 + // 1.2 本次保存后,本规则的前缀指纹(排除正在修改的分段,加入本次保存的分段)
132 List<MesMdAutoCodePartDO> myFixedParts = new ArrayList<>( 128 List<MesMdAutoCodePartDO> myFixedParts = new ArrayList<>(
133 fixedPartsByRuleId.getOrDefault(reqVO.getRuleId(), Collections.emptyList())); 129 fixedPartsByRuleId.getOrDefault(reqVO.getRuleId(), Collections.emptyList()));
134 myFixedParts.removeIf(part -> ObjUtil.equal(id, part.getId())); 130 myFixedParts.removeIf(part -> ObjUtil.equal(id, part.getId()));
@@ -136,20 +132,23 @@ public class MesMdAutoCodePartServiceImpl implements MesMdAutoCodePartService { @@ -136,20 +132,23 @@ public class MesMdAutoCodePartServiceImpl implements MesMdAutoCodePartService {
136 myFixedParts.add(BeanUtils.toBean(reqVO, MesMdAutoCodePartDO.class)); 132 myFixedParts.add(BeanUtils.toBean(reqVO, MesMdAutoCodePartDO.class));
137 } 133 }
138 String myFingerprint = buildFixedCharFingerprint(myFixedParts); 134 String myFingerprint = buildFixedCharFingerprint(myFixedParts);
139 - // 3. 与租户内其他未删除规则的前缀指纹逐一比对  
140 - for (MesMdAutoCodeRuleDO rule : ruleMapper.selectList()) { 135 +
  136 + // 2. 与租户内其他未删除规则的前缀指纹逐一比对
  137 + List<MesMdAutoCodeRuleDO> rules = ruleService.getAutoCodeRuleList();
  138 + for (MesMdAutoCodeRuleDO rule : rules) {
141 if (ObjUtil.equal(rule.getId(), reqVO.getRuleId())) { 139 if (ObjUtil.equal(rule.getId(), reqVO.getRuleId())) {
142 continue; 140 continue;
143 } 141 }
144 List<MesMdAutoCodePartDO> otherFixedParts = fixedPartsByRuleId.getOrDefault(rule.getId(), Collections.emptyList()); 142 List<MesMdAutoCodePartDO> otherFixedParts = fixedPartsByRuleId.getOrDefault(rule.getId(), Collections.emptyList());
145 - if (myFingerprint.equals(buildFixedCharFingerprint(otherFixedParts))) { 143 + String otherFingerprint = buildFixedCharFingerprint(otherFixedParts);
  144 + if (myFingerprint.equals(otherFingerprint)) {
146 throw exception(AUTO_CODE_PART_FIXED_CHAR_DUPLICATE); 145 throw exception(AUTO_CODE_PART_FIXED_CHAR_DUPLICATE);
147 } 146 }
148 } 147 }
149 } 148 }
150 149
151 /** 150 /**
152 - * 构建前缀指纹:固定字符分段按分段排序,拼接截取到分段长度内的固定字符(空值按空串) 151 + * 构建固定字符分段的前缀指纹:按分段排序,拼接截取到分段长度内的固定字符
153 */ 152 */
154 private String buildFixedCharFingerprint(List<MesMdAutoCodePartDO> fixedParts) { 153 private String buildFixedCharFingerprint(List<MesMdAutoCodePartDO> fixedParts) {
155 return fixedParts.stream() 154 return fixedParts.stream()
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRuleService.java
@@ -46,6 +46,13 @@ public interface MesMdAutoCodeRuleService { @@ -46,6 +46,13 @@ public interface MesMdAutoCodeRuleService {
46 MesMdAutoCodeRuleDO getAutoCodeRule(Long id); 46 MesMdAutoCodeRuleDO getAutoCodeRule(Long id);
47 47
48 /** 48 /**
  49 + * 获得编码规则列表
  50 + *
  51 + * @return 编码规则列表
  52 + */
  53 + List<MesMdAutoCodeRuleDO> getAutoCodeRuleList();
  54 +
  55 + /**
49 * 获得编码规则分页 56 * 获得编码规则分页
50 * 57 *
51 * @param pageReqVO 分页查询 58 * @param pageReqVO 分页查询
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRuleServiceImpl.java
@@ -73,6 +73,11 @@ public class MesMdAutoCodeRuleServiceImpl implements MesMdAutoCodeRuleService { @@ -73,6 +73,11 @@ public class MesMdAutoCodeRuleServiceImpl implements MesMdAutoCodeRuleService {
73 } 73 }
74 74
75 @Override 75 @Override
  76 + public List<MesMdAutoCodeRuleDO> getAutoCodeRuleList() {
  77 + return ruleMapper.selectList();
  78 + }
  79 +
  80 + @Override
76 public PageResult<MesMdAutoCodeRuleDO> getAutoCodeRulePage(MesMdAutoCodeRulePageReqVO pageReqVO) { 81 public PageResult<MesMdAutoCodeRuleDO> getAutoCodeRulePage(MesMdAutoCodeRulePageReqVO pageReqVO) {
77 return ruleMapper.selectPage(pageReqVO); 82 return ruleMapper.selectPage(pageReqVO);
78 } 83 }
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodePartServiceImplTest.java
@@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.mes.controller.admin.md.autocode.vo.part.MesMdAut @@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.mes.controller.admin.md.autocode.vo.part.MesMdAut
5 import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO; 5 import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO;
6 import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodeRuleDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodeRuleDO;
7 import cn.iocoder.yudao.module.mes.dal.mysql.md.autocode.MesMdAutoCodePartMapper; 7 import cn.iocoder.yudao.module.mes.dal.mysql.md.autocode.MesMdAutoCodePartMapper;
8 -import cn.iocoder.yudao.module.mes.dal.mysql.md.autocode.MesMdAutoCodeRuleMapper;  
9 import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodePartTypeEnum; 8 import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodePartTypeEnum;
10 import org.junit.jupiter.api.Test; 9 import org.junit.jupiter.api.Test;
11 import org.junit.jupiter.api.extension.ExtendWith; 10 import org.junit.jupiter.api.extension.ExtendWith;
@@ -37,8 +36,6 @@ public class MesMdAutoCodePartServiceImplTest { @@ -37,8 +36,6 @@ public class MesMdAutoCodePartServiceImplTest {
37 @Mock 36 @Mock
38 private MesMdAutoCodePartMapper partMapper; 37 private MesMdAutoCodePartMapper partMapper;
39 @Mock 38 @Mock
40 - private MesMdAutoCodeRuleMapper ruleMapper;  
41 - @Mock  
42 private MesMdAutoCodeRuleService ruleService; 39 private MesMdAutoCodeRuleService ruleService;
43 40
44 private static MesMdAutoCodePartDO fixedPart(Long id, Long ruleId, Integer sort, String fixCharacter, Integer length) { 41 private static MesMdAutoCodePartDO fixedPart(Long id, Long ruleId, Integer sort, String fixCharacter, Integer length) {
@@ -61,9 +58,9 @@ public class MesMdAutoCodePartServiceImplTest { @@ -61,9 +58,9 @@ public class MesMdAutoCodePartServiceImplTest {
61 @Test 58 @Test
62 public void testCreateAutoCodePart_fixedCharDuplicate() { 59 public void testCreateAutoCodePart_fixedCharDuplicate() {
63 // 准备:其他规则已占用相同前缀 "ITEM_" 60 // 准备:其他规则已占用相同前缀 "ITEM_"
64 - when(partMapper.selectFixedCharPartList()).thenReturn(Arrays.asList( 61 + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Arrays.asList(
65 fixedPart(1L, 1L, 1, "ITEM_", 5))); 62 fixedPart(1L, 1L, 1, "ITEM_", 5)));
66 - when(ruleMapper.selectList()).thenReturn(Arrays.asList( 63 + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList(
67 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); 64 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L)));
68 65
69 // 断言:保存被拒,且未插入 66 // 断言:保存被拒,且未插入
@@ -76,9 +73,9 @@ public class MesMdAutoCodePartServiceImplTest { @@ -76,9 +73,9 @@ public class MesMdAutoCodePartServiceImplTest {
76 @Test 73 @Test
77 public void testCreateAutoCodePart_fixedCharUnique() { 74 public void testCreateAutoCodePart_fixedCharUnique() {
78 // 准备:其他规则前缀 "ITEM_",本次保存 "SN_" 75 // 准备:其他规则前缀 "ITEM_",本次保存 "SN_"
79 - when(partMapper.selectFixedCharPartList()).thenReturn(Arrays.asList( 76 + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Arrays.asList(
80 fixedPart(1L, 1L, 1, "ITEM_", 5))); 77 fixedPart(1L, 1L, 1, "ITEM_", 5)));
81 - when(ruleMapper.selectList()).thenReturn(Arrays.asList( 78 + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList(
82 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); 79 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L)));
83 80
84 // 断言:保存成功 81 // 断言:保存成功
@@ -89,9 +86,9 @@ public class MesMdAutoCodePartServiceImplTest { @@ -89,9 +86,9 @@ public class MesMdAutoCodePartServiceImplTest {
89 @Test 86 @Test
90 public void testCreateAutoCodePart_fixedCharTruncatedDuplicate() { 87 public void testCreateAutoCodePart_fixedCharTruncatedDuplicate() {
91 // 准备:其他规则固定字符 "IT_X" 长度 2(生成时截断为 "IT") 88 // 准备:其他规则固定字符 "IT_X" 长度 2(生成时截断为 "IT")
92 - when(partMapper.selectFixedCharPartList()).thenReturn(Arrays.asList( 89 + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Arrays.asList(
93 fixedPart(1L, 1L, 1, "IT_X", 2))); 90 fixedPart(1L, 1L, 1, "IT_X", 2)));
94 - when(ruleMapper.selectList()).thenReturn(Arrays.asList( 91 + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList(
95 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); 92 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L)));
96 93
97 // 断言:本次保存 "IT" 截断后同为 "IT",保存被拒 94 // 断言:本次保存 "IT" 截断后同为 "IT",保存被拒
@@ -104,8 +101,8 @@ public class MesMdAutoCodePartServiceImplTest { @@ -104,8 +101,8 @@ public class MesMdAutoCodePartServiceImplTest {
104 @Test 101 @Test
105 public void testCreateAutoCodePart_bothRulesWithoutFixedChar() { 102 public void testCreateAutoCodePart_bothRulesWithoutFixedChar() {
106 // 准备:租户内另一规则无任何固定字符分段(前缀为空串),本次保存流水号分段(本规则也无固定字符) 103 // 准备:租户内另一规则无任何固定字符分段(前缀为空串),本次保存流水号分段(本规则也无固定字符)
107 - when(partMapper.selectFixedCharPartList()).thenReturn(Collections.emptyList());  
108 - when(ruleMapper.selectList()).thenReturn(Arrays.asList( 104 + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Collections.emptyList());
  105 + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList(
109 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); 106 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L)));
110 107
111 MesMdAutoCodePartSaveReqVO reqVO = new MesMdAutoCodePartSaveReqVO(); 108 MesMdAutoCodePartSaveReqVO reqVO = new MesMdAutoCodePartSaveReqVO();
@@ -127,9 +124,9 @@ public class MesMdAutoCodePartServiceImplTest { @@ -127,9 +124,9 @@ public class MesMdAutoCodePartServiceImplTest {
127 // 准备:本规则已有固定字符 "OLD_",修改为与其他规则相同的 "ITEM_" 124 // 准备:本规则已有固定字符 "OLD_",修改为与其他规则相同的 "ITEM_"
128 MesMdAutoCodePartDO myPart = fixedPart(100L, 10L, 1, "OLD_", 4); 125 MesMdAutoCodePartDO myPart = fixedPart(100L, 10L, 1, "OLD_", 4);
129 when(partMapper.selectById(100L)).thenReturn(myPart); 126 when(partMapper.selectById(100L)).thenReturn(myPart);
130 - when(partMapper.selectFixedCharPartList()).thenReturn(Arrays.asList( 127 + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Arrays.asList(
131 myPart, fixedPart(1L, 1L, 1, "ITEM_", 5))); 128 myPart, fixedPart(1L, 1L, 1, "ITEM_", 5)));
132 - when(ruleMapper.selectList()).thenReturn(Arrays.asList( 129 + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList(
133 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); 130 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L)));
134 131
135 // 断言:保存被拒,且未更新 132 // 断言:保存被拒,且未更新
@@ -144,9 +141,9 @@ public class MesMdAutoCodePartServiceImplTest { @@ -144,9 +141,9 @@ public class MesMdAutoCodePartServiceImplTest {
144 // 准备:本规则已有固定字符 "ITEM_"(与其他规则重复),修改为 "SN_" 141 // 准备:本规则已有固定字符 "ITEM_"(与其他规则重复),修改为 "SN_"
145 MesMdAutoCodePartDO myPart = fixedPart(100L, 10L, 1, "ITEM_", 5); 142 MesMdAutoCodePartDO myPart = fixedPart(100L, 10L, 1, "ITEM_", 5);
146 when(partMapper.selectById(100L)).thenReturn(myPart); 143 when(partMapper.selectById(100L)).thenReturn(myPart);
147 - when(partMapper.selectFixedCharPartList()).thenReturn(Arrays.asList( 144 + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Arrays.asList(
148 myPart, fixedPart(1L, 1L, 1, "WO_", 3))); 145 myPart, fixedPart(1L, 1L, 1, "WO_", 3)));
149 - when(ruleMapper.selectList()).thenReturn(Arrays.asList( 146 + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList(
150 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); 147 new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L)));
151 148
152 // 断言:修改后前缀不再重复,保存成功 149 // 断言:修改后前缀不再重复,保存成功