Spring Boot项目实现订单超时未支付自动取消策略

Spring Boot项目实现订单超时未支付自动取消策略
彼岸的風需求说明
在涉及到支付相关的应用中,通常需要实现个功能,用户在生成订单一段时间未完成支付,系统将会自动取消这个订单。本文将基于SpringBoot项目实现订单超时未支付的几种方案策略。
方案1:定时任务
- 利用SpringBoot中的 @Scheduled 注解,实现定时任务。周期性的检查数据库中是否存在超时未支付的订单,如果存在则取消。代码如下:(cron在线生成表达式参考)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void cancelUnpaidOrders() {
log.info("每分钟扫描超过30分钟未支付的订单");
List<FuOrder> list = orderService.list();
for (FuOrder fuOrder : list) {
if (fuOrder.getCreateTime().plusMinutes(30).isBefore(LocalDateTime.now())
&& fuOrder.getStatus() == 1) {
FuOrder newInfo = new FuOrder();
BeanUtils.copyProperties(fuOrder, newInfo);
newInfo.setStatus(0);
newInfo.setClosetTime(LocalDateTime.now());
newInfo.setUpdateTime(LocalDateTime.now());
orderService.updateById(newInfo);
}
}
log.info("定时处理订单表所有未支付订单结束");
} - PS:注意不要忘记在启动类中添加开启定时任务 @EnableScheduling 注解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//开启定时任务
public class NoteApplication {
public static void main(String[] args) throws UnknownHostException {
ConfigurableApplicationContext application = SpringApplication.run(NoteApplication.class, args);
TomcatServletWebServerFactory tomcatServlet = application.getBean(TomcatServletWebServerFactory.class);
String ip = InetAddress.getLocalHost().getHostAddress();
int port = tomcatServlet.getPort();
String path = tomcatServlet.getContextPath();
log.info("\n----------------------------------------------------------\n\t" +
"External: \thttp://" + ip + ":" + port + path + "/\n\t" +
"swagger-ui: \thttp://" + ip + ":" + port + path + "/swagger-ui/index.html\n\t" +
"Doc: \t\thttp://" + ip + ":" + port + path + "/doc.html\n" +
"----------------------------------------------------------");
}
}
方案2:Redis过期事件
- 利用redis的键过期事件功能,在用户下单时,生成一个令牌(有效期)30分钟,存放到redis。通过redis的过期事件通知功能触发订单取消操作。
- 首先写个创建订单并存入redis的接口(这里只演示Impl的代码),在写一个过期时,处理订单的方法用于redis自动调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public R createOrderByRedisKey( { FuOrder fuOrder, HttpServletRequest request)
String token = request.getHeader("token");
Random random = new Random();
String orderNo = String.valueOf(random.nextLong()).replace("-", "");
fuOrder.setOrderNo(Long.valueOf(orderNo));
fuOrder.setCreateTime(LocalDateTime.now());
fuOrder.setUserId(Math.toIntExact(JwtUtils.getUserId(token)));
boolean order = orderService.save(fuOrder);
if (order) {
redisTemplate.opsForValue().set("fu99999:order:" + fuOrder.getId(), fuOrder,30 , TimeUnit.MINUTES);
return R.ok().message("创建订单成功");
} else {
return R.error().message("创建订单失败");
}
} - 然后要确保redis的配置(Windows系统通常是叫redis.windows.conf)中开启了键空间通知功能。通过在配置文件中添加或修改如下配置实现。
1
notify-keyspace-events "Ex"
- 在项目中新建Listener 并继承自 KeyExpirationEventMessageListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
private FuOrderService orderService;
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* 重写onMessage()方法处理过期Key
* 用于对订单超时的的逻辑处理
*/
public void onMessage(Message message, byte[] pattern) {
String expiredKey = message.toString();
log.info("------------------redis key 失效; key = " + expiredKey);
if (expiredKey.startsWith("fu99999:order:")) {
// 处理订单超时逻辑
String orderId = expiredKey.split(":")[2];
orderService.cancelOrder(Long.valueOf(orderId));
}
}
}1
2
3
4
5
6
7
8
9
10
11
public void cancelOrder(Long orderId) {
FuOrder fuOrder = baseMapper.selectById(orderId);
FuOrder overdueOrder = new FuOrder();
BeanUtils.copyProperties(fuOrder,overdueOrder);
overdueOrder.setStatus(0);
overdueOrder.setClosetTime(LocalDateTime.now());
overdueOrder.setUpdateTime(LocalDateTime.now());
baseMapper.updateById(overdueOrder);
log.info("redis过期未支付订单处理结束");
} - 新建配置类 RedisListenerConfig
1
2
3
4
5
6
7
8
9
public class RedisListenerConfig {
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
方案3:延迟队列(死信队列)
- 使用消息队列(如RabbitMQ)的延迟队列功能,当订单生成时将订单ID推送到延迟队列,设置30分钟后过期,过期后消费该消息,取消订单。
具体实现代码详见 RabbitMQ实现订单30分钟超时自动关闭 一文。
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果