1. 需求:
1. Spring boot项目,集成mqtt,订阅物联网设备数据上报topic1
2. 处理topic1,并异步入库到mongodb
3.订阅MQTT客户端上下线主题
4. mongodb 根据设备id分集合
2. 代码实现
准备工作:需要在EMQX 5.0允许订阅:$SYS/brokers/+/clients/# (客户端上下线主题)
第一步:打开授权文件
第二步:添加允许后,更新即可
{allow, all, subscribe, ["$SYS/brokers/+/clients/#"]}.
2.1 引入pom
- 子项目(本项目)
<!--mqtt-->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.2</version>
</dependency>
<!--mongodb + 线程池-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>mongodb-plus-spring-boot-starter</artifactId>
</dependency>
- 父-引入pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<!--spring-cloud 分布式架构引进-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
2.2 启动类引入注解 @EnableMongoPlus
2.3 application.yml mongodb配置
spring:
profiles:
active: prod
data:
mongodb:
uri: mongodb://root:${mongo-password}@${host}:${mongodbport}/hzd
option:
# 最小连接数
min-connection-per-host: 10
# 最大连接数
max-connection-per-host: 200
#允许阻塞连接乘数的线程
threads-allowed-to-block-for-connection-multiplier: 5
#服务器超时
server-selection-timeout: 30000
#最长等待时间
max-wait-time: 120000
#最大连接空闲时间
max-connection-idle-time: 0
#最大连接生存时间
max-connection-life-time: 0
#连接超时
connect-timeout: 10000
#套接字超时10000
socket-timeout: 0
socket-keep-alive: false
ssl-enabled: false
ssl-invalid-host-name-allowed: false
always-use-m-beans: false
heartbeat-socket-timeout: 20000
heartbeat-connect-timeout: 20000
min-heartbeat-frequency: 500
heartbeat-frequency: 10000
local-threshold: 15
2.4 代码
1. EmqClient.java 连接emqx
- init() 使用@PostConstruct注解,开机自动连接EMQX + 订阅
- reConnect() 断线重连后,订阅主题
package com.hzd.mqtt.client;
import com.hzd.mqtt.enums.QosEnum;
import com.hzd.mqtt.properties.MqttProperties;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* Created by
*/
@Component
@Slf4j
public class EmqClient {
private IMqttClient client;
@Autowired
private MqttProperties mqttProperties;
@Autowired
private MqttCallback mqttCallback;
@Autowired
private MqttProperties properties;
private static final String topic_hzd = "/hzd/pub/#";
private static final String topic_device_connect = "$SYS/brokers/+/clients/#";
@PostConstruct
public void init() {
MqttClientPersistence mempersitence = new MemoryPersistence();
try {
if (client == null) {
client = new MqttClient(mqttProperties.getBrokerUrl(), mqttProperties.getClientId(), mempersitence);
}
} catch (MqttException e) {
log.error("初始化客户端mqttClient对象失败,errormsg={},brokerUrl={},clientId={}", e.getMessage(), mqttProperties.getBrokerUrl(), mqttProperties.getClientId());
}
//连接服务端
connect(properties.getUsername(), properties.getPassword());
//订阅一个主题
subscribe(topic_hzd, QosEnum.QoS2);
subscribe(topic_device_connect, QosEnum.QoS2);
log.info("EmqClient连接成功,订阅成功.topic={},{}", topic_hzd, topic_device_connect);
}
/**
* 连接broker
*
* @param username
* @param password
*/
public void connect(String username, String password) {
MqttConnectOptions options = new MqttConnectOptions();
options.setAutomaticReconnect(true);
options.setUserName(username);
options.setPassword(password.toCharArray());
options.setCleanSession(true);
client.setCallback(mqttCallback);
try {
client.connect(options);
} catch (MqttException e) {
log.error("mqtt客户端连接服务端失败,失败原因{}", e.getMessage());
}
}
/**
* 断开连接
*/
@PreDestroy
public void disConnect() {
try {
client.disconnect();
} catch (MqttException e) {
log.error("断开连接产生异常,异常信息{}", e.getMessage());
}
}
/**
* 重连
*/
public void reConnect() {
try {
client.reconnect();
while (true){
if (client.isConnected()){
subscribe(topic_hzd, QosEnum.QoS2);
subscribe(topic_device_connect, QosEnum.QoS2);
break;
}else{
Thread.sleep(100);
log.info("重连中,请稍后......");
}
}
log.info("EmqClient连接成功,订阅成功.topic={},{}", topic_hzd, topic_device_connect);
} catch (MqttException | InterruptedException e) {
log.error("重连失败,失败原因{}", e.getMessage());
}
}
/**
* 发布消息
*
* @param topic
* @param msg
* @param qos
* @param retain
*/
public void publish(String topic, String msg, QosEnum qos, boolean retain) {
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(msg.getBytes());
mqttMessage.setQos(qos.value());
mqttMessage.setRetained(retain);
try {
client.publish(topic, mqttMessage);
} catch (MqttException e) {
log.error("发布消息失败,errormsg={},topic={},msg={},qos={},retain={}", e.getMessage(), topic, msg, qos.value(), retain);
}
}
/**
* 订阅
*
* @param topicFilter
* @param qos
*/
public void subscribe(String topicFilter, QosEnum qos) {
try {
client.subscribe(topicFilter, qos.value());
} catch (MqttException e) {
log.error("订阅主题失败,errormsg={},topicFilter={},qos={}", e.getMessage(), topicFilter, qos.value());
}
}
/**
* 取消订阅
*
* @param topicFilter
*/
public void unSubscribe(String topicFilter) {
try {
client.unsubscribe(topicFilter);
} catch (MqttException e) {
log.error("取消订阅失败,errormsg={},topicfiler={}", e.getMessage(), topicFilter);
}
}
}
2. MessageCallback.java 处理订阅消息
- init() 加载异步入库处理类DataStorage
- connectionLost(Throwable cause) 断线重连
- messageArrived(String topic, MqttMessage message) 处理订阅消息
package com.hzd.mqtt.client;
import com.hzd.mqtt.async.DataStorage;
import lombok.extern.slf4j.Slf4j;
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 javax.annotation.PostConstruct;
/**
*
*/
@Component
@Slf4j
public class MessageCallback implements MqttCallback {
@Autowired
private DataStorage dataStorage;
@Autowired
private EmqClient emqClient;
public static MessageCallback MessageCallback;
@PostConstruct //通过@PostConstruct实现初始化bean之前进行的操作
public void init() {
log.info("初始化");
MessageCallback = this;
// MessageCallback.dataPageRepository = this.dataPageRepository;
MessageCallback.dataStorage = this.dataStorage;
}
/**
* 丢失了对服务端的连接后触发的回调
*
* @param cause
*/
@Override
public void connectionLost(Throwable cause) {
// 资源的清理 重连
log.info("断开emqx连接,开始重连......");
emqClient.reConnect();
log.info("emqx连接成功......");
}
/**
* 应用收到消息后触发的回调
*
* @param topic
* @param message
* @throws Exception
*/
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
dataStorage.saveDataToMongo(topic, message);
}
/**
* 消息发布者消息发布完成产生的回调
*
* @param token
*/
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
int messageId = token.getMessageId();
String[] topics = token.getTopics();
log.info("消息发布完成,messageid={},topics={}", messageId, topics);
}
}
3. DataStorage.java 异步入库处理类
package com.hzd.mqtt.async;
import com.alibaba.fastjson2.JSON;
import com.hzd.entity.po.ControlLog;
import com.hzd.entity.po.DataExtend;
import com.hzd.entity.po.MqttClientLog;
import com.hzd.entity.vo.MqttPayload;
import com.hzd.entity.vo.MqttTopic;
import com.hzd.mqtt.enums.TopicType;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
/**
* Coding by 李炯 on 2022/10/28 15:12
*/
@Component
@Slf4j
public class DataStorage {
@Resource
private MongoTemplate mongoTemplate;
// @Resource
// private DataPageRepository dataPageRepository;
@Async
public void saveDataToMongo(String topic, MqttMessage message) {
MqttTopic t = MqttTopic.init(topic);
String payload = new String(message.getPayload());
if (t == null) return;
if (payload.contains("\"type\":\"data\"")) {
DataExtend dataExtend = DataExtend.init(payload);
mongoTemplate.insert(dataExtend, t.getFac_id());
log.info("设备数据上报,topic={},fac_id={},messageid={},qos={},payload={}",
topic, t.getFac_id(), message.getId(), message.getQos(), payload);
} else if (payload.contains("\"type\":\"config\"")) {
ControlLog controlLog = ControlLog.init(payload);
Query query = new Query(Criteria.where("_id").is(controlLog.get_id()));
Update update = Update.update("status", controlLog.getStatus());
update.set("status", controlLog.getStatus());
update.set("msg", controlLog.getMsg());
if (null != controlLog.getRelayIndexLst()) {
update.set("relayIndexLst", controlLog.getRelayIndexLst());
}
if (null != controlLog.getRelayNumLst()) {
update.set("relayNumLst", controlLog.getRelayNumLst());
}
if (null != controlLog.getReadInterval()) {
update.set("readInterval", controlLog.getReadInterval());
}
if (null != controlLog.getRelayphoto()) {
update.set("relayphoto", controlLog.getRelayphoto());
}
if (null != controlLog.getLongitude()) {
update.set("longitude", controlLog.getLongitude());
}
if (null != controlLog.getLatitude()) {
update.set("latitude", controlLog.getLatitude());
}
if (null != controlLog.getControlStatus()) {
update.set("controlStatus", controlLog.getControlStatus());
}
if (null != controlLog.getReserve()) {
update.set("reserve", controlLog.getReserve());
}
update.set("updateTime", new Date());
mongoTemplate.upsert(query, update, "log_control");
log.info("{},topic={},messageid={},qos={},payload={}",
"配置返回", topic, message.getId(), message.getQos(), payload);
} else if (t.getTopicType() == TopicType.connect) {
MqttPayload pl = MqttPayload.init(payload);
MqttClientLog clientLog = MqttClientLog.init(pl);
updateConnectStatus(topic, clientLog);
log.info("{},topic={},messageid={},qos={},payload={}",
clientLog.getStatus(), topic, message.getId(), message.getQos(), payload);
}
}
@Async
public void updateConnectStatus(String topic, MqttClientLog clientLog) {
try{
MqttTopic t = MqttTopic.init(topic);
if (t == null) return;
mongoTemplate.insert(clientLog, "log_mqtt_client");
Query query = new Query(Criteria.where("username").is(clientLog.getUserName())
.and("deviceid").is(clientLog.getDeviceId()));
Update update = Update.update("connectStatus", clientLog.getConnectStatus());
update.set("status", clientLog.getStatus());
update.set("updateTime", clientLog.getUpdateTime());
mongoTemplate.upsert(query, update, "status_mqtt_client");
}catch (Exception e){
log.error("mongoDB 错误:MqttClientLog={},错误消息为{}",JSON.toJSONString(clientLog),e.getMessage());
}
}
}