說明
商城系統的訂單模塊都應該有:訂單未支付超時後自動取消訂單的操作。我們在開發過程中實現該功能也有很多,例如 消息中間件、定時任務等,每種方法都有各自的優點。這裏我使用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中的數據。這裏我就沒做這麼複雜。