diff --git a/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java b/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java index 2751277..70fd33b 100644 --- a/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java +++ b/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java @@ -163,11 +163,13 @@ public class GltTicketIssueService { if (shouldCompleteOrder) { LocalDateTime now = LocalDateTime.now(); - // 任务执行完后将订单置为“已完成”:order_status=1 + // 任务执行完后将订单置为“已完成”,避免后续扫描重复处理(幂等虽可挡住,但会产生大量无意义查询)。 shopOrderService.update( new LambdaUpdateWrapper() .eq(ShopOrder::getOrderId, order.getOrderId()) .eq(ShopOrder::getTenantId, tenantId) + .eq(ShopOrder::getOrderStatus, 0) + .set(ShopOrder::getOrderStatus, 1) .set(ShopOrder::getHasTakeGift, true) .set(ShopOrder::getUpdateTime, now) ); @@ -186,11 +188,29 @@ public class GltTicketIssueService { return IssueOutcome.SKIPPED; } + // 并发幂等兜底: + // - 该任务可能在多实例部署下并发执行;若不加锁,在没有唯一索引的情况下可能重复发放/重复核销。 + // - 这里先对商城订单行加行锁,保证同一订单在同一时刻只会被一个事务处理。 + // (注意:需数据库支持 SELECT ... FOR UPDATE,且 shop_order.order_id 为主键/有索引) + if (order.getOrderId() != null) { + shopOrderService.getOne( + new LambdaQueryWrapper() + .eq(ShopOrder::getOrderId, order.getOrderId()) + .eq(ShopOrder::getTenantId, tenantId) + .last("for update") + ); + } + + // 同一商品允许存在多条模板记录(历史数据/误操作)。为避免取到“错误模板”,这里做确定性排序取第一条。 + // 排序规则与后台 getByGoodsId 保持一致:sortNumber 越小越靠前,其次取最新创建时间。 GltTicketTemplate template = gltTicketTemplateService.getOne( new LambdaQueryWrapper() .eq(GltTicketTemplate::getTenantId, tenantId) .eq(GltTicketTemplate::getGoodsId, og.getGoodsId()) .eq(GltTicketTemplate::getDeleted, 0) + .orderByAsc(GltTicketTemplate::getSortNumber) + .orderByDesc(GltTicketTemplate::getCreateTime) + .orderByDesc(GltTicketTemplate::getId) .last("limit 1") ); @@ -313,6 +333,8 @@ public class GltTicketIssueService { int usedBefore = userTicket.getUsedQty() != null ? userTicket.getUsedQty() : 0; int toUse = Math.min(startSendQty, availableBefore); if (toUse > 0) { + log.info("起始送水自动核销 - tenantId={}, orderNo={}, templateId={}, userTicketId={}, startSendQty={}, availableBefore={}, toUse={}", + tenantId, order.getOrderNo(), template.getId(), userTicket.getId(), startSendQty, availableBefore, toUse); userTicket.setAvailableQty(availableBefore - toUse); userTicket.setUsedQty(usedBefore + toUse); userTicket.setUpdateTime(now); @@ -350,8 +372,8 @@ public class GltTicketIssueService { } } - log.info("套票发放成功 - tenantId={}, orderNo={}, orderGoodsId={}, templateId={}, userTicketId={}, totalQty={}", - tenantId, order.getOrderNo(), og.getId(), template.getId(), userTicket.getId(), totalQty); + log.info("套票发放成功 - tenantId={}, orderNo={}, orderGoodsId={}, templateId={}, userTicketId={}, orderGoodsQty={}, buyQty={}, giftQty={}, startSendQty={}, totalQty={}", + tenantId, order.getOrderNo(), og.getId(), template.getId(), userTicket.getId(), og.getTotalNum(), buyQty, giftQty, startSendQty, totalQty); return IssueOutcome.ISSUED; }