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,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"; |