SpringBoot整合MQTT实现MQTT多连接

业务场景

硬件采集的数据传入MQTT(这边MQTT的服务器用的是EMQX,有兴趣的可以自己去了解一下),JAVA通过代码连接MQTT服务器,对数据进行处理

新建SpringBoot项目,pom文件中直接引入下面MQTT的依赖

        
        
            org.springframework.integration
            spring-integration-stream
        
        
            org.springframework.boot
            spring-boot-starter-integration
        
        
            org.springframework.integration
            spring-integration-mqtt
        
        

MQTT工具类

MqttServerData

因为是多连接,所以说MQTT的配置需要存放在数据库中,需要新增MQTT基础表MqttServerData,我这边用的是Spring Data JPA,大家可以用自己的ORM框架生成这些基础表的信息

package com.huibo.xx.xx.entity;

import com.huibo.core.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.Entity;

/**
 * @author :cola
 * @description: Mqtt配置表
 * @date :2023/7/18 8:51
 */
@Data
@Entity
@EqualsAndHashCode(callSuper = true)
public class MqttServerData extends BaseEntity {

    private String serverHost;

    private String serverPort;

    private String userName;

    private String password;

    private String clientId;

    /**
     * mqtt订阅的主体,以“,”隔开
     */
    private String clientTopic;

    /**
     * 客户端每次重连是否清除session
     */
    private String clientCleanSession;

    private String remark;

    private Boolean enableFlag;
}

MqttServerDataRepository

package com.xx.xx.mqtt.repository;

import com.xx.xx.mqtt.entity.MqttServerData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface MqttServerDataRepository extends JpaRepository, JpaSpecificationExecutor

MqttServerDataService

package com.xx.xx.mqtt.service;


import com.xx.xx.entity.Device;
import com.xx.xx.mqtt.controller.dto.MqttServerDataAddDto;
import com.xx.xx.mqtt.controller.dto.MqttServerDataQueryDto;
import com.xx.xx.mqtt.controller.dto.MqttServerDataUpdateDto;
import com.xx.xx.mqtt.entity.MqttServerData;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public interface MqttServerDataService {

    MqttServerData add(MqttServerDataAddDto addDto);

    boolean update(MqttServerDataUpdateDto updateDto) throws MqttException;

    MqttServerData findById(Long id);

    boolean delete(Long id) throws MqttException;

    List findByList(MqttServerDataQueryDto queryDto);

    Page findByPage(MqttServerDataQueryDto queryDto, Pageable page);

    /**
     * 初始化MQTT连接
     *
     * @param response 响应体
     */
    void initMqttServer(MqttServerData mqttServerData) throws MqttException;
}

MqttServerDataServiceImpl

/**
 * @author cola
 * @date 2023/7/13 16:23
 */
@Service
@Slf4j
public class MqttServerDataServiceImpl implements MqttServerDataService {

    @Autowired
    private MqttServerDataRepository mqttServerDataRepository;

    @Autowired
    private ExcelHandler excelHandler;

    @Override
    public MqttServerData add(MqttServerDataAddDto addDto) {
        MqttServerData mqttServerData = mqttServerDataRepository.save(from(addDto));
        if (mqttServerData.getEnableFlag()) {
            this.initMqttServer(mqttServerData);
        }
        return mqttServerData;
    }

    @Override
    @Transactional
    public boolean update(MqttServerDataUpdateDto updateDto) throws MqttException {
        MqttServerData entity = findById(updateDto.getId());
        assemble(entity, updateDto);

        if (!entity.getEnableFlag() && updateDto.getEnableFlag()) {
            if (!MqttClientListener.mqttClients.containsKey(entity.getId().toString())) {
                this.initMqttServer(entity);
            } else {

            }
        }
        if (entity.getEnableFlag() && !updateDto.getEnableFlag()) {
            MqttClientConnect mqttClientConnect = MqttClientListener.mqttClients.get(entity.getId().toString());
            mqttClientConnect.close();
        }


        return true;
    }

    @Override
    public MqttServerData findById(Long id) {
        return mqttServerDataRepository.findById(id).orElseThrow(() -> new NoEntityFoundException("No record found by id = " + id));
    }

    @Override
    @Transactional
    public boolean delete(Long id) throws MqttException {
        MqttServerData entity = findById(id);
        entity.setDeleted(true);

        MqttClientConnect mqttClientConnect = MqttClientListener.mqttClients.get(entity.getId().toString());
        if (Objects.nonNull(mqttClientConnect)) {
            mqttClientConnect.close();
        }

        return true;
    }

    @Override
    public List findByList(MqttServerDataQueryDto queryDto) {
        return mqttServerDataRepository.findAll(getSpecification(queryDto));
    }

    @Override
    public Page findByPage(MqttServerDataQueryDto queryDto, Pageable page) {
        return mqttServerDataRepository.findAll(getSpecification(queryDto), page);
    }

    @Override
    public void initMqttServer(MqttServerData mqttServerData) {
        try {
            MqttClientConnect mqttClientConnect = new MqttClientConnect();
            mqttClientConnect.setMqttClientId(UUID.randomUUID().toString());
            String host = "tcp://" + mqttServerData.getServerHost() + ":" + mqttServerData.getServerPort();
            mqttClientConnect.setMqttClient(host, UUID.randomUUID().toString(), mqttServerData.getUserName(), mqttServerData.getPassword(), new MqttClientCallback(String.valueOf(mqttServerData.getId())));
            //订阅CPUId
            mqttClientConnect.sub(String.format(DEVICE_DOWN_TOPIC, ServerUtil.getCPUSerial()));
            MqttClientListener.mqttClients.put(String.valueOf(mqttServerData.getId()), mqttClientConnect);
            log.info("{}--连接成功!!订阅主题--{}", mqttServerData.getId(), mqttServerData.getClientTopic());
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void addSubTopicForDevice(Device device) {
        MqttClientListener.mqttClients.forEach((key, value) -> {
            try {
                value.sub(String.format(DEVICE_DOWN_TOPIC, device.getName()));
            } catch (MqttException e) {
                e.printStackTrace();
            }
        });

    }

    private MqttServerData from(MqttServerDataAddDto dto) {
        MqttServerData entity = new MqttServerData();
        BeanUtils.copyProperties(dto, entity);
        entity.setClientTopic("/topic/default");
        return entity;
    }

    private void assemble(MqttServerData entity, MqttServerDataUpdateDto updateDto) {
        BeanUtils.copyProperties(updateDto, entity);
    }

    private Specification getSpecification(MqttServerDataQueryDto queryDto) {
        return (root, query, builder) -> {
            List predicates = Lists.newArrayList();
            if (!StringUtils.isEmpty(queryDto.getServerHost())) {
                predicates.add(builder.like(root.get("serverHost"), "%".concat(queryDto.getServerHost()).concat("%")));
            }
            if (Objects.nonNull(queryDto.getEnableFlag())) {
                predicates.add(builder.isTrue(root.get("enableFlag")));
            }
            predicates.add(builder.isFalse(root.get("isDeleted")));
            return builder.and(predicates.toArray(new Predicate[0]));
        };
    }
}

MqttClientListener

项目启动,监听主题

package com.huibo.acquisition.mqtt.listener;

import com.xx.xx.mqtt.config.MqttClientConnect;
import com.xx.xx.acquisition.mqtt.controller.dto.MqttServerDataQueryDto;
import com.xx.xx.acquisition.mqtt.entity.MqttServerData;
import com.xx.xx.acquisition.mqtt.service.MqttServerDataService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author :cola
 * @description: 项目启动 监听主题
 * @date :2023/7/18 10:53
 */
@Slf4j
@Component
public class MqttClientListener implements ApplicationListener {

    @Autowired
    private MqttServerDataService mqttServerDataService;

    /**
     * 客户端
     */
    public static ConcurrentHashMap mqttClients = new ConcurrentHashMap();

    private final AtomicBoolean isInit = new AtomicBoolean(false);

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        //防止重复触发
        if (!isInit.compareAndSet(false, true)) {
            return;
        }
        //TODO 需要传入启用参数
        List mqttServers = mqttServerDataService.findByList(new MqttServerDataQueryDto() {{
            setEnableFlag(Boolean.TRUE);
        }});
        try {
            if (!CollectionUtils.isEmpty(mqttServers)) {
                for (MqttServerData mqttServerData : mqttServers) {
                    mqttServerDataService.initMqttServer(mqttServerData);
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

MqttClientConnect

package com.xx.xx.mqtt.config;

import com.huibo.acquisition.mqtt.callback.MqttClientCallback;
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;

/**
 * @author :cola
 * @description: Mqtt
 * @date :2023/7/18 10:33
 */
@Slf4j
@Component
public class MqttClientConnect {

    private MqttClient mqttClient;
    /**
     * 系统的mqtt客户端id
     */
    private String mqttClientId;

    /**
     * 客户端connect连接mqtt服务器
     *
     * @param userName     用户名
     * @param passWord     密码
     * @param mqttCallback 回调函数
     **/
    public void setMqttClient(String host, String clientId, String userName, String passWord, MqttCallback mqttCallback) throws MqttException {
        // 连接设置
        MqttConnectOptions options = mqttConnectOptions(host, clientId, userName, passWord);
        if (mqttCallback == null) {
            mqttClient.setCallback(new MqttClientCallback(mqttClientId));
        } else {
            mqttClient.setCallback(mqttCallback);
        }
        mqttClient.connect(options);
    }

    /**
     * MQTT连接参数设置
     */
    private MqttConnectOptions mqttConnectOptions(String host, String clientId, String userName, String passWord) throws MqttException {
        // 创建 MQTT 客户端对象
        mqttClient = new MqttClient(host, clientId, new MemoryPersistence());
        // 连接设置
        MqttConnectOptions options = new MqttConnectOptions();
        // 设置连接用户名
        options.setUserName(userName);
        // 设置连接密码
        options.setPassword(passWord.toCharArray());
        // 设置超时时间,单位为秒
        options.setConnectionTimeout(30);
        // 设置心跳时间 单位为秒,表示服务器每 30 秒向客户端发送心跳判断客户端是否在线
        options.setKeepAliveInterval(30);
        // 设置遗嘱消息的话题,若客户端和服务器之间的连接意外断开,服务器将发布客户端的遗嘱信息
        options.setWill("willTopic", (mqttClientId + "与服务器断开连接").getBytes(), 0, false);
        // 断线自动重连
        options.setAutomaticReconnect(true);
        // 是否清空 session,设置为 false 表示服务器会保留客户端的连接记录,客户端重连之后能获取到服务器在客户端断开连接期间推送的消息
        // 设置为 true 表示每次连接到服务端都是以新的身份
        options.setCleanSession(true);
        return options;
    }

    /**
     * 关闭MQTT连接
     */
    public void close() throws MqttException {
        mqttClient.close();
        mqttClient.disconnect();
    }

    /**
     * 向某个主题发布消息 默认qos:1
     *
     * @param topic:发布的主题
     * @param msg:发布的消息
     */
    public void pub(String topic, String msg) throws MqttException {
        MqttMessage mqttMessage = new MqttMessage();
        //mqttMessage.setQos(2);
        try {
            mqttMessage.setPayload(msg.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage());
        }
        MqttTopic mqttTopic = mqttClient.getTopic(topic);
        MqttDeliveryToken token = mqttTopic.publish(mqttMessage);
        token.waitForCompletion();
    }

    /**
     * 向某个主题发布消息
     *
     * @param topic: 发布的主题
     * @param msg:   发布的消息
     * @param qos:   消息质量    Qos:0、1、2
     */
    public void pub(String topic, String msg, int qos) throws MqttException {
        MqttMessage mqttMessage = new MqttMessage();
        mqttMessage.setQos(qos);
        mqttMessage.setPayload(msg.getBytes());
        MqttTopic mqttTopic = mqttClient.getTopic(topic);
        MqttDeliveryToken token = mqttTopic.publish(mqttMessage);
        token.waitForCompletion();
    }

    /**
     * 订阅多个主题 ,此方法默认的的Qos等级为:1
     *
     * @param topic 主题
     */
    public void sub(String[] topic) throws MqttException {
        mqttClient.subscribe(topic);
    }

    /**
     * 订阅一个个主题 ,此方法默认的的Qos等级为:1
     *
     * @param topic 主题
     */
    public void sub(String topic) throws MqttException {
        mqttClient.subscribe(topic);
    }

    /**
     * 订阅某一个主题,可携带Qos
     *
     * @param topic 所要订阅的主题
     * @param qos   消息质量:0、1、2
     */
    public void sub(String topic, int qos) throws MqttException {
        mqttClient.subscribe(topic, qos);
    }

    public String getMqttClientId() {
        return mqttClientId;
    }

    public void setMqttClientId(String mqttClientId) {
        this.mqttClientId = mqttClientId;
    }
}

MqttClientCallback

Mqtt的回调

package com.huibo.acquisition.mqtt.callback;

import com.huibo.acquisition.mqtt.listener.MqttClientListener;
import com.huibo.acquisition.service.DeviceService;
import com.huibo.acquisition.utils.ApplicationContextUtil;
import com.huibo.acquisition.utils.ServerUtil;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.nio.charset.StandardCharsets;

import static com.huibo.acquisition.constant.GlobalConstant.DEVICE_DOWN_TOPIC;

/**
 * @author :cola
 * @description:Mqtt的回调
 * @date :2023/7/18 10:43
 */

@Slf4j
public class MqttClientCallback implements MqttCallbackExtended {

    /**
     * 系统的mqtt客户端id
     */
    private String mqttClientId;

    public MqttClientCallback(String mqttClientId) {
        this.mqttClientId = mqttClientId;
    }

    /**
     * TODO subscribe订阅后得到的消息会执行到这里
     */
    @Override
    public void messageArrived(String topic, MqttMessage message) {
        String data = new String(message.getPayload(), StandardCharsets.UTF_8);
        System.out.printf("接收消息主题: %s%n", topic);
        System.out.printf("接收消息Qos: %d%n", message.getQos());
        // message.toString() 也可以拿到消息内容
        System.out.printf("接收消息内容: %s%n", data);
        System.out.printf("接收消息retained: %b%n", message.isRetained());

        //处理消息
        ApplicationContextUtil.getBean(DeviceService.class).handleMqttMessage(data);
    }

    /**
     * MQTT 断开连接会执行此方法
     */
    @Override
    public void connectionLost(Throwable cause) {
        log.info("断开了MQTT连接 :{}", cause.getMessage());
    }

    /**
     * publish发布成功后会执行到这里
     */
    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        log.info("发布消息成功");
    }

    @Override
    public void connectComplete(boolean reconnect, String serverURI) {
            MqttClientListener.mqttClients.forEach((k, v) -> {
                try {
                    log.info("Mqtt重连成功:" + v.getMqttClientId() + " 已连接");
                    v.sub(String.format(DEVICE_DOWN_TOPIC, ServerUtil.getCPUSerial()));
                } catch (MqttException e) {
                    e.printStackTrace();
                }
            });
    }
}

你可能感兴趣的:(SpringBoot整合MQTT实现MQTT多连接)