application.yml
mqtt:
username: admin # 用户名
password: 123456 # 密码
hostUrl: tcp://broker.emqx.io:1883 # tcp://ip:端口
clientId: mqttx_59ba2b45 # 客户端id
defaultTopic: cest # 订阅主题
timeout: 100 # 超时时间 (单位:秒)
keepalive: 60 # 心跳 (单位:秒)
enabled: true # 是否使用mqtt功能
----------------------------MqttPushClient.java
package com.aide.mqtt.protocols;
import lombok.Getter;
import lombok.Setter;
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 javax.annotation.Resource;
@Component
@Slf4j
public class MqttPushClient {
@Getter
@Setter
private static MqttClient client; // MQTT客户端
@Resource
private PushCallback pushCallback; // 推送回调
/**
* 客户端连接
*
* @param host ip+端口
* @param clientID 客户端Id
* @param username 用户名
* @param password 密码
* @param timeout 超时时间
* @param keepalive 保留数
*/
public void connect(String host, String clientID, String username, String password, int timeout, int keepalive) {
MqttClient client;
try {
client = new MqttClient(host, clientID, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
options.setUserName(username);
options.setPassword(password.toCharArray());
options.setConnectionTimeout(timeout);
options.setKeepAliveInterval(keepalive);
MqttPushClient.setClient(client);
client.setCallback(pushCallback);
client.connect(options);
} catch (Exception e) {
log.error("连接失败:{}", e.getMessage());
}
}
/**
* 发布
*
* @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);
message.setPayload(pushMessage.getBytes());
MqttTopic mTopic = MqttPushClient.getClient().getTopic(topic);
if (null == mTopic) {
log.error("topic不存在:{}", topic);
}
MqttDeliveryToken token = null;
try {
if (mTopic != null) {
token = mTopic.publish(message);
}
if (token != null) {
token.waitForCompletion();
}
} catch (MqttException e) {
log.error("发布失败:{}", e.getMessage());
}
}
/**
* 订阅某个主题
*
* @param topic 主题
* @param qos 连接方式
*/
public void subscribe(String topic, int qos) {
log.info("开始订阅主题:{}", topic);
try {
MqttPushClient.getClient().subscribe(topic, qos);
} catch (MqttException e) {
log.error("订阅主题失败:{}", e.getMessage());
}
}
}
-----------------------------------------------MqttConfig.java
package com.aide.mqtt.protocols;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
@ConfigurationProperties("mqtt")
@Data
public class MqttConfig {
@Resource
private MqttPushClient mqttPushClient;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 连接地址
*/
private String hostUrl;
/**
* 客户Id
*/
private String clientId;
/**
* 默认连接话题
*/
private String defaultTopic;
/**
* 超时时间
*/
private int timeout;
/**
* 保持连接数
*/
private int keepalive;
/**
* mqtt功能使能
*/
private boolean enabled;
@Bean
public MqttPushClient getMqttPushClient() {
if (enabled) {
mqttPushClient.connect(hostUrl, clientId, username, password, timeout, keepalive);//连接
// 使用通配符订阅所有设备的报告主题
mqttPushClient.subscribe("device/+/report", 0);
// 使用通配符订阅所有设备的命令主题
mqttPushClient.subscribe("device/+/send", 0);
// 使用通配符订阅所有设备的原始数据报告主题
mqttPushClient.subscribe("device/+/raw_data_report", 0);
}
return mqttPushClient;
}
}
-------------------------------------MqttConfig.java
package com.aide.mqtt.protocols;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
@ConfigurationProperties("mqtt")
@Data
public class MqttConfig {
@Resource
private MqttPushClient mqttPushClient;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 连接地址
*/
private String hostUrl;
/**
* 客户Id
*/
private String clientId;
/**
* 默认连接话题
*/
private String defaultTopic;
/**
* 超时时间
*/
private int timeout;
/**
* 保持连接数
*/
private int keepalive;
/**
* mqtt功能使能
*/
private boolean enabled;
@Bean
public MqttPushClient getMqttPushClient() {
if (enabled) {
mqttPushClient.connect(hostUrl, clientId, username, password, timeout, keepalive);//连接
// 使用通配符订阅所有设备的报告主题
mqttPushClient.subscribe("device/+/report", 0);
// 使用通配符订阅所有设备的命令主题
mqttPushClient.subscribe("device/+/send", 0);
// 使用通配符订阅所有设备的原始数据报告主题
mqttPushClient.subscribe("device/+/raw_data_report", 0);
}
return mqttPushClient;
}
}
-----------------------------------------PushCallback.java
package com.aide.mqtt.protocols;
import com.aide.mqtt.service.TopicHandlerService;
import com.aide.mqtt.service.impl.DeviceRawDataHandlerImpl;
import com.aide.mqtt.service.impl.DeviceReportHandlerImpl;
import com.aide.mqtt.service.impl.DeviceSubscribingHandlerImpl;
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.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class PushCallback implements MqttCallback {
private MqttClient client;
private final Map<String, TopicHandlerService> handlerMap = new HashMap<>(); // 主题与处理器的映射
@Resource
private MqttConfig mqttConfig;
// 创建一个阻塞队列,用于存储待处理的任务
private final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
// 创建自定义线程池,核心线程数20,最大线程数50
private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(20, 50, 60, TimeUnit.SECONDS, workQueue);
public PushCallback(DeviceSubscribingHandlerImpl deviceSubscribingHandlerImpl,
DeviceReportHandlerImpl deviceReportHandlerImpl,
DeviceRawDataHandlerImpl deviceRawDataHandlerImpl) {
handlerMap.put("report", deviceReportHandlerImpl);
handlerMap.put("send", deviceSubscribingHandlerImpl);
handlerMap.put("raw_data_report", deviceRawDataHandlerImpl);
}
@Override
public void connectionLost(Throwable throwable) {
log.info("连接断开,重连");
if (client == null || !client.isConnected()) {
mqttConfig.getMqttPushClient();
}
}
@Override
public void messageArrived(String topic, MqttMessage mqttMessage) {
log.info("接收消息主题: {}", topic);
log.info("接收消息Qos: {}", mqttMessage.getQos());
log.info("接收消息内容: {}", new String(mqttMessage.getPayload()));
String extractedTopic = topic.substring(topic.lastIndexOf("/") + 1);
TopicHandlerService handler = handlerMap.get(extractedTopic);
threadPoolExecutor.submit(() -> {
try {
if (handler != null) {
handler.handleMessage(topic, mqttMessage);
} else {
log.warn("没有找到对应topic的处理程序: {}", topic);
}
} catch (Exception e) {
log.error("处理消息时出现异常:", e);
}
});
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
log.info("请求响应完成状态 -> {}", iMqttDeliveryToken.isComplete());
}
@PreDestroy
public void shutdown() {
threadPoolExecutor.shutdown(); // 关闭线程池
try {
if (!threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
threadPoolExecutor.shutdownNow(); // 超过时间强制关闭
}
} catch (InterruptedException ex) {
threadPoolExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
--------------------------------------------------pom.xml
<!--mgtt-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>