news 2026/3/16 13:49:30

别再用定时任务扫库了!SpringBoot集成Redis实现订单超时管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
别再用定时任务扫库了!SpringBoot集成Redis实现订单超时管理

大家好,我是小悟。

听说你要用Redis来处理超时支付订单?Redis就像一个住在你内存里的闪电侠,它跑得飞快,但记性有点差(断电就失忆)。它是个键值对存储的社交恐惧症患者,就喜欢简单直接的交流。不过对付订单超时这种“限时任务”,它可是专业的“时间管理大师”!

为什么选Redis来做这个?

你开了一家网红奶茶店,顾客下单后30分钟不付款,订单就自动取消。你总不能雇个店员盯着每个订单看30分钟吧?Redis的过期键和发布订阅功能,就是那个不知疲倦的“自动取消专员”!


详细步骤:让我们开始组装这个“订单取消机器人”

第1步:引入Redis依赖包

<!-- pom.xml 里加入这个“能量饮料” --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>

第2步:配置Redis连接

# application.yml spring: redis: # Redis的地址,默认是本地6379端口 host: localhost port: 6379 # 密码(如果设置了的话) password: # 数据库索引,就像给闪电侠安排的第几个房间 database: 0 lettuce: pool: # 连接池配置,别让闪电侠累着了 max-active: 8 max-idle: 8 min-idle: 0 max-wait: 100ms

第3步:配置RedisTemplate

import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 键的序列化 - 字符串序列化 template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); // 值的序列化 - JSON序列化 Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.activateDefaultTyping( mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); serializer.setObjectMapper(mapper); template.setValueSerializer(serializer); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }

第4步:订单实体类

import lombok.Data; import java.time.LocalDateTime; @Data public class Order { private String orderId; // 订单ID private String userId; // 用户ID private Double amount; // 订单金额 private Integer status; // 订单状态:0-待支付,1-已支付,2-已取消 private LocalDateTime createTime;// 创建时间 private LocalDateTime expireTime;// 过期时间 // 判断是否已过期 public boolean isExpired() { return LocalDateTime.now().isAfter(expireTime); } }

第5步:Redis服务类

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service public class RedisOrderService { @Autowired private RedisTemplate<String, Object> redisTemplate; @Autowired private StringRedisTemplate stringRedisTemplate; // 订单前缀,避免键冲突 private static final String ORDER_KEY_PREFIX = "order:pay:"; private static final String ORDER_EXPIRE_CHANNEL = "order.expire"; /** * 创建订单并设置30分钟过期时间 * 就像给闪电侠说:“盯着这个订单,30分钟后提醒我” */ public void createOrderWithExpire(Order order, int expireMinutes) { String orderKey = ORDER_KEY_PREFIX + order.getOrderId(); // 保存订单到Redis,30分钟后自动删除 redisTemplate.opsForValue().set( orderKey, order, expireMinutes, TimeUnit.MINUTES ); // 同时设置一个简单的标志,用于监听过期事件 stringRedisTemplate.opsForValue().set( orderKey + ":flag", "1", expireMinutes, TimeUnit.MINUTES ); System.out.println("订单 " + order.getOrderId() + " 已放入Redis,设置" + expireMinutes + "分钟后过期"); } /** * 用户支付成功,删除过期键 * 相当于告诉闪电侠:“不用盯了,顾客付钱了!” */ public void handlePaymentSuccess(String orderId) { String orderKey = ORDER_KEY_PREFIX + orderId; // 手动删除订单和标志 redisTemplate.delete(orderKey); stringRedisTemplate.delete(orderKey + ":flag"); System.out.println("订单 " + orderId + " 支付成功,已从Redis移除"); } /** * 检查订单是否还存在(是否已过期) */ public boolean isOrderExist(String orderId) { String orderKey = ORDER_KEY_PREFIX + orderId; return Boolean.TRUE.equals(redisTemplate.hasKey(orderKey)); } /** * 获取订单信息 */ public Order getOrder(String orderId) { String orderKey = ORDER_KEY_PREFIX + orderId; return (Order) redisTemplate.opsForValue().get(orderKey); } }

第6步:Redis过期监听配置

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; @Configuration public class RedisExpireConfig { @Bean public RedisMessageListenerContainer container( RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); // 监听所有key过期事件 container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@0__:expired")); return container; } @Bean public MessageListenerAdapter listenerAdapter(RedisKeyExpireListener receiver) { return new MessageListenerAdapter(receiver, "handleMessage"); } }

第7步:过期事件监听器

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; @Component public class RedisKeyExpireListener extends MessageListenerAdapter { @Autowired private OrderService orderService; /** * 当Redis键过期时,这个方法会被调用 * 闪电侠会喊:“嘿!那个订单过期了!” */ @Override public void handleMessage(Message message, byte[] pattern) { String expiredKey = new String(message.getBody(), StandardCharsets.UTF_8); // 只处理我们的订单过期键 if (expiredKey.startsWith("order:pay:")) { // 去掉":flag"后缀获取订单ID String orderId = expiredKey .replace("order:pay:", "") .replace(":flag", ""); System.out.println("Redis报告:订单 " + orderId + " 已超时!"); // 处理订单超时逻辑 orderService.cancelExpiredOrder(orderId); } } }

第8步:订单服务层

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class OrderService { @Autowired private RedisOrderService redisOrderService; @Autowired private OrderRepository orderRepository; /** * 创建订单 */ @Transactional public Order createOrder(String userId, Double amount) { Order order = new Order(); order.setOrderId(generateOrderId()); order.setUserId(userId); order.setAmount(amount); order.setStatus(0); // 待支付 order.setCreateTime(LocalDateTime.now()); order.setExpireTime(LocalDateTime.now().plusMinutes(30)); // 保存到数据库 orderRepository.save(order); // 保存到Redis并设置30分钟过期 redisOrderService.createOrderWithExpire(order, 30); return order; } /** * 处理支付回调 */ @Transactional public void handlePaymentCallback(String orderId) { // 检查订单是否已过期 if (!redisOrderService.isOrderExist(orderId)) { throw new RuntimeException("订单已超时,请重新下单"); } // 更新订单状态为已支付 orderRepository.updateOrderStatus(orderId, 1); // 从Redis移除过期键 redisOrderService.handlePaymentSuccess(orderId); System.out.println("订单 " + orderId + " 支付处理完成"); } /** * 取消超时订单 */ @Transactional public void cancelExpiredOrder(String orderId) { // 再次检查,防止重复处理 Order order = orderRepository.findById(orderId); if (order != null && order.getStatus() == 0) { order.setStatus(2); // 已取消 orderRepository.save(order); // 可以在这里添加其他逻辑,比如释放库存、发送通知等 System.out.println("订单 " + orderId + " 因超时未支付已被自动取消"); // 发送取消通知 sendCancelNotification(order); } } /** * 发送取消通知(模拟) */ private void sendCancelNotification(Order order) { // 这里可以集成消息队列、邮件、短信等 System.out.println("发送通知:亲爱的用户" + order.getUserId() + ",您的订单" + order.getOrderId() + "因超时未支付已取消"); } private String generateOrderId() { return "ORD" + System.currentTimeMillis() + (int)(Math.random() * 1000); } }

第9步:控制器层

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/orders") public class OrderController { @Autowired private OrderService orderService; @Autowired private RedisOrderService redisOrderService; /** * 创建订单 */ @PostMapping("/create") public ApiResult createOrder(@RequestParam String userId, @RequestParam Double amount) { try { Order order = orderService.createOrder(userId, amount); return ApiResult.success("订单创建成功", order); } catch (Exception e) { return ApiResult.error("订单创建失败:" + e.getMessage()); } } /** * 模拟支付 */ @PostMapping("/pay") public ApiResult payOrder(@RequestParam String orderId) { try { // 模拟支付处理时间 Thread.sleep(1000); orderService.handlePaymentCallback(orderId); return ApiResult.success("支付成功"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return ApiResult.error("支付处理中断"); } catch (Exception e) { return ApiResult.error("支付失败:" + e.getMessage()); } } /** * 检查订单状态 */ @GetMapping("/status/{orderId}") public ApiResult checkOrderStatus(@PathVariable String orderId) { boolean exists = redisOrderService.isOrderExist(orderId); if (exists) { return ApiResult.success("订单有效,请尽快支付"); } else { return ApiResult.success("订单已超时或不存在"); } } } // 简单的返回结果类 class ApiResult { private boolean success; private String message; private Object data; // 构造方法和getter/setter省略... public static ApiResult success(String message) { return new ApiResult(true, message, null); } public static ApiResult success(String message, Object data) { return new ApiResult(true, message, data); } public static ApiResult error(String message) { return new ApiResult(false, message, null); } }

第10步:别忘了开启Redis的键空间通知(重要!)

在Redis配置文件(redis.conf)中或通过Redis命令行开启:

# 方式1:配置文件 notify-keyspace-events "Ex" # 方式2:命令行(临时生效) redis-cli config set notify-keyspace-events Ex

或者在你的Spring Boot应用启动时自动配置:

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class RedisConfigRunner implements CommandLineRunner { @Autowired private StringRedisTemplate stringRedisTemplate; @Override public void run(String... args) { // 开启键过期事件通知 stringRedisTemplate.getConnectionFactory() .getConnection() .serverCommands() .configSet("notify-keyspace-events", "Ex"); System.out.println("Redis键空间通知已开启"); } }

完整的工作流程

  1. 顾客下单POST /orders/create→ 订单存入数据库和Redis,开始30分钟倒计时
  2. Redis盯梢:闪电侠开始计时,30分钟寸步不离
  3. 顾客支付
    • 30分钟内支付:POST /orders/pay→ Redis删除订单,交易完成
    • 超过30分钟:Redis键自动过期 → 触发过期事件 → 自动取消订单
  4. 系统通知:给顾客发送“订单已取消”的贴心小提示

一些高级玩法

方案优化:使用Redisson的延迟队列(更可靠)

import org.redisson.api.RBlockingDeque; import org.redisson.api.RDelayedQueue; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.concurrent.TimeUnit; @Service public class RedissonOrderService { @Autowired private RedissonClient redissonClient; private RBlockingDeque<String> orderQueue; private RDelayedQueue<String> delayedQueue; @PostConstruct public void init() { orderQueue = redissonClient.getBlockingDeque("orderDelayQueue"); delayedQueue = redissonClient.getDelayedQueue(orderQueue); // 启动消费者线程 new Thread(this::consumeExpiredOrders).start(); } /** * 添加延迟订单 */ public void addDelayOrder(String orderId, long delay, TimeUnit unit) { delayedQueue.offer(orderId, delay, unit); System.out.println("订单 " + orderId + " 已加入延迟队列," + delay + " " + unit + "后过期"); } /** * 消费过期订单 */ private void consumeExpiredOrders() { while (true) { try { // 阻塞获取过期订单 String orderId = orderQueue.take(); System.out.println("延迟队列报告:订单 " + orderId + " 已过期"); // 处理订单取消逻辑... } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } }

注意事项:防重复处理(幂等性)

// 在OrderService中添加防重复处理 @Transactional public void cancelExpiredOrder(String orderId) { // 使用Redis分布式锁,防止多个实例同时处理同一个订单 String lockKey = "order:cancel:lock:" + orderId; Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { try { // 再次检查订单状态(双重校验) Order order = orderRepository.findById(orderId); if (order != null && order.getStatus() == 0) { // 更新订单状态 order.setStatus(2); orderRepository.save(order); System.out.println("订单 " + orderId + " 已取消"); } } finally { // 释放锁 redisTemplate.delete(lockKey); } } }

总结

  1. 性能爆表:Redis基于内存操作,处理速度堪比闪电侠跑步
  2. 精准定时:Redis的过期机制精准可靠,误差极小
  3. 解耦神器:业务逻辑和定时任务分离,代码清爽不油腻
  4. 扩展性强:轻松应对高并发,加个Redis集群就能撑起双11
  5. 资源友好:不需要额外的定时任务中间件,省心省力

但也要注意这些“坑”

  1. Redis持久化:记得配置RDB/AOF,不然闪电侠“失忆”就麻烦了
  2. 网络波动:Redis挂了怎么办?要有降级方案
  3. 事件丢失:Redis的过期事件可能丢失,重要业务要有补偿机制
  4. 时钟同步:多服务器时间要同步,别自己人跟自己人“打架”

最后

想象一下:

  • 没有Redis时:你的数据库被定时任务扫得气喘吁吁,每次都要问:“哪些订单超时了?”
  • 有了Redis后:Redis主动报告:“嘿!这几个订单超时了,快处理!”

这就好比从“挨家挨户查水表”变成了“水表自己打电话报警”,效率提升不是一点点!好的架构,就是让合适的工具做合适的事。Redis就是这个场景下的“时间管理大师”!

谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。

您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/8 20:20:15

用YOLOv9官方镜像做毕业设计,简单又出彩

用YOLOv9官方镜像做毕业设计&#xff0c;简单又出彩 毕业设计是本科阶段最能体现综合能力的实践环节。对计算机视觉方向的同学来说&#xff0c;目标检测项目既实用又有展示度——但真正动手时&#xff0c;很多人卡在环境配置、数据准备、训练调参这些“看不见的功夫”上。你可…

作者头像 李华
网站建设 2026/3/8 3:00:37

基于spring的地产企业工程项目管理系统[spring]-计算机毕业设计源码+LW文档

摘要&#xff1a;随着房地产行业的蓬勃发展&#xff0c;地产企业工程项目管理面临着诸多挑战。为了提高管理效率、降低成本并确保项目顺利进行&#xff0c;开发一套高效的工程项目管理系统至关重要。本文介绍了一个基于Spring框架的地产企业工程项目管理系统&#xff0c;详细阐…

作者头像 李华
网站建设 2026/3/16 3:13:59

Live Avatar商业应用前景:教育、客服、直播三大场景展望

Live Avatar商业应用前景&#xff1a;教育、客服、直播三大场景展望 1. Live Avatar是什么&#xff1a;不只是数字人&#xff0c;而是可落地的实时交互系统 Live Avatar不是概念演示&#xff0c;也不是实验室里的玩具。它是阿里联合高校开源的一套真正能跑起来的实时数字人生…

作者头像 李华
网站建设 2026/3/13 17:31:20

Java API 设计终极指南

第一部分&#xff1a;API设计哲学与核心原则 1.1 API设计的重要性 API作为契约&#xff1a;API&#xff08;应用程序编程接口&#xff09;是不同软件组件之间的契约。良好的API设计能够&#xff1a; 降低学习成本 提高代码可维护性 减少错误使用 促进团队协作 支持长期演…

作者头像 李华
网站建设 2026/3/14 9:17:03

Qwen3-4B-Instruct智能客服实战:多轮对话系统搭建教程

Qwen3-4B-Instruct智能客服实战&#xff1a;多轮对话系统搭建教程 1. 为什么选Qwen3-4B-Instruct做智能客服&#xff1f; 你有没有遇到过这样的问题&#xff1a;客户在咨询时连续追问&#xff0c;比如先问“订单怎么查”&#xff0c;接着问“物流卡在哪儿了”&#xff0c;再问…

作者头像 李华
网站建设 2026/3/6 3:48:32

信号处理仿真:滤波器设计与仿真_10.滤波器设计实例与应用

10. 滤波器设计实例与应用 在信号处理仿真中&#xff0c;滤波器设计是一个非常重要的环节。滤波器可以用于去除信号中的噪声、提取特定频率的信号成分、平滑信号等。本节将通过具体的实例来介绍不同类型的滤波器设计方法及其在实际中的应用。 10.1 低通滤波器设计 10.1.1 原…

作者头像 李华