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 32 @Validated
33 33 public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordService {
34 34  
  35 + private static final int GENERATE_MAX_RETRY_COUNT = 10;
  36 +
35 37 @Resource
36 38 private MesMdAutoCodeRecordMapper recordMapper;
37 39  
... ... @@ -63,10 +65,34 @@ public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordServic
63 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 96 StringBuilder codeBuilder = new StringBuilder();
71 97 for (MesMdAutoCodePartDO part : parts) {
72 98 MesMdAutoCodePartStrategy strategy = strategyMap.get(part.getType());
... ... @@ -78,22 +104,11 @@ public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordServic
78 104 // 拼接分段编码
79 105 codeBuilder.append(partCode);
80 106 }
81   - // 2.3 补齐处理
  107 + // 补齐处理
82 108 String result = codeBuilder.toString();
83 109 if (Boolean.TRUE.equals(rule.getPadded()) && rule.getMaxLength() != null) {
84 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 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 99 }
100 100  
101 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 138 public void testGenerateAutoCode_withInputChar() {
103 139 // 准备参数
104 140 String ruleCode = "CLIENT_CODE";
... ...