Skip to content

超时订单自动取消

基于任务调度方案

开启任务调度功能

java
@Configuration
@EnableScheduling
public class ScheduledConfig {
}

定时调度任务服务

java
@Component
public class OrderScheduleService {
    private final OrderRepository orderRepository;

    public OrderScheduleService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Scheduled(cron = "0 0/1 * * * ?")
    public void cancelUnpaidOrders() {
        LocalDateTime now = LocalDateTime.now();
        List<Order> orderList = orderRepository.findByStatus(0);
        orderList.forEach(order -> {
            if (order.getCreateTime().plusMinutes(1).isBefore(now)) {
                order.setStatus(2);
                this.orderRepository.save(order);
            }
        });
    }
}

基于 redis 过期事件

开启 redis 通知事件功能

shell
notify-keyspace-events Ex

配置监听器

java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

@Configuration
public class OrderRedisConfig {
    @Bean
    RedisMessageListenerContainer redisContainer(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }

    @Bean
    KeyExpirationEventMessageListener keyExpirationListener(RedisMessageListenerContainer container, OrderRepository orderRepository) {
        return new KeyExpirationEventMessageListener(container) {
            @Override
            public void onMessage(Message message, byte[] pattern) {
                String key = new String(message.getBody());
                System.err.printf("channel: %s, key: %s%n", new String(message.getChannel()), key);
                Long id = Long.valueOf(key);
                orderRepository.findById(id).ifPresent(order -> {
                    // 如果订单状态还是未支付
                    if (order.getStatus() == 0) {
                        order.setStatus(2);
                        orderRepository.save(order);
                    }
                });
            }
        };
    }
}

创建订单时保存数据到 redis

java
public void createOrder(Order order) {
    // 保存订单;默认状态status=0未支付
    this.orderRepository.saveAndFlush(order);
    // 将订单数据(id)作为key存入Redis
    this.stringRedisTemplate.opsForValue().set(String.valueOf(order.getId()), "", Duration.ofSeconds(10));
}

基于 RabbitMQ 死信队列

创建订单队列和死信队列

  • [x] 订单队列 - order.queue

1752902874216-52b2d94e-732a-48d0-8d52-256baf7ec277.png

  • [x] 死信队列 - dlx.queue

1752903135548-8a61a5b6-d1f6-47d5-a3c8-d949acb4198b.png

创建订单并发送到 order.queue 队列中

java
public void createOrder(Order order) {
    // 保存订单;默认状态status=0未支付
    this.orderRepository.saveAndFlush(order);
    // 将订单数据(id)发送到rabbitmq中
    rabbitTemplate.convertAndSend(
        "order.exchange",
        "order.key", 
        String.valueOf(order.getId())
    );
}

监听死信队列

java
@Component
public class OrderCancelService {
    private final OrderRepository orderRepository;

    public OrderCancelService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(name = "dlx.order.exchange", type = ExchangeTypes.TOPIC),
            value = @Queue(
                    name = "dlx.queue",
                    durable = "true",
                    arguments = {
                            @Argument(name = "x-dead-letter-exchange", value = "dlx.order.exchange"),
                            @Argument(name = "x-message-ttl", value = "60000", type = "java.lang.Integer")
                    }
            ),
            key = "dlx.key"
    ))
    public void onMessage(Message message, Channel channel) throws
            IOException {
        String body = new String(message.getBody());
        if (StringUtils.hasLength(body)) {
            Long id = Long.valueOf(body);
            System.err.printf("收到死信消息: %s%n", id);
            this.orderRepository.findById(id).ifPresent(order -> {
                // 取消订单
                if (order.getStatus() == 0) {
                    order.setStatus(2);
                    this.orderRepository.save(order);
                }
            });
        }
        // 手动ack
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

更新: 2025-07-19 13:44:56
原文: https://www.yuque.com/lsxxyg/sz/dghf0iwemfod95yy