Commit 85c1b05bcaa51cad318ee2ca35ddf063dfd6bea0

Authored by YunaiV
1 parent e2b995d4

feat(iot):【协议改造】emqx 初步改造(50%):修复 Review Agent 反馈的 bug(增强稳定性)

yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxProtocol.java
@@ -176,7 +176,8 @@ public class IotEmqxProtocol implements IotProtocol { @@ -176,7 +176,8 @@ public class IotEmqxProtocol implements IotProtocol {
176 // 2.4 关闭 Vertx 176 // 2.4 关闭 Vertx
177 if (vertx != null) { 177 if (vertx != null) {
178 try { 178 try {
179 - vertx.close().result(); 179 + vertx.close().toCompletionStage().toCompletableFuture()
  180 + .get(10, TimeUnit.SECONDS);
180 log.info("[stop][IoT EMQX 协议 {} Vertx 已关闭]", getId()); 181 log.info("[stop][IoT EMQX 协议 {} Vertx 已关闭]", getId());
181 } catch (Exception e) { 182 } catch (Exception e) {
182 log.error("[stop][IoT EMQX 协议 {} Vertx 关闭失败]", getId(), e); 183 log.error("[stop][IoT EMQX 协议 {} Vertx 关闭失败]", getId(), e);
@@ -195,7 +196,7 @@ public class IotEmqxProtocol implements IotProtocol { @@ -195,7 +196,7 @@ public class IotEmqxProtocol implements IotProtocol {
195 private void startHttpServer() { 196 private void startHttpServer() {
196 // 1. 创建路由 197 // 1. 创建路由
197 Router router = Router.router(vertx); 198 Router router = Router.router(vertx);
198 - router.route().handler(BodyHandler.create()); 199 + router.route().handler(BodyHandler.create().setBodyLimit(1024 * 1024)); // 限制 body 大小为 1MB,防止大包攻击
199 200
200 // 2. 创建处理器 201 // 2. 创建处理器
201 IotEmqxAuthEventHandler handler = new IotEmqxAuthEventHandler(serverId); 202 IotEmqxAuthEventHandler handler = new IotEmqxAuthEventHandler(serverId);
@@ -218,12 +219,13 @@ public class IotEmqxProtocol implements IotProtocol { @@ -218,12 +219,13 @@ public class IotEmqxProtocol implements IotProtocol {
218 httpServer = vertx.createHttpServer(options) 219 httpServer = vertx.createHttpServer(options)
219 .requestHandler(router) 220 .requestHandler(router)
220 .listen() 221 .listen()
221 - .result(); 222 + .toCompletionStage().toCompletableFuture()
  223 + .get(10, TimeUnit.SECONDS);
222 log.info("[startHttpServer][IoT EMQX 协议 {} HTTP Hook 服务启动成功, port: {}, ssl: {}]", 224 log.info("[startHttpServer][IoT EMQX 协议 {} HTTP Hook 服务启动成功, port: {}, ssl: {}]",
223 getId(), properties.getPort(), httpConfig != null && Boolean.TRUE.equals(httpConfig.getSslEnabled())); 225 getId(), properties.getPort(), httpConfig != null && Boolean.TRUE.equals(httpConfig.getSslEnabled()));
224 } catch (Exception e) { 226 } catch (Exception e) {
225 log.error("[startHttpServer][IoT EMQX 协议 {} HTTP Hook 服务启动失败, port: {}]", getId(), properties.getPort(), e); 227 log.error("[startHttpServer][IoT EMQX 协议 {} HTTP Hook 服务启动失败, port: {}]", getId(), properties.getPort(), e);
226 - throw e; 228 + throw new RuntimeException("HTTP Hook 服务启动失败", e);
227 } 229 }
228 } 230 }
229 231
@@ -232,7 +234,8 @@ public class IotEmqxProtocol implements IotProtocol { @@ -232,7 +234,8 @@ public class IotEmqxProtocol implements IotProtocol {
232 return; 234 return;
233 } 235 }
234 try { 236 try {
235 - httpServer.close().result(); 237 + httpServer.close().toCompletionStage().toCompletableFuture()
  238 + .get(5, TimeUnit.SECONDS);
236 log.info("[stopHttpServer][IoT EMQX 协议 {} HTTP Hook 服务已停止]", getId()); 239 log.info("[stopHttpServer][IoT EMQX 协议 {} HTTP Hook 服务已停止]", getId());
237 } catch (Exception e) { 240 } catch (Exception e) {
238 log.error("[stopHttpServer][IoT EMQX 协议 {} HTTP Hook 服务停止失败]", getId(), e); 241 log.error("[stopHttpServer][IoT EMQX 协议 {} HTTP Hook 服务停止失败]", getId(), e);
@@ -258,19 +261,21 @@ public class IotEmqxProtocol implements IotProtocol { @@ -258,19 +261,21 @@ public class IotEmqxProtocol implements IotProtocol {
258 261
259 private void stopMqttClient() { 262 private void stopMqttClient() {
260 MqttClient client = this.mqttClient; 263 MqttClient client = this.mqttClient;
261 - if (client == null || !client.isConnected()) { 264 + this.mqttClient = null; // 先清理引用
  265 + if (client == null) {
262 return; 266 return;
263 } 267 }
264 - this.mqttClient = null;  
265 268
266 - // 1. 批量取消订阅  
267 - List<String> topicList = emqxConfig.getMqttTopics();  
268 - if (CollUtil.isNotEmpty(topicList)) {  
269 - try {  
270 - client.unsubscribe(topicList).toCompletionStage().toCompletableFuture()  
271 - .get(5, TimeUnit.SECONDS);  
272 - } catch (Exception e) {  
273 - log.warn("[stopMqttClient][IoT EMQX 协议 {} 取消订阅异常]", getId(), e); 269 + // 1. 批量取消订阅(仅在连接时)
  270 + if (client.isConnected()) {
  271 + List<String> topicList = emqxConfig.getMqttTopics();
  272 + if (CollUtil.isNotEmpty(topicList)) {
  273 + try {
  274 + client.unsubscribe(topicList).toCompletionStage().toCompletableFuture()
  275 + .get(5, TimeUnit.SECONDS);
  276 + } catch (Exception e) {
  277 + log.warn("[stopMqttClient][IoT EMQX 协议 {} 取消订阅异常]", getId(), e);
  278 + }
274 } 279 }
275 } 280 }
276 281
@@ -296,15 +301,15 @@ public class IotEmqxProtocol implements IotProtocol { @@ -296,15 +301,15 @@ public class IotEmqxProtocol implements IotProtocol {
296 .setClientId(emqxConfig.getMqttClientId()) 301 .setClientId(emqxConfig.getMqttClientId())
297 .setUsername(emqxConfig.getMqttUsername()) 302 .setUsername(emqxConfig.getMqttUsername())
298 .setPassword(emqxConfig.getMqttPassword()) 303 .setPassword(emqxConfig.getMqttPassword())
299 - .setSsl(emqxConfig.getMqttSsl())  
300 - .setCleanSession(emqxConfig.getCleanSession()) 304 + .setSsl(Boolean.TRUE.equals(emqxConfig.getMqttSsl()))
  305 + .setCleanSession(Boolean.TRUE.equals(emqxConfig.getCleanSession()))
301 .setKeepAliveInterval(emqxConfig.getKeepAliveIntervalSeconds()) 306 .setKeepAliveInterval(emqxConfig.getKeepAliveIntervalSeconds())
302 .setMaxInflightQueue(emqxConfig.getMaxInflightQueue()); 307 .setMaxInflightQueue(emqxConfig.getMaxInflightQueue());
303 options.setConnectTimeout(emqxConfig.getConnectTimeoutSeconds() * 1000); // Vert.x 需要毫秒 308 options.setConnectTimeout(emqxConfig.getConnectTimeoutSeconds() * 1000); // Vert.x 需要毫秒
304 - options.setTrustAll(emqxConfig.getTrustAll()); 309 + options.setTrustAll(Boolean.TRUE.equals(emqxConfig.getTrustAll()));
305 // 1.2 配置遗嘱消息 310 // 1.2 配置遗嘱消息
306 IotEmqxConfig.Will will = emqxConfig.getWill(); 311 IotEmqxConfig.Will will = emqxConfig.getWill();
307 - if (will.isEnabled()) { 312 + if (will != null && will.isEnabled()) {
308 Assert.notBlank(will.getTopic(), "遗嘱消息主题(emqx.will.topic)不能为空"); 313 Assert.notBlank(will.getTopic(), "遗嘱消息主题(emqx.will.topic)不能为空");
309 Assert.notNull(will.getPayload(), "遗嘱消息内容(emqx.will.payload)不能为空"); 314 Assert.notNull(will.getPayload(), "遗嘱消息内容(emqx.will.payload)不能为空");
310 options.setWillFlag(true) 315 options.setWillFlag(true)
@@ -313,9 +318,11 @@ public class IotEmqxProtocol implements IotProtocol { @@ -313,9 +318,11 @@ public class IotEmqxProtocol implements IotProtocol {
313 .setWillQoS(will.getQos()) 318 .setWillQoS(will.getQos())
314 .setWillRetain(will.isRetain()); 319 .setWillRetain(will.isRetain());
315 } 320 }
316 - // 1.3 配置高级 SSL/TLS(仅在启用 SSL 且不信任所有证书时生效)  
317 - if (Boolean.TRUE.equals(emqxConfig.getMqttSsl()) && !Boolean.TRUE.equals(emqxConfig.getTrustAll())) {  
318 - IotEmqxConfig.Ssl sslOptions = emqxConfig.getSslOptions(); 321 + // 1.3 配置高级 SSL/TLS(仅在启用 SSL 且不信任所有证书时生效,且需要 sslOptions 非空)
  322 + IotEmqxConfig.Ssl sslOptions = emqxConfig.getSslOptions();
  323 + if (Boolean.TRUE.equals(emqxConfig.getMqttSsl())
  324 + && Boolean.FALSE.equals(emqxConfig.getTrustAll())
  325 + && sslOptions != null) {
319 if (StrUtil.isNotBlank(sslOptions.getTrustStorePath())) { 326 if (StrUtil.isNotBlank(sslOptions.getTrustStorePath())) {
320 options.setTrustStoreOptions(new JksOptions() 327 options.setTrustStoreOptions(new JksOptions()
321 .setPath(sslOptions.getTrustStorePath()) 328 .setPath(sslOptions.getTrustStorePath())
@@ -365,10 +372,11 @@ public class IotEmqxProtocol implements IotProtocol { @@ -365,10 +372,11 @@ public class IotEmqxProtocol implements IotProtocol {
365 */ 372 */
366 private void closeMqttClient() { 373 private void closeMqttClient() {
367 MqttClient oldClient = this.mqttClient; 374 MqttClient oldClient = this.mqttClient;
368 - if (oldClient == null || !oldClient.isConnected()) { 375 + this.mqttClient = null; // 先清理引用
  376 + if (oldClient == null) {
369 return; 377 return;
370 } 378 }
371 - this.mqttClient = null; 379 + // 尽力释放(无论是否连接都尝试 disconnect)
372 try { 380 try {
373 oldClient.disconnect().toCompletionStage().toCompletableFuture() 381 oldClient.disconnect().toCompletionStage().toCompletableFuture()
374 .get(5, TimeUnit.SECONDS); 382 .get(5, TimeUnit.SECONDS);
@@ -391,7 +399,11 @@ public class IotEmqxProtocol implements IotProtocol { @@ -391,7 +399,11 @@ public class IotEmqxProtocol implements IotProtocol {
391 return; 399 return;
392 } 400 }
393 log.info("[startMqttClientReconnectChecker][IoT EMQX 协议 {} 检测到断开,尝试重连]", getId()); 401 log.info("[startMqttClientReconnectChecker][IoT EMQX 协议 {} 检测到断开,尝试重连]", getId());
394 - tryReconnectMqttClient(); 402 + // 用 executeBlocking 避免阻塞 event-loop(tryReconnectMqttClient 内部有同步等待)
  403 + vertx.executeBlocking(() -> {
  404 + tryReconnectMqttClient();
  405 + return null;
  406 + });
395 }); 407 });
396 } 408 }
397 409
@@ -449,7 +461,11 @@ public class IotEmqxProtocol implements IotProtocol { @@ -449,7 +461,11 @@ public class IotEmqxProtocol implements IotProtocol {
449 return; 461 return;
450 } 462 }
451 log.warn("[setupMqttClientHandlers][IoT EMQX 协议 {} 连接断开,立即尝试重连]", getId()); 463 log.warn("[setupMqttClientHandlers][IoT EMQX 协议 {} 连接断开,立即尝试重连]", getId());
452 - vertx.runOnContext(v -> tryReconnectMqttClient()); 464 + // 用 executeBlocking 避免阻塞 event-loop(tryReconnectMqttClient 内部有同步等待)
  465 + vertx.executeBlocking(() -> {
  466 + tryReconnectMqttClient();
  467 + return null;
  468 + });
453 }); 469 });
454 470
455 // 2. 异常处理 471 // 2. 异常处理
@@ -497,7 +513,8 @@ public class IotEmqxProtocol implements IotProtocol { @@ -497,7 +513,8 @@ public class IotEmqxProtocol implements IotProtocol {
497 return; 513 return;
498 } 514 }
499 MqttQoS qos = MqttQoS.valueOf(emqxConfig.getMqttQos()); 515 MqttQoS qos = MqttQoS.valueOf(emqxConfig.getMqttQos());
500 - mqttClient.publish(topic, Buffer.buffer(payload), qos, false, false); 516 + mqttClient.publish(topic, Buffer.buffer(payload), qos, false, false)
  517 + .onFailure(e -> log.error("[publishMessage][IoT EMQX 协议 {} 发布失败, topic: {}]", getId(), topic, e));
501 } 518 }
502 519
503 } 520 }