delayQueue實現訂單超時自動取消

說明

商城系統的訂單模塊都應該有:訂單未支付超時後自動取消訂單的操作。我們在開發過程中實現該功能也有很多,例如 消息中間件、定時任務等,每種方法都有各自的優點。這裏我使用java DelayQueue容器來實現,優點是本地存儲,系統資源消耗低,缺點是單機模式。

實現

1.編寫Delayed實現類

@Data
@Accessors(chain = true)
@NoArgsConstructor
public class OrderAutoEntity implements Delayed {

    //訂單編號
    private String orderNo;
    //訂單具體的過期時間
    private long expire;
    //訂單過期時間間隔定義(毫秒),這裏設置30分鐘
    public static final long expireTime = TimeUnit.MINUTES.toMillis(30);

    public OrderAutoEntity(String orderNo, LocalDateTime orderTime) {
        this.orderNo = orderNo;
        //轉成毫秒
        long creatTime = orderTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        this.expire = expireTime + creatTime;
    }

    /**
     * 獲取剩餘時間
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed other) {
        return (int) (this.getDelay(TimeUnit.MILLISECONDS) - other.getDelay(TimeUnit.MILLISECONDS));
    }

}

2.編寫DelayQueue業務類

@PostConstruct 這個註解會讓標註的方法在系統啓動後自定加載

@Service
@Slf4j
public class OrderCanelService {
	
	//訂單增刪改查業務邏輯
    @Resource
    private OrderService orderService;
    
    //用於存放需要未支付計時訂單
    private final static DelayQueue<OrderAutoEntity> delayQueue = new DelayQueue<>();


    //訂單取消,數據庫改變訂單狀態,DelayQueue容器移除該訂單記錄
    public void cancelOrder(String orderNo) {
       //數據庫改變訂單狀態
       orderService.cancelOrder(orderNo);
       //容器遍歷找到對應的訂單記錄,並從容器中移除
       Iterator val = delayQueue.iterator();
       while (val.hasNext()) {
           OrderAutoEntity orderAutoEntity = (OrderAutoEntity) val.next();
           if(orderAutoEntity.getOrderNo().equals(orderNo)){
               delayQueue.remove(orderAutoEntity);
           }
       }
    }

    //往隊列中新增訂單記錄
    public void add(OrderAutoEntity orderAutoEntity) {
        delayQueue.put(orderAutoEntity);
    }

    /**
     * 服務器啓動時,自動加載待支付訂單
     */
    @PostConstruct
    public void initOrderStatus() {
        log.info(">>>>>>>>>>> 系統啓動時,加載所有待支付訂單到延時隊列 >>>>>>>>>>>>.");
        //未支付訂單查詢
        QueryWrapper<Order> wrapper = new QueryWrapper();
        wrapper.select("order_no", "create_time").eq("status", "0");
        //獲取所有未支付訂單,這裏用了mybatisplus操作數據庫
        List<Map<String, Object>> orders = orderService.listMaps(wrapper);
        
        //逐個構造Delay的實現類,添加進容器
        for (Map<String, Object> order : orders) {
            Timestamp createTime = (Timestamp) order.get("create_time");
            OrderAutoEntity orderAutoEntity = new OrderAutoEntity((String) order.get("order_no"), createTime.toLocalDateTime());
            delayQueue.add(orderAutoEntity);
        }
        
        //啓動一個線程持續健康訂單超時
        Executors.newSingleThreadExecutor().execute(() -> {
            try {
                while (true) {
                    if (delayQueue.size() > 0) {
                    	//容器中超時的訂單記錄會被取出
                        OrderAutoEntity order = delayQueue.take();
                        //修改數據庫,容器中移除數據
                        cancelOrder(order.getOrderNo());
                    }
                }
            } catch (InterruptedException e) {
                log.error("InterruptedException error:", e);
            }
        });
    }
}

3.編寫訂單業務邏輯

需要朱阿姨的是:我們前臺每次新增訂單時,也需要再給容器中添加一條記錄。

@RestController
@Slf4j
@RequestMapping("/api")
public class OrderController {
	@Resource
    private OrderCanelService orderCanelService ;
	...
	//前端頁面調用的訂單新增接口
	@PostMapping("/order")
    @Transactional(rollbackFor = Exception.class)
    public ResponseEntity addOrder(@RequestBody Map<String, Object> params) {
		...
			//構造訂單延時類(OrderAutoEntity),這裏是僞代碼
			OrderAutoEntity orderAutoEntity = new OrderAutoEntity(orderNo, createTime);
            orderAutoService.add(orderAutoEntity);
		...
	}
	...
}

總結說明

我這種操作只支持單機情況,一般還可以進一步優化,利用redis:在新增訂單時,除了存到數據庫,再保留一份到redis中,這樣我們在OrderCanelService 的初始化函數initOrderStatus中就不需要查數據庫,每次啓動就直接讀redis中的數據。這裏我就沒做這麼複雜。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章