| | |
| | | |
| | | /** |
| | | * 工程序号信息服务实现类 |
| | | * |
| | | * |
| | | * @author mes |
| | | * @since 2024-11-20 |
| | | * @since 2025-11-20 |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class EngineeringSequenceServiceImpl extends ServiceImpl<EngineeringSequenceMapper, EngineeringSequence> implements EngineeringSequenceService { |
| | | |
| | | // 日期格式化器(线程不安全,使用ThreadLocal保证线程安全) |
| | | private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd"); |
| | | // 修复:使用ThreadLocal保证DateTimeFormatter的线程安全 |
| | | private static final ThreadLocal<DateTimeFormatter> DATE_FORMATTER_THREAD_LOCAL = ThreadLocal.withInitial( |
| | | () -> DateTimeFormatter.ofPattern("yyMMdd") |
| | | ); |
| | | |
| | | // 重试间隔(毫秒),使用随机数避免并发请求同时重试 |
| | | private static final int RETRY_INTERVAL_MIN = 50; |
| | | private static final int RETRY_INTERVAL_MAX = 200; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public String generateAndSaveEngineeringId(Date date) { |
| | | // 乐观重试,防止并发写入造成重复键 |
| | | int retry = 0; |
| | | final int maxRetry = 5; |
| | | while (true) { |
| | | try { |
| | | // 1. 查询当天最大序号,并加行锁避免并发重复 |
| | | Integer maxSequence = baseMapper.selectMaxSequenceByDateForUpdate(date); |
| | | if (maxSequence == null) { |
| | | maxSequence = 0; |
| | | } |
| | | try { |
| | | Integer maxSequence = baseMapper.selectMaxSequenceByDate(date); |
| | | maxSequence = (maxSequence == null) ? 0 : maxSequence; |
| | | int newSequence = maxSequence + 1; |
| | | |
| | | // 2. 序号自增1 |
| | | int newSequence = maxSequence + 1; |
| | | LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); |
| | | String dateStr = DATE_FORMATTER_THREAD_LOCAL.get().format(localDate); |
| | | String engineeringId = "P" + dateStr + String.format("%02d", newSequence); |
| | | |
| | | // 3. 生成工程号:P + yyMMdd + 两位序号 |
| | | LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); |
| | | String dateStr = localDate.format(DATE_FORMATTER); |
| | | String engineeringId = "P" + dateStr + String.format("%02d", newSequence); |
| | | EngineeringSequence engineeringSequence = new EngineeringSequence(); |
| | | engineeringSequence.setEngineeringId(engineeringId); |
| | | engineeringSequence.setDate(date); |
| | | engineeringSequence.setSequence(newSequence); |
| | | engineeringSequence.setCreatedTime(new Date()); |
| | | engineeringSequence.setUpdatedTime(new Date()); |
| | | engineeringSequence.setCreatedBy("system"); |
| | | engineeringSequence.setUpdatedBy("system"); |
| | | |
| | | // 4. 保存到数据库 |
| | | EngineeringSequence engineeringSequence = new EngineeringSequence(); |
| | | engineeringSequence.setEngineeringId(engineeringId); |
| | | engineeringSequence.setDate(date); |
| | | engineeringSequence.setSequence(newSequence); |
| | | engineeringSequence.setCreatedTime(new Date()); |
| | | engineeringSequence.setUpdatedTime(new Date()); |
| | | engineeringSequence.setCreatedBy("system"); |
| | | engineeringSequence.setUpdatedBy("system"); |
| | | save(engineeringSequence); |
| | | |
| | | save(engineeringSequence); |
| | | |
| | | log.info("生成工程号成功: engineeringId={}, date={}, sequence={}", engineeringId, date, newSequence); |
| | | return engineeringId; |
| | | |
| | | } catch (DuplicateKeyException dup) { |
| | | // 并发导致的唯一键冲突,重试 |
| | | if (++retry > maxRetry) { |
| | | log.error("生成工程号重试超过上限, date={}", date, dup); |
| | | throw new RuntimeException("生成工程号失败", dup); |
| | | } |
| | | log.warn("工程号生成发生并发冲突,准备重试,第{}次,date={}", retry, date); |
| | | } catch (Exception e) { |
| | | log.error("生成工程号失败, date={}", date, e); |
| | | throw new RuntimeException("生成工程号失败", e); |
| | | } |
| | | log.info("生成工程号成功: engineeringId={}, date={}, sequence={}", engineeringId, date, newSequence); |
| | | return engineeringId; |
| | | } catch (DuplicateKeyException dup) { |
| | | log.error("生成工程号唯一键冲突: date={}", date, dup); |
| | | throw new RuntimeException("生成工程号失败", dup); |
| | | } catch (Exception e) { |
| | | log.error("生成工程号失败, date={}", date, e); |
| | | throw new RuntimeException("生成工程号失败", e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |