SpringBoot集成Redis实现订单超时管理的详细步骤

 更新时间:2026年03月10日 09:59:50   作者:悟空码字  
文章描述了如何使用Redis来处理超时支付订单,包括引入依赖、配置连接、定义实体类、实现服务和监听器等步骤,文章讨论了优化方案、注意事项以及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就是这个场景下的“时间管理大师”!

到此这篇关于别再用定时任务扫库了!SpringBoot集成Redis实现订单超时管理的文章就介绍到这了,更多相关SpringBoot集成Redis内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java8中Stream使用的一个注意事项

    Java8中Stream使用的一个注意事项

    最近在工作中发现了对于集合操作转换的神器,java8新特性 stream,但在使用中遇到了一个非常重要的注意点,所以这篇文章主要给大家介绍了关于Java8中Stream使用过程中的一个注意事项,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-11-11
  • 一文快速掌握Spring Cloud Stream

    一文快速掌握Spring Cloud Stream

    这篇文章主要介绍了Spring Cloud Stream详解,本篇文章所涉及到的demo练习使用的cloud 2021.0.3+ springboot2.6.8,通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-08-08
  • Java Math类、Random类、System类及BigDecimal类用法示例

    Java Math类、Random类、System类及BigDecimal类用法示例

    这篇文章主要介绍了Java Math类、Random类、System类及BigDecimal类用法,结合实例形式分析了java数值运算相关的Math类、Random类、System类及BigDecimal类基本功能与使用技巧,需要的朋友可以参考下
    2019-03-03
  • 关于Java中重定向传参与取值

    关于Java中重定向传参与取值

    这篇文章主要介绍了Java中重定向传参与取值问题,重定向不仅可以重定向到当前应用程序中的其他资源,还可以重定向到同一个站点上的其他应用程序中的资源,甚至是使用绝对URL重定向到其他站点的资源,本文给大家介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • 进一步理解Java中的多态概念

    进一步理解Java中的多态概念

    这篇文章主要介绍了进一步理解Java中的多态概念,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-08-08
  • Java通过JavaMail发送邮件功能

    Java通过JavaMail发送邮件功能

    这篇文章主要为大家详细介绍了Java通过JavaMail发送邮件功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08
  • 通过FeignClient如何获取文件流steam is close问题

    通过FeignClient如何获取文件流steam is close问题

    这篇文章主要介绍了通过FeignClient如何获取文件流steam is close问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Java struts2 package元素配置及实例解析

    Java struts2 package元素配置及实例解析

    这篇文章主要介绍了Java struts2 package元素配置及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • 高斯混合模型与EM算法图文详解

    高斯混合模型与EM算法图文详解

    高斯模型就是用高斯概率密度函数(正态分布曲线)精确地量化事物,将一个事物分解为若干的基于高斯概率密度函数(正态分布曲线)形成的模型
    2021-08-08
  • SpringBoot项目中同时操作多个数据库的实现方法

    SpringBoot项目中同时操作多个数据库的实现方法

    在实际项目开发中可能存在需要同时操作两个数据库的场景,本文主要介绍了SpringBoot项目中同时操作多个数据库的实现方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03

最新评论