实现以上功能
方法1:定时任务批量执行
写一个定时任务,每隔 30分钟执行一次,列出所有超出时间范围得订单id的列表
@Async@Scheduled(cron = "20 20 1 * * ?")public void cancelOrder(){log.info("【取消订单任务开始】");QueryWrapper<Order> qw = new QueryWrapper<>();qw.eq("status", Constants.OrderStatus.NOTPAID);qw.eq("aftersale_status", 1);List<Order> orderList = orderMapper.selectList(qw);List<Long> idList = orderList.stream().filter(order -> LocalDateTimeUtil.between(order.getCreateTime(), LocalDateTime.now()).toMinutes() >= 15).map(Order::getId).collect(Collectors.toList());CancelOrderRequest request = new CancelOrderRequest();request.setIdList(idList);h5OrderService.orderBatchCancel(request, null);log.info("【取消订单任务结束】");}
批量执行取消订单操作
@Transactionalpublic String orderBatchCancel(CancelOrderRequest request, Long userId) {LocalDateTime optDate = LocalDateTime.now();if (CollectionUtil.isEmpty(request.getIdList())) {throw new RuntimeException("未指定需要取消的订单号");}QueryWrapper<Order> orderQw = new QueryWrapper<>();orderQw.in("id", request.getIdList());List<Order> orderList = orderMapper.selectList(orderQw);if (orderList.size() < request.getIdList().size()) {throw new RuntimeException("未查询到订单信息");}Order order = orderList.get(0);//查orderItemQueryWrapper<OrderItem> qw = new QueryWrapper<>();qw.in("order_id", request.getIdList());List<OrderItem> orderItems = orderItemMapper.selectList(qw);if (CollectionUtil.isEmpty(orderItems)) {throw new RuntimeException("未查询到订单信息");}long count = orderList.stream().filter(it -> !Constants.H5OrderStatus.UN_PAY.equals(it.getStatus())).count();if (count > 0) {throw new RuntimeException("订单状态已更新,请刷新页面");}List<OrderOperateHistory> addHistoryList = new ArrayList<>();orderList.forEach(item -> {item.setStatus(Constants.H5OrderStatus.CLOSED);item.setUpdateTime(optDate);item.setUpdateBy(userId);OrderOperateHistory history = new OrderOperateHistory();history.setOrderId(item.getId());history.setOrderSn(item.getOrderSn());history.setOperateMan(userId == null ? "后台管理员" : "" + item.getMemberId());history.setOrderStatus(Constants.H5OrderStatus.CLOSED);history.setCreateTime(optDate);history.setCreateBy(userId);history.setUpdateBy(userId);history.setUpdateTime(optDate);addHistoryList.add(history);});//取消订单int rows = orderMapper.cancelBatch(orderList);if (rows < 1) {throw new RuntimeException("更改订单状态失败");}orderItems.stream().collect(Collectors.groupingBy(it -> it.getSkuId())).forEach((k, v) -> {AtomicReference<Integer> totalCount = new AtomicReference<>(0);v.forEach(it -> totalCount.updateAndGet(v1 -> v1 + it.getQuantity()));skuMapper.updateStockById(k, optDate, -1 * totalCount.get());});//创建订单操作记录boolean flag = orderOperateHistoryService.saveBatch(addHistoryList);if (!flag) {throw new RuntimeException("创建订单操作记录失败");}// 根据order 退还积分orderUsePointsService.refundOrderUsePoints(order.getId());return "取消订单成功";}
方法2:使用jdk自带的阻塞队列
实现一个简单的队列,每隔一定时间执行队列。
/*** (30分钟扫描三十分钟内需要发送的订单)*/@Scheduled(cron = "0 0/30 * * * ?")public void checkOrderStatus() {DelayQueue<ItemVo<Order>> queue = new DelayQueue<ItemVo<Order>>();try {// 插入订单new Thread(new PutOrder(queue)).start();} catch (Exception e) {e.printStackTrace();}}
这里使用队列的优势可以跟前端时间匹配上,前端读秒几秒这里就什么时候取消
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.toolkit.CollectionUtils;
import com.kxmall.market.biz.BeanContext;
import com.kxmall.market.data.dto.PrivatePlanAndDetailDO;
import com.kxmall.market.data.mapper.PrivatePlanMapper;
import com.kxmall.market.data.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;import java.util.Date;
import java.util.List;
import java.util.concurrent.DelayQueue;/*** 模拟订单插入的功能*/public class PutOrder implements Runnable {private static final Logger logger = LoggerFactory.getLogger(PutOrder.class);// public static PutOrder putOrder;@Autowiredprivate PrivatePlanMapper privatePlanMapper;// 使用DelayQueue:一个使用优先级队列实现的无界阻塞队列。private DelayQueue<ItemVo<Order>> queue;public PutOrder(DelayQueue<ItemVo<Order>> queue) {super();this.queue = queue;}@Overridepublic void run() {Date startTime = new Date();privatePlanMapper = BeanContext.getApplicationContext().getBean(PrivatePlanMapper.class);// 每隔半小时获取半小时内需要取消的List<PrivatePlanAndDetailDO> privatePlanDOS = privatePlanMapper.getPrivatePlanDetailList();logger.info("待取消清单->{}", JSON.toJSONString(privatePlanDOS));if (CollectionUtils.isNotEmpty(privatePlanDOS)) {privatePlanDOS.forEach(s -> {long count = DateUtil.calLastedTime(startTime,s.getTodoTime() )*1000;Order tbOrder = new Order(s.getId().toString(), 0.0);ItemVo<Order> itemVoTb = new ItemVo<Order>(count, tbOrder);queue.offer(itemVoTb);logger.info("订单{}将在->{}秒后取消", s.getId().toString(),count/1000);});// 取出过期订单的线程new Thread(new FetchOrder(queue)).start();}else {logger.info("没有待发送订单->");}}
}
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/*** 存到队列里的元素* 支持延时获取的元素的阻塞队列,元素必须要实现Delayed接口。* 根据订单有效时间作为队列的优先级* @param <T>*/
public class ItemVo<T> implements Delayed{// 到期时间 单位:msprivate long activeTime;// 订单实体(使用泛型是因为后续扩展其他业务共用此业务类)private T data;public ItemVo(long activeTime, T data) {super();// 将传入的时间转换为超时的时刻this.activeTime = TimeUnit.NANOSECONDS.convert(activeTime, TimeUnit.MILLISECONDS)+ System.nanoTime();this.data = data;}public long getActiveTime() {return activeTime;}public T getData() {return data;}// 按照剩余时间进行排序@Overridepublic int compareTo(Delayed o) {// 订单剩余时间-当前传入的时间= 实际剩余时间(单位纳秒)long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);// 根据剩余时间判断等于0 返回1 不等于0// 有可能大于0 有可能小于0 大于0返回1 小于返回-1return (d == 0) ? 0 : ((d > 0) ? 1 : -1);}// 获取剩余时间@Overridepublic long getDelay(TimeUnit unit) {// 剩余时间= 到期时间-当前系统时间,系统一般是纳秒级的,所以这里做一次转换long d = unit.convert(activeTime-System.nanoTime(), TimeUnit.NANOSECONDS);return d;}}
方法3:分布式场景(mq队列)
使用mq队列,消费消息。如果消息到达30分钟没有付款,那么就取消
方法4:分布式场景(redis)
使用redis商品下单,设置过期时间 30分钟,并且写一个redis监听器,监听过期需要操作的key,然后判单是否过期