springboot 整合rabbitmq延时队列实现支付结果阶梯通知

1,422 阅读2分钟

springboot 整合rabbitmq延时队列实现支付结果阶梯通知

延时队列

最近在写支付项目,其中用到了支付结果异步通知,之前也接入过很多支付公司,大部分都是阶梯性通知,策略一般设定30秒、60秒、3分钟、6分钟、10分钟调度多次,我最终确定使用RabbitMq消息延迟+死信队列来实现。

实现思路:

img

图片来源与网络

导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!--spring-boot-starter-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<!--spring-boot-starter-web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--hutool-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.6.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>21.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

application.properties 配置RabbitMq

spring.rabbitmq.username=page
spring.rabbitmq.password=page
spring.rabbitmq.host=192.168.1.2
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
## 自定义消息发送规则
trade.query.rule={1:10,2:20,3:30}

代码实现

  1. 定义队列和交换机
@Configuration
@Slf4j
public class RabbitConfig {
    @Resource
    private MqProperties mqProperties;
    @Bean
    public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) {
        return new RabbitTemplate(connectionFactory);
    }
    @Bean
    public Queue delayProcessQueue() {
        Map<String, Object> params = new HashMap<>();
        params.put("x-dead-letter-exchange", mqProperties.getExchangeName());
        params.put("x-dead-letter-routing-key", mqProperties.getRoutingKey());
        log.info("wypMqProperties={}", JsonUtils.toJSON(mqProperties));
        return new Queue(mqProperties.getDelayQueue(), true, false, false, params);
    }
    @Bean
    public DirectExchange delayExchange() {
        return new DirectExchange(mqProperties.getDelayExchange());
    }
    @Bean
    public Binding dlxBinding() {
        return BindingBuilder.bind(delayProcessQueue()).to(delayExchange()).with(mqProperties.getDelayRoutingKey());
    }
    @Bean
    public Queue registerBookQueue() {
        return new Queue(mqProperties.getQueueName(), true);
    }
    @Bean
    public TopicExchange registerBookTopicExchange() {
        return new TopicExchange(mqProperties.getExchangeName());
    }
    @Bean
    public Binding registerBookBinding() {
        return BindingBuilder.bind(registerBookQueue()).to(registerBookTopicExchange()).with(mqProperties.getRoutingKey());
    }
}
  1. 定义MqProperties 队列属性

    @Component
    @ConfigurationProperties("trade.queue")
    @Data
    public class MqProperties {
    
        private String delayQueue;
        private String delayExchange;
        private String delayRoutingKey;
        private String queueName;
        private String exchangeName;
        private String routingKey;
    }
    
  2. 定义QueueData 消息实体

    @Data
    @ToString
    public class QueueData implements Serializable {
        private boolean success;
        private String tradeId;
        private Integer current = 1;
        private Integer currentTime;
        private Integer ruleSize;
        //执行次数+1
        public void next() {
            this.current++;
        }
    }
    
  3. 定义消息发送

@Component
@Slf4j
public class MessageSender {
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private MqProperties mqProperties;
    @Value("#{${trade.query.rule}}")
    private Map<Integer, Integer> rules;

    public void send(QueueData data) {
        data.setRuleSize(rules.size());
        Integer current = data.getCurrent();
        Integer currentTime = rules.get(current) * 1000;
        data.setCurrentTime(currentTime);
        if (current.compareTo(rules.size()) > 0) {
            log.info("规则结束");
            return;
        }
        this.rabbitTemplate.convertAndSend(wypMqProperties.getDelayExchange(), wypMqProperties.getDelayRoutingKey(), data, message -> {
            message.getMessageProperties().setExpiration(String.valueOf(currentTime));
            return message;
        });
    }
}

rules: 自定义在application.properties中的消息发送规则,例如:{1:10,2:20,3:30} 分别是第一次10S后执行,第二次20S后执行,第三次30S后执行,具体规则可以根据业务自己实现,因为我配置中心使用的是apollo,使用这种方式可以动态调整发送策略。

  1. 定义消息消费

    @Component
    @Slf4j
    public class TradeCustomer {
        @Resource
        private MessageSender messageSender;
        @RabbitListener(queues = {"${trade.queue.queueName}"})
        public void listenerDelayQueue(QueueData data) {
            log.info("第[{}]次监听的消息[{}]]-[消费时间]-[{}]", data.getCurrent(), data.getCurrentTime(), LocalDateTime.now());
            try {
                if (data.getCurrent().compareTo(data.getRuleSize()) <= 0) {
                    log.info("第[{}]次处理业务", data.getCurrent());
                    //TODO 执行自己的业务.....
                    
                }
                if (data.getCurrent().compareTo(data.getRuleSize()) < 0) {
                    data.next();
                    messageSender.send(data);
                } else {
                    log.info("[执行结束]", data.getTradeId());
                }
            } catch (Exception e) {
                log.info("异常了={}",Throwables.getStackTraceAsString(e));
            }
        }
    }
    

    总结

    目前网上有很多类似的文档,也有很多解决方案,我根据自己实际应用情况写了这篇文章,如果大家有更好的方案或者想法,欢迎评论,最后不要纠结我的代码质量,能够帮助到你就好,谢谢大家支持。