Springboot整合MQTT

=============== 发布端================
发布端连接得到sampleClient实例

package com.chalk.mqtt.publish;

import com.chalk.common.constant.MqttConstants;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author gluoh
 * @Description 发布客户端连接
 * @date 2021/02/26 11:30
 */
@Component
public class PublishConnect {
    @Value("${mq.broker}")
    private String broker;

    public MqttClient connectMqttClient(MqttPublishDto mqttPublishDto, MqttConnectOptions mqttConnectOptions) throws MqttException {
        // 内存存储
        MemoryPersistence persistence = new MemoryPersistence();
        // 创建客户端
        MqttClient sampleClient = new MqttClient(broker, MqttConstants.DEVICE_NUM_FLAG+mqttPublishDto.getDeviceNum(), persistence);
        // 在重新启动和重新连接时记住状态
        mqttConnectOptions.setCleanSession(false);
        // 设置回调
        sampleClient.setCallback(new PublishCallback());
        // 建立连接
        sampleClient.connect(mqttConnectOptions);
        return sampleClient;
    }
}

发布主题的回调类

package com.chalk.mqtt.publish;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;

/**
 * @author gluoh
 * @Description 发布主题的回调类
 * @date 2021/02/26 10:46
 */
public class PublishCallback implements MqttCallback {

    /**
     * 在断开连接时调用
     * @param cause
     */
    @Override
    public void connectionLost(Throwable cause) {
        System.out.println("连接断开,可以做重连");
    }

    /**
     * 接收已经预订的发布
     * @param topic
     * @param message
     * @throws Exception
     */
    @Override
    public void messageArrived(String topic, MqttMessage message) throws Exception {
        // subscribe后得到的消息会执行到这里面
        System.out.println("Server 接收消息主题 : " + topic);
        System.out.println("Server 接收消息Qos : " + message.getQos());
        System.out.println("Server 接收消息内容 : " + new String(message.getPayload()));
    }

    /**
     * 接收到已经发布的 QoS 1 或 QoS 2 消息的传递令牌时调用。
     * 由 MqttClient.connect 激活此回调。
     * @param token
     */
    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        System.out.println("deliveryComplete---------" + token.isComplete());
    }
}

通用发布端DTO

package com.chalk.mqtt.publish;

import lombok.Data;

/**
 * @author gluoh
 * @Description
 * @date 2021/02/26 18:21
 */
@Data
public class MqttPublishDto {
    // MQTT Client部分
    /** MQTT服务器地址 */
    private String broker;
    /** 主题 */
    private String topic;
    /** 消息内容 */
    private String message;
    /** 账号 */
    private String userName;
    /** 密码 */
    private String password;
    /** 客户端ID */
    private String clientId;
    /** 信号质量 */
    private Integer qos;
    
    /*-------------以下部分根据自己业务处理---------------*/

    // 平台设备部分
    /** 设备号 */
    private String deviceNum;
    /** 消息版本号 */
    private Integer version;
    /** 消息名称 */
    private String name;
    /** 消息值 */
    private String value;

    // 业务部分
    /** 订单ID */
    private String orderId;
}

发布接口

package com.chalk.mqtt.publish.service;

import com.chalk.common.util.Result;
import com.chalk.mqtt.publish.MqttPublishDto;

/**
 * @author gluoh
 * @Description
 * @date 2021/02/26 9:50
 */
public interface MqttPublishService {

    /**
     * 项目中发布主题
     * @param mqttPublishDto 发布主题DTO
     * @return
     */
    Result publish(MqttPublishDto mqttPublishDto);

    /**
     * 原始发布主题
     * @param mqttPublishDto 发布主题DTO
     * @return
     */
    Result publishOrigin(MqttPublishDto mqttPublishDto);

}

发布接口实现

package com.chalk.mqtt.publish.service.impl;

import com.chalk.common.constant.MqttConstants;
import com.chalk.common.util.RedisUtil;
import com.chalk.common.util.Result;
import com.chalk.dao.CarWashProjectDao;
import com.chalk.dao.OrderDao;
import com.chalk.model.CarWashProject;
import com.chalk.model.Order;
import com.chalk.mqtt.publish.MqttPublishDto;
import com.chalk.mqtt.publish.PublishConnect;
import com.chalk.mqtt.publish.service.MqttPublishService;
import com.chalk.mqtt.util.MqttUtil;
import org.apache.commons.lang3.StringUtils;
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.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Objects;

/**
 * @author gluoh
 * @Description 发布主题
 * @date 2032/02/26 10:49
 */
@Service
public class MqttPublishServiceImpl implements MqttPublishService {
    private static final Logger LOGGER = LoggerFactory.getLogger(MqttPublishServiceImpl.class);

    @Autowired
    private PublishConnect publishConnect;
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private CarWashProjectDao carWashProjectDao;
    @Autowired
    private RedisUtil redisUtil;

    /**
     * 发布主题
     * @param mqttPublishDto 发布主题DTO
     * @return
     */
    @Override
    public Result publish(MqttPublishDto mqttPublishDto) {
        LOGGER.info("进入【MqttServiceImpl】的[publish]方法,入参:{}",mqttPublishDto);
        try {
            MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
            // 设置连接的用户名
            mqttConnectOptions.setUserName(mqttPublishDto.getDeviceNum());
            mqttConnectOptions.setPassword(mqttPublishDto.getName().toCharArray());
            MqttClient sampleClient = publishConnect.connectMqttClient(mqttPublishDto, mqttConnectOptions);
            // 创建消息
            String messageStr = "{\"Version\":"+mqttPublishDto.getVersion()+",\n" +
                    "\"Data\":[\n" +
                    "{\"name\":\""+mqttPublishDto.getName()+"\",\"value\":\""+mqttPublishDto.getValue()+"\"}\n" +
                    "]}";
            LOGGER.info("【MQTT】发布的消息:{}", messageStr);
            MqttMessage message = new MqttMessage(messageStr.getBytes());
            // 设置消息的服务质量
            message.setQos(MqttConstants.QOS);
            String topic = MqttConstants.WRITE_DATA_TOPIC.replace(MqttConstants.DEVICE_NUM_FLAG, mqttPublishDto.getDeviceNum());
            LOGGER.info("【MQTT_TOPIC】:{}",topic);
            sampleClient.publish(topic, message);
            // 让盒子立即发布监控点主题
            String pubNowTopic = MqttConstants.DATA_PUB_NOW_TOPIC.replace(MqttConstants.DEVICE_NUM_FLAG, mqttPublishDto.getDeviceNum());
            sampleClient.publish(pubNowTopic, message);
            // 断开连接
            sampleClient.disconnect();
            // 关闭客户端
            sampleClient.close();
            LOGGER.info("【MQTT】发布的消息<成功>");
            return Result.success("消息发布成功!");
        } catch (MqttException me) {
            LOGGER.error("reasonCode:{},msg:{},loc:{},cause:{}",me.getReasonCode(),me.getMessage(),me.getLocalizedMessage(),me.getCause());
            me.printStackTrace();
            return Result.fail("消息发布失败,失败原因:{}", me.getMessage());
        }
    }

    /**
     * 原始发布主题
     * @param mqttPublishDto 发布主题DTO
     * @return
     */
    @Override
    public Result publishOrigin(MqttPublishDto mqttPublishDto) {
        LOGGER.info("进入【MqttServiceImpl】的[publishOrigin]方法,入参:{}",mqttPublishDto);
        try {
            // 内存存储
            MemoryPersistence persistence = new MemoryPersistence();
            // 创建客户端
            MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
            mqttConnectOptions.setUserName(mqttPublishDto.getUserName());
            mqttConnectOptions.setPassword(mqttPublishDto.getPassword().toCharArray());
            MqttClient sampleClient = publishConnect.connectMqttClient(mqttPublishDto, mqttConnectOptions);
            // 创建消息
            LOGGER.info("【MQTT】发布的消息:{}", mqttPublishDto.getMessage());
            MqttMessage message = new MqttMessage(mqttPublishDto.getMessage().getBytes());
            // 设置消息的服务质量
            Integer qos = mqttPublishDto.getQos() == null ? 1: mqttPublishDto.getQos();
            message.setQos(qos);
            LOGGER.info("【MQTT_TOPIC】:{}",mqttPublishDto.getTopic());
            sampleClient.publish(mqttPublishDto.getTopic(), message);
            // 断开连接
            sampleClient.disconnect();
            // 关闭客户端
            sampleClient.close();
            LOGGER.info("【MQTT】发布的消息<成功>");
            return Result.success("消息发布成功!");
        } catch (MqttException me) {
            LOGGER.error("reasonCode:{},msg:{},loc:{},cause:{}",me.getReasonCode(),me.getMessage(),me.getLocalizedMessage(),me.getCause());
            me.printStackTrace();
            return Result.fail("消息发布失败,失败原因:{}", me.getMessage());
        }
    }
}

=============== 订阅端================
通用订阅端DTO

package com.chalk.mqtt.subscribe;

import lombok.Data;

/**
 * @author gluoh
 * @Description
 * @date 2021/02/26 10:47
 */
@Data
public class MqttSubscribeDto {
    /** MQTT服务器地址 */
    private String broker;
    /** 主题 */
    private String topic;
    /** 账号 */
    private String userName;
    /** 密码 */
    private String password;
    /** 客户端ID */
    private String clientId;
    /** 是否清除session*/
    private boolean cleanSession;
    /** 设置连接超时时间 单位秒*/
    private Integer connectionTimeOut;
    /** 设置会话心跳时间 单位秒*/
    private Integer keepAlive;
    /** 信号质量 */
    private Integer qos;

}

订阅回调类

package com.chalk.mqtt.subscribe;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.chalk.common.constant.CommonConstants;
import com.chalk.common.constant.MqttConstants;
import com.chalk.common.constant.StatusConstants;
import com.chalk.common.util.RedisUtil;
import com.chalk.common.util.Result;
import com.chalk.common.util.ResultCode;
import com.chalk.common.util.SpringContextUtil;
import com.chalk.dao.OrderDao;
import com.chalk.model.Order;
import com.chalk.mqtt.publish.MqttPublishDto;
import com.chalk.mqtt.publish.service.impl.MqttPublishServiceImpl;
import com.chalk.mqtt.util.MqttUtil;
import com.chalk.service.OrderService;
import com.chalk.websocket.server.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * @author gluoh
 * @Description 订阅回调类
 * @date 2021/02/26 11:44
 */
@Slf4j
@Component
public class SubscribeCallback implements MqttCallback {

    private MqttSubscribeDto mqttSubscribeDto;

    @Autowired
    private SubscribeConnect subscribeConnect;

    public SubscribeCallback() {
    }

    public SubscribeCallback(MqttSubscribeDto mqttSubscribeDto) {
        this.mqttSubscribeDto = mqttSubscribeDto;
    }

    @Override
    public void connectionLost(Throwable cause) {
        log.info("Subscribe connectLost");
        subscribeConnect.getConn(mqttSubscribeDto);
    }

    @Override
    public void messageArrived(String topic, MqttMessage mqttMessage) {
        String content = new String(mqttMessage.getPayload());
        log.info("接收消息主题:{},接收消息Qos:{},接收消息内容:{}",topic,mqttMessage.getQos(),content);
        try {
            // 取设备号
            String deviceNum = topic.split("/")[3];
            // 保存到redis
            RedisUtil redisUtil = SpringContextUtil.getBean(RedisUtil.class);
            redisUtil.set(MqttConstants.GERMANY_MONITOR_TOPIC_REDIS + deviceNum, content, MqttConstants.GERMANY_MONITOR_TOPIC_EXPIRED);
            // 发送给前端(手动抬杆业务)
            WebSocketServer webSocketServer = SpringContextUtil.getBean(WebSocketServer.class);
            webSocketServer.sendMessage(JSONUtil.parseFromMap(Result.success(ResultCode.WEBSOCKET_SUCCESS, content)).toString(),deviceNum.trim());

        }catch (Exception e){
            e.printStackTrace();
            log.info("接收消息主题:{},errorMessage:{}",topic,e.getMessage());
        }

    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
        log.info("deliveryComplete:{}",iMqttDeliveryToken.isComplete());
    }
}

订阅客户端连接

package com.chalk.mqtt.subscribe;

import com.chalk.common.util.IdGenerator;
import com.chalk.mqtt.ServiceInfoUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttToken;
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.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author gluoh
 * @Description 订阅客户端连接
 * @date 2021/02/26 11:32
 */
@Slf4j
@Data
@Component
public class SubscribeConnect {
    @Value("${mq.broker}")
    private String broker;
    @Autowired
    private ServiceInfoUtil serviceInfoUtil;
    private MqttClient mqttClient;
    private MqttConnectOptions mqttConnectOptions;

    /**
     * 连接emq服务器
     */
    public MqttClient getConn(MqttSubscribeDto subscribeDto) {
        try {
            // host为主机名,clientId即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientId的保存形式,默认为以内存保存
            String clientId =   IdGenerator.getIdStr() + "["+serviceInfoUtil.getPort() + "]";
            mqttClient = new MqttClient(broker, clientId, new MemoryPersistence());
            // MQTT的连接设置
            mqttConnectOptions = new MqttConnectOptions();
            // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
            mqttConnectOptions.setCleanSession(true);
            // 设置连接的用户名
            mqttConnectOptions.setUserName(subscribeDto.getUserName());
            // 设置连接的密码
            mqttConnectOptions.setPassword(subscribeDto.getPassword().toCharArray());
            // 设置超时时间 单位为秒
            mqttConnectOptions.setConnectionTimeout(10);
            // 设置会话心跳时间 单位为秒 服务器会每隔1.5*keepAliveInterval秒的时间向客户端发送个消息判断客户端是否在线,
            // 但这个方法并没有重连的机制
            mqttConnectOptions.setKeepAliveInterval(5);
            // mqttConnectOptions.setServerURIs(); 配置多个服务器列表,一个挂掉会自动切换到其他正常的服务器,挂掉的服务器正常后,客户端会再次切换回来
            mqttClient.setCallback(new SubscribeCallback(subscribeDto));
            IMqttToken iMqttToken = mqttClient.connectWithResult(mqttConnectOptions);
            log.info("连接服务器成功,IMqttToken:{}",iMqttToken);

        } catch (MqttException e) {
            log.info("订阅连接MQTT服务器失败,失败原因:{}", e.getMessage());
        }
        return mqttClient;
    }
}

实现ApplicationRunner 接口 重写run方法springboot启动后启动mqtt订阅

package com.chalk.mqtt;

import com.chalk.mqtt.subscribe.MqttSubscribeDto;
import com.chalk.mqtt.subscribe.service.MqttWrapperService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

/**
 * @author gluoh
 * @Description MQTT订阅启动
 * @date 2021/02/26 16:34
 */
@Slf4j
@Component
public class MyApplicationRunner implements ApplicationRunner {

    @Value("${mq.flexem-topic}")
    private String topic;
    @Value("${mq.username}")
    private String username;
    @Value("${mq.password}")
    private String password;
    @Autowired
    private MqttWrapperService mqttWrapperService;
    //String TOPIC = "Topic/flexem/fbox/+/system/MonitorData";

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        log.info(" MQTT Subscribe Server 开始...");
        MqttSubscribeDto mqttSubscribeDto = new MqttSubscribeDto();
        mqttSubscribeDto.setUserName(username);
        mqttSubscribeDto.setPassword(password);
        mqttSubscribeDto.setTopic(topic);
        mqttWrapperService.subscribe(mqttSubscribeDto);
    }
}

获取服务端口号

package com.chalk.mqtt;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author gluoh
 * @Description 监听WebServerInitializedEvent事件,用来获取Servlet容器初始化的端口
 * @date 2021/02/26 12:24
 */
@Slf4j
@Component
public class ServiceInfoUtil implements ApplicationListener<WebServerInitializedEvent> {

    private static WebServerInitializedEvent event;

    @Override
    public void onApplicationEvent(WebServerInitializedEvent event) {
        ServiceInfoUtil.event = event;
    }

    public int getPort() {
        return event.getWebServer().getPort();
    }

}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值