From d1be16755f078dea9f0fc63fba75edaa59b9e62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Sun, 18 Oct 2026 21:30:37 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=B6=E7=A5=A8=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E9=87=91=E8=9D=B6=E6=94=B9=E4=B8=BA=E9=80=90?= =?UTF-8?q?=E5=8D=95=E4=BA=8B=E5=8A=A1=E8=A1=8C=E9=94=81=E4=B8=B2=E8=A1=8C?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 批量编排逐单经 TransactionTemplate 开单据级短事务,推送首行 FOR UPDATE 行锁读并重查推送状态,已推送幂等跳过 - 同一登记的审批自动推/手动批量/定时重推并发时串行化,不再重复调金蝶保存 - 单笔失败只回滚自身不中断整批,失败状态由事务外独立补落,纳入定时重推 --- .../impl/InvoiceReceiptServiceImpl.java | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java index 9a1580fd..bb136e21 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java @@ -90,6 +90,7 @@ import org.springblade.transport.wrapper.InvoiceReceiptWrapper; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; import java.io.IOException; import java.io.InputStream; @@ -151,6 +152,8 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl failures = new ArrayList<>(); for (InvoiceReceipt receipt : receipts) { try { - pushOneReceivedInvoice(planByReceipt.get(receipt.getId()), kingdeeRowByInvoiceNo, apBillNoBySrcCode, context, autoSync); + // 逐单单据级事务:推送方法首行 FOR UPDATE 串行化同一登记的并发推送,单笔失败只回滚自身不中断整批 + transactionTemplate.executeWithoutResult(status -> pushOneReceivedInvoice( + planByReceipt.get(receipt.getId()), kingdeeRowByInvoiceNo, apBillNoBySrcCode, context, autoSync)); success++; } catch (RuntimeException pushException) { failures.add("发票" + Func.toStrWithEmpty(receipt.getInvoiceNo(), String.valueOf(receipt.getId())) + ":" + pushException.getMessage()); log.warn("推送金蝶收票单失败,收票登记id={}", receipt.getId(), pushException); + // 单笔事务已回滚,事务外独立补落"推送失败"状态,纳入定时重推(与付款/开票失败口径统一) + markReceivedInvoicePushFailed(receipt, planByReceipt.get(receipt.getId()), pushException, autoSync); } } String summary = "推送金蝶收票单完成:本批 " + receipts.size() + " 笔,成功 " + success + " 笔"; @@ -674,7 +681,21 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl kingdeeRows, Map apBillNoBySrcCode, KingdeeSyncContext context, boolean autoSync) { - InvoiceReceipt receipt = plan.receipt(); + // 行锁读:同一登记并发推送串行化(锁随本单事务提交释放,事务由批量编排的 TransactionTemplate 开启) + InvoiceReceipt receipt = baseMapper.selectOne(Wrappers.lambdaQuery() + .eq(InvoiceReceipt::getId, plan.receipt().getId()).last("FOR UPDATE")); + if (receipt == null || Objects.equals(receipt.getIsDeleted(), 1)) { + throw new ServiceException("收票登记不存在"); + } + // 锁后重查推送状态:并发场景另一事务已完成推送则幂等跳过,不再重复调金蝶 + if (KingdeeBfiConstant.RECEIVED_PUSH_PUSHED.equals(receipt.getKingdeePushStatus()) + || KingdeeBfiConstant.RECEIVED_PUSH_ASSIGNED.equals(receipt.getKingdeePushStatus())) { + log.info("收票登记[{}]金蝶收票单已推送(状态[{}]),幂等跳过", receipt.getInvoiceNo(), receipt.getKingdeePushStatus()); + return "发票" + Func.toStrWithEmpty(receipt.getInvoiceNo(), String.valueOf(receipt.getId())) + "已推送金蝶,幂等跳过"; + } + // 计划内登记实体以行锁后最新数据替换(并发下推送状态可能已被其他事务改写) + plan = new ReceivedInvoicePushPlan(receipt, plan.mirror(), plan.settlement(), plan.details(), + plan.apBillSrcCode(), plan.blockedReason()); log.info("收票登记[{}]推送金蝶收票单开始:id={},推送状态={},应付单防重键={}", receipt.getInvoiceNo(), receipt.getId(), receipt.getKingdeePushStatus(), plan.apBillSrcCode()); if (Func.isNotBlank(plan.blockedReason())) { @@ -765,6 +786,25 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl