29

Spring Boot集成DelayQueue实现订单到期自动取消

 3 years ago
source link: http://www.zhouzhaodong.xyz/archives/springboot集成delayqueue实现订单到期自动取消
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近在考虑购物网站上的未支付订单到期自动取消是怎么实现的,发现了一个DelayQueue队列可以实现,所以来整理一下。。。

DelayQueue

java延迟队列提供了在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素。没有过期元素的话,使用poll()方法会返回null值,超时判定是通过getDelay(TimeUnit.NANOSECONDS)方法的返回值小于等于0来判断。

1. 新建Spring Boot项目

1.1 pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.4.2</version>
</dependency>

1.2 application.yml

server:
  port: 8888

order:
  isStarted: 1 # 是否开启自动取消功能(0关闭,1开启)

1.3 新建Order订单类

/**
 * 订单类
 * @author zhouzhaodong
 */
public class Order implements Delayed {

    /**
     * 订单号
     */
    private String orderNo;

    /**
     * 用户id
     */
    private String userId;

    /**
     * 订单状态(0待支付,1已支付,2已取消)
     */
    private Integer status;

    /**
     * 订单创建时间
     */
    private Date createTime;

    /**
     * 订单取消时间
     */
    private Date cancelTime;

    public String getOrderNo() {
        return orderNo;
    }

    public void setOrderNo(String orderNo) {
        this.orderNo = orderNo;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getCancelTime() {
        return cancelTime;
    }

    public void setCancelTime(Date cancelTime) {
        this.cancelTime = cancelTime;
    }

    public Order(String orderNo, String userId, Integer status, Date createTime, Date cancelTime) {
        this.orderNo = orderNo;
        this.userId = userId;
        this.status = status;
        this.createTime = createTime;
        this.cancelTime = cancelTime;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderNo='" + orderNo + '\'' +
                ", userId='" + userId + '\'' +
                ", status=" + status +
                ", createTime=" + createTime +
                ", cancelTime=" + cancelTime +
                '}';
    }

    /**
     * 获得延迟时间,用过期时间-当前时间,时间单位需要统一
     * @param unit
     * @return
     */
    @Override
    public long getDelay(TimeUnit unit) {
        //下面用到unit.convert()方法,其实在这个小场景不需要用到,只是学习一下如何使用罢了
        return unit.convert(cancelTime.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    /**
     * 用于延迟队列内部比较排序,当前时间的延迟时间 - 比较对象的延迟时间
     * @param o
     * @return
     */
    @Override
    public int compareTo(Delayed o) {
        //这里根据取消时间来比较,如果取消时间小的,就会优先被队列提取出来
        return this.getCancelTime().compareTo(((Order) o).getCancelTime());
    }
}

1.4 新建CancelOrderService

/**
 * 取消订单service类
 * @author zhouzhaodong
 */
public interface CancelOrderService {

    /**
     * 取消订单
     */
    void cancelOrder();

    /**
     * 获取队列
     * @return
     */
    DelayQueue<Order> getOrder();

}

1.5 新建servie实现类

/**
 * 取消订单实现类
 *
 * @author zhouzhaodong
 */
@Service
public class CancelOrderServiceImpl implements CancelOrderService {

    /**
     * 是否开启自动取消功能
     */
    @Value("${order.isStarted}")
    private int isStarted;

    /**
     * 延迟队列,用来存放订单对象
     */
    DelayQueue<Order> queue = new DelayQueue<>();

    @Resource
    private ThreadPoolTaskExecutor executorService;

    @Override
    public void cancelOrder() {
        //新建一个线程,用来模拟定时取消订单job
        executorService.submit(()->{
            try {
                System.out.println("开启自动取消订单job,当前时间:" + DateUtil.date());
                while (isStarted == 1) {
                    try {
                        Order order = queue.take();
                        order.setStatus(2);
                        System.out.println("订单:" + order.getOrderNo() + "付款超时,自动取消,当前时间:" + DateUtil.date());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    @Override
    public DelayQueue<Order> getOrder(){
        executorService.submit(()->{
            try {
                Date date = DateUtil.date();
                queue.add(new Order("SO001", "001", 0, date, DateUtil.offset(date, DateField.SECOND, 3)));
                queue.add(new Order("SO002", "002", 0, DateUtil.offset(date, DateField.SECOND, 3), DateUtil.offset(date, DateField.SECOND, 6)));
                queue.add(new Order("SO003", "003", 0, DateUtil.offset(date, DateField.SECOND, 6), DateUtil.offset(date, DateField.SECOND, 9)));
                queue.add(new Order("SO004", "004", 0, DateUtil.offset(date, DateField.SECOND, 9), DateUtil.offset(date, DateField.SECOND, 12)));
                queue.add(new Order("SO005", "005", 0, DateUtil.offset(date, DateField.SECOND, 12), DateUtil.offset(date, DateField.SECOND, 15)));
                queue.add(new Order("SO006", "006", 0, DateUtil.offset(date, DateField.SECOND, 15), DateUtil.offset(date, DateField.SECOND, 18)));
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        return queue;
    }
}

1.6 新建controller

/**
 * 控制器
 * @author zhouzhaodong
 */
@RestController
public class CancelOrderController {

    @Resource
    CancelOrderService cancelOrderService;

    @RequestMapping("/")
    public void cancelOrder(){
        cancelOrderService.getOrder();
        cancelOrderService.cancelOrder();
    }

    @RequestMapping("/queue")
    public void getOrder(){
        cancelOrderService.getOrder();
    }
    
}

2. 测试

2.1 启动项目,访问localhost:8888/

rA7Ffav.png!mobile

2.2 访问localhost:8888/queue

因为队列中已经没有订单了,所以控制台不再打印相关订单取消的信息,所以我们再创建一遍刚才的订单,观察控制台打印信息:

N7NRrm7.png!mobile

至此,我们简单的订单定时取消功能就完成了,当然可以根据自己具体的要求进行修改!

GitHub源码地址:

https://github.com/zhouzhaodong/springboot/tree/master/spring-boot-delayqueue


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK