fix(ticket): 解决订单状态更新和并发处理问题
- 在订单完成后添加状态检查和更新,避免重复处理 - 增加行锁机制防止多实例并发执行导致的重复发放 - 对票券模板查询添加确定性排序确保一致性 - 添加详细日志记录起始送水核销和套票发放过程 - 优化查询条件防止重复扫描已处理订单
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user