自动取消订单任务
This commit is contained in:
@@ -33,6 +33,11 @@ public class OrderConfigProperties {
|
||||
*/
|
||||
private DefaultConfig defaultConfig = new DefaultConfig();
|
||||
|
||||
/**
|
||||
* 订单自动取消配置
|
||||
*/
|
||||
private AutoCancel autoCancel = new AutoCancel();
|
||||
|
||||
@Data
|
||||
public static class TestAccount {
|
||||
/**
|
||||
@@ -112,6 +117,71 @@ public class OrderConfigProperties {
|
||||
testAccount.getPhoneNumbers().contains(phone);
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class AutoCancel {
|
||||
/**
|
||||
* 是否启用自动取消功能
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 默认超时时间(分钟)
|
||||
*/
|
||||
private Integer defaultTimeoutMinutes = 30;
|
||||
|
||||
/**
|
||||
* 定时任务检查间隔(分钟)
|
||||
*/
|
||||
private Integer checkIntervalMinutes = 5;
|
||||
|
||||
/**
|
||||
* 批量处理大小
|
||||
*/
|
||||
private Integer batchSize = 100;
|
||||
|
||||
/**
|
||||
* 租户特殊配置
|
||||
*/
|
||||
private List<TenantCancelConfig> tenantConfigs;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TenantCancelConfig {
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private Integer tenantId;
|
||||
|
||||
/**
|
||||
* 租户名称
|
||||
*/
|
||||
private String tenantName;
|
||||
|
||||
/**
|
||||
* 超时时间(分钟)
|
||||
*/
|
||||
private Integer timeoutMinutes;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定租户的超时时间
|
||||
*/
|
||||
public Integer getTimeoutMinutes(Integer tenantId) {
|
||||
if (autoCancel.getTenantConfigs() != null) {
|
||||
for (TenantCancelConfig config : autoCancel.getTenantConfigs()) {
|
||||
if (config.isEnabled() && config.getTenantId().equals(tenantId)) {
|
||||
return config.getTimeoutMinutes();
|
||||
}
|
||||
}
|
||||
}
|
||||
return autoCancel.getDefaultTimeoutMinutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户规则
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,8 @@ import com.gxwebsoft.common.system.entity.Payment;
|
||||
import com.gxwebsoft.shop.service.ShopOrderGoodsService;
|
||||
import com.gxwebsoft.shop.service.ShopOrderService;
|
||||
import com.gxwebsoft.shop.service.OrderBusinessService;
|
||||
import com.gxwebsoft.shop.service.OrderCancelService;
|
||||
import com.gxwebsoft.shop.task.OrderAutoCancelTask;
|
||||
import com.gxwebsoft.shop.entity.ShopOrder;
|
||||
import com.gxwebsoft.shop.param.ShopOrderParam;
|
||||
import com.gxwebsoft.shop.dto.OrderCreateRequest;
|
||||
@@ -36,6 +38,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -63,6 +67,10 @@ public class ShopOrderController extends BaseController {
|
||||
@Resource
|
||||
private OrderBusinessService orderBusinessService;
|
||||
@Resource
|
||||
private OrderCancelService orderCancelService;
|
||||
@Resource
|
||||
private OrderAutoCancelTask orderAutoCancelTask;
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
@Resource
|
||||
private ConfigProperties conf;
|
||||
@@ -214,6 +222,74 @@ public class ShopOrderController extends BaseController {
|
||||
return success(shopOrderService.total());
|
||||
}
|
||||
|
||||
@Operation(summary = "取消订单")
|
||||
@PutMapping("/cancel/{id}")
|
||||
public ApiResult<?> cancelOrder(@PathVariable("id") Integer id) {
|
||||
try {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("用户未登录");
|
||||
}
|
||||
|
||||
ShopOrder order = shopOrderService.getById(id);
|
||||
if (order == null) {
|
||||
return fail("订单不存在");
|
||||
}
|
||||
|
||||
// 检查订单是否属于当前用户(非管理员用户)
|
||||
if (!loginUser.getUserId().equals(order.getUserId()) &&
|
||||
!hasOrderCancelAuthority()) {
|
||||
return fail("无权限取消此订单");
|
||||
}
|
||||
|
||||
// 检查订单状态
|
||||
if (order.getPayStatus() != null && order.getPayStatus()) {
|
||||
return fail("订单已支付,无法取消");
|
||||
}
|
||||
|
||||
if (order.getOrderStatus() != null && order.getOrderStatus() != 0) {
|
||||
return fail("订单状态不允许取消");
|
||||
}
|
||||
|
||||
boolean success = orderCancelService.cancelOrder(order);
|
||||
if (success) {
|
||||
return success("订单取消成功");
|
||||
} else {
|
||||
return fail("订单取消失败");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("取消订单失败,订单ID:{}", id, e);
|
||||
return fail("取消订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopOrder:manage')")
|
||||
@Operation(summary = "手动触发订单自动取消任务(管理员)")
|
||||
@PostMapping("/auto-cancel/trigger")
|
||||
public ApiResult<?> triggerAutoCancelTask() {
|
||||
try {
|
||||
orderAutoCancelTask.manualCancelExpiredOrders();
|
||||
return success("自动取消任务已触发");
|
||||
} catch (Exception e) {
|
||||
logger.error("触发自动取消任务失败", e);
|
||||
return fail("触发失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopOrder:manage')")
|
||||
@Operation(summary = "获取自动取消任务状态(管理员)")
|
||||
@GetMapping("/auto-cancel/status")
|
||||
public ApiResult<?> getAutoCancelTaskStatus() {
|
||||
try {
|
||||
String status = orderAutoCancelTask.getTaskStatus();
|
||||
return success(status);
|
||||
} catch (Exception e) {
|
||||
logger.error("获取自动取消任务状态失败", e);
|
||||
return fail("获取状态失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(description = "异步通知11")
|
||||
@PostMapping("/notify/{tenantId}")
|
||||
public String wxNotify(@RequestHeader Map<String, String> header, @RequestBody String body, @PathVariable("tenantId") Integer tenantId) {
|
||||
@@ -379,4 +455,27 @@ public class ShopOrderController extends BaseController {
|
||||
return "fail";
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有订单取消权限
|
||||
*/
|
||||
private boolean hasOrderCancelAuthority() {
|
||||
try {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否有管理员权限
|
||||
return authentication.getAuthorities().stream()
|
||||
.anyMatch(authority ->
|
||||
authority.getAuthority().equals("shop:shopOrder:cancel") ||
|
||||
authority.getAuthority().equals("shop:shopOrder:update") ||
|
||||
authority.getAuthority().equals("ROLE_ADMIN") ||
|
||||
authority.getAuthority().equals("shop:shopOrder:manage"));
|
||||
} catch (Exception e) {
|
||||
logger.warn("检查订单取消权限时发生异常", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -206,6 +206,13 @@ public class ShopOrder implements Serializable {
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime refundApplyTime;
|
||||
|
||||
@Schema(description = "取消时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime cancelTime;
|
||||
|
||||
@Schema(description = "取消原因")
|
||||
private String cancelReason;
|
||||
|
||||
@Schema(description = "过期时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime expirationTime;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.gxwebsoft.shop.service;
|
||||
|
||||
import com.gxwebsoft.shop.entity.ShopOrder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单取消服务接口
|
||||
*
|
||||
* @author WebSoft
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
public interface OrderCancelService {
|
||||
|
||||
/**
|
||||
* 取消单个订单
|
||||
*
|
||||
* @param order 订单对象
|
||||
* @return 是否取消成功
|
||||
*/
|
||||
boolean cancelOrder(ShopOrder order);
|
||||
|
||||
/**
|
||||
* 批量取消订单
|
||||
*
|
||||
* @param orders 订单列表
|
||||
* @return 成功取消的订单数量
|
||||
*/
|
||||
int batchCancelOrders(List<ShopOrder> orders);
|
||||
|
||||
/**
|
||||
* 查找超时的待付款订单
|
||||
*
|
||||
* @param timeoutMinutes 超时时间(分钟)
|
||||
* @param batchSize 批量大小
|
||||
* @return 超时订单列表
|
||||
*/
|
||||
List<ShopOrder> findExpiredUnpaidOrders(Integer timeoutMinutes, Integer batchSize);
|
||||
|
||||
/**
|
||||
* 查找指定租户的超时订单
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param timeoutMinutes 超时时间(分钟)
|
||||
* @param batchSize 批量大小
|
||||
* @return 超时订单列表
|
||||
*/
|
||||
List<ShopOrder> findExpiredUnpaidOrdersByTenant(Integer tenantId, Integer timeoutMinutes, Integer batchSize);
|
||||
|
||||
/**
|
||||
* 回退订单库存
|
||||
*
|
||||
* @param order 订单对象
|
||||
* @return 是否回退成功
|
||||
*/
|
||||
boolean restoreOrderStock(ShopOrder order);
|
||||
|
||||
/**
|
||||
* 退还订单优惠券
|
||||
*
|
||||
* @param order 订单对象
|
||||
* @return 是否退还成功
|
||||
*/
|
||||
boolean returnOrderCoupon(ShopOrder order);
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package com.gxwebsoft.shop.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.gxwebsoft.shop.entity.*;
|
||||
import com.gxwebsoft.shop.service.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单取消服务实现
|
||||
*
|
||||
* @author WebSoft
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OrderCancelServiceImpl implements OrderCancelService {
|
||||
|
||||
@Autowired
|
||||
private ShopOrderService shopOrderService;
|
||||
|
||||
@Autowired
|
||||
private ShopOrderGoodsService shopOrderGoodsService;
|
||||
|
||||
@Autowired
|
||||
private ShopGoodsService shopGoodsService;
|
||||
|
||||
@Autowired
|
||||
private ShopGoodsSkuService shopGoodsSkuService;
|
||||
|
||||
@Autowired
|
||||
private CouponStatusService couponStatusService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean cancelOrder(ShopOrder order) {
|
||||
try {
|
||||
log.info("开始取消订单,订单号:{},订单ID:{}", order.getOrderNo(), order.getOrderId());
|
||||
|
||||
// 1. 检查订单状态
|
||||
if (order.getPayStatus() != null && order.getPayStatus()) {
|
||||
log.warn("订单已支付,无法取消,订单号:{}", order.getOrderNo());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (order.getOrderStatus() != null && order.getOrderStatus() != 0) {
|
||||
log.warn("订单状态不是待支付,无法取消,订单号:{},当前状态:{}", order.getOrderNo(), order.getOrderStatus());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 更新订单状态为已取消
|
||||
order.setOrderStatus(2); // 2表示已取消
|
||||
order.setCancelTime(LocalDateTime.now());
|
||||
order.setCancelReason("系统自动取消(超时未支付)");
|
||||
|
||||
boolean updateSuccess = shopOrderService.updateById(order);
|
||||
if (!updateSuccess) {
|
||||
log.error("更新订单状态失败,订单号:{}", order.getOrderNo());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 回退库存
|
||||
boolean stockRestored = restoreOrderStock(order);
|
||||
if (!stockRestored) {
|
||||
log.error("回退库存失败,订单号:{}", order.getOrderNo());
|
||||
// 注意:这里不直接返回false,因为订单状态已经更新,需要记录错误但继续处理
|
||||
}
|
||||
|
||||
// 4. 退还优惠券
|
||||
boolean couponReturned = returnOrderCoupon(order);
|
||||
if (!couponReturned) {
|
||||
log.error("退还优惠券失败,订单号:{}", order.getOrderNo());
|
||||
// 同样不直接返回false
|
||||
}
|
||||
|
||||
log.info("订单取消成功,订单号:{},库存回退:{},优惠券退还:{}",
|
||||
order.getOrderNo(), stockRestored, couponReturned);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("取消订单失败,订单号:{}", order.getOrderNo(), e);
|
||||
throw e; // 重新抛出异常,触发事务回滚
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int batchCancelOrders(List<ShopOrder> orders) {
|
||||
if (orders == null || orders.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int successCount = 0;
|
||||
for (ShopOrder order : orders) {
|
||||
try {
|
||||
if (cancelOrder(order)) {
|
||||
successCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("批量取消订单时发生错误,订单号:{}", order.getOrderNo(), e);
|
||||
// 继续处理下一个订单
|
||||
}
|
||||
}
|
||||
|
||||
log.info("批量取消订单完成,总数:{},成功:{}", orders.size(), successCount);
|
||||
return successCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ShopOrder> findExpiredUnpaidOrders(Integer timeoutMinutes, Integer batchSize) {
|
||||
LocalDateTime expireTime = LocalDateTime.now().minusMinutes(timeoutMinutes);
|
||||
|
||||
LambdaQueryWrapper<ShopOrder> queryWrapper = new LambdaQueryWrapper<ShopOrder>()
|
||||
.eq(ShopOrder::getPayStatus, false) // 未支付
|
||||
.eq(ShopOrder::getOrderStatus, 0) // 待支付状态
|
||||
.lt(ShopOrder::getCreateTime, expireTime) // 创建时间小于过期时间
|
||||
.orderByAsc(ShopOrder::getCreateTime)
|
||||
.last("LIMIT " + batchSize);
|
||||
|
||||
return shopOrderService.list(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ShopOrder> findExpiredUnpaidOrdersByTenant(Integer tenantId, Integer timeoutMinutes, Integer batchSize) {
|
||||
LocalDateTime expireTime = LocalDateTime.now().minusMinutes(timeoutMinutes);
|
||||
|
||||
LambdaQueryWrapper<ShopOrder> queryWrapper = new LambdaQueryWrapper<ShopOrder>()
|
||||
.eq(ShopOrder::getTenantId, tenantId)
|
||||
.eq(ShopOrder::getPayStatus, false) // 未支付
|
||||
.eq(ShopOrder::getOrderStatus, 0) // 待支付状态
|
||||
.lt(ShopOrder::getCreateTime, expireTime) // 创建时间小于过期时间
|
||||
.orderByAsc(ShopOrder::getCreateTime)
|
||||
.last("LIMIT " + batchSize);
|
||||
|
||||
return shopOrderService.list(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean restoreOrderStock(ShopOrder order) {
|
||||
try {
|
||||
// 获取订单商品信息
|
||||
List<ShopOrderGoods> orderGoods = shopOrderGoodsService.list(
|
||||
new LambdaQueryWrapper<ShopOrderGoods>()
|
||||
.eq(ShopOrderGoods::getOrderId, order.getOrderId())
|
||||
);
|
||||
|
||||
if (orderGoods == null || orderGoods.isEmpty()) {
|
||||
log.warn("订单没有商品信息,订单号:{}", order.getOrderNo());
|
||||
return true; // 没有商品信息也算成功
|
||||
}
|
||||
|
||||
for (ShopOrderGoods orderGood : orderGoods) {
|
||||
if (orderGood.getSkuId() != null && orderGood.getSkuId() > 0) {
|
||||
// 多规格商品,恢复SKU库存
|
||||
restoreSkuStock(orderGood);
|
||||
} else {
|
||||
// 单规格商品,恢复商品库存
|
||||
restoreGoodsStock(orderGood);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("订单库存回退成功,订单号:{},商品数量:{}", order.getOrderNo(), orderGoods.size());
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("回退订单库存失败,订单号:{}", order.getOrderNo(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean returnOrderCoupon(ShopOrder order) {
|
||||
try {
|
||||
if (order.getCouponId() == null || order.getCouponId() <= 0) {
|
||||
log.debug("订单未使用优惠券,订单号:{}", order.getOrderNo());
|
||||
return true; // 没有使用优惠券也算成功
|
||||
}
|
||||
|
||||
boolean success = couponStatusService.returnCoupon(order.getOrderId());
|
||||
if (success) {
|
||||
log.info("订单优惠券退还成功,订单号:{},优惠券ID:{}", order.getOrderNo(), order.getCouponId());
|
||||
} else {
|
||||
log.warn("订单优惠券退还失败,订单号:{},优惠券ID:{}", order.getOrderNo(), order.getCouponId());
|
||||
}
|
||||
return success;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("退还订单优惠券失败,订单号:{}", order.getOrderNo(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复SKU库存
|
||||
*/
|
||||
private void restoreSkuStock(ShopOrderGoods orderGoods) {
|
||||
ShopGoodsSku sku = shopGoodsSkuService.getById(orderGoods.getSkuId());
|
||||
if (sku != null) {
|
||||
int newStock = (sku.getStock() != null ? sku.getStock() : 0) + orderGoods.getTotalNum();
|
||||
sku.setStock(newStock);
|
||||
shopGoodsSkuService.updateById(sku);
|
||||
log.debug("恢复SKU库存 - SKU ID:{},恢复数量:{},当前库存:{}",
|
||||
orderGoods.getSkuId(), orderGoods.getTotalNum(), newStock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复商品库存
|
||||
*/
|
||||
private void restoreGoodsStock(ShopOrderGoods orderGoods) {
|
||||
ShopGoods goods = shopGoodsService.getById(orderGoods.getGoodsId());
|
||||
if (goods != null) {
|
||||
int newStock = (goods.getStock() != null ? goods.getStock() : 0) + orderGoods.getTotalNum();
|
||||
goods.setStock(newStock);
|
||||
shopGoodsService.updateById(goods);
|
||||
log.debug("恢复商品库存 - 商品ID:{},恢复数量:{},当前库存:{}",
|
||||
orderGoods.getGoodsId(), orderGoods.getTotalNum(), newStock);
|
||||
}
|
||||
}
|
||||
}
|
||||
193
src/main/java/com/gxwebsoft/shop/task/OrderAutoCancelTask.java
Normal file
193
src/main/java/com/gxwebsoft/shop/task/OrderAutoCancelTask.java
Normal file
@@ -0,0 +1,193 @@
|
||||
package com.gxwebsoft.shop.task;
|
||||
|
||||
import com.gxwebsoft.shop.config.OrderConfigProperties;
|
||||
import com.gxwebsoft.shop.entity.ShopOrder;
|
||||
import com.gxwebsoft.shop.service.OrderCancelService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单自动取消定时任务
|
||||
*
|
||||
* @author WebSoft
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = "shop.order.auto-cancel", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class OrderAutoCancelTask {
|
||||
|
||||
@Autowired
|
||||
private OrderCancelService orderCancelService;
|
||||
|
||||
@Autowired
|
||||
private OrderConfigProperties orderConfig;
|
||||
|
||||
@Value("${spring.profiles.active:dev}")
|
||||
private String activeProfile;
|
||||
|
||||
/**
|
||||
* 自动取消超时订单
|
||||
* 生产环境:每5分钟执行一次
|
||||
* 开发环境:每1分钟执行一次(便于测试)
|
||||
*/
|
||||
@Scheduled(cron = "${shop.order.auto-cancel.cron:0 */5 * * * ?}")
|
||||
public void cancelExpiredOrders() {
|
||||
if (!orderConfig.getAutoCancel().isEnabled()) {
|
||||
log.debug("订单自动取消功能已禁用");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("开始执行订单自动取消任务...");
|
||||
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
int totalCancelledCount = 0;
|
||||
|
||||
// 处理默认配置的订单
|
||||
int defaultCancelledCount = processDefaultTimeoutOrders();
|
||||
totalCancelledCount += defaultCancelledCount;
|
||||
|
||||
// 处理租户特殊配置的订单
|
||||
int tenantCancelledCount = processTenantSpecificOrders();
|
||||
totalCancelledCount += tenantCancelledCount;
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
long duration = endTime - startTime;
|
||||
|
||||
log.info("订单自动取消任务完成,总取消数量: {},默认配置: {},租户配置: {},耗时: {}ms",
|
||||
totalCancelledCount, defaultCancelledCount, tenantCancelledCount, duration);
|
||||
|
||||
// 开发环境输出更详细的日志
|
||||
if ("dev".equals(activeProfile)) {
|
||||
log.debug("开发环境 - 订单自动取消详情: 总共取消{}个订单", totalCancelledCount);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("订单自动取消任务执行失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理使用默认超时配置的订单
|
||||
*/
|
||||
private int processDefaultTimeoutOrders() {
|
||||
try {
|
||||
Integer defaultTimeout = orderConfig.getAutoCancel().getDefaultTimeoutMinutes();
|
||||
Integer batchSize = orderConfig.getAutoCancel().getBatchSize();
|
||||
|
||||
log.debug("处理默认超时订单,超时时间: {}分钟,批量大小: {}", defaultTimeout, batchSize);
|
||||
|
||||
List<ShopOrder> expiredOrders = orderCancelService.findExpiredUnpaidOrders(defaultTimeout, batchSize);
|
||||
|
||||
if (expiredOrders.isEmpty()) {
|
||||
log.debug("没有找到使用默认配置的超时订单");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 过滤掉有特殊租户配置的订单
|
||||
List<ShopOrder> ordersToCancel = filterOrdersWithoutTenantConfig(expiredOrders);
|
||||
|
||||
if (ordersToCancel.isEmpty()) {
|
||||
log.debug("过滤后没有需要使用默认配置取消的订单");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cancelledCount = orderCancelService.batchCancelOrders(ordersToCancel);
|
||||
log.info("默认配置取消订单完成,找到: {}个,过滤后: {}个,成功取消: {}个",
|
||||
expiredOrders.size(), ordersToCancel.size(), cancelledCount);
|
||||
|
||||
return cancelledCount;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理默认超时订单失败", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理租户特殊配置的订单
|
||||
*/
|
||||
private int processTenantSpecificOrders() {
|
||||
try {
|
||||
List<OrderConfigProperties.TenantCancelConfig> tenantConfigs = orderConfig.getAutoCancel().getTenantConfigs();
|
||||
if (tenantConfigs == null || tenantConfigs.isEmpty()) {
|
||||
log.debug("没有配置租户特殊超时规则");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int totalCancelledCount = 0;
|
||||
Integer batchSize = orderConfig.getAutoCancel().getBatchSize();
|
||||
|
||||
for (OrderConfigProperties.TenantCancelConfig tenantConfig : tenantConfigs) {
|
||||
if (!tenantConfig.isEnabled()) {
|
||||
log.debug("租户{}的自动取消功能已禁用", tenantConfig.getTenantId());
|
||||
continue;
|
||||
}
|
||||
|
||||
log.debug("处理租户{}的超时订单,超时时间: {}分钟",
|
||||
tenantConfig.getTenantId(), tenantConfig.getTimeoutMinutes());
|
||||
|
||||
List<ShopOrder> tenantExpiredOrders = orderCancelService.findExpiredUnpaidOrdersByTenant(
|
||||
tenantConfig.getTenantId(), tenantConfig.getTimeoutMinutes(), batchSize);
|
||||
|
||||
if (!tenantExpiredOrders.isEmpty()) {
|
||||
int cancelledCount = orderCancelService.batchCancelOrders(tenantExpiredOrders);
|
||||
totalCancelledCount += cancelledCount;
|
||||
|
||||
log.info("租户{}取消订单完成,找到: {}个,成功取消: {}个",
|
||||
tenantConfig.getTenantId(), tenantExpiredOrders.size(), cancelledCount);
|
||||
}
|
||||
}
|
||||
|
||||
return totalCancelledCount;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理租户特殊配置订单失败", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤掉有租户特殊配置的订单
|
||||
*/
|
||||
private List<ShopOrder> filterOrdersWithoutTenantConfig(List<ShopOrder> orders) {
|
||||
List<OrderConfigProperties.TenantCancelConfig> tenantConfigs = orderConfig.getAutoCancel().getTenantConfigs();
|
||||
if (tenantConfigs == null || tenantConfigs.isEmpty()) {
|
||||
return orders;
|
||||
}
|
||||
|
||||
return orders.stream()
|
||||
.filter(order -> {
|
||||
// 检查该订单的租户是否有特殊配置
|
||||
return tenantConfigs.stream()
|
||||
.noneMatch(config -> config.isEnabled() && config.getTenantId().equals(order.getTenantId()));
|
||||
})
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发订单自动取消任务(用于测试)
|
||||
*/
|
||||
public void manualCancelExpiredOrders() {
|
||||
log.info("手动触发订单自动取消任务...");
|
||||
cancelExpiredOrders();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务状态信息
|
||||
*/
|
||||
public String getTaskStatus() {
|
||||
return String.format("订单自动取消任务状态 - 启用: %s, 默认超时: %d分钟, 检查间隔: %d分钟, 批量大小: %d",
|
||||
orderConfig.getAutoCancel().isEnabled(),
|
||||
orderConfig.getAutoCancel().getDefaultTimeoutMinutes(),
|
||||
orderConfig.getAutoCancel().getCheckIntervalMinutes(),
|
||||
orderConfig.getAutoCancel().getBatchSize());
|
||||
}
|
||||
}
|
||||
@@ -139,6 +139,34 @@ shop:
|
||||
min-order-amount: 0
|
||||
order-timeout-minutes: 30
|
||||
|
||||
# 订单自动取消配置
|
||||
auto-cancel:
|
||||
# 是否启用自动取消功能
|
||||
enabled: true
|
||||
# 默认超时时间(分钟)
|
||||
default-timeout-minutes: 30
|
||||
# 定时任务检查间隔(分钟)
|
||||
check-interval-minutes: 5
|
||||
# 批量处理大小
|
||||
batch-size: 100
|
||||
# 定时任务执行时间(cron表达式)
|
||||
# 生产环境:每5分钟执行一次
|
||||
# 开发环境:每1分钟执行一次(便于测试)
|
||||
cron: "0 */5 * * * ?"
|
||||
# 开发环境可以设置为: "0 */1 * * * ?"
|
||||
|
||||
# 租户特殊配置
|
||||
tenant-configs:
|
||||
- tenant-id: 10324
|
||||
tenant-name: "百色中学"
|
||||
timeout-minutes: 60 # 捐款订单给更长的支付时间
|
||||
enabled: true
|
||||
# 可以添加更多租户配置
|
||||
# - tenant-id: 10550
|
||||
# tenant-name: "其他租户"
|
||||
# timeout-minutes: 15
|
||||
# enabled: true
|
||||
|
||||
# 证书配置
|
||||
certificate:
|
||||
# 证书加载模式: CLASSPATH, FILESYSTEM, VOLUME
|
||||
|
||||
Reference in New Issue
Block a user