8 小时以前 d7cda8aec488caa2623547893d633063a09888e0
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -164,6 +164,9 @@
        purchaseLedger.setApprovalStatus(1);
        // 3. 新增或更新主表
        if (purchaseLedger.getId() == null) {
            if (!StringUtils.hasText(purchaseLedger.getPurchaseContractNumber())) {
                purchaseLedger.setPurchaseContractNumber(generatePurchaseContractNo(purchaseLedger.getEntryDate()));
            }
            purchaseLedgerMapper.insert(purchaseLedger);
        } else {
            // 删除采购审核,重新提交
@@ -1073,4 +1076,82 @@
        }
        return sb.toString();
    }
    private static final String PURCHASE_LOCK_PREFIX = "purchase_contract_no:";
    private static final long PURCHASE_LOCK_WAIT_TIMEOUT = 10;
    private static final long PURCHASE_LOCK_EXPIRE_TIME = 30;
    private String generatePurchaseContractNo(Date entryDate) {
        LocalDate currentDate = entryDate != null ? DateUtils.toLocalDate(entryDate) : LocalDate.now();
        String datePart = currentDate.format(DateTimeFormatter.BASIC_ISO_DATE);
        String lockKey = PURCHASE_LOCK_PREFIX + datePart;
        String lockValue = Thread.currentThread().getId() + "-" + System.nanoTime();
        try {
            long startWaitTime = System.currentTimeMillis();
            while (System.currentTimeMillis() - startWaitTime < PURCHASE_LOCK_WAIT_TIMEOUT * 1000) {
                Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, PURCHASE_LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
                if (Boolean.TRUE.equals(locked)) {
                    break;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("获取锁时被中断", e);
                }
            }
            if (Boolean.FALSE.equals(redisTemplate.hasKey(lockKey))) {
                throw new RuntimeException("获取采购合同编号生成锁失败:超时");
            }
            String prefix = "CG-" + datePart + "-";
            List<PurchaseLedger> existingList = purchaseLedgerMapper.selectList(
                    new LambdaQueryWrapper<PurchaseLedger>()
                            .likeRight(PurchaseLedger::getPurchaseContractNumber, prefix)
                            .select(PurchaseLedger::getPurchaseContractNumber));
            List<Integer> existingSequences = existingList.stream()
                    .map(PurchaseLedger::getPurchaseContractNumber)
                    .filter(Objects::nonNull)
                    .map(no -> {
                        int lastDash = no.lastIndexOf('-');
                        if (lastDash >= 0) {
                            try {
                                return Integer.parseInt(no.substring(lastDash + 1));
                            } catch (NumberFormatException e) {
                                return 0;
                            }
                        }
                        return 0;
                    })
                    .collect(Collectors.toList());
            int nextSequence = findFirstMissingSequence(existingSequences);
            return prefix + String.format("%03d", nextSequence);
        } finally {
            String luaScript = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";
            redisTemplate.execute(
                    new org.springframework.data.redis.core.script.DefaultRedisScript<>(luaScript, Long.class),
                    Collections.singletonList(lockKey),
                    lockValue
            );
        }
    }
    private int findFirstMissingSequence(List<Integer> sequences) {
        if (sequences.isEmpty()) {
            return 1;
        }
        sequences.sort(Integer::compareTo);
        int next = 1;
        for (int seq : sequences) {
            if (seq == next) {
                next++;
            } else if (seq > next) {
                break;
            }
        }
        return next;
    }
}