Springboot整合mqtt实现消息发送站内信功能

1,524 阅读3分钟

我正在参与掘金创作者训练营第6期,点击了解活动详情

MQTT简介:

MQTT是基于二进制消息的发布/订阅编程模式的消息协议,最早是由IBM提出的,主要的场景有:

  • 遥感数据
  • 汽车
  • 智能家居

由于物联网的环境是非常特别的,所以MQTT遵循以下设计原则:

  1. 精简,不添加可有可无的功能。
  2. 发布/订阅模式,方便消息在传感器之间传递。
  3. 允许用户动态创建主题,零运维成本。
  4. 支持连续的会话控制。

环境:

  • springboot
  • mqtt
  • rocketmq
  • emqx

说明: 因为使用的是mqtt,mqtt有一个消息服务器emqx去存放消息,所以在启动之前需要先把eqmx服务搭建起来,可以使用dorker,与此同时消息发送之后需要有一个消息接收者。 整体消息发送的流程是:

消息接收者(创建topic)---->消息发送者(发送给指定的topic上,同时把消息存放在emqx)--->emqx服务器---->消息订阅者收到消息

可以看到整体的流程和常见的mq使用流程差不多,但是他的topic创建和mq不一样是由消费者创建,同时也支持多个消费者订阅同一个topic这样可以实现一个消息多人消费场景的话就类似于直播聊天室,主播发一条消息,每个观看者可以都可以收到,在这个场景下就是每个观众订阅主播的topic

emqx控制台界面 WX20220905-202343@2x.png

可以对主题进行监控,看看消息的流入流出数量 WX20220905-202322@2x.png

注册到emqx之后可以在这里看到每个客户端的信息 WX20220905-202334@2x.png

POM

<!--webSocket实时通信 socket依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<!--MQTT使用包-->
<dependency>
    <groupId>org.eclipse.paho</groupId>
    <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
    <version>1.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.80</version>
</dependency>
<!--XXl-job依赖-->
<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>2.3.0</version>
</dependency>
<!--rocketmq依赖-->
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

目前的配置有依赖中有rocketmq和xxl-job这两个是为mqtt消息异步发送以及定时消息做的准备。

import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.net.InetAddress;

/**
 * @author ge.gao
 * @version 1.0
 * @date 2022/3/1 10:16 上午
 */
@Slf4j
@Component(value = "MqttPushConfig")
public class MqttPushConfig {
    private static MqttClient client;

    public static MqttClient getClient() {
        return client;
    }

    public static void setClient(MqttClient client) {
        MqttPushConfig.client = client;
    }

    /**
     * 编辑连接信息
     * @param userName
     * @param password
     * @param outTime
     * @param KeepAlive
     * @return
     */
    private MqttConnectOptions getOption(String userName, String password, int outTime, int KeepAlive) {
        //MQTT连接设置
        MqttConnectOptions option = new MqttConnectOptions();
        //设置是否清空session,false表示服务器会保留客户端的连接记录,true表示每次连接到服务器都以新的身份连接
        option.setCleanSession(false);
        //设置连接的用户名
        option.setUserName(userName);
        //设置连接的密码
        option.setPassword(password.toCharArray());
        //设置超时时间 单位为秒
        option.setConnectionTimeout(outTime);
        //设置会话心跳时间 单位为秒 服务器会每隔(1.5*keepTime)秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
        option.setKeepAliveInterval(KeepAlive);
        option.setAutomaticReconnect(true);
        //setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
        //option.setWill("willTopic",(client.getClientId() + "与服务器断开连接").getBytes(),0,false);
        option.setMaxInflight(1000);
        return option;
    }

    /**
     * 发起连接
     */
    public void connect(MqttConfiguration mqttConfiguration) {
        MqttClient client;
        try {
            String hostAddress = InetAddress.getLocalHost().getHostAddress();
            client = new MqttClient(mqttConfiguration.getHost(), hostAddress, new MemoryPersistence());
            MqttConnectOptions options = getOption(mqttConfiguration.getUsername(), mqttConfiguration.getPassword(),
                    mqttConfiguration.getTimeout(), mqttConfiguration.getKeepAlive());
            MqttPushConfig.setClient(client);
            try {
                client.setCallback(new PushCallbackConfig(this, mqttConfiguration));
                if (!client.isConnected()) {
                    client.connect(options);
                    log.info("================>>>MQTT连接成功<<======================");

                } else {//这里的逻辑是如果连接不成功就重新连接
                    client.disconnect();
                    client.connect(options);
                    log.info("===================>>>MQTT断连成功<<<======================");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 发布
     *
     * @param qos
     * @param retained
     * @param topic
     * @param pushMessage
     */
    public void publish(int qos, boolean retained, String topic, String pushMessage) {
        MqttMessage message = new MqttMessage();
        message.setQos(qos);
        message.setRetained(retained);
        try {
            message.setPayload(pushMessage.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        MqttTopic mTopic = MqttPushConfig.getClient().getTopic(topic);
        if (null == mTopic) {
            log.error("===============>>>MQTT topic 不存在<<=======================");
        }
        MqttDeliveryToken token;
        try {
            token = mTopic.publish(message);
            token.waitForCompletion();
        } catch (MqttPersistenceException e) {
            e.printStackTrace();
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }
}

MqttConfiguration

import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

/**
 * @author ge.gao
 * @version 1.0
 * @date 2022/3/1 10:14 上午
 */
@Getter
@Setter
@Component
@Configuration
public class MqttConfiguration {

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

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

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

    @Value("${spring.mqtt.timeout}")
    private int timeout;

    @Value("${spring.mqtt.KeepAlive}")
    private int KeepAlive;

    @Value("${spring.mqtt.topics}")
    private String topics;

    @Value("${spring.mqtt.qos}")
    private int qos;

}

我是用的nacos作为配置中心所以配置全部都放在的nacos上

WX20220905-201209.png

服务启动之后可以在eqmx上面看到自己服务,这个时候需要一个消息的消费者,可以下载一个MQTTX这是官方提供的一个软件同时,我的配置里面有rocketmq,之所以用到这个是为了给系统做异步,因为发送消息这个功能是有可能多个人同时去使用和发送消息,所以在发送消息的时候为了消息解耦,

消息流程:

graph TD
发送请求 --> rocketmq --> 异步消费 --> 消费者监听消费消息 --> 消息发送至emqx --> 各消费端消费消息