在电商等需要在线支付的应用中,通常需要设置订单自动取消的功能。本文将介绍几种在 Spring Boot 中实现订单 30 分钟自动取消的方案,包括定时任务、延迟队列和 Redis 过期事件。

方案一:定时任务

定时任务是一种简单且常用的实现订单自动取消的方案。在 Spring Boot 中,可以使用注解@Scheduled来定义定时任务,任务会按照指定的时间间隔执行。在这个方案中,我们可以定义一个定时任务,每隔 30 分钟检查一次未支付的订单,如果订单生成时间超过 30 分钟,则自动取消该订单。

代码示例

import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@EnableScheduling
@Component
public class OrderCancelSchedule {
    @Autowired
    private OrderService orderService;

    @Scheduled(cron = "0 0/1 * * *?")
    public void cancelUnpaidOrders() {
        List<Order> unpaidOrders = orderService.getUnpaidOrders();
        unpaidOrders.forEach(order -> {
            if (order.getCreationTime().plusMinutes(30).isBefore(LocalDateTime.now())) {
                orderService.cancelOrder(order.getId());
            }
        });
    }
}

在上面的代码中,我们定义了一个名为OrderCancelSchedule的组件,并使用@EnableScheduling注解启用定时任务功能。在组件中,我们定义了一个名为cancelUnpaidOrders的方法,并使用@Scheduled注解来指定该方法作为定时任务执行。cron表达式"0 0/1 * * *?"表示任务每隔 1 分钟执行一次。

方案二:延迟队列

延迟队列是一种将任务延迟执行的机制,入队的元素在一定的延迟时间之后才能出队。在这个方案中,我们可以将订单的 ID 放入延迟队列中,并设置延迟时间为 30 分钟。当延迟时间到期时,从队列中取出订单 ID,并执行取消订单的操作。

代码示例

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {
    @Bean
    public RabbitTemplate rabbitTemplate() {
        return new RabbitTemplate();
    }
}

@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void createOrder(Order order) {
        // 保存订单至数据库
        saveOrderToDB(order);
        // 将订单 ID 推送至延迟队列
        rabbitTemplate.convertAndSend("orderDelayExchange", "orderDelayKey", order.getId(), message -> {
            message.getMessageProperties().setDelay(30 * 60 * 1000); // 设置延迟时间
            return message;
        });
    }
}

@Component
@RabbitListener(queues = "orderDelayQueue")
public class OrderDelayConsumer {
    @Autowired
    private OrderService orderService;

    @RabbitHandler
    public void process(String orderId) {
        // 取消订单
        orderService.cancelOrder(orderId);
    }
}

在上面的代码中,我们定义了一个名为RabbitMQConfig的配置类,并在其中定义了一个RabbitTemplate的 bean。在OrderService中,我们使用rabbitTemplate.convertAndSend方法将订单 ID 推送至延迟队列,并设置延迟时间为 30 分钟。在OrderDelayConsumer中,我们使用@RabbitListener注解来监听延迟队列,并在接收到订单 ID 时执行取消订单的操作。

方案三:Redis 过期事件

Redis 是一种常用的 NoSQL 数据库,它提供了键过期事件的功能。在这个方案中,我们可以将订单的 ID 作为 Redis 键,并设置过期时间为 30 分钟。当键过期时,Redis 会触发过期事件,我们可以通过订阅该事件来执行取消订单的操作。

代码示例

import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class RedisConfig {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Bean
    RedisMessageListenerContainer container() {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        // 订阅所有 db 的过期事件
        container.addMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message, byte[] pattern) {
                String expiredKey = message.toString();
                if (expiredKey.startsWith("order:")) {
                    // 处理订单超时逻辑
                    String orderId = expiredKey.split(":")[1];
                    // 这里调用你的服务类方法,处理订单超时逻辑
                    // orderService.cancelOrder(orderId);
                }
            }
        }, new PatternTopic("__keyevent@*__:expired"));
        return container;
    }
}

在上面的代码中,我们定义了一个名为RedisConfig的组件,并在其中定义了一个StringRedisTemplate的 bean。在组件中,我们使用RedisMessageListenerContainer来订阅 Redis 的过期事件,并在接收到订单 ID 时执行取消订单的操作。

总结

以上三种方案都可以实现订单在 30 分钟内未支付则自动取消的需求。

  • 定时任务方案简单易懂,但可能存在时间不准的问题。
  • 延迟队列方案可以保证任务的准时执行,但需要引入 RabbitMQ 等中间件。
  • Redis 过期事件方案也可以保证任务的准时执行,但需要引入 Redis 等中间件。在实际应用中,需要根据具体情况选择最适合的方案。