Commit 633bd70dfe157fbbb00bf5c9860f506747e77e10
1 parent
08f7c33c
fix(mes): 自动编码重复时支持自动重试,修复流水号落后导致的生成失败
当 Redis 流水号落后于历史数据时,生成的编码可能与已有记录重复,原逻辑会直接抛出 AUTO_CODE_GENERATE_FAILED 异常。现改为循环重试(最多 10 次),重复时自动跳过并 生成下一个可用编码,仅在重试耗尽后才抛异常。 - 抽取 generateCode 方法,分离编码拼接/补齐与重复校验、记录保存逻辑 - 补充重复重试场景的单元测试
Showing
2 changed files
with
67 additions
and
16 deletions
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"; | ... | ... |