一、需求
延时订单的场景比较常见,比如下一个订单后,允许在倒计时的30分钟内去支付。如果一直未支付,则需要自动关闭订单释放库存;如果支付了,则不再需要关注该订单的过期与否。
这里将用一个具体的例子,通过DelayQueue
来实现该需求,具体需求如下:
- 支持下延时支付的订单
- 支持在有效期内对订单进行支付
- 支持一直未支付的订单的自动到期处理
- 支持查询所有订单信息
二、实现
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
里,
- 维护一个延时订单队列
DelayQueue
,用于延时订单的存取; - 维护一个工作线程,用于阻塞式获取每一个到期的延时订单;
- 维护一个并发哈希表,用于模拟持久化的订单数据表。
@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自动失效