fix(ticket): 解决订单状态更新和并发处理问题

- 在订单完成后添加状态检查和更新,避免重复处理
- 增加行锁机制防止多实例并发执行导致的重复发放
- 对票券模板查询添加确定性排序确保一致性
- 添加详细日志记录起始送水核销和套票发放过程
- 优化查询条件防止重复扫描已处理订单
This commit is contained in:
2026-02-27 22:06:19 +08:00
parent ecbe4fbaea
commit 3cadaab214

View File

@@ -163,11 +163,13 @@ public class GltTicketIssueService {
if (shouldCompleteOrder) {
LocalDateTime now = LocalDateTime.now();
// 任务执行完后将订单置为“已完成”order_status=1
// 任务执行完后将订单置为“已完成”,避免后续扫描重复处理(幂等虽可挡住,但会产生大量无意义查询)。
shopOrderService.update(
new LambdaUpdateWrapper<ShopOrder>()
.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<ShopOrder>()
.eq(ShopOrder::getOrderId, order.getOrderId())
.eq(ShopOrder::getTenantId, tenantId)
.last("for update")
);
}
// 同一商品允许存在多条模板记录(历史数据/误操作)。为避免取到“错误模板”,这里做确定性排序取第一条。
// 排序规则与后台 getByGoodsId 保持一致sortNumber 越小越靠前,其次取最新创建时间。
GltTicketTemplate template = gltTicketTemplateService.getOne(
new LambdaQueryWrapper<GltTicketTemplate>()
.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;
}