SpringBoot 项目中使用 spring-boot-starter-amqp 依赖实现 RabbitMQ

文章目录

前言

本文是工作之余的随手记,记录在工作期间使用 RabbitMQ 的笔记。

1、application.yml

  • 使用 use 属性,方便随时打开和关闭使用 MQ ,并且可以做到细化控制。
yml 复制代码
spring:
   rabbitmq:
    use: true
    host: 10.100.10.100
    port: 5672
    username: wen
    password: 123456
    exchangeSubPush: 'exWen'
    queueSubPush: 'ha.queue.SubPush'
    routeSubPush: '1000'
    exchangeState: sync.ex.State
    queueState: ha.q.Server
    queueStateSync: ha.q.StateServer
    routeState: state
    exchangeOnlineMonitor: 'sync.ex.State'
    routeOnlineMonitor: 'state'
    queueOnlineMonitor: 'ha.q.Online'
  • pom.xml 文件中使用的是 SpringBoot 项目,使用 spring-boot-starter-amqp 依赖。
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wen</groupId>
    <artifactId>springboot-mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.5.3</version>
    </parent>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.18</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>
    </dependencies>
</project>

2、RabbitMqConfig

  • 配置类,将可配置的参数使用 @Value 做好配置,与 application.yml 相互对应。
java 复制代码
package com.wen.mq;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;

@Slf4j
@Configuration
@Data
public class RabbitMqConfig {

    @Value("${spring.rabbitmq.use:true}")
    private boolean use;

    @Value("${spring.rabbitmq.host}")
    private String host;

    @Value("${spring.rabbitmq.port}")
    private int port;

    @Value("${spring.rabbitmq.username}")
    private String username;

    @Value("${spring.rabbitmq.password}")
    private String password;

    @Value("${spring.rabbitmq.virtual-host:}")
    private String virtualHost;

    @Value("${spring.rabbitmq.exchangeState}")
    private String exchangeState;
    
    @Value("${spring.rabbitmq.queueState}")
    private String queueState;

    @Value("${spring.rabbitmq.routeState}")
    private String routeState;

    @Value(("${spring.rabbitmq.queueStateSync}"))
    private String queueStateSync;

    @Value("${spring.rabbitmq.exchangeOnlineInfo}")
    private String exchangeOnlineInfo;

    @Value("${spring.rabbitmq.routeOnlineInfo}")
    private String routeOnlineInfo;

    @Value("${spring.rabbitmq.queueOnlineInfo}")
    private String queueOnlineInfo;

    @PostConstruct
    private void init() {

    }
}

3、MqMessage

  • MQ 消息实体类
java 复制代码
package com.wen.mq;

import lombok.Data;

@Data
public class MqMessage<T> {

    private String msgType;

    private String msgOrigin;

    private long time;
    
    private T data;

}

4、MqMessageItem

  • MQ 消息实体类
java 复制代码
package com.wen.mq;

import lombok.Data;

@Data
public class MqMessageItem {

    private long userId;

    private String userName;

    private int userAge;

    private String userSex;

    private String userPhone;

    private String op;

}

5、DirectMode

  • 配置中心:使用 SimpleMessageListenerContainer 进行配置。
  • 新加一个消费者队列就要在这里进行配置。
java 复制代码
package com.wen.mq;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class DirectMode {

    @Autowired
    RabbitMqConfig rabbitMqConfig;
    
    @Autowired
    private CachingConnectionFactory connectionFactory;

    @Autowired
    private StateConsumer stateConsumer;

    @Autowired
    private InfoConsumer infoConsumer;

    @Bean
    public SimpleMessageListenerContainer initMQ() {
        if (!rabbitMqConfig.isUse()) {
            return null;
        }
        log.info("begin!");
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        container.setConcurrentConsumers(1);
        container.setMaxConcurrentConsumers(1);
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认

        // 设置一个队列
        container.setQueueNames(rabbitMqConfig.getQueueStateSync());
        //如果同时设置多个队列如下: 前提是队列都是必须已经创建存在的
        //container.setQueueNames("TestDirectQueue","TestDirectQueue2","TestDirectQueue3");
        //另一种设置队列的方法,如果使用这种情况,那么要设置多个,就使用addQueues
        //container.setQueues(new Queue("TestDirectQueue",true));
        //container.addQueues(new Queue("TestDirectQueue2",true));
        //container.addQueues(new Queue("TestDirectQueue3",true));
        container.setMessageListener(stateConsumer);
        log.info("end");
        return container;
    }

    @Bean
    public SimpleMessageListenerContainer contactSyncContainer() {
        if (!rabbitMqConfig.isUse()) {
            return null;
        }
        log.info("contact begin");
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        container.setConcurrentConsumers(1);
        container.setMaxConcurrentConsumers(1);
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认消息
        //设置一个队列
        container.setQueueNames(rabbitMqConfig.getQueueOnlineInfo());
        container.setMessageListener(infoConsumer);
        log.info("contact end");
        return container;
    }

    @Bean
    public Queue queueState() {
        if (!rabbitMqConfig.isUse()) {
            return null;
        }
        return new Queue(rabbitMqConfig.getQueueState());
    }

    @Bean
    public Queue queueStateSync() {
        if (!rabbitMqConfig.isUse()) {
            return null;
        }
        return new Queue(rabbitMqConfig.getQueueStateSync());
    }
    
    @Bean
    DirectExchange exchangeState() {
        if (!rabbitMqConfig.isUse()) {
            return null;
        }
        return new DirectExchange(rabbitMqConfig.getExchangeState());
    }

    @Bean
    Binding bindingState() {
        if (!rabbitMqConfig.isUse()) {
            return null;
        }
        return BindingBuilder.bind(queueState()).to(exchangeState()).with(rabbitMqConfig.getRouteState());
    }

    @Bean
    Binding bindingStateSync() {
        if (!rabbitMqConfig.isUse()) {
            return null;
        }
        return BindingBuilder.bind(queueStateSync()).to(exchangeState()).with(rabbitMqConfig.getRouteState());
    }

    // 新加一个消费者
    @Bean
    public Queue queueOnlineMonitor() {
        if (!rabbitMqConfig.isUse()) {
            return null;
        }
        return new Queue(rabbitMqConfig.getQueueOnlineInfo());
    }

    @Bean
    DirectExchange exchangeOnlineMonitor() {
        if (!rabbitMqConfig.isUse()) {
            return null;
        }
        return new DirectExchange(rabbitMqConfig.getExchangeOnlineInfo());
    }

    @Bean
    Binding bindingExchangeOnlineMonitor() {
        if (!rabbitMqConfig.isUse()) {
            return null;
        }
        return BindingBuilder.bind(queueOnlineMonitor()).to(exchangeOnlineMonitor()).with(rabbitMqConfig.getRouteOnlineInfo());
    }
}

6、StateConsumer:消费者

  • 实现 ChannelAwareMessageListener 接口,可以在这里面做相应的操作,例如存缓存,存库等。
java 复制代码
package com.wen.mq;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
public class StateConsumer implements ChannelAwareMessageListener {

    @Autowired
    RabbitMqConfig rabbitMqConfig;

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        String queueName = message.getMessageProperties().getConsumerQueue();
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        if (!rabbitMqConfig.getQueueStateSync().equals(queueName)) {
            String bodyStr = new String(message.getBody(), StandardCharsets.UTF_8);
            try {
                MqMessage<List<MqMessageItem>> mqMessage = 
                	JSON.parseObject(bodyStr, new TypeReference<MqMessage<List<MqMessageItem>>>() {});
                // 这里可以对消息做其他处理,例如存储到缓存中
                List<MqMessageItem> items = mqMessage.getData();
                if (CollectionUtil.isNotEmpty(items)) {
                    applyToRedis(mqMessage);
                }
                log.info("consume mq msg ok, queue:{}, deliveryTag:{}, msg:{}", queueName, deliveryTag, mqMessage);
                channel.basicAck(deliveryTag, false);
            } catch (JSONException e) {
                log.error("parse mq msg exception, queue:{}, deliveryTag:{}", queueName, deliveryTag, e);
                channel.basicReject(deliveryTag, false);
            } catch (Exception e) {
                log.error("consume mq msg exception, queue:{}, deliveryTag:{}", queueName, deliveryTag, e);
                channel.basicReject(deliveryTag, true); //为true会重新放回队列
            }
        }
    }

    public static final String MQ_STATE_OP_REMOVE_STATE = "REMOVE_STATE";

    public static final String MQ_STATE_OP_CHANGE_STATE = "CHANGE_STATE";

    private void applyToRedis(MqMessage<List<MqMessageItem>> mqMessage) {

        List<MqMessageItem> data = mqMessage.getData();

        Map<String, List<MqMessageItem>> itemGroupByOp = 
        	data.stream().collect(Collectors.groupingBy(item -> item.getOp()));

        List<MqMessageItem> stateToRemove = itemGroupByOp.get(MQ_STATE_OP_REMOVE_STATE);

        List<MqMessageItem> stateToChange = itemGroupByOp.get(MQ_STATE_OP_CHANGE_STATE);

        if (CollectionUtil.isNotEmpty(stateToRemove)) {
            Map<Long, Set<String>> map = new HashMap<>();
            for (MqMessageItem item : stateToRemove) {
                map.computeIfAbsent(item.getUserId(), u -> new HashSet<>())
                .add(String.valueOf(item.getUserAge()));
            }
            // cacheService.removeUserState(map);
        }

        if (CollectionUtil.isNotEmpty(stateToChange)) {
            List<MqMessageItem> list = stateToChange.stream().map(u -> {
                MqMessageItem dto = new MqMessageItem();
                dto.setUserId(u.getUserId());
                dto.setUserAge(u.getUserAge());
                dto.setUserName(u.getUserName());
                dto.setUserSex(u.getUserSex());
                dto.setUserPhone(u.getUserPhone());
                return dto;
            }).collect(Collectors.toList());
            // cacheService.saveUserState(list);
        }
    }
}

7、InfoConsumer:消费者

  • 实现 ChannelAwareMessageListener 接口,可以在这里面做相应的操作,例如存缓存,存库等。
java 复制代码
package com.wen.mq;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class InfoConsumer implements ChannelAwareMessageListener {

    @Autowired
    RabbitMqConfig rabbitMqConfig;

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        String queueName = message.getMessageProperties().getConsumerQueue();
        log.info("queueName: {}", queueName);
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {
            byte[] body = message.getBody();
            String content = new String(body);
            MqMessage msg = JSONObject.parseObject(content, MqMessage.class);
            if (rabbitMqConfig.getQueueOnlineInfo().equals(queueName)) {
                // 订阅到的消息就是变更的消息
                // 这里可使用service对消息进行消费,返回一个boolean
                log.info("用户监控数据写入失败!数据:{}", msg);
            }
            log.info("consume mq msg ok, queue:{}, deliveryTag:{}, msg:{}", queueName, deliveryTag, msg);
            channel.basicAck(deliveryTag, false);
        } catch (JSONException e) {
            log.error("parse mq msg exception, queue:{}, deliveryTag:{}", queueName, deliveryTag, e);
            channel.basicReject(deliveryTag, false); //为true会重新放回队列
        } catch (Exception e) {
            log.error("consume mq msg exception, queue:{}, deliveryTag:{}", queueName, deliveryTag, e);
            channel.basicReject(deliveryTag, true); //为true会重新放回队列
        }
    }
}
相关推荐
JH30736 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
qq_12498707539 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_9 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
2301_818732069 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
汤姆yu13 小时前
基于springboot的尿毒症健康管理系统
java·spring boot·后端
暮色妖娆丶13 小时前
Spring 源码分析 单例 Bean 的创建过程
spring boot·后端·spring
biyezuopinvip14 小时前
基于Spring Boot的企业网盘的设计与实现(任务书)
java·spring boot·后端·vue·ssm·任务书·企业网盘的设计与实现
JavaGuide15 小时前
一款悄然崛起的国产规则引擎,让业务编排效率提升 10 倍!
java·spring boot
figo10tf15 小时前
Spring Boot项目集成Redisson 原始依赖与 Spring Boot Starter 的流程
java·spring boot·后端
zhangyi_viva15 小时前
Spring Boot(七):Swagger 接口文档
java·spring boot·后端