Commit 2737ffa1162dbb92041119ebe0db4de8756f6780

Authored by haohao
1 parent 569eef4a

reactor:【IoT 物联网】重构 MQTT 客户端连接和订阅主题流程,更新配置属性

yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
1 package cn.iocoder.yudao.module.iot.gateway.config; 1 package cn.iocoder.yudao.module.iot.gateway.config;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
4 import jakarta.validation.constraints.NotNull; 5 import jakarta.validation.constraints.NotNull;
5 import lombok.Data; 6 import lombok.Data;
@@ -175,7 +176,7 @@ public class IotGatewayProperties { @@ -175,7 +176,7 @@ public class IotGatewayProperties {
175 * @return MQTT 客户端 ID 176 * @return MQTT 客户端 ID
176 */ 177 */
177 public String getMqttClientId() { 178 public String getMqttClientId() {
178 - if (cn.hutool.core.util.StrUtil.isBlank(mqttClientId)) { 179 + if (StrUtil.isBlank(mqttClientId)) {
179 mqttClientId = "iot-gateway-mqtt-" + System.currentTimeMillis(); 180 mqttClientId = "iot-gateway-mqtt-" + System.currentTimeMillis();
180 } 181 }
181 return mqttClientId; 182 return mqttClientId;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttDownstreamSubscriber.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
2 2
3 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;  
4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
5 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; 4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -45,13 +44,13 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDevi @@ -45,13 +44,13 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDevi
45 44
46 @Override 45 @Override
47 public void onMessage(IotDeviceMessage message) { 46 public void onMessage(IotDeviceMessage message) {
48 - log.info("[onMessage][接收到下行消息][messageId: {}][method: {}][deviceId: {}]", 47 + log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]",
49 message.getId(), message.getMethod(), message.getDeviceId()); 48 message.getId(), message.getMethod(), message.getDeviceId());
50 try { 49 try {
51 // 1. 校验 50 // 1. 校验
52 String method = message.getMethod(); 51 String method = message.getMethod();
53 if (method == null) { 52 if (method == null) {
54 - log.warn("[onMessage][消息方法为空][messageId: {}][deviceId: {}]", 53 + log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]",
55 message.getId(), message.getDeviceId()); 54 message.getId(), message.getDeviceId());
56 return; 55 return;
57 } 56 }
@@ -59,20 +58,9 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDevi @@ -59,20 +58,9 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDevi
59 // 2. 处理下行消息 58 // 2. 处理下行消息
60 downstreamHandler.handle(message); 59 downstreamHandler.handle(message);
61 } catch (Exception e) { 60 } catch (Exception e) {
62 - log.error("[onMessage][处理下行消息失败][messageId: {}][method: {}][deviceId: {}]", 61 + log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]",
63 message.getId(), message.getMethod(), message.getDeviceId(), e); 62 message.getId(), message.getMethod(), message.getDeviceId(), e);
64 } 63 }
65 } 64 }
66 65
67 - /**  
68 - * 判断是否为上行消息  
69 - *  
70 - * @param method 消息方法  
71 - * @return 是否为上行消息  
72 - */  
73 - private boolean isUpstreamMessage(String method) {  
74 - IotDeviceMessageMethodEnum methodEnum = IotDeviceMessageMethodEnum.of(method);  
75 - return methodEnum != null && methodEnum.getUpstream();  
76 - }  
77 -  
78 } 66 }
79 \ No newline at end of file 67 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java
@@ -21,7 +21,6 @@ import lombok.Getter; @@ -21,7 +21,6 @@ import lombok.Getter;
21 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
22 22
23 import java.util.List; 23 import java.util.List;
24 -import java.util.concurrent.TimeUnit;  
25 24
26 /** 25 /**
27 * IoT 网关 MQTT 协议:接收设备上行消息 26 * IoT 网关 MQTT 协议:接收设备上行消息
@@ -61,10 +60,10 @@ public class IotMqttUpstreamProtocol { @@ -61,10 +60,10 @@ public class IotMqttUpstreamProtocol {
61 @PostConstruct 60 @PostConstruct
62 public void start() { 61 public void start() {
63 if (isRunning) { 62 if (isRunning) {
64 - log.warn("[start][MQTT 统一协议服务已经在运行中,请勿重复启动]"); 63 + log.warn("[start][MQTT 协议服务已经在运行中,请勿重复启动]");
65 return; 64 return;
66 } 65 }
67 - log.info("[start][开始启动 MQTT 统一协议服务]"); 66 + log.info("[start][启动 MQTT 协议服务]");
68 67
69 try { 68 try {
70 // 1. 创建共享的 Vertx 实例 69 // 1. 创建共享的 Vertx 实例
@@ -77,9 +76,9 @@ public class IotMqttUpstreamProtocol { @@ -77,9 +76,9 @@ public class IotMqttUpstreamProtocol {
77 startMqttClient(); 76 startMqttClient();
78 77
79 isRunning = true; 78 isRunning = true;
80 - log.info("[start][MQTT 统一协议服务启动完成]"); 79 + log.info("[start][MQTT 协议服务启动完成]");
81 } catch (Exception e) { 80 } catch (Exception e) {
82 - log.error("[start][MQTT 统一协议服务启动失败]", e); 81 + log.error("[start][MQTT 协议服务启动失败]", e);
83 // 启动失败时清理资源 82 // 启动失败时清理资源
84 stop(); 83 stop();
85 throw e; 84 throw e;
@@ -89,10 +88,10 @@ public class IotMqttUpstreamProtocol { @@ -89,10 +88,10 @@ public class IotMqttUpstreamProtocol {
89 @PreDestroy 88 @PreDestroy
90 public void stop() { 89 public void stop() {
91 if (!isRunning) { 90 if (!isRunning) {
92 - log.warn("[stop][MQTT 统一协议服务已经停止,无需再次停止]"); 91 + log.warn("[stop][MQTT 协议服务已经停止,无需再次停止]");
93 return; 92 return;
94 } 93 }
95 - log.info("[stop][开始停止 MQTT 统一协议服务]"); 94 + log.info("[stop][停止 MQTT 协议服务]");
96 95
97 // 1. 停止 MQTT 客户端 96 // 1. 停止 MQTT 客户端
98 stopMqttClient(); 97 stopMqttClient();
@@ -104,28 +103,28 @@ public class IotMqttUpstreamProtocol { @@ -104,28 +103,28 @@ public class IotMqttUpstreamProtocol {
104 if (vertx != null) { 103 if (vertx != null) {
105 try { 104 try {
106 vertx.close(); 105 vertx.close();
107 - log.info("[stop][Vertx 实例已关闭]"); 106 + log.debug("[stop][Vertx 实例已关闭]");
108 } catch (Exception e) { 107 } catch (Exception e) {
109 log.warn("[stop][关闭 Vertx 实例失败]", e); 108 log.warn("[stop][关闭 Vertx 实例失败]", e);
110 } 109 }
111 } 110 }
112 111
113 isRunning = false; 112 isRunning = false;
114 - log.info("[stop][MQTT 统一协议服务已停止]"); 113 + log.info("[stop][MQTT 协议服务已停止]");
115 } 114 }
116 115
117 /** 116 /**
118 * 启动 HTTP 认证服务 117 * 启动 HTTP 认证服务
119 */ 118 */
120 private void startHttpAuthServer() { 119 private void startHttpAuthServer() {
121 - log.info("[startHttpAuthServer][开始启动 HTTP 认证服务]"); 120 + log.info("[startHttpAuthServer][启动 HTTP 认证服务]");
122 121
123 // 创建路由 122 // 创建路由
124 Router router = Router.router(vertx); 123 Router router = Router.router(vertx);
125 router.route().handler(BodyHandler.create()); 124 router.route().handler(BodyHandler.create());
126 125
127 // 创建认证处理器 126 // 创建认证处理器
128 - IotMqttHttpAuthHandler authHandler = new IotMqttHttpAuthHandler(); 127 + IotMqttHttpAuthHandler authHandler = new IotMqttHttpAuthHandler(this);
129 router.post(IotMqttTopicUtils.MQTT_AUTH_AUTHENTICATE_PATH).handler(authHandler::authenticate); 128 router.post(IotMqttTopicUtils.MQTT_AUTH_AUTHENTICATE_PATH).handler(authHandler::authenticate);
130 router.post(IotMqttTopicUtils.MQTT_AUTH_CONNECTED_PATH).handler(authHandler::connected); 129 router.post(IotMqttTopicUtils.MQTT_AUTH_CONNECTED_PATH).handler(authHandler::connected);
131 router.post(IotMqttTopicUtils.MQTT_AUTH_DISCONNECTED_PATH).handler(authHandler::disconnected); 130 router.post(IotMqttTopicUtils.MQTT_AUTH_DISCONNECTED_PATH).handler(authHandler::disconnected);
@@ -137,7 +136,7 @@ public class IotMqttUpstreamProtocol { @@ -137,7 +136,7 @@ public class IotMqttUpstreamProtocol {
137 .requestHandler(router) 136 .requestHandler(router)
138 .listen(authPort) 137 .listen(authPort)
139 .result(); 138 .result();
140 - log.info("[startHttpAuthServer][HTTP 认证服务启动成功,端口:{}]", authPort); 139 + log.info("[startHttpAuthServer][HTTP 认证服务启动成功, 端口: {}]", authPort);
141 } catch (Exception e) { 140 } catch (Exception e) {
142 log.error("[startHttpAuthServer][HTTP 认证服务启动失败]", e); 141 log.error("[startHttpAuthServer][HTTP 认证服务启动失败]", e);
143 throw e; 142 throw e;
@@ -163,23 +162,24 @@ public class IotMqttUpstreamProtocol { @@ -163,23 +162,24 @@ public class IotMqttUpstreamProtocol {
163 * 启动 MQTT 客户端 162 * 启动 MQTT 客户端
164 */ 163 */
165 private void startMqttClient() { 164 private void startMqttClient() {
166 - log.info("[startMqttClient][开始启动 MQTT 客户端]"); 165 + log.info("[startMqttClient][启动 MQTT 客户端]");
167 166
168 - // 1. 初始化消息处理器  
169 - this.upstreamHandler = new IotMqttUpstreamHandler(this); 167 + try {
  168 + // 1. 初始化消息处理器
  169 + this.upstreamHandler = new IotMqttUpstreamHandler(this);
170 170
171 - // 2. 创建 MQTT 客户端  
172 - log.info("[startMqttClient][使用 MQTT 客户端 ID: {}]", emqxProperties.getMqttClientId()); 171 + // 2. 创建 MQTT 客户端
  172 + log.info("[startMqttClient][使用 MQTT 客户端 ID: {}]", emqxProperties.getMqttClientId());
  173 + createMqttClient();
173 174
174 - MqttClientOptions options = new MqttClientOptions()  
175 - .setClientId(emqxProperties.getMqttClientId())  
176 - .setUsername(emqxProperties.getMqttUsername())  
177 - .setPassword(emqxProperties.getMqttPassword())  
178 - .setSsl(emqxProperties.getMqttSsl());  
179 - this.mqttClient = MqttClient.create(vertx, options); 175 + // 3. 连接 MQTT Broker(异步连接,不会抛出异常)
  176 + connectMqtt();
180 177
181 - // 3. 连接 MQTT Broker  
182 - connectMqtt(); 178 + log.info("[startMqttClient][MQTT 客户端启动完成,正在异步连接中...]");
  179 + } catch (Exception e) {
  180 + log.error("[startMqttClient][MQTT 客户端启动失败]", e);
  181 + throw new RuntimeException("MQTT 客户端启动失败", e);
  182 + }
183 } 183 }
184 184
185 /** 185 /**
@@ -195,7 +195,7 @@ public class IotMqttUpstreamProtocol { @@ -195,7 +195,7 @@ public class IotMqttUpstreamProtocol {
195 mqttClient.unsubscribe(topic); 195 mqttClient.unsubscribe(topic);
196 log.debug("[stopMqttClient][取消订阅主题: {}]", topic); 196 log.debug("[stopMqttClient][取消订阅主题: {}]", topic);
197 } catch (Exception e) { 197 } catch (Exception e) {
198 - log.warn("[stopMqttClient][取消订阅主题异常: {}]", topic, e); 198 + log.warn("[stopMqttClient][取消订阅主题({})异常]", topic, e);
199 } 199 }
200 } 200 }
201 } 201 }
@@ -216,43 +216,107 @@ public class IotMqttUpstreamProtocol { @@ -216,43 +216,107 @@ public class IotMqttUpstreamProtocol {
216 * 连接 MQTT Broker 并订阅主题 216 * 连接 MQTT Broker 并订阅主题
217 */ 217 */
218 private void connectMqtt() { 218 private void connectMqtt() {
  219 + connectMqtt(false);
  220 + }
  221 +
  222 + /**
  223 + * 连接 MQTT Broker 并订阅主题
  224 + *
  225 + * @param isReconnect 是否为重连
  226 + */
  227 + private void connectMqtt(boolean isReconnect) {
219 // 1. 参数校验 228 // 1. 参数校验
220 String host = emqxProperties.getMqttHost(); 229 String host = emqxProperties.getMqttHost();
221 Integer port = emqxProperties.getMqttPort(); 230 Integer port = emqxProperties.getMqttPort();
222 if (StrUtil.isBlank(host)) { 231 if (StrUtil.isBlank(host)) {
223 - log.error("[connectMqtt][MQTT Host 为空,无法连接]"); 232 + log.error("[connectMqtt][MQTT Host 为空, 无法连接]");
224 throw new IllegalArgumentException("MQTT Host 不能为空"); 233 throw new IllegalArgumentException("MQTT Host 不能为空");
225 } 234 }
226 if (port == null || port <= 0) { 235 if (port == null || port <= 0) {
227 - log.error("[connectMqtt][MQTT Port 无效:{}]", port); 236 + log.error("[connectMqtt][MQTT Port({}) 无效]", port);
228 throw new IllegalArgumentException("MQTT Port 必须为正整数"); 237 throw new IllegalArgumentException("MQTT Port 必须为正整数");
229 } 238 }
230 - log.info("[connectMqtt][开始连接 MQTT Broker][host: {}][port: {}]", host, port);  
231 239
232 - // 2. 连接  
233 - try {  
234 - mqttClient.connect(port, host)  
235 - .toCompletionStage()  
236 - .toCompletableFuture()  
237 - .get(emqxProperties.getConnectTimeoutSeconds(), TimeUnit.SECONDS);  
238 - log.info("[connectMqtt][MQTT 客户端连接成功][host: {}][port: {}]", host, port);  
239 -  
240 - // 3. 设置处理器  
241 - // 3.1 设置断开重连监听器  
242 - mqttClient.closeHandler(closeEvent -> {  
243 - log.warn("[closeHandler][MQTT 连接已断开,准备重连]");  
244 - reconnectWithDelay();  
245 - });  
246 - // 3.2 设置消息处理器  
247 - mqttClient.publishHandler(upstreamHandler::handle);  
248 - log.debug("[setupMessageHandler][MQTT 消息处理器设置完成]"); 240 + if (isReconnect) {
  241 + log.info("[connectMqtt][开始重连 MQTT Broker, host: {}, port: {}]", host, port);
  242 + // 重连时重新创建客户端实例
  243 + createMqttClient();
  244 + } else {
  245 + log.info("[connectMqtt][开始连接 MQTT Broker, host: {}, port: {}]", host, port);
  246 + }
249 247
250 - // 4. 订阅主题  
251 - subscribeToTopics();  
252 - } catch (Exception e) {  
253 - log.error("[connectMqtt][连接 MQTT Broker 失败][host: {}][port: {}]", host, port, e);  
254 - reconnectWithDelay(); // 连接失败时,也要尝试重连  
255 - throw new RuntimeException("MQTT 客户端启动失败", e); 248 + // 2. 异步连接
  249 + mqttClient.connect(port, host, connectResult -> {
  250 + if (connectResult.succeeded()) {
  251 + if (isReconnect) {
  252 + log.info("[connectMqtt][MQTT 客户端重连成功, host: {}, port: {}]", host, port);
  253 + } else {
  254 + log.info("[connectMqtt][MQTT 客户端连接成功, host: {}, port: {}]", host, port);
  255 + }
  256 +
  257 + // 3. 设置处理器
  258 + setupMqttHandlers();
  259 +
  260 + // 4. 订阅主题
  261 + subscribeToTopics();
  262 +
  263 + } else {
  264 + log.error("[connectMqtt][连接 MQTT Broker 失败, host: {}, port: {}, isReconnect: {}]",
  265 + host, port, isReconnect, connectResult.cause());
  266 +
  267 + if (!isReconnect) {
  268 + // 首次连接失败时,也要尝试重连
  269 + log.warn("[connectMqtt][首次连接失败,将开始重连机制]");
  270 + reconnectWithDelay();
  271 + } else {
  272 + // 重连失败时,继续尝试重连
  273 + reconnectWithDelay();
  274 + }
  275 + }
  276 + });
  277 + }
  278 +
  279 + /**
  280 + * 创建 MQTT 客户端
  281 + */
  282 + private void createMqttClient() {
  283 + log.debug("[createMqttClient][创建 MQTT 客户端, clientId: {}]", emqxProperties.getMqttClientId());
  284 +
  285 + MqttClientOptions options = new MqttClientOptions()
  286 + .setClientId(emqxProperties.getMqttClientId())
  287 + .setUsername(emqxProperties.getMqttUsername())
  288 + .setPassword(emqxProperties.getMqttPassword())
  289 + .setSsl(emqxProperties.getMqttSsl());
  290 +
  291 + this.mqttClient = MqttClient.create(vertx, options);
  292 + }
  293 +
  294 + /**
  295 + * 设置 MQTT 处理器
  296 + */
  297 + private void setupMqttHandlers() {
  298 + if (mqttClient == null) {
  299 + log.warn("[setupMqttHandlers][MQTT 客户端为空,跳过处理器设置]");
  300 + return;
  301 + }
  302 +
  303 + // 设置断开重连监听器
  304 + mqttClient.closeHandler(closeEvent -> {
  305 + log.warn("[closeHandler][MQTT 连接已断开, 准备重连]");
  306 + reconnectWithDelay();
  307 + });
  308 +
  309 + // 设置异常处理器
  310 + mqttClient.exceptionHandler(exception -> {
  311 + log.error("[exceptionHandler][MQTT 客户端异常]", exception);
  312 + });
  313 +
  314 + // 设置消息处理器
  315 + if (upstreamHandler != null) {
  316 + mqttClient.publishHandler(upstreamHandler::handle);
  317 + log.debug("[setupMqttHandlers][MQTT 消息处理器设置完成]");
  318 + } else {
  319 + log.warn("[setupMqttHandlers][上行消息处理器为空,跳过设置]");
256 } 320 }
257 } 321 }
258 322
@@ -261,15 +325,43 @@ public class IotMqttUpstreamProtocol { @@ -261,15 +325,43 @@ public class IotMqttUpstreamProtocol {
261 */ 325 */
262 private void subscribeToTopics() { 326 private void subscribeToTopics() {
263 List<String> topicList = emqxProperties.getMqttTopics(); 327 List<String> topicList = emqxProperties.getMqttTopics();
  328 + if (CollUtil.isEmpty(topicList)) {
  329 + log.warn("[subscribeToTopics][订阅主题列表为空, 跳过订阅]");
  330 + return;
  331 + }
  332 +
  333 + if (mqttClient == null || !mqttClient.isConnected()) {
  334 + log.warn("[subscribeToTopics][MQTT 客户端未连接, 跳过订阅]");
  335 + return;
  336 + }
  337 +
264 int qos = emqxProperties.getMqttQos(); 338 int qos = emqxProperties.getMqttQos();
  339 + log.info("[subscribeToTopics][开始订阅主题, 共 {} 个, QoS: {}]", topicList.size(), qos);
  340 +
  341 + int[] successCount = {0}; // 使用数组以便在 lambda 中修改
  342 + int[] failCount = {0};
265 343
266 - log.info("[subscribeToTopics][开始订阅主题,共 {} 个]", topicList.size());  
267 for (String topic : topicList) { 344 for (String topic : topicList) {
268 mqttClient.subscribe(topic, qos, subscribeResult -> { 345 mqttClient.subscribe(topic, qos, subscribeResult -> {
269 if (subscribeResult.succeeded()) { 346 if (subscribeResult.succeeded()) {
270 - log.info("[subscribeToTopics][订阅主题成功: {}][QoS: {}]", topic, qos); 347 + successCount[0]++;
  348 + log.debug("[subscribeToTopics][订阅主题成功, topic: {}, qos: {}]", topic, qos);
  349 +
  350 + // 当所有主题都处理完成时,记录汇总日志
  351 + if (successCount[0] + failCount[0] == topicList.size()) {
  352 + log.info("[subscribeToTopics][主题订阅完成, 成功: {}, 失败: {}, 总计: {}]",
  353 + successCount[0], failCount[0], topicList.size());
  354 + }
271 } else { 355 } else {
272 - log.error("[subscribeToTopics][订阅主题失败: {}]", topic, subscribeResult.cause()); 356 + failCount[0]++;
  357 + log.error("[subscribeToTopics][订阅主题失败, topic: {}, qos: {}, 原因: {}]",
  358 + topic, qos, subscribeResult.cause().getMessage(), subscribeResult.cause());
  359 +
  360 + // 当所有主题都处理完成时,记录汇总日志
  361 + if (successCount[0] + failCount[0] == topicList.size()) {
  362 + log.info("[subscribeToTopics][主题订阅完成, 成功: {}, 失败: {}, 总计: {}]",
  363 + successCount[0], failCount[0], topicList.size());
  364 + }
273 } 365 }
274 }); 366 });
275 } 367 }
@@ -281,15 +373,21 @@ public class IotMqttUpstreamProtocol { @@ -281,15 +373,21 @@ public class IotMqttUpstreamProtocol {
281 private void reconnectWithDelay() { 373 private void reconnectWithDelay() {
282 long delay = emqxProperties.getReconnectDelayMs(); 374 long delay = emqxProperties.getReconnectDelayMs();
283 vertx.setTimer(delay, timerId -> { 375 vertx.setTimer(delay, timerId -> {
284 - if (!isRunning || (mqttClient != null && mqttClient.isConnected())) { 376 + if (!isRunning) {
  377 + log.debug("[reconnectWithDelay][服务已停止, 取消重连]");
  378 + return;
  379 + }
  380 + // 检查连接状态,如果已连接则无需重连
  381 + if (mqttClient != null && mqttClient.isConnected()) {
  382 + log.debug("[reconnectWithDelay][MQTT 客户端已连接, 无需重连]");
285 return; 383 return;
286 } 384 }
287 - log.info("[reconnectWithDelay][开始重连 MQTT Broker,延迟 {} 毫秒]", delay); 385 + log.info("[reconnectWithDelay][开始重连 MQTT Broker, 延迟: {} ms]", delay);
288 try { 386 try {
289 - connectMqtt(); 387 + connectMqtt(true); // 标记为重连
290 } catch (Exception e) { 388 } catch (Exception e) {
291 - log.error("[reconnectWithDelay][重连失败,将继续尝试重连]", e);  
292 - reconnectWithDelay(); // 失败后,继续尝试 389 + log.error("[reconnectWithDelay][重连失败, 将继续尝试]", e);
  390 + // 重连失败时,不需要重复调用,因为 connectMqtt(true) 内部已经处理了重连逻辑
293 } 391 }
294 }); 392 });
295 } 393 }
@@ -302,7 +400,7 @@ public class IotMqttUpstreamProtocol { @@ -302,7 +400,7 @@ public class IotMqttUpstreamProtocol {
302 */ 400 */
303 public void publishMessage(String topic, String payload) { 401 public void publishMessage(String topic, String payload) {
304 if (mqttClient == null || !mqttClient.isConnected()) { 402 if (mqttClient == null || !mqttClient.isConnected()) {
305 - log.warn("[publishMessage][MQTT 客户端未连接,无法发布消息][topic: {}]", topic); 403 + log.warn("[publishMessage][MQTT 客户端未连接, 无法发布消息到 topic({})]", topic);
306 return; 404 return;
307 } 405 }
308 MqttQoS qos = MqttQoS.valueOf(emqxProperties.getMqttQos()); 406 MqttQoS qos = MqttQoS.valueOf(emqxProperties.getMqttQos());
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttDownstreamHandler.java
@@ -41,14 +41,14 @@ public class IotMqttDownstreamHandler { @@ -41,14 +41,14 @@ public class IotMqttDownstreamHandler {
41 // 1. 获取设备信息(使用缓存) 41 // 1. 获取设备信息(使用缓存)
42 IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId()); 42 IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId());
43 if (deviceInfo == null) { 43 if (deviceInfo == null) {
44 - log.warn("[handle][设备信息不存在][deviceId: {}]", message.getDeviceId()); 44 + log.warn("[handle][设备信息不存在, deviceId: {}]", message.getDeviceId());
45 return; 45 return;
46 } 46 }
47 47
48 // 2. 根据方法构建主题 48 // 2. 根据方法构建主题
49 String topic = buildTopicByMethod(message.getMethod(), deviceInfo.getProductKey(), deviceInfo.getDeviceName()); 49 String topic = buildTopicByMethod(message.getMethod(), deviceInfo.getProductKey(), deviceInfo.getDeviceName());
50 if (StrUtil.isBlank(topic)) { 50 if (StrUtil.isBlank(topic)) {
51 - log.warn("[handle][未知的消息方法:{}]", message.getMethod()); 51 + log.warn("[handle][未知的消息方法: {}]", message.getMethod());
52 return; 52 return;
53 } 53 }
54 54
@@ -57,7 +57,7 @@ public class IotMqttDownstreamHandler { @@ -57,7 +57,7 @@ public class IotMqttDownstreamHandler {
57 57
58 // 4. 发布消息 58 // 4. 发布消息
59 protocol.publishMessage(topic, payload.toString()); 59 protocol.publishMessage(topic, payload.toString());
60 - log.info("[handle][发布下行消息成功][method: {}][topic: {}]", message.getMethod(), topic); 60 + log.debug("[handle][发布下行消息成功, method: {}, topic: {}]", message.getMethod(), topic);
61 } 61 }
62 62
63 /** 63 /**
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttHttpAuthHandler.java
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
8 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 8 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
11 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 12 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
12 import io.vertx.core.json.JsonObject; 13 import io.vertx.core.json.JsonObject;
13 import io.vertx.ext.web.RoutingContext; 14 import io.vertx.ext.web.RoutingContext;
@@ -46,14 +47,27 @@ public class IotMqttHttpAuthHandler { @@ -46,14 +47,27 @@ public class IotMqttHttpAuthHandler {
46 private static final int INTERNAL_ERROR_STATUS_CODE = 500; 47 private static final int INTERNAL_ERROR_STATUS_CODE = 500;
47 48
48 /** 49 /**
  50 + * MQTT 协议实例,用于获取服务器ID
  51 + */
  52 + private final IotMqttUpstreamProtocol protocol;
  53 +
  54 + /**
  55 + * 构造器
  56 + *
  57 + * @param protocol MQTT 协议实例
  58 + */
  59 + public IotMqttHttpAuthHandler(IotMqttUpstreamProtocol protocol) {
  60 + this.protocol = protocol;
  61 + }
  62 +
  63 + /**
49 * EMQX 认证接口 64 * EMQX 认证接口
50 */ 65 */
51 public void authenticate(RoutingContext context) { 66 public void authenticate(RoutingContext context) {
52 try { 67 try {
53 // 解析请求体 68 // 解析请求体
54 - JsonObject body = context.body().asJsonObject(); 69 + JsonObject body = parseRequestBody(context);
55 if (body == null) { 70 if (body == null) {
56 - sendErrorResponse(context, 400, "请求体不能为空");  
57 return; 71 return;
58 } 72 }
59 73
@@ -61,34 +75,23 @@ public class IotMqttHttpAuthHandler { @@ -61,34 +75,23 @@ public class IotMqttHttpAuthHandler {
61 String username = body.getString("username"); 75 String username = body.getString("username");
62 String password = body.getString("password"); 76 String password = body.getString("password");
63 77
64 - log.info("[authenticate][EMQX 设备认证请求][clientId: {}][username: {}]", clientid, username); 78 + log.debug("[authenticate][EMQX 设备认证, clientId: {}, username: {}]", clientid, username);
65 79
66 // 参数校验 80 // 参数校验
67 - if (StrUtil.isEmpty(clientid) || StrUtil.isEmpty(username) || StrUtil.isEmpty(password)) {  
68 - log.warn("[authenticate][认证参数不完整][clientId: {}][username: {}]", clientid, username);  
69 - sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "认证参数不完整"); 81 + if (!validateAuthParams(context, clientid, username, password)) {
70 return; 82 return;
71 } 83 }
72 84
73 // 执行设备认证 85 // 执行设备认证
74 - IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);  
75 - CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO()  
76 - .setClientId(clientid)  
77 - .setUsername(username)  
78 - .setPassword(password));  
79 -  
80 - result.checkError();  
81 - if (!BooleanUtil.isTrue(result.getData())) {  
82 - log.warn("[authenticate][设备认证失败][clientId: {}][username: {}]", clientid, username);  
83 - sendErrorResponse(context, UNAUTHORIZED_STATUS_CODE, DEVICE_AUTH_FAIL.getMsg()); 86 + if (!performDeviceAuth(context, clientid, username, password)) {
84 return; 87 return;
85 } 88 }
86 89
87 - log.info("[authenticate][设备认证成功][clientId: {}][username: {}]", clientid, username); 90 + log.debug("[authenticate][设备认证成功, clientId: {}, username: {}]", clientid, username);
88 sendSuccessResponse(context, "认证成功"); 91 sendSuccessResponse(context, "认证成功");
89 92
90 } catch (Exception e) { 93 } catch (Exception e) {
91 - log.error("[authenticate][设备认证异常]", e); 94 + log.error("[authenticate][设备认证异常, 详细信息: {}]", e.getMessage(), e);
92 sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "认证服务异常"); 95 sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "认证服务异常");
93 } 96 }
94 } 97 }
@@ -99,9 +102,8 @@ public class IotMqttHttpAuthHandler { @@ -99,9 +102,8 @@ public class IotMqttHttpAuthHandler {
99 public void connected(RoutingContext context) { 102 public void connected(RoutingContext context) {
100 try { 103 try {
101 // 解析请求体 104 // 解析请求体
102 - JsonObject body = context.body().asJsonObject(); 105 + JsonObject body = parseRequestBody(context);
103 if (body == null) { 106 if (body == null) {
104 - sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "请求体不能为空");  
105 return; 107 return;
106 } 108 }
107 109
@@ -109,13 +111,14 @@ public class IotMqttHttpAuthHandler { @@ -109,13 +111,14 @@ public class IotMqttHttpAuthHandler {
109 String username = body.getString("username"); 111 String username = body.getString("username");
110 Long timestamp = body.getLong("timestamp"); 112 Long timestamp = body.getLong("timestamp");
111 113
112 - log.info("[connected][设备连接事件][clientId: {}][username: {}]", clientid, username); 114 + log.debug("[connected][设备连接, clientId: {}, username: {}, timestamp: {}]",
  115 + clientid, username, timestamp);
113 116
114 - handleDeviceStateChange(username, true); 117 + handleDeviceStateChange(username, true, "设备连接");
115 sendSuccessResponse(context, "处理成功"); 118 sendSuccessResponse(context, "处理成功");
116 119
117 } catch (Exception e) { 120 } catch (Exception e) {
118 - log.error("[connected][处理设备连接事件失败]", e); 121 + log.error("[connected][处理设备连接事件失败, 详细信息: {}]", e.getMessage(), e);
119 sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "处理失败"); 122 sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "处理失败");
120 } 123 }
121 } 124 }
@@ -126,9 +129,8 @@ public class IotMqttHttpAuthHandler { @@ -126,9 +129,8 @@ public class IotMqttHttpAuthHandler {
126 public void disconnected(RoutingContext context) { 129 public void disconnected(RoutingContext context) {
127 try { 130 try {
128 // 解析请求体 131 // 解析请求体
129 - JsonObject body = context.body().asJsonObject(); 132 + JsonObject body = parseRequestBody(context);
130 if (body == null) { 133 if (body == null) {
131 - sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "请求体不能为空");  
132 return; 134 return;
133 } 135 }
134 136
@@ -137,58 +139,144 @@ public class IotMqttHttpAuthHandler { @@ -137,58 +139,144 @@ public class IotMqttHttpAuthHandler {
137 String reason = body.getString("reason"); 139 String reason = body.getString("reason");
138 Long timestamp = body.getLong("timestamp"); 140 Long timestamp = body.getLong("timestamp");
139 141
140 - log.info("[disconnected][设备断开连接事件][clientId: {}][username: {}][reason: {}]",  
141 - clientid, username, reason); 142 + log.debug("[disconnected][设备断开连接, clientId: {}, username: {}, reason: {}, timestamp: {}]",
  143 + clientid, username, reason, timestamp);
142 144
143 - handleDeviceStateChange(username, false); 145 + handleDeviceStateChange(username, false, "设备断开连接,原因:" + reason);
144 sendSuccessResponse(context, "处理成功"); 146 sendSuccessResponse(context, "处理成功");
145 147
146 } catch (Exception e) { 148 } catch (Exception e) {
147 - log.error("[disconnected][处理设备断开连接事件失败]", e); 149 + log.error("[disconnected][处理设备断开连接事件失败, 详细信息: {}]", e.getMessage(), e);
148 sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "处理失败"); 150 sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "处理失败");
149 } 151 }
150 } 152 }
151 153
152 /** 154 /**
153 - * 处理设备状态变化 155 + * 解析请求体
  156 + *
  157 + * @param context 路由上下文
  158 + * @return 请求体JSON对象,解析失败时返回null
  159 + */
  160 + private JsonObject parseRequestBody(RoutingContext context) {
  161 + try {
  162 + JsonObject body = context.body().asJsonObject();
  163 + if (body == null) {
  164 + log.warn("[parseRequestBody][请求体为空]");
  165 + sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "请求体不能为空");
  166 + return null;
  167 + }
  168 + return body;
  169 + } catch (Exception e) {
  170 + log.error("[parseRequestBody][解析请求体失败]", e);
  171 + sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "请求体格式错误");
  172 + return null;
  173 + }
  174 + }
  175 +
  176 + /**
  177 + * 验证认证参数
  178 + *
  179 + * @param context 路由上下文
  180 + * @param clientid 客户端ID
  181 + * @param username 用户名
  182 + * @param password 密码
  183 + * @return 验证是否通过
  184 + */
  185 + private boolean validateAuthParams(RoutingContext context, String clientid, String username, String password) {
  186 + if (StrUtil.hasEmpty(clientid, username, password)) {
  187 + log.warn("[validateAuthParams][认证参数不完整, clientId: {}, username: {}, password: {}]",
  188 + clientid, username, StrUtil.isNotEmpty(password) ? "***" : "空");
  189 + sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "认证参数不完整");
  190 + return false;
  191 + }
  192 + return true;
  193 + }
  194 +
  195 + /**
  196 + * 执行设备认证
154 * 197 *
  198 + * @param context 路由上下文
  199 + * @param clientid 客户端ID
155 * @param username 用户名 200 * @param username 用户名
156 - * @param online 是否在线 201 + * @param password 密码
  202 + * @return 认证是否成功
157 */ 203 */
158 - private void handleDeviceStateChange(String username, boolean online) { 204 + private boolean performDeviceAuth(RoutingContext context, String clientid, String username, String password) {
  205 + try {
  206 + IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
  207 + CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO()
  208 + .setClientId(clientid)
  209 + .setUsername(username)
  210 + .setPassword(password));
  211 +
  212 + result.checkError();
  213 + if (!BooleanUtil.isTrue(result.getData())) {
  214 + log.warn("[performDeviceAuth][设备认证失败, clientId: {}, username: {}]", clientid, username);
  215 + sendErrorResponse(context, UNAUTHORIZED_STATUS_CODE, DEVICE_AUTH_FAIL.getMsg());
  216 + return false;
  217 + }
  218 + return true;
  219 + } catch (Exception e) {
  220 + log.error("[performDeviceAuth][设备认证异常, clientId: {}, username: {}]", clientid, username, e);
  221 + sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "认证服务异常");
  222 + return false;
  223 + }
  224 + }
  225 +
  226 + /**
  227 + * 处理设备状态变化
  228 + *
  229 + * @param username 用户名
  230 + * @param online 是否在线
  231 + * @param actionDesc 操作描述
  232 + */
  233 + private void handleDeviceStateChange(String username, boolean online, String actionDesc) {
159 if (StrUtil.isEmpty(username) || "undefined".equals(username)) { 234 if (StrUtil.isEmpty(username) || "undefined".equals(username)) {
160 - log.warn("[handleDeviceStateChange][用户名为空或未定义][username: {}]", username); 235 + log.warn("[handleDeviceStateChange][用户名为空或'undefined', username: {}, action: {}]",
  236 + username, actionDesc);
161 return; 237 return;
162 } 238 }
163 239
164 // 解析设备信息 240 // 解析设备信息
165 IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username); 241 IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username);
166 if (deviceInfo == null) { 242 if (deviceInfo == null) {
167 - log.warn("[handleDeviceStateChange][无法解析设备信息][username: {}]", username); 243 + log.warn("[handleDeviceStateChange][无法从 username({}) 解析设备信息, action: {}]",
  244 + username, actionDesc);
168 return; 245 return;
169 } 246 }
170 247
171 try { 248 try {
172 - // 获取服务器 ID  
173 - String serverId = "mqtt_auth_gateway"; 249 + // 从协议实例获取服务器 ID
  250 + String serverId = protocol.getServerId();
  251 + if (StrUtil.isEmpty(serverId)) {
  252 + log.error("[handleDeviceStateChange][获取服务器ID失败, username: {}, action: {}]",
  253 + username, actionDesc);
  254 + return;
  255 + }
174 256
175 // 构建设备状态消息 257 // 构建设备状态消息
176 IotDeviceMessageService deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); 258 IotDeviceMessageService deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
177 IotDeviceMessage message; 259 IotDeviceMessage message;
178 if (online) { 260 if (online) {
179 message = IotDeviceMessage.buildStateOnline(); 261 message = IotDeviceMessage.buildStateOnline();
180 - log.info("[handleDeviceStateChange][发送设备上线消息成功][username: {}]", username); 262 + log.debug("[handleDeviceStateChange][发送设备上线消息, username: {}, serverId: {}]",
  263 + username, serverId);
181 } else { 264 } else {
182 message = IotDeviceMessage.buildStateOffline(); 265 message = IotDeviceMessage.buildStateOffline();
183 - log.info("[handleDeviceStateChange][发送设备下线消息成功][username: {}]", username); 266 + log.debug("[handleDeviceStateChange][发送设备下线消息, username: {}, serverId: {}]",
  267 + username, serverId);
184 } 268 }
185 269
186 // 发送消息到消息总线 270 // 发送消息到消息总线
187 deviceMessageService.sendDeviceMessage(message, 271 deviceMessageService.sendDeviceMessage(message,
188 deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId); 272 deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId);
  273 +
  274 + log.info("[handleDeviceStateChange][{}处理成功, productKey: {}, deviceName: {}, serverId: {}]",
  275 + actionDesc, deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId);
  276 +
189 } catch (Exception e) { 277 } catch (Exception e) {
190 - log.error("[handleDeviceStateChange][发送设备状态消息失败][username: {}][online: {}]",  
191 - username, online, e); 278 + log.error("[handleDeviceStateChange][发送设备状态消息失败, username: {}, online: {}, action: {}]",
  279 + username, online, actionDesc, e);
192 } 280 }
193 } 281 }
194 282
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java
@@ -9,6 +9,8 @@ import io.vertx.mqtt.messages.MqttPublishMessage; @@ -9,6 +9,8 @@ import io.vertx.mqtt.messages.MqttPublishMessage;
9 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
10 import org.springframework.util.Assert; 10 import org.springframework.util.Assert;
11 11
  12 +import java.util.Arrays;
  13 +
12 /** 14 /**
13 * IoT 网关 MQTT 上行消息处理器 15 * IoT 网关 MQTT 上行消息处理器
14 * 16 *
@@ -32,12 +34,12 @@ public class IotMqttUpstreamHandler { @@ -32,12 +34,12 @@ public class IotMqttUpstreamHandler {
32 String topic = message.topicName(); 34 String topic = message.topicName();
33 byte[] payload = message.payload().getBytes(); 35 byte[] payload = message.payload().getBytes();
34 36
35 - log.debug("[handle][收到 MQTT 消息][topic: {}]", topic); 37 + log.debug("[handle][收到 MQTT 消息, topic: {}]", topic);
36 38
37 try { 39 try {
38 // 1. 前置校验 40 // 1. 前置校验
39 if (StrUtil.isBlank(topic)) { 41 if (StrUtil.isBlank(topic)) {
40 - log.warn("[validateInput][主题为空,忽略消息]"); 42 + log.warn("[handle][主题为空, 忽略消息]");
41 return; 43 return;
42 } 44 }
43 // 注意:payload 可以为空 45 // 注意:payload 可以为空
@@ -45,7 +47,7 @@ public class IotMqttUpstreamHandler { @@ -45,7 +47,7 @@ public class IotMqttUpstreamHandler {
45 // 2. 识别并验证消息类型 47 // 2. 识别并验证消息类型
46 String messageType = getMessageType(topic); 48 String messageType = getMessageType(topic);
47 Assert.notNull(messageType, String.format("未知的消息类型, topic(%s)", topic)); 49 Assert.notNull(messageType, String.format("未知的消息类型, topic(%s)", topic));
48 - log.info("[handle][接收到{}][topic: {}]", messageType, topic); 50 + log.debug("[handle][接收到上行消息({}), topic: {}]", messageType, topic);
49 51
50 // 3. 解析主题,获取 productKey 和 deviceName 52 // 3. 解析主题,获取 productKey 和 deviceName
51 String[] topicParts = topic.split("/"); 53 String[] topicParts = topic.split("/");
@@ -72,7 +74,7 @@ public class IotMqttUpstreamHandler { @@ -72,7 +74,7 @@ public class IotMqttUpstreamHandler {
72 deviceMessageService.sendDeviceMessage(deviceMessage, productKey, deviceName, serverId); 74 deviceMessageService.sendDeviceMessage(deviceMessage, productKey, deviceName, serverId);
73 75
74 // 6. 记录成功日志 76 // 6. 记录成功日志
75 - log.info("[handle][处理{}成功,已转发到 MQ][topic: {}]", messageType, topic); 77 + log.debug("[handle][处理上行消息({})成功, topic: {}]", messageType, topic);
76 } catch (Exception e) { 78 } catch (Exception e) {
77 log.error("[handle][处理 MQTT 消息失败][topic: {}][payload: {}]", topic, new String(payload), e); 79 log.error("[handle][处理 MQTT 消息失败][topic: {}][payload: {}]", topic, new String(payload), e);
78 } 80 }
@@ -86,10 +88,13 @@ public class IotMqttUpstreamHandler { @@ -86,10 +88,13 @@ public class IotMqttUpstreamHandler {
86 */ 88 */
87 private String getMessageType(String topic) { 89 private String getMessageType(String topic) {
88 String[] topicParts = topic.split("/"); 90 String[] topicParts = topic.split("/");
89 - if (topicParts.length < 7) {  
90 - return null; 91 + // 约定:topic 第 4 个部分开始为消息类型
  92 + // 例如:/sys/{productKey}/{deviceName}/thing/property/post ->
  93 + // thing/property/post
  94 + if (topicParts.length > 4) {
  95 + return String.join("/", Arrays.copyOfRange(topicParts, 4, topicParts.length));
91 } 96 }
92 - return topicParts[3]; 97 + return topicParts[topicParts.length - 1];
93 } 98 }
94 99
95 } 100 }
96 \ No newline at end of file 101 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application-local.yaml
@@ -37,6 +37,7 @@ yudao: @@ -37,6 +37,7 @@ yudao:
37 mqtt-port: 1883 # MQTT Broker 端口 37 mqtt-port: 1883 # MQTT Broker 端口
38 mqtt-username: admin # MQTT 用户名 38 mqtt-username: admin # MQTT 用户名
39 mqtt-password: public # MQTT 密码 39 mqtt-password: public # MQTT 密码
  40 + mqtt-client-id: iot-gateway-mqtt # MQTT 客户端 ID
40 mqtt-ssl: false # 是否开启 SSL 41 mqtt-ssl: false # 是否开启 SSL
41 mqtt-topics: 42 mqtt-topics:
42 - "/sys/#" # 系统主题 43 - "/sys/#" # 系统主题