快速整合Spring与MQTT

297 阅读4分钟

大家好, 我是Open, 是一位新进的掘金作家, 今天我带来的是MQTT的配置教程, 如果大家感觉我写的不错可以点点赞👍, 好了我们废话不多说, 直接开始操作。

本次使用的版本如下:

  • SpringBoot: 2.7.14
  • Spring-MQTT:5.5.18

1: 读取YML配置

#MQTT
mqtt:
  host: 
  userName: 
  passWord: 
  qos: 1
  clientId:
  timeout: 10
  keepalive: 20
  topic1: 
  topic2: 
  topic3: 
package com.ruoyi.factory.mqtt.properties;
import lombok.Data;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 1. @author WXY
 2. @date 2022/6/29 20:42
 */

@Data
@Configuration
public class MqttProperties {

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

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

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

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

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

    @Value("${mqtt.keepalive}")
    private int keepAlive;

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

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

    /**
     * 设置mqtt连接参数
     *
     * @return
     */
    public MqttConnectOptions setMqttConnectOptions() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setUserName(username);
        options.setPassword(password.toCharArray());
        options.setConnectionTimeout(timeOut);
        options.setKeepAliveInterval(keepAlive);
        options.setCleanSession(true);
        options.setAutomaticReconnect(true);
        return options;
    }


}

2: 更新自带的Bean、设置回调、一些回调配置

  1. newClient(MqttProperties config)中的cofig会自带yml中的配置
  2. 回调工厂可以新增自己的内容, 具体像是@Resource中注入,在放进Factory
  3. MqttConnectOptions,Mqtt的连接配置在上方的pro中,可以自己选择将其他config放入yml
  4. 所有的内容都需要从该配置中注入到工厂中使用
package com.ruoyi.factory.config;

import com.ruoyi.factory.mqtt.MqttCallbackFactory;
import com.ruoyi.factory.mqtt.MqttServer;
import com.ruoyi.factory.mqtt.properties.MqttProperties;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.annotation.Resource;

/**
 * @author OpenACloud
 * @create 2024/3/20 14:12
 */
@Configuration
public class MqttConfig {

    @Resource
    private MqttServer server;

    @Bean
    @Primary
    public MqttClient newClient(MqttProperties config) throws MqttException {
        MqttClient mqttClient = new MqttClient(config.getHost(), config.getClientId(), new MemoryPersistence());
        MqttCallbackFactory callbackFactory =
                new MqttCallbackFactory(mqttClient, 100L, config, server)
                                .reconnectConfig(2500L, 2L)
                                .completeConfig(true);
        mqttClient.setCallback(callbackFactory);
        MqttConnectOptions mqttConnectOptions = config.setMqttConnectOptions();
        if(mqttClient.isConnected()) {
            mqttClient.disconnect();
        }
        mqttClient.connect(mqttConnectOptions);
        return mqttClient;
    }
}

3: 回调工厂的演示

  • 需要什么自己去改,这个地方可以扩展开来, 其次在message中需要去做自己的消息类型体
package com.ruoyi.factory.mqtt;

import cn.hutool.core.util.CharsetUtil;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.factory.mqtt.properties.MqttProperties;
import org.eclipse.paho.client.mqttv3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

/**
 *
 * @author OpenACloud
 * @create 2024/3/20 14:29
 */
public class MqttCallbackFactory implements MqttCallbackExtended {

    /**
     * Lost reconnect
     */
    private Long speed = 1000L;
    private Long speedMultiple = 1L;
    private Long sleepTime = speed * speedMultiple;

    /**
     * Complete Log
     */
    private boolean completeLog;

    protected Long connectCount;
    protected MqttClient mqttClient;
    protected MqttServer mqttServer;
    protected MqttProperties mqttConfiguration;
    private static final Logger log = LoggerFactory.getLogger(MqttCallbackFactory.class);

    @Override
    public void connectionLost(Throwable throwable) {
        Long nowCount = 1L;
        log.warn("连接以断开, 错误原因[ reason: {} ]", throwable.getMessage());
        log.warn("正在尝试重新连接, 本次连接最大限度为[ count: {}]", connectCount);
        while (nowCount <= connectCount) {
            if(mqttClient.isConnected()) {
                log.warn("本次重连结束, 本次重连次数[ count: {}]", nowCount);
                return;
            }else {
                try {
                    mqttClient.connect();
                }catch (MqttException exception) {
                    log.warn("重连失败, 当前连接次数[ count: {}]", nowCount);
                }
            }
            try {
                Thread.sleep(sleepTime);
            }catch (InterruptedException e) {
                log.warn("重连睡眠失败-------------------------> , 暂无引发原因, 请管理员排查");
            }
            nowCount++;
        }
    }

    /**
     *  
     *  重新连接后  主题也需要再次订阅  将重新订阅主题放在连接成功后的回调 比较合理
     *
     * @param connectType
     * @param serverURI
     */
    @Override
    public void connectComplete(boolean connectType, String serverURI) {
        log.info("MQTT连接成功,连接方式:{}", connectType ? "重连" : "直连");
    }


    /**
     * subscribe后得到的消息会执行到这里面
     *
     * @param topic
     * @param mqttMessage
     * @throws Exception
     */
    @Override
    public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
        log.info("接收消息主题:{},接收消息内容:{}", topic, new String(mqttMessage.getPayload()));
    }


    /**
     * publish后,配送完成后回调的方法
     *
     * @param iMqttDeliveryToken
     */
    @Override
    public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
        if(completeLog) {
            try {
                log.info("Complete: {}, Msg: {}", iMqttDeliveryToken.isComplete(), iMqttDeliveryToken.getMessage());
            } catch (MqttException e) {
                throw new RuntimeException(e);
            }
        }
    }


    /**
     *  初始化连接信息
     *
     * @param mqttClient     客户端
     * @param connectCount   最大重连次数
     * @param mqttConfiguration 配置
     * @param mqttServer        服务
     */
    public MqttCallbackFactory(MqttClient mqttClient, Long connectCount, MqttProperties mqttConfiguration, MqttServer mqttServer)
    {
        this.mqttClient = mqttClient;
        this.mqttServer = mqttServer;
        this.connectCount = connectCount;
        this.mqttConfiguration = mqttConfiguration;
    }

    /**
     * 如果你还想对其他配置进行初始化, 请在这里配置
     *
     * @param speed 速度
     * @param speedMultiple 加成
     * 速度 * 加成 等于所需的睡眠时间, 请您在初始化时调整合适大小
     */
    public MqttCallbackFactory reconnectConfig(Long speed, Long speedMultiple)
    {
        this.speed = speed;
        this.speedMultiple = speedMultiple;
        this.sleepTime = speed * speedMultiple;
        return this;
    }

    /**
     * 如果你还想对其他配置进行初始化, 请在这里配置
     *
     * @param completeLog 是否打印日志
     */
    public MqttCallbackFactory completeConfig(Boolean completeLog)
    {
        this.completeLog = completeLog;
        return this;
    }

}

4: 订阅跟发布

package com.ruoyi.factory.mqtt;

import org.eclipse.paho.client.mqttv3.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author OpenACloud
 * @create 2024/3/20 14:34
 */
@Component
public class MqttPublish {

    @Resource
    private MqttClient mqttClient;

    /**
     * 发布,默认qos为0,非持久化
     *
     * @param pushMessage
     * @param topic
     */
    public void publish(String pushMessage, String topic) {
        publish(pushMessage, topic, 0, false);
    }

    /**
     * 发布消息
     *
     * @param pushMessage
     * @param topic
     * @param qos
     * @param retained:留存
     */
    public void publish(String pushMessage, String topic, int qos, boolean retained) {
        MqttMessage message = new MqttMessage();
        message.setPayload(pushMessage.getBytes());
        message.setQos(qos);
        message.setRetained(retained);
        MqttTopic mqttTopic = mqttClient.getTopic(topic);
        MqttDeliveryToken token;//Delivery:配送
        synchronized (this) {//注意:这里一定要同步,否则,在多线程publish的情况下,线程会发生死锁,分析见文章最后补充
            try {
                token = mqttTopic.publish(message);//也是发送到执行队列中,等待执行线程执行,将消息发送到消息中间件
                token.waitForCompletion(1000L);
            } catch (MqttPersistenceException e) {
                e.printStackTrace();
            } catch (MqttException e) {
                e.printStackTrace();
            }
        }
    }

}
package com.ruoyi.factory.mqtt;

import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author OpenACloud
 * @create 2024/3/20 15:05
 */
@Component
public class MqttSubscribe {

    @Resource
    private MqttClient client;

    /**
     * 订阅某个主题
     *
     * @param topic
     * @param qos
     */
    public void subscribe(String topic, int qos) {
        try {
            client.subscribe(topic, qos);
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }


    /**
     * 取消订阅主题
     *
     * @param topic 主题名称
     */
    public void cleanTopic(String topic) {
        if (client != null && client.isConnected()) {
            try {
                client.unsubscribe(topic);
            } catch (MqttException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("取消订阅失败!");
        }
    }

}

OK, 代码内容就是以上内容啦 本次代码不提供太多的,大部分内容都是对Config方面进行扩展, 如果需要对数据接入方面,请在Factory内容的Msg方法下进行添加即可, 如果要插入数据库, 请将需要的插入信息在MqttConfig的目录下以Autowire或Resoure的方式注入, 在放入Factory中即可 本次的内容不多,如果喜欢请多多点赞~