Commit 633bd70dfe157fbbb00bf5c9860f506747e77e10

Authored by YunaiV
1 parent 08f7c33c

fix(mes): 自动编码重复时支持自动重试,修复流水号落后导致的生成失败

当 Redis 流水号落后于历史数据时,生成的编码可能与已有记录重复,原逻辑会直接抛出
AUTO_CODE_GENERATE_FAILED 异常。现改为循环重试(最多 10 次),重复时自动跳过并
生成下一个可用编码,仅在重试耗尽后才抛异常。

- 抽取 generateCode 方法,分离编码拼接/补齐与重复校验、记录保存逻辑
- 补充重复重试场景的单元测试
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRecordServiceImpl.java
@@ -32,6 +32,8 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; @@ -32,6 +32,8 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
32 @Validated 32 @Validated
33 public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordService { 33 public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordService {
34 34
  35 + private static final int GENERATE_MAX_RETRY_COUNT = 10;
  36 +
35 @Resource 37 @Resource
36 private MesMdAutoCodeRecordMapper recordMapper; 38 private MesMdAutoCodeRecordMapper recordMapper;
37 39
@@ -63,10 +65,34 @@ public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordServic @@ -63,10 +65,34 @@ public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordServic
63 throw exception(AUTO_CODE_GENERATE_FAILED); 65 throw exception(AUTO_CODE_GENERATE_FAILED);
64 } 66 }
65 67
66 - // 2.1 构建上下文  
67 - MesMdAutoCodeContext context = new MesMdAutoCodeContext()  
68 - .setRule(rule).setParts(parts).setInputChar(inputChar);  
69 - // 2.2 遍历分段,生成编码 68 + // 2. 循环生成编码,跳过因 Redis 流水号落后导致的历史重复编码
  69 + for (int i = 0; i < GENERATE_MAX_RETRY_COUNT; i++) {
  70 + // 2.1 生成编码
  71 + MesMdAutoCodeContext context = new MesMdAutoCodeContext()
  72 + .setRule(rule).setParts(parts).setInputChar(inputChar);
  73 + String result = generateCode(rule, parts, context);
  74 + // 2.2 二次校验(防止重复)
  75 + MesMdAutoCodeRecordDO existRecord = recordMapper.selectByResult(result);
  76 + if (existRecord != null) {
  77 + continue;
  78 + }
  79 + // 2.3 保存生成记录
  80 + MesMdAutoCodeRecordDO record = new MesMdAutoCodeRecordDO()
  81 + .setRuleId(rule.getId()).setResult(result)
  82 + .setSerialNo(context.getSerialNo()).setInputChar(inputChar);
  83 + recordMapper.insert(record);
  84 + return result;
  85 + }
  86 +
  87 + // 3. 重试多次后仍然失败,抛出异常
  88 + throw exception(AUTO_CODE_GENERATE_FAILED);
  89 + }
  90 +
  91 + /**
  92 + * 生成编码
  93 + */
  94 + private String generateCode(MesMdAutoCodeRuleDO rule, List<MesMdAutoCodePartDO> parts,
  95 + MesMdAutoCodeContext context) {
70 StringBuilder codeBuilder = new StringBuilder(); 96 StringBuilder codeBuilder = new StringBuilder();
71 for (MesMdAutoCodePartDO part : parts) { 97 for (MesMdAutoCodePartDO part : parts) {
72 MesMdAutoCodePartStrategy strategy = strategyMap.get(part.getType()); 98 MesMdAutoCodePartStrategy strategy = strategyMap.get(part.getType());
@@ -78,22 +104,11 @@ public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordServic @@ -78,22 +104,11 @@ public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordServic
78 // 拼接分段编码 104 // 拼接分段编码
79 codeBuilder.append(partCode); 105 codeBuilder.append(partCode);
80 } 106 }
81 - // 2.3 补齐处理 107 + // 补齐处理
82 String result = codeBuilder.toString(); 108 String result = codeBuilder.toString();
83 if (Boolean.TRUE.equals(rule.getPadded()) && rule.getMaxLength() != null) { 109 if (Boolean.TRUE.equals(rule.getPadded()) && rule.getMaxLength() != null) {
84 result = padCode(result, rule); 110 result = padCode(result, rule);
85 } 111 }
86 -  
87 - // 3.1 二次校验(防止重复)  
88 - MesMdAutoCodeRecordDO existRecord = recordMapper.selectByResult(result);  
89 - if (existRecord != null) {  
90 - throw exception(AUTO_CODE_GENERATE_FAILED);  
91 - }  
92 - // 3.2 保存生成记录  
93 - MesMdAutoCodeRecordDO record = new MesMdAutoCodeRecordDO()  
94 - .setRuleId(rule.getId()).setResult(result)  
95 - .setSerialNo(context.getSerialNo()).setInputChar(inputChar);  
96 - recordMapper.insert(record);  
97 return result; 112 return result;
98 } 113 }
99 114
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRecordServiceImplTest.java
@@ -99,6 +99,42 @@ public class MesMdAutoCodeRecordServiceImplTest { @@ -99,6 +99,42 @@ public class MesMdAutoCodeRecordServiceImplTest {
99 } 99 }
100 100
101 @Test 101 @Test
  102 + public void testGenerateAutoCode_retryWhenResultExists() {
  103 + // 准备参数
  104 + String ruleCode = "DV_MACHINERY_CODE";
  105 + MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(5L).setCode(ruleCode).setPadded(false);
  106 + MesMdAutoCodePartDO part1 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())
  107 + .setFixCharacter("M").setLength(1).setSort(1);
  108 + MesMdAutoCodePartDO part2 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.SERIAL_NUMBER.getType())
  109 + .setSerialStartNo(1).setSerialStep(1).setLength(5).setSort(2).setCycleFlag(false);
  110 + // mock
  111 + when(ruleService.getAutoCodeRuleByCode(ruleCode)).thenReturn(rule);
  112 + when(partService.getAutoCodePartListByRuleId(5L)).thenReturn(Arrays.asList(part1, part2));
  113 + when(fixedCharStrategy.generate(eq(part1), any(MesMdAutoCodeContext.class))).thenReturn("M");
  114 + long[] serialNoRef = {0L};
  115 + when(serialNumberStrategy.generate(eq(part2), any(MesMdAutoCodeContext.class))).thenAnswer(invocation -> {
  116 + MesMdAutoCodeContext context = invocation.getArgument(1);
  117 + long serialNo = ++serialNoRef[0];
  118 + context.setSerialNo(serialNo);
  119 + return String.format("%05d", serialNo);
  120 + });
  121 + when(recordMapper.selectByResult("M00001")).thenReturn(new MesMdAutoCodeRecordDO());
  122 + when(recordMapper.selectByResult("M00002")).thenReturn(new MesMdAutoCodeRecordDO());
  123 + when(recordMapper.selectByResult("M00003")).thenReturn(null);
  124 +
  125 + // 调用
  126 + String result = recordService.generateAutoCode(ruleCode, null);
  127 + // 断言
  128 + assertEquals("M00003", result);
  129 + ArgumentCaptor<MesMdAutoCodeRecordDO> captor = ArgumentCaptor.forClass(MesMdAutoCodeRecordDO.class);
  130 + verify(recordMapper).insert(captor.capture());
  131 + MesMdAutoCodeRecordDO record = captor.getValue();
  132 + assertEquals(5L, record.getRuleId());
  133 + assertEquals("M00003", record.getResult());
  134 + assertEquals(3L, record.getSerialNo());
  135 + }
  136 +
  137 + @Test
102 public void testGenerateAutoCode_withInputChar() { 138 public void testGenerateAutoCode_withInputChar() {
103 // 准备参数 139 // 准备参数
104 String ruleCode = "CLIENT_CODE"; 140 String ruleCode = "CLIENT_CODE";