延时订单的实现

一、需求

延时订单的场景比较常见,比如下一个订单后,允许在倒计时的30分钟内去支付。如果一直未支付,则需要自动关闭订单释放库存;如果支付了,则不再需要关注该订单的过期与否。
这里将用一个具体的例子,通过DelayQueue来实现该需求,具体需求如下:

  1. 支持下延时支付的订单
  2. 支持在有效期内对订单进行支付
  3. 支持一直未支付的订单的自动到期处理
  4. 支持查询所有订单信息

二、实现

2.1、定义订单接口

将以上需求通过以下3个接口实现。

@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    @RequestMapping("create")
    public String createOrder() {
        return orderService.createOrder();
    }

    @RequestMapping("query")
    public Map<String, Order> queryOrders() {
        return orderService.queryOrders();
    }

    @RequestMapping("pay")
    public Order payForOrder(@RequestParam("orderNo") String orderNo) {
        return orderService.payForOrder(orderNo);
    }
}

2.2、定义业务订单实体类

其他字段无需过多说明,对delayedVo字段稍加解释,通过该字段可以找到业务订单对应的延时队列里的订单对象,以便在支付后,立即将其从延时队列中移除。

@Data
public class Order implements Serializable {

    private Long id;
    private String orderNo;
    /**
     * 0: 未支付;1: 已支付;-1: 已过期,关闭
     */
    private OrderStatusEnum status;
    /**
     * 设定过期时长,单位:秒
     */
    private Long expireDuration;
    private String desc;
    private Date createTime;
    private Date expireTime;

    // 在队列里对应的延时对象,用于支付后主动移出队列
    @JsonIgnore
    private DelayedVo delayedVo;
}

2.3、定义延时对象类

实现延时接口Delayed,并以泛型的方式支持包括业务订单在内的任意种类的延时业务对象。

@Getter
public class DelayedVo<T> implements Delayed {

    // 激活时间=到期时间,单位:ms
    private long activeTime;
    // 如订单或其他类型延时对象
    private T item;

    public DelayedVo(long activeTime, T item) {
        super();
        this.activeTime = activeTime;
        this.item = item;
    }

    /**
     * 返回距离到期的剩余时间段,时间单位由单位参数指定
     */
    @Override
    public long getDelay(TimeUnit unit) {
        long excessTime = unit.convert(this.activeTime - System.currentTimeMillis(), unit);
        return excessTime;
    }

    /**
     * 剩余时间段排序由小到大排序(纳秒)
     */
    @Override
    public int compareTo(Delayed delayed) {
        long excessTime = getDelay(TimeUnit.NANOSECONDS) - delayed.getDelay(TimeUnit.NANOSECONDS);
        return excessTime == 0 ? 0 : ((excessTime < 0 ? -1 : 1));
    }
}

2.4、实现延时订单处理类

在订单处理类OrderProcessor里,

  1. 维护一个延时订单队列DelayQueue,用于延时订单的存取;
  2. 维护一个工作线程,用于阻塞式获取每一个到期的延时订单;
  3. 维护一个并发哈希表,用于模拟持久化的订单数据表。
@Slf4j
@Getter
@Service
public class OrderProcessor implements Runnable {

    // 使用DelayQueue:一个使用优先级队列实现的无界阻塞队列(按照剩余延时时间排序)
    private DelayQueue<DelayedVo<Order>> delayQueue = new DelayQueue<>();
    private Thread orderWorkThread;
    public static Map<String, Order> orderMap = new ConcurrentHashMap<>();

    @Override
    public void run() {
        while (true) {
            try {
                // 阻塞式获取下一个到期的订单
                DelayedVo<Order> orderDelayedVo = delayQueue.take();
                Order order = orderDelayedVo.getItem();
                order.setStatus(OrderStatusEnum.EXPIRED);
                log.info("订单[{}]已在{}自动失效", order.getOrderNo(), order.getExpireTime());
            } catch (InterruptedException e) {
                log.error("出队异常:", e);
            }
        }
    }

    @PostConstruct
    public void init() {
        orderWorkThread = new Thread(this);
        orderWorkThread.start();
        log.info("启动监控延时订单");
    }

    @PreDestroy
    public void close() {
        orderWorkThread.interrupt();
        log.info("停止监控延时订单");
    }
}

2.5、实现业务订单处理类

实现延时业务订单的创建、支付和查询。

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderProcessor orderProcessor;

    @Override
    public String createOrder() {
        Order order = new Order();
        long now = System.currentTimeMillis();
        order.setOrderNo(UUID.randomUUID().toString());
        order.setStatus(OrderStatusEnum.TO_BE_PAID);
        Random random = new Random();
        long expireDuration = random.nextInt(20) + 5l; // 随机5-25s过期
        order.setExpireDuration(expireDuration);
        order.setCreateTime(new Date(now));
        order.setExpireTime(new Date(now + expireDuration * 1000));
        order.setDesc("我和我的祖国 8号厅08排x座");
        OrderProcessor.orderMap.put(order.getOrderNo(), order);
        // 再给delayQueue插入一份
        DelayedVo<Order> delayedOrder = new DelayedVo<>(order.getExpireTime().getTime(), order);
        orderProcessor.getDelayQueue().put(delayedOrder);
        order.setDelayedVo(delayedOrder);
        log.info("订单[{}]创建成功,请在{}之前及时支付,否则将在{}s后自动失效", order.getOrderNo(), order.getExpireTime(), order.getExpireDuration());
        return order.getOrderNo();
    }

    @Override
    public Map<String, Order> queryOrders() {
        return OrderProcessor.orderMap;
    }

    @Override
    public Order payForOrder(String orderNo) {
        Order order = OrderProcessor.orderMap.get(orderNo);
        if (order == null) {
            throw new RuntimeException("订单未找到");
        }
        if (OrderStatusEnum.EXPIRED.equals(order.getStatus())) {
            throw new RuntimeException("订单已过期");
        }
        if (OrderStatusEnum.PAID.equals(order.getStatus())) {
            throw new RuntimeException("订单已支付,不能重复支付");
        }
//        log.info("before size: {}", orderManager.getDelayQueue().size());
        boolean remove = orderProcessor.getDelayQueue().remove(order.getDelayedVo());
        if (remove) {
            log.info("订单[{}]已从延时队列移出", orderNo);
//        log.info("after size: {}", orderManager.getDelayQueue().size());
            order.setStatus(OrderStatusEnum.PAID);
            log.info("订单[{}]支付成功", orderNo);
        } else {
            log.error("订单[{}]支付异常", orderNo);
        }
        return order;
    }
}

三、效果演示

多次创建订单,以及支付某些订单后,通过日志查看效果。

2024-07-12 22:48:28.742  INFO 9216 --- [nio-8080-exec-1] org.order.service.impl.OrderServiceImpl  : 订单[abb93f92-6cce-4054-9704-2484c0dd37f1]创建成功,请在Fri Jul 12 22:48:33 CST 2024之前及时支付,否则将在5s后自动失效
2024-07-12 22:48:30.904  INFO 9216 --- [nio-8080-exec-3] org.order.service.impl.OrderServiceImpl  : 订单[1d375dfd-0849-42d0-8934-a7506fef4723]创建成功,请在Fri Jul 12 22:48:53 CST 2024之前及时支付,否则将在23s后自动失效
2024-07-12 22:48:32.019  INFO 9216 --- [nio-8080-exec-4] org.order.service.impl.OrderServiceImpl  : 订单[b6b620af-049a-4f97-866d-e81f4e690ac6]创建成功,请在Fri Jul 12 22:48:51 CST 2024之前及时支付,否则将在19s后自动失效
2024-07-12 22:48:33.741  INFO 9216 --- [       Thread-2] org.order.service.OrderProcessor         : 订单[abb93f92-6cce-4054-9704-2484c0dd37f1]已在Fri Jul 12 22:48:33 CST 2024自动失效
2024-07-12 22:48:36.515  INFO 9216 --- [nio-8080-exec-5] org.order.service.impl.OrderServiceImpl  : 订单[b6b620af-049a-4f97-866d-e81f4e690ac6]已从延时队列移出
2024-07-12 22:48:36.515  INFO 9216 --- [nio-8080-exec-5] org.order.service.impl.OrderServiceImpl  : 订单[b6b620af-049a-4f97-866d-e81f4e690ac6]支付成功
2024-07-12 22:48:38.534  INFO 9216 --- [nio-8080-exec-6] org.order.service.impl.OrderServiceImpl  : 订单[f6c1d451-ce6a-45a7-8451-8ff1066e99b0]创建成功,请在Fri Jul 12 22:48:54 CST 2024之前及时支付,否则将在16s后自动失效
2024-07-12 22:48:53.904  INFO 9216 --- [       Thread-2] org.order.service.OrderProcessor         : 订单[1d375dfd-0849-42d0-8934-a7506fef4723]已在Fri Jul 12 22:48:53 CST 2024自动失效
2024-07-12 22:48:54.534  INFO 9216 --- [       Thread-2] org.order.service.OrderProcessor         : 订单[f6c1d451-ce6a-45a7-8451-8ff1066e99b0]已在Fri Jul 12 22:48:54 CST 2024自动失效

相关推荐

  1. 订单实现

    2024-07-13 05:46:08       28 阅读
  2. 用Redis队列搞定订单超时业务

    2024-07-13 05:46:08       31 阅读
  3. redis实现队列

    2024-07-13 05:46:08       24 阅读
  4. DelayQueue实现任务

    2024-07-13 05:46:08       18 阅读
  5. 基于Redis实现任务

    2024-07-13 05:46:08       34 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-13 05:46:08       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-13 05:46:08       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-13 05:46:08       58 阅读
  4. Python语言-面向对象

    2024-07-13 05:46:08       69 阅读

热门阅读

  1. 数学基础 -- 三角学

    2024-07-13 05:46:08       27 阅读
  2. 07-7.5.2 散列函数的构造

    2024-07-13 05:46:08       27 阅读
  3. React vs Vue:谁是前端界的冠军?

    2024-07-13 05:46:08       24 阅读
  4. [NeetCode 150] Longest Consecutive Sequence

    2024-07-13 05:46:08       21 阅读
  5. sqlserver设置端口

    2024-07-13 05:46:08       22 阅读
  6. C++:using重新定义继承时访问权限

    2024-07-13 05:46:08       29 阅读
  7. git列出提交记录的文件路径

    2024-07-13 05:46:08       23 阅读
  8. 关于对于短视频的认识-复盘与再次复盘

    2024-07-13 05:46:08       23 阅读
  9. sqlalchemy反射视图

    2024-07-13 05:46:08       21 阅读
  10. vue 组件里面的方法修改外面的数据

    2024-07-13 05:46:08       25 阅读
  11. 使用Trie树高亮关键词

    2024-07-13 05:46:08       25 阅读
  12. qt 的布局

    2024-07-13 05:46:08       29 阅读
  13. 《每天十分钟》-红宝书第4版-函数

    2024-07-13 05:46:08       23 阅读
  14. 【Scrapy】Scrapy 中间件等级设置规则

    2024-07-13 05:46:08       25 阅读
  15. 智能运维提升企业长期安全防御能力

    2024-07-13 05:46:08       23 阅读