java订单到期自动取消_订单自动过期实现方案

需求分析:

24小时内未支付的订单过期失效。

解决方案

被动设置:在查询订单的时候检查是否过期并设置过期状态。

定时调度:定时器定时查询并过期需要过期的订单。

延时队列:将未支付的订单放入一个延时队列中,依次取出过期订单。

过期提醒:reids支持将一个过期的key(订单号)通知给客户端,根据过期的订单号进行相应的处理。

1. 被动设置

这个太简单了,就是在查询的时候判断是否失效,如果失效了就给他设置失效状态。但是弊端也很明显,每次查询都要对未失效的订单做判断,如果用户不查询,订单就不失效,那么如果有类似统计失效状态个数的功能,将会受到影响,所以只能适用于简单独立的场景。简直low爆了。

2. 定时调度

这种是常见的方法,利用一个定时器,在设置的周期内轮询检查并处理需要过期的订单。

具体实现有基于Timer的,有基于Quartz,还有springboot自带的Scheduler,实现起来比较简单。

就写一下第三个的实现方法吧:

启动类加上注解@EnableScheduling

新建一个定时调度类,方法上加上@Scheduled注解,如下图那么简单。

9b0adbcd5ba95f1bee042ff2a02b3b62.png

弊端

不能够精准的去处理过期订单,轮询周期设置的越小,精准度越高,但是项目的压力越大,我们上一个项目就有这种状况,太多定时器在跑,项目运行起来比较笨重。

而且需要处理的是过期的订单,但是要查询所有未支付的订单,范围大。对于大订单量的操作不合适。

3. 延时队列

基于JDK的实现方法,将未支付的订单放到一个有序的队列中,程序会自动依次取出过期的订单。

如果当前没有过期的订单,就会阻塞,直至有过期的订单。由于每次只处理过期的订单,并且处理的时间也很精准,不存在定时调度方案的那两个弊端。

实现:

1.首先创建一个订单类OrderDelayDto需要实现Delayed接口。然后重写getDelay()方法和compareTo()方法,只加了订单编号和过期时间两个属性。

这两个方法很重要,

getDelay()方法实现过期的策略,比如,订单的过期时间等于当前时间就是过期,返回负数就代表需要处理。否则不处理。

compareTo()方法实现订单在队列中的排序规则,这样即使后面加入的订单,也能加入到排序中,我这里写的规则是按照过期时间排序,最先过期的排到最前面,这一点很重要,因为排在最前面的如果没有被处理,就会进入阻塞状态,后面的不会被处理。

import lombok.Data;

import java.util.Date;

import java.util.concurrent.Delayed;

import java.util.concurrent.TimeUnit;

/**

* @author mashu

* Date 2020/5/17 16:25

*/

@Data

public class OrderDelayDto implements Delayed {

/**

* 订单编号

*/

private String orderCode;

/**

* 过期时间

*/

private Date expirationTime;

/**

* 判断过期的策略:过期时间大于等于当前时间就算过期

*

* @param unit

* @return

*/

@Override

public long getDelay(TimeUnit unit) {

return unit.convert(this.expirationTime.getTime() - System.currentTimeMillis(), TimeUnit.NANOSECONDS);

}

/**

* 订单加入队列的排序规则

*

* @param o

* @return

*/

@Override

public int compareTo(Delayed o) {

OrderDelayDto orderDelayDto = (OrderDelayDto) o;

long time = orderDelayDto.getExpirationTime().getTime();

long time1 = this.getExpirationTime().getTime();

return time == time1 ? 0 : time < time1 ? 1 : -1;

}

}

其实这样已经算是写好了。我没有耍你。

写个main 方法测试一下,创建两个订单o1和o2,放入到延时队列中,然后while()方法不断的去取。

在此方法内通过队列的take()方法获得已过期的订单,然后做出相应的处理。

public static void main(String[] args) {

DelayQueue queue = new DelayQueue<>();

OrderDelayDto o1 = new OrderDelayDto();

//第一个订单,过期时间设置为一分钟后

o1.setOrderCode("1001");

Calendar calendar = Calendar.getInstance();

calendar.add(Calendar.MINUTE, 1);

o1.setExpirationTime(calendar.getTime());

OrderDelayDto o2 = new OrderDelayDto();

//第二个订单,过期时间设置为现在

o2.setOrderCode("1002");

o2.setExpirationTime(new Date());

//往队列中放入数据

queue.offer(o1);

queue.offer(o2);

// 延时队列

while (true) {

try {

OrderDelayDto take = queue.take();

System.out.println("订单编号:" + take.getOrderCode() + " 过期时间:" + take.getExpirationTime());

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

运行结果:

a0d288c4c692b87b4edfd91be2e046a4.png

我故意把第二个订单的过期时间设置为第一个订单之前,从结果可以看出,他们已经自动排序把最先过期的排到了最前面。

第一个订单的失效时间是当前时间的后一分钟,结果也显示一分钟后处理了第一条订单。

2.然而通常情况下,我们会使用多线程去取延时队列中的数据,这样即使线程启动之后也能动态的向队列中添加订单。

创建一个线程类OrderCheckScheduler实现Runnable接口,

添加一个延时队列属性,重写run()方法,在此方法内通过队列的take()方法获得已过期的订单,然后做出相应的处理。

import java.util.concurrent.DelayQueue;

/**

* @author mashu

* Date 2020/5/17 14:27

*/

public class OrderCheckScheduler implements Runnable {

// 延时队列

private DelayQueue queue;

public OrderCheckScheduler(DelayQueue queue) {

this.queue = queue;

}

@Override

public void run() {

while (true) {

try {

OrderDelayDto take = queue.take();

System.out.println("订单编号:" + take.getOrderCode() + " 过期时间:" + take.getExpirationTime());

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

好了,写个方法测试一下:

public static void main(String[] args) {

// 创建延时队列

DelayQueue queue = new DelayQueue<>();

OrderDelayDto o1 = new OrderDelayDto();

//第一个订单,过期时间设置为一分钟后

o1.setOrderCode("1001");

Calendar calendar = Calendar.getInstance();

calendar.add(Calendar.MINUTE, 1);

o1.setExpirationTime(calendar.getTime());

OrderDelayDto o2 = new OrderDelayDto();

//第二个订单,过期时间设置为现在

o2.setOrderCode("1002");

o2.setExpirationTime(new Date());

//运行线程

ExecutorService exec = Executors.newFixedThreadPool(1);

exec.execute(new OrderCheckScheduler(queue));

//往队列中放入数据

queue.offer(o1);

queue.offer(o2);

exec.shutdown();

}

结果和上面的一样,图就不截了,相信我。

过期提醒

基于redis的过期提醒功能,听名字就知道这个方案最是纯真、最直接的,就是单纯处理过期的订单。

修改个redis的配置吧先,因为redis默认不开启过期提醒。

notify-keyspace-events改为notify-keyspace-events "Ex"

写一个类用来接收来自redis的暖心提醒OrderExpirationListener,继承一下KeyExpirationEventMessageListener抽象类。重写onMessage()方法,在此方法中处理接收到的过期key.

import org.springframework.data.redis.connection.Message;

import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;

import org.springframework.data.redis.listener.RedisMessageListenerContainer;

import org.springframework.stereotype.Component;

import java.util.Date;

/**

* @author mashu

* Date 2020/5/17 23:01

*/

@Component

public class OrderExpirationListener extends KeyExpirationEventMessageListener {

public OrderExpirationListener(RedisMessageListenerContainer listenerContainer) {

super(listenerContainer);

}

@Override

public void onMessage(Message message, byte[] pattern) {

final String expiredKey = message.toString();

System.out.println("我过期了" + expiredKey+"当前时间:"+new Date());

}

}

ok,向redis中存入一个订单,过期时间为1分钟。

redis.set("orderCode/10010", "1", 1L, TimeUnit.MINUTES);

System.out.println("redis存入订单号 key: orderCode/10010,value:1,过期时间一分钟,当前时间"+new Date());

运行结果:

9b7b50edba1cc9de6e2b2af885b58502.png

除此之外还有用到消息队列的。夜深了,我得玩会游戏了。

没有绝对的好方案,只有在不同场景下的更合适的方案。随着需求的变化,技术的革新,方案也会不断的被优化和迭代,唯一不变的是工资。

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[订单自动过期实现方案]http://www.zyiz.net/tech/detail-145615.html

### Java 实现订单超时自动删除的方法 #### 方法一:定时轮询数据库 通过启动一个定时任务,定期扫描订单表并取消超时未支付的订单。这种方式的优点在于实现较为简单,但缺点是难以确定合适的轮询时间间隔,并可能造成服务器资源浪费以及影响数据库性能[^1]。 ```java // 使用ScheduledExecutorService创建周期性的线程池执行器实例 private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); public void startOrderCheck() { // 设置每分钟检查一次是否有超时未付款订单 scheduler.scheduleAtFixedRate(() -> checkAndCancelUnpaidOrders(), 0, 1, TimeUnit.MINUTES); } private void checkAndCancelUnpaidOrders() { List<Order> unpaidOrders = orderRepository.findUnpaidOrders(); for (Order order : unpaidOrders) { if (isOverdue(order)) { cancelOrder(order.getId()); } } } ``` #### 方法二:惰性取消机制 当访问某个特定订单详情页面或其他涉及读取单个订单的操作时,在返回之前先验证其状态是否已经过期。一旦发现订单确实超过了规定期限,则立即对其进行标记为已取消操作。此方案容易实施,不过它可能会干扰其他依赖于正常订单记录的功能模块,并降低查询速度。 ```java public Order getOrderDetails(Long orderId){ Optional<Order> optionalOrder = orderRepository.findById(orderId); if (!optionalOrder.isPresent()) throw new ResourceNotFoundException("找不到指定ID对应的订单"); Order order = optionalOrder.get(); if(isOverdue(order)){ cancelOrder(order.getId()); } return order; } ``` #### 方法三:JDK 延迟队列 利用`PriorityBlockingQueue`配合自定义类来构建基于优先级排序的任务列表;每当有新的待处理项加入时都会按照预设的时间戳进行排列等待被执行。这种方法可以有效地管理大量具有不同到期日的对象而不会频繁打扰主线程工作流程。 ```java import java.util.concurrent.*; class DelayedItem implements Comparable<DelayedItem>, Delayed { private String id; private Date deadline; public DelayedItem(String id, long delayInMillis) { this.id = id; this.deadline = new Date(System.currentTimeMillis() + delayInMillis); } @Override public int compareTo(Delayed o) { return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), ((Delayed)o).getDelay(TimeUnit.MILLISECONDS)); } @Override public long getDelay(TimeUnit unit) { return unit.convert(deadline.getTime()-System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public boolean equals(Object obj) { if (!(obj instanceof DelayedItem)) return false; DelayedItem other = (DelayedItem)obj; return Objects.equals(id,other.id)&&Objects.equals(deadline,other.deadline); } @Override public int hashCode(){ return Objects.hash(id,deadline); } } public class OrderManager { private PriorityBlockingQueue<DelayedItem> queue = new PriorityBlockingQueue<>(); public void addOrderWithExpiration(String orderId, long expireTimeMillis) { queue.put(new DelayedItem(orderId, expireTimeMillis)); } public void processExpiredItems() throws InterruptedException{ while(true){ DelayedItem item = queue.take(); // 队首元素一定是最早应该被处理的那个项目 // 执行相应的业务逻辑... System.out.println("Processing expired item with ID:" +item.id); } } } ``` #### 方法四:Redis Sorted Set 结合 ZADD 和 ZRANGEBYSCORE 操作 借助 Redis 的有序集合功能存储即将到达截止日期的商品编号及其对应的时间戳作为分数值。之后可以通过ZRANGEBYSCORE命令获取所有满足条件(即当前时刻之前的那些成员)的数据集来进行批量更新或清理动作[^2]。 ```java @Autowired private JedisPool jedisPool; public void addOrderToQueue(String orderId, long expireTime) { try(Jedis jedis = jedisPool.getResource()){ jedis.zadd("order_delay_queue", expireTime, orderId); } } public void processExpiredOrders() { try(Jedis jedis = jedisPool.getResource()){ long now = System.currentTimeMillis(); Set<String> expiredOrders = jedis.zrangeByScore("order_delay_queue", 0, now); for (String orderId : expiredOrders) { System.out.println("Cancelling order:" + orderId); // 进行业务上的取消订单操作 jedis.zrem("order_delay_queue", orderId); // 删除已处理过的订单条目 } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值