明白了,你想用 Spring Boot + Redis 来实现 订单超时自动删除的功能,这在电商系统中非常常见。下面我给你一个 完整可用方案,包含思路、代码示例和注意点。


Spring Boot + Redis 实现订单超时自动删除

方案思路

  1. 订单超时定义:用户下单后,如果在指定时间内未支付,订单自动取消/删除。
  2. Redis 的优势
    • Redis 支持 键过期(TTL, Time-To-Live)
    • Redis 支持 事件通知(Keyspace Notification)
  3. 实现方式
    • 方案 1(简单):用 RedisTemplate 设置订单 key 的过期时间,利用 RedisListener 监听过期事件。
    • 方案 2(定时轮询):把订单存到 Redis 或数据库中,定时扫描判断超时(简单但效率低)。
    • 方案 3(消息队列 + 延迟队列):可以用 Redis 的 ZSetStream 做延迟队列,但 Redis key TTL 足够简单。

推荐方案:使用 Redis TTL + 过期事件监听,最简单高效。


具体实现

1️⃣ 添加依赖(Maven)

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- Lettuce 客户端 -->
    <dependency>
        <groupId>io.lettuce.core</groupId>
        <artifactId>lettuce-core</artifactId>
    </dependency>

    <!-- 可选:JSON 序列化 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>


2️⃣ Redis 配置

application.ymlapplication.properties 中配置 Redis:

spring:
  redis:
    host: localhost
    port: 6379
    timeout: 5000

⚠️ 注意:要开启 Redis key 过期事件通知。修改 redis.conf 文件,添加或确保有:

notify-keyspace-events Ex

Ex 表示 Key 过期事件


3️⃣ 订单服务:下单 + 超时自动删除

3.1 订单对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    private String orderId;
    private String userId;
    private double amount;
    private long createTime;
}

3.2 Redis 存订单并设置 TTL

@Service
public class OrderService {

    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public OrderService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 保存订单,设置过期时间(秒)
     */
    public void saveOrder(Order order, long expireSeconds) {
        String key = "order:" + order.getOrderId();
        redisTemplate.opsForValue().set(key, order, expireSeconds, TimeUnit.SECONDS);
        System.out.println("订单已保存,key=" + key + ",超时秒数=" + expireSeconds);
    }

    /**
     * 获取订单
     */
    public Order getOrder(String orderId) {
        return (Order) redisTemplate.opsForValue().get("order:" + orderId);
    }
}

✅ 这里使用 opsForValue().set(key, value, expireTime, TimeUnit) 就可以自动过期。


4️⃣ 监听订单过期事件

@Component
public class RedisKeyExpiredListener {

    @EventListener
    public void onExpiredEvent(RedisKeyExpiredEvent event) {
        String expiredKey = event.getKey();
        if (expiredKey.startsWith("order:")) {
            String orderId = expiredKey.split(":")[1];
            // 处理订单超时逻辑
            System.out.println("订单超时自动删除: " + orderId);
            // TODO: 同步更新数据库状态为“已取消”
        }
    }
}

⚠️ 注意

  • 监听器会接收到 Redis 过期事件,你可以在这里执行取消订单逻辑,比如更新数据库状态。
  • Redis TTL 只保证 key 消失,不会自动删除数据库里的订单,需要自己同步。

5️⃣ 测试 Controller

@RestController
@RequestMapping("/orders")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("/create")
    public String createOrder(@RequestParam String userId, @RequestParam double amount) {
        String orderId = UUID.randomUUID().toString();
        Order order = new Order(orderId, userId, amount, System.currentTimeMillis());
        // 设置订单超时时间,例如 60 秒
        orderService.saveOrder(order, 60);
        return "订单创建成功, orderId=" + orderId;
    }

    @GetMapping("/{orderId}")
    public Order getOrder(@PathVariable String orderId) {
        return orderService.getOrder(orderId);
    }
}

✅ 测试:

  1. 调用 /orders/create?userId=123&amount=100
  2. 60 秒后,Redis key 自动过期
  3. RedisKeyExpiredListener 收到事件,可以执行数据库取消逻辑

6️⃣ 注意事项

  1. 数据库同步:Redis key 过期只能删除缓存,真正订单状态必须更新数据库。
  2. Redis 配置
    • 确保 notify-keyspace-events 设置为 Ex
    • 不然过期事件无法触发
  3. 分布式环境
    • 如果你有多台 Redis 或微服务,确保事件监听和数据库更新不会重复执行。可以用 消息队列Redisson 分布式锁 保证幂等。
  4. 订单时间可灵活配置
    • 秒级、分钟级都可以
    • 实际电商系统常用 15 分钟支付超时

✅ 总结

  • 利用 Redis TTL 自动过期 key
  • 利用 Keyspace Notification 监听 key 过期
  • 在过期事件中执行 取消订单 / 同步数据库

这种方案 性能高、实现简单、无需额外定时任务


明白了,我给你写一个 完整可运行的 Spring Boot + Redis + MySQL demo,实现 订单下单 + Redis 超时自动删除 + 同步数据库取消订单,可以直接运行测试。


Spring Boot + Redis + MySQL 订单超时自动删除完整 Demo


1️⃣ Maven 依赖(pom.xml)

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- Lettuce 客户端 -->
    <dependency>
        <groupId>io.lettuce.core</groupId>
        <artifactId>lettuce-core</artifactId>
    </dependency>

    <!-- MySQL 数据库 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>


2️⃣ application.yml 配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

  redis:
    host: localhost
    port: 6379
    timeout: 5000

⚠️ Redis 配置文件 redis.conf 中需开启过期事件:

notify-keyspace-events Ex


3️⃣ 订单实体类

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "orders")
public class Order {

    @Id
    private String orderId;

    private String userId;

    private double amount;

    private String status; // NEW, PAID, CANCELLED

    private long createTime;
}


4️⃣ 订单 JPA 仓库

import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, String> {
}


5️⃣ Redis 配置(开启 Keyspace Notification)

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 RedisConfig {

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 监听过期事件
        container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@0__:expired"));
        return container;
    }

    @Bean
    MessageListenerAdapter listenerAdapter(OrderExpiredListener listener) {
        return new MessageListenerAdapter(listener, "onMessage");
    }
}


6️⃣ Redis 订单服务

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class OrderService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private OrderRepository orderRepository;

    /**
     * 保存订单并设置超时时间
     */
    public void saveOrder(Order order, long expireSeconds) {
        order.setStatus("NEW");
        order.setCreateTime(System.currentTimeMillis());
        // 保存数据库
        orderRepository.save(order);
        // 保存 Redis 并设置 TTL
        String key = "order:" + order.getOrderId();
        redisTemplate.opsForValue().set(key, order, expireSeconds, TimeUnit.SECONDS);
        System.out.println("订单保存,Redis key=" + key + ",超时秒数=" + expireSeconds);
    }

    public Order getOrder(String orderId) {
        return orderRepository.findById(orderId).orElse(null);
    }

    /**
     * 取消订单
     */
    public void cancelOrder(String orderId) {
        orderRepository.findById(orderId).ifPresent(order -> {
            order.setStatus("CANCELLED");
            orderRepository.save(order);
            System.out.println("订单已取消:" + orderId);
        });
    }
}


7️⃣ Redis 过期监听器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;

@Component
public class OrderExpiredListener implements MessageListener {

    @Autowired
    private OrderService orderService;

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String expiredKey = message.toString();
        if (expiredKey.startsWith("order:")) {
            String orderId = expiredKey.split(":")[1];
            // 自动取消订单
            orderService.cancelOrder(orderId);
        }
    }
}


8️⃣ Controller 测试下单

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.UUID;

@RestController
@RequestMapping("/orders")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("/create")
    public String createOrder(@RequestParam String userId, @RequestParam double amount) {
        String orderId = UUID.randomUUID().toString();
        Order order = new Order(orderId, userId, amount, "NEW", System.currentTimeMillis());
        // 设置订单超时60秒
        orderService.saveOrder(order, 60);
        return "订单创建成功, orderId=" + orderId;
    }

    @GetMapping("/{orderId}")
    public Order getOrder(@PathVariable String orderId) {
        return orderService.getOrder(orderId);
    }
}


9️⃣ 测试流程

  1. 启动 Spring Boot 项目
  2. 调用接口创建订单:
POST http://localhost:8080/orders/create?userId=123&amount=100

  1. Redis 自动生成 key,例如 order:UUID
  2. 设置 TTL(例如 60 秒),过期后触发监听器
  3. Listener 调用 cancelOrder() 同步数据库,将订单状态改为 CANCELLED
  4. 查询接口返回订单状态即可验证自动取消功能

10️⃣ 注意事项

  • Redis 配置notify-keyspace-events Ex 必须开启
  • 数据库同步:过期事件只能删除 Redis 缓存,数据库必须更新
  • 分布式环境
    • 多个微服务监听时可能会重复执行,可加 Redisson 分布式锁 保证幂等
  • 订单时间:可按业务需求调整 TTL 秒数