在电商平台中,处理未支付的订单过期自动关单的需求是非常常见的。下面提供几种常见的设计方案,并附上每种方案的代码示例。
方案1:基于定时任务(Scheduled Task)
设计思路:
使用定时任务(如Spring的@Scheduled
注解)定期检查订单,如果订单未支付且超时,自动将其关闭。
数据库设计:
假设订单表有如下字段:
order_id
: 订单IDstatus
: 订单状态(“PENDING”、“PAID”、“CLOSED”)create_time
: 订单创建时间expire_time
: 订单过期时间(即支付超时限制)
代码示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository; // 使用JPA仓库
// 创建订单
public void createOrder(Order order) {
order.setCreateTime(LocalDateTime.now());
order.setExpireTime(order.getCreateTime().plusMinutes(30)); // 设定过期时间为30分钟
order.setStatus("PENDING");
orderRepository.save(order);
}
// 定时任务:检查超时未支付的订单
@Scheduled(fixedRate = 60000) // 每60秒执行一次
public void checkExpiredOrders() {
List<Order> orders = orderRepository.findByStatusAndExpireTimeBefore("PENDING", LocalDateTime.now());
for (Order order : orders) {
order.setStatus("CLOSED");
orderRepository.save(order);
}
}
}
JPA Repository:
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDateTime;
import java.util.List;
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByStatusAndExpireTimeBefore(String status, LocalDateTime expireTime);
}
优点:
- 简单直观,使用Spring提供的定时任务机制。
- 不需要其他复杂的组件和配置。
缺点:
- 每隔固定时间检查一次订单,可能存在延迟,无法实现实时关单。
- 定时任务频率较高时,可能对性能产生一定压力。
方案2:基于消息队列(如RabbitMQ、Kafka)
设计思路:
在创建订单时,使用消息队列发送一个“订单超时”的事件,系统通过消费者异步处理这些超时事件。如果订单超时且未支付,自动关闭订单。
数据库设计:
同方案1相同,orders
表包含status
、create_time
、expire_time
等字段。
代码示例:
消息生产者(OrderService)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate; // 使用RabbitMQ发送消息
@Autowired
private OrderRepository orderRepository;
// 创建订单
public void createOrder(Order order) {
order.setCreateTime(LocalDateTime.now());
order.setExpireTime(order.getCreateTime().plusMinutes(30)); // 订单30分钟后过期
order.setStatus("PENDING");
orderRepository.save(order);
// 发送超时消息到消息队列
rabbitTemplate.convertAndSend("order.timeout.queue", order.getOrderId());
}
}
消息消费者(TimeoutConsumer)
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class TimeoutConsumer {
@Autowired
private OrderRepository orderRepository;
@RabbitListener(queues = "order.timeout.queue")
public void handleTimeout(Long orderId) {
Order order = orderRepository.findById(orderId).orElse(null);
if (order != null && order.getExpireTime().isBefore(LocalDateTime.now()) && "PENDING".equals(order.getStatus())) {
// 订单超时未支付,关闭订单
order.setStatus("CLOSED");
orderRepository.save(order);
}
}
}
配置(Spring配置RabbitMQ等消息中间件)
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
listener:
simple:
concurrency: 3
max-concurrency: 10
优点:
- 更加高效,不需要周期性地检查订单,可以在订单超时后立即处理。
- 消息队列的可靠性可以保证系统的高可用性。
缺点:
- 增加了系统复杂度,需要配置和维护消息队列。
- 消息队列系统故障可能会导致超时消息丢失。
方案3:基于数据库定时触发器(数据库触发器+存储过程)
设计思路:
使用数据库的触发器和存储过程来定期检查订单的超时状态。如果订单未支付且超时,则自动更新状态为“已关闭”。
数据库设计:
在订单表中增加status
、create_time
和expire_time
字段。
SQL 示例:
- 创建触发器,每隔一分钟运行一次检查未支付且过期的订单:
DELIMITER //
CREATE EVENT close_expired_orders
ON SCHEDULE EVERY 1 MINUTE
DO
BEGIN
UPDATE orders
SET status = 'CLOSED'
WHERE status = 'PENDING' AND expire_time < NOW();
END //
DELIMITER ;
优点:
- 操作简便,无需额外的Java代码,完全依赖于数据库。
- 可以保证实时性,能够在数据库层面直接处理。
缺点:
- 依赖于数据库,可能增加数据库的负担,特别是在订单量大时。
- 难以灵活调整超时策略。
方案4:基于分布式定时任务框架(如Quartz)
设计思路:
使用Quartz等分布式定时任务调度框架来定期检查超时订单,处理未支付且超时的订单。
配置:
假设在Quartz中配置一个定时任务,定期检查订单。
代码示例:
QuartzJob(定时任务)
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class OrderTimeoutJob implements Job {
@Autowired
private OrderRepository orderRepository;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
List<Order> orders = orderRepository.findByStatusAndExpireTimeBefore("PENDING", LocalDateTime.now());
for (Order order : orders) {
order.setStatus("CLOSED");
orderRepository.save(order);
}
}
}
Quartz配置(Spring配置Quartz)
@Configuration
public class QuartzConfig {
@Bean
public JobDetail orderTimeoutJobDetail() {
return JobBuilder.newJob(OrderTimeoutJob.class)
.withIdentity("orderTimeoutJob")
.storeDurably()
.build();
}
@Bean
public Trigger orderTimeoutTrigger() {
return TriggerBuilder.newTrigger()
.forJob(orderTimeoutJobDetail())
.withIdentity("orderTimeoutTrigger")
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInMinutes(1).repeatForever())
.build();
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setJobDetails(orderTimeoutJobDetail());
factory.setTriggers(orderTimeoutTrigger());
return factory;
}
}
优点:
- 灵活的定时任务框架,可以控制任务的调度和执行策略。
- 支持分布式,适用于大规模系统。
缺点:
- 配置较为复杂,需要引入外部库。
- 需要额外的资源来管理和维护定时任务。
总结
- 定时任务(@Scheduled):实现简单,适合较小的系统,但对大规模订单处理的实时性可能不足。
- 消息队列:适用于高并发、高可用的系统,能够及时处理订单超时,但增加了消息队列的维护负担。
- 数据库触发器:依赖数据库,适用于一些小型项目,但可能会增加数据库负担。
- 分布式定时任务框架(Quartz):适合大规模、分布式的系统,灵活性高,但配置和运维复杂。