明白了,你想用 Spring Boot + Redis 来实现 订单超时自动删除的功能,这在电商系统中非常常见。下面我给你一个 完整可用方案,包含思路、代码示例和注意点。
Spring Boot + Redis 实现订单超时自动删除
方案思路
- 订单超时定义:用户下单后,如果在指定时间内未支付,订单自动取消/删除。
- Redis 的优势:
- Redis 支持 键过期(TTL, Time-To-Live)
- Redis 支持 事件通知(Keyspace Notification)
- 实现方式:
- 方案 1(简单):用
RedisTemplate设置订单 key 的过期时间,利用RedisListener监听过期事件。 - 方案 2(定时轮询):把订单存到 Redis 或数据库中,定时扫描判断超时(简单但效率低)。
- 方案 3(消息队列 + 延迟队列):可以用 Redis 的
ZSet或Stream做延迟队列,但 Redis key TTL 足够简单。
- 方案 1(简单):用
推荐方案:使用 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.yml 或 application.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);
}
}
✅ 测试:
- 调用
/orders/create?userId=123&amount=100 - 60 秒后,Redis key 自动过期
RedisKeyExpiredListener收到事件,可以执行数据库取消逻辑
6️⃣ 注意事项
- 数据库同步:Redis key 过期只能删除缓存,真正订单状态必须更新数据库。
- Redis 配置:
- 确保
notify-keyspace-events设置为Ex - 不然过期事件无法触发
- 确保
- 分布式环境:
- 如果你有多台 Redis 或微服务,确保事件监听和数据库更新不会重复执行。可以用 消息队列 或 Redisson 分布式锁 保证幂等。
- 订单时间可灵活配置:
- 秒级、分钟级都可以
- 实际电商系统常用 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️⃣ 测试流程
- 启动 Spring Boot 项目
- 调用接口创建订单:
POST http://localhost:8080/orders/create?userId=123&amount=100
- Redis 自动生成 key,例如
order:UUID - 设置 TTL(例如 60 秒),过期后触发监听器
- Listener 调用
cancelOrder()同步数据库,将订单状态改为CANCELLED - 查询接口返回订单状态即可验证自动取消功能
10️⃣ 注意事项
- Redis 配置:
notify-keyspace-events Ex必须开启 - 数据库同步:过期事件只能删除 Redis 缓存,数据库必须更新
- 分布式环境:
- 多个微服务监听时可能会重复执行,可加 Redisson 分布式锁 保证幂等
- 订单时间:可按业务需求调整 TTL 秒数
发表回复