fix(order): 修复订单取消和退款流程中的并发安全问题
- 添加订单ID空值检查,防止空指针异常 - 使用条件更新替代直接更新,避免并发导致的状态污染 - 扩展退款状态检查范围,包含更多退款相关状态 - 添加未支付订单退款验证,防止脏状态产生 - 增加重复退款申请检查,避免状态冲突 - 区分用户取消和系统取消的原因标记 - 优化更新逻辑确保状态一致性
This commit is contained in:
@@ -279,7 +279,10 @@ public class ShopOrderController extends BaseController {
|
||||
return fail("订单不存在");
|
||||
}
|
||||
// 退款相关操作单独走退款接口,便于做财务权限隔离
|
||||
if (Objects.equals(shopOrder.getOrderStatus(), 6)) {
|
||||
if (Objects.equals(shopOrder.getOrderStatus(), 4)
|
||||
|| Objects.equals(shopOrder.getOrderStatus(), 5)
|
||||
|| Objects.equals(shopOrder.getOrderStatus(), 6)
|
||||
|| Objects.equals(shopOrder.getOrderStatus(), 7)) {
|
||||
return fail("退款相关操作请使用退款接口: PUT /api/shop/shop-order/refund");
|
||||
}
|
||||
ShopOrder shopOrderNow = shopOrderService.getById(shopOrder.getOrderId());
|
||||
@@ -394,6 +397,20 @@ public class ShopOrderController extends BaseController {
|
||||
|
||||
// 申请退款:只记录申请时间/原因/金额(如有)
|
||||
if (Objects.equals(req.getOrderStatus(), 4)) {
|
||||
// 未支付订单不允许进入退款流程(否则会出现“取消订单/超时取消后变成退款申请中”的严重脏状态)
|
||||
if (!Boolean.TRUE.equals(current.getPayStatus())) {
|
||||
return fail("订单未支付,无法申请退款");
|
||||
}
|
||||
if (Objects.equals(current.getOrderStatus(), 2)) {
|
||||
return fail("订单已取消,无法申请退款");
|
||||
}
|
||||
if (Objects.equals(current.getOrderStatus(), 4)
|
||||
|| Objects.equals(current.getOrderStatus(), 5)
|
||||
|| Objects.equals(current.getOrderStatus(), 6)
|
||||
|| Objects.equals(current.getOrderStatus(), 7)) {
|
||||
return fail("订单已在退款流程中,请勿重复申请");
|
||||
}
|
||||
|
||||
ShopOrder patch = new ShopOrder();
|
||||
patch.setOrderId(req.getOrderId());
|
||||
patch.setOrderStatus(4);
|
||||
@@ -506,7 +523,10 @@ public class ShopOrderController extends BaseController {
|
||||
if (batchParam != null && batchParam.getData() != null) {
|
||||
Integer status = batchParam.getData().getOrderStatus();
|
||||
// 退款相关操作单独走退款接口,避免绕过财务权限
|
||||
if (Objects.equals(status, 4) || Objects.equals(status, 6)) {
|
||||
if (Objects.equals(status, 4)
|
||||
|| Objects.equals(status, 5)
|
||||
|| Objects.equals(status, 6)
|
||||
|| Objects.equals(status, 7)) {
|
||||
return fail("退款相关操作请使用退款接口: PUT /api/shop/shop-order/refund");
|
||||
}
|
||||
}
|
||||
@@ -571,6 +591,8 @@ public class ShopOrderController extends BaseController {
|
||||
return fail("订单状态不允许取消");
|
||||
}
|
||||
|
||||
// 标记为用户主动取消(定时任务/系统取消会走默认文案)
|
||||
order.setCancelReason("用户取消");
|
||||
boolean success = orderCancelService.cancelOrder(order);
|
||||
if (success) {
|
||||
return success("订单取消成功");
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.gxwebsoft.shop.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.gxwebsoft.common.core.annotation.IgnoreTenant;
|
||||
import com.gxwebsoft.shop.entity.*;
|
||||
import com.gxwebsoft.shop.service.*;
|
||||
@@ -44,6 +46,11 @@ public class OrderCancelServiceImpl implements OrderCancelService {
|
||||
try {
|
||||
log.info("开始取消订单,订单号:{},订单ID:{}", order.getOrderNo(), order.getOrderId());
|
||||
|
||||
if (order.getOrderId() == null) {
|
||||
log.warn("订单ID为空,无法取消");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. 检查订单状态
|
||||
if (order.getPayStatus() != null && order.getPayStatus()) {
|
||||
log.warn("订单已支付,无法取消,订单号:{}", order.getOrderNo());
|
||||
@@ -55,17 +62,31 @@ public class OrderCancelServiceImpl implements OrderCancelService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 更新订单状态为已取消
|
||||
order.setOrderStatus(2); // 2表示已取消
|
||||
order.setCancelTime(LocalDateTime.now());
|
||||
order.setCancelReason("系统自动取消(超时未支付)");
|
||||
// 2. 更新订单状态为已取消(只更新必要字段 + 加条件,避免并发/越权导致“取消后又被改成退款中”等脏状态)
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String reason = StrUtil.isNotBlank(order.getCancelReason())
|
||||
? order.getCancelReason()
|
||||
: "系统自动取消(超时未支付)";
|
||||
|
||||
boolean updateSuccess = shopOrderService.updateById(order);
|
||||
boolean updateSuccess = shopOrderService.update(
|
||||
new LambdaUpdateWrapper<ShopOrder>()
|
||||
.eq(ShopOrder::getOrderId, order.getOrderId())
|
||||
.eq(ShopOrder::getPayStatus, false)
|
||||
.eq(ShopOrder::getOrderStatus, 0)
|
||||
.set(ShopOrder::getOrderStatus, 2) // 2表示已取消
|
||||
.set(ShopOrder::getCancelTime, now)
|
||||
.set(ShopOrder::getCancelReason, reason)
|
||||
);
|
||||
if (!updateSuccess) {
|
||||
log.error("更新订单状态失败,订单号:{}", order.getOrderNo());
|
||||
log.error("更新订单状态失败(可能已被支付/退款/取消),订单号:{},订单ID:{}", order.getOrderNo(), order.getOrderId());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 让后续库存/优惠券逻辑使用最新状态(不再依赖 updateById 回写)
|
||||
order.setOrderStatus(2);
|
||||
order.setCancelTime(now);
|
||||
order.setCancelReason(reason);
|
||||
|
||||
// 3. 回退库存
|
||||
boolean stockRestored = restoreOrderStock(order);
|
||||
if (!stockRestored) {
|
||||
|
||||
Reference in New Issue
Block a user