Commit 53697b55c242ce246ba01a9ee85559c21aed0320

Authored by 安浩浩
1 parent 8cf8af1f

【功能完善】IoT: 更新 EMQX 插件配置,添加 MQTT 连接参数,重构相关逻辑

yudao-dependencies/pom.xml
@@ -67,7 +67,7 @@ @@ -67,7 +67,7 @@
67 <netty.version>4.1.116.Final</netty.version> 67 <netty.version>4.1.116.Final</netty.version>
68 <mqtt.version>1.2.5</mqtt.version> 68 <mqtt.version>1.2.5</mqtt.version>
69 <pf4j-spring.version>0.9.0</pf4j-spring.version> 69 <pf4j-spring.version>0.9.0</pf4j-spring.version>
70 - <vertx.version>4.5.11</vertx.version> 70 + <vertx.version>4.5.13</vertx.version>
71 <!-- 三方云服务相关 --> 71 <!-- 三方云服务相关 -->
72 <commons-io.version>2.17.0</commons-io.version> 72 <commons-io.version>2.17.0</commons-io.version>
73 <commons-compress.version>1.27.1</commons-compress.version> 73 <commons-compress.version>1.27.1</commons-compress.version>
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
@@ -62,7 +62,7 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi { @@ -62,7 +62,7 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
62 62
63 @Override 63 @Override
64 public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) { 64 public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
65 - Boolean result = deviceUpstreamService.authenticateEmqxConnection(authReqDTO); 65 + boolean result = deviceUpstreamService.authenticateEmqxConnection(authReqDTO);
66 return success(result); 66 return success(result);
67 } 67 }
68 68
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java
@@ -79,18 +79,6 @@ public class IotDeviceRespVO { @@ -79,18 +79,6 @@ public class IotDeviceRespVO {
79 @ExcelProperty("设备密钥") 79 @ExcelProperty("设备密钥")
80 private String deviceSecret; 80 private String deviceSecret;
81 81
82 - @Schema(description = "MQTT 客户端 ID", example = "24602")  
83 - @ExcelProperty("MQTT 客户端 ID")  
84 - private String mqttClientId;  
85 -  
86 - @Schema(description = "MQTT 用户名", example = "芋艿")  
87 - @ExcelProperty("MQTT 用户名")  
88 - private String mqttUsername;  
89 -  
90 - @Schema(description = "MQTT 密码")  
91 - @ExcelProperty("MQTT 密码")  
92 - private String mqttPassword;  
93 -  
94 @Schema(description = "认证类型(如一机一密、动态注册)", example = "2") 82 @Schema(description = "认证类型(如一机一密、动态注册)", example = "2")
95 @ExcelProperty("认证类型(如一机一密、动态注册)") 83 @ExcelProperty("认证类型(如一机一密、动态注册)")
96 private String authType; 84 private String authType;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamService.java
@@ -67,6 +67,6 @@ public interface IotDeviceUpstreamService { @@ -67,6 +67,6 @@ public interface IotDeviceUpstreamService {
67 * 67 *
68 * @param authReqDTO Emqx 连接认证 DTO 68 * @param authReqDTO Emqx 连接认证 DTO
69 */ 69 */
70 - Boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO); 70 + boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO);
71 71
72 } 72 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java
@@ -174,7 +174,7 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { @@ -174,7 +174,7 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
174 } 174 }
175 175
176 private void registerDevice0(String productKey, String deviceName, Long gatewayId, 176 private void registerDevice0(String productKey, String deviceName, Long gatewayId,
177 - IotDeviceUpstreamAbstractReqDTO registerReqDTO) { 177 + IotDeviceUpstreamAbstractReqDTO registerReqDTO) {
178 // 1.1 注册设备 178 // 1.1 注册设备
179 IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(productKey, deviceName); 179 IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(productKey, deviceName);
180 boolean registerNew = device == null; 180 boolean registerNew = device == null;
@@ -280,16 +280,15 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { @@ -280,16 +280,15 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
280 sendDeviceMessage(message, device); 280 sendDeviceMessage(message, device);
281 } 281 }
282 282
283 - // TODO @haohao:建议返回 boolean;  
284 @Override 283 @Override
285 - public Boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) { 284 + public boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
286 log.info("[authenticateEmqxConnection][认证 Emqx 连接: {}]", authReqDTO); 285 log.info("[authenticateEmqxConnection][认证 Emqx 连接: {}]", authReqDTO);
287 // 1. 校验设备是否存在 286 // 1. 校验设备是否存在
288 // username 格式:${DeviceName}&${ProductKey} 287 // username 格式:${DeviceName}&${ProductKey}
289 String[] usernameParts = authReqDTO.getUsername().split("&"); 288 String[] usernameParts = authReqDTO.getUsername().split("&");
290 if (usernameParts.length != 2) { 289 if (usernameParts.length != 2) {
291 log.error("[authenticateEmqxConnection][认证失败,username 格式不正确]"); 290 log.error("[authenticateEmqxConnection][认证失败,username 格式不正确]");
292 - return Boolean.FALSE; 291 + return false;
293 } 292 }
294 String deviceName = usernameParts[0]; 293 String deviceName = usernameParts[0];
295 String productKey = usernameParts[1]; 294 String productKey = usernameParts[1];
@@ -298,19 +297,18 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { @@ -298,19 +297,18 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
298 if (device == null) { 297 if (device == null) {
299 log.error("[authenticateEmqxConnection][设备({}/{}) 不存在]", 298 log.error("[authenticateEmqxConnection][设备({}/{}) 不存在]",
300 productKey, deviceName); 299 productKey, deviceName);
301 - return Boolean.FALSE; 300 + return false;
302 } 301 }
303 // 2. 校验密码 302 // 2. 校验密码
304 String deviceSecret = device.getDeviceSecret(); 303 String deviceSecret = device.getDeviceSecret();
305 String clientId = authReqDTO.getClientId(); 304 String clientId = authReqDTO.getClientId();
306 MqttSignResult sign = MqttSignUtils.calculate(productKey, deviceName, deviceSecret, clientId); 305 MqttSignResult sign = MqttSignUtils.calculate(productKey, deviceName, deviceSecret, clientId);
307 - // TODO @haohao:notEquals,尽量不走取反逻辑哈  
308 - if (!StrUtil.equals(sign.getPassword(), authReqDTO.getPassword())) {  
309 - log.error("[authenticateEmqxConnection][认证失败,密码不正确]");  
310 - return Boolean.FALSE; 306 + if (StrUtil.equals(sign.getPassword(), authReqDTO.getPassword())) {
  307 + log.info("[authenticateEmqxConnection][认证成功]");
  308 + return true;
311 } 309 }
312 - log.info("[authenticateEmqxConnection][认证成功]");  
313 - return Boolean.TRUE; 310 + log.error("[authenticateEmqxConnection][认证失败,密码不正确]");
  311 + return false;
314 } 312 }
315 313
316 private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) { 314 private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) {
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/MqttSignUtils.java
1 package cn.iocoder.yudao.module.iot.util; 1 package cn.iocoder.yudao.module.iot.util;
2 2
  3 +import cn.hutool.crypto.digest.HMac;
  4 +import cn.hutool.crypto.digest.HmacAlgorithm;
  5 +import lombok.AllArgsConstructor;
3 import lombok.Getter; 6 import lombok.Getter;
4 7
5 -import javax.crypto.Mac;  
6 -import javax.crypto.spec.SecretKeySpec;  
7 import java.nio.charset.StandardCharsets; 8 import java.nio.charset.StandardCharsets;
8 9
9 /** 10 /**
@@ -13,10 +14,6 @@ import java.nio.charset.StandardCharsets; @@ -13,10 +14,6 @@ import java.nio.charset.StandardCharsets;
13 */ 14 */
14 public class MqttSignUtils { 15 public class MqttSignUtils {
15 16
16 - private static final String SIGN_METHOD = "hmacsha256";  
17 -  
18 - // TODO @haohao:calculate 方法,可以融合么?  
19 -  
20 /** 17 /**
21 * 计算 MQTT 连接参数 18 * 计算 MQTT 连接参数
22 * 19 *
@@ -26,14 +23,7 @@ public class MqttSignUtils { @@ -26,14 +23,7 @@ public class MqttSignUtils {
26 * @return 包含 clientId, username, password 的结果对象 23 * @return 包含 clientId, username, password 的结果对象
27 */ 24 */
28 public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret) { 25 public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret) {
29 - String clientId = productKey + "." + deviceName;  
30 - String username = deviceName + "&" + productKey;  
31 - // 生成 password  
32 - // TODO @haohao:signContent 和 signContentBuilder 风格保持统一的实现哈  
33 - String signContent = String.format("clientId%sdeviceName%sdeviceSecret%sproductKey%s",  
34 - clientId, deviceName, deviceSecret, productKey);  
35 - String password = sign(signContent, deviceSecret);  
36 - return new MqttSignResult(clientId, username, password); 26 + return calculate(productKey, deviceName, deviceSecret, productKey + "." + deviceName);
37 } 27 }
38 28
39 /** 29 /**
@@ -47,56 +37,31 @@ public class MqttSignUtils { @@ -47,56 +37,31 @@ public class MqttSignUtils {
47 */ 37 */
48 public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret, String clientId) { 38 public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret, String clientId) {
49 String username = deviceName + "&" + productKey; 39 String username = deviceName + "&" + productKey;
50 - String signContentBuilder = "clientId" + clientId +  
51 - "deviceName" + deviceName +  
52 - "deviceSecret" + deviceSecret +  
53 - "productKey" + productKey; 40 + // 构建签名内容
  41 + StringBuilder signContentBuilder = new StringBuilder()
  42 + .append("clientId").append(clientId)
  43 + .append("deviceName").append(deviceName)
  44 + .append("deviceSecret").append(deviceSecret)
  45 + .append("productKey").append(productKey);
54 46
55 - String password = sign(signContentBuilder, deviceSecret); 47 + // 使用 HMac 计算签名
  48 + byte[] key = deviceSecret.getBytes(StandardCharsets.UTF_8);
  49 + String signContent = signContentBuilder.toString();
  50 + HMac mac = new HMac(HmacAlgorithm.HmacSHA256, key);
  51 + String password = mac.digestHex(signContent);
56 52
57 return new MqttSignResult(clientId, username, password); 53 return new MqttSignResult(clientId, username, password);
58 } 54 }
59 55
60 - // TODO @haohao:hutool 貌似有工具类可以用哈。  
61 - private static String sign(String content, String key) {  
62 - try {  
63 - Mac mac = Mac.getInstance(SIGN_METHOD);  
64 - mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), SIGN_METHOD));  
65 - byte[] signData = mac.doFinal(content.getBytes(StandardCharsets.UTF_8));  
66 - return bytesToHex(signData);  
67 - } catch (Exception e) {  
68 - throw new RuntimeException("Failed to sign content with HmacSHA256", e);  
69 - }  
70 - }  
71 -  
72 - private static String bytesToHex(byte[] bytes) {  
73 - StringBuilder hexString = new StringBuilder(bytes.length * 2);  
74 - for (byte b : bytes) {  
75 - String hex = Integer.toHexString(0xFF & b);  
76 - if (hex.length() == 1) {  
77 - hexString.append('0');  
78 - }  
79 - hexString.append(hex);  
80 - }  
81 - return hexString.toString();  
82 - }  
83 -  
84 /** 56 /**
85 * MQTT 签名结果类 57 * MQTT 签名结果类
86 */ 58 */
87 @Getter 59 @Getter
88 - // TODO @haohao:可以用 lombok 哈 60 + @AllArgsConstructor
89 public static class MqttSignResult { 61 public static class MqttSignResult {
90 -  
91 private final String clientId; 62 private final String clientId;
92 private final String username; 63 private final String username;
93 private final String password; 64 private final String password;
94 -  
95 - public MqttSignResult(String clientId, String username, String password) {  
96 - this.clientId = clientId;  
97 - this.username = username;  
98 - this.password = password;  
99 - }  
100 -  
101 } 65 }
  66 +
102 } 67 }
103 \ No newline at end of file 68 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/pom.xml
@@ -160,5 +160,9 @@ @@ -160,5 +160,9 @@
160 <groupId>io.vertx</groupId> 160 <groupId>io.vertx</groupId>
161 <artifactId>vertx-web</artifactId> 161 <artifactId>vertx-web</artifactId>
162 </dependency> 162 </dependency>
  163 + <dependency>
  164 + <groupId>io.vertx</groupId>
  165 + <artifactId>vertx-mqtt</artifactId>
  166 + </dependency>
163 </dependencies> 167 </dependencies>
164 </project> 168 </project>
165 \ No newline at end of file 169 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/config/IotPluginEmqxProperties.java
@@ -17,22 +17,29 @@ public class IotPluginEmqxProperties { @@ -17,22 +17,29 @@ public class IotPluginEmqxProperties {
17 /** 17 /**
18 * 服务主机 18 * 服务主机
19 */ 19 */
20 - private String host;  
21 - 20 + private String mqttHost;
22 /** 21 /**
23 * 服务端口 22 * 服务端口
24 */ 23 */
25 - private int port; 24 + private int mqttPort;
  25 + /**
  26 + * 服务用户名
  27 + */
  28 + private String mqttUsername;
26 29
27 /** 30 /**
  31 + * 服务密码
  32 + */
  33 + private String mqttPassword;
  34 + /**
28 * 是否启用 SSL 35 * 是否启用 SSL
29 */ 36 */
30 - private boolean ssl; 37 + private boolean mqttSsl;
31 38
32 /** 39 /**
33 * 订阅的主题 40 * 订阅的主题
34 */ 41 */
35 - private String topics; 42 + private String mqttTopics;
36 43
37 /** 44 /**
38 * 认证端口 45 * 认证端口
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/downstream/IotDeviceDownstreamHandlerImpl.java
@@ -7,8 +7,6 @@ import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamH @@ -7,8 +7,6 @@ import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamH
7 /** 7 /**
8 * EMQX 插件的 {@link IotDeviceDownstreamHandler} 实现类 8 * EMQX 插件的 {@link IotDeviceDownstreamHandler} 实现类
9 * <p> 9 * <p>
10 - * 但是:由于设备通过 HTTP 短链接接入,导致其实无法下行指导给 device 设备,所以基本都是直接返回失败!!!  
11 - * 类似 MQTT、WebSocket、TCP 插件,是可以实现下行指令的。  
12 * 10 *
13 * @author 芋道源码 11 * @author 芋道源码
14 */ 12 */
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/IotDeviceUpstreamServer.java
1 package cn.iocoder.yudao.module.iot.plugin.emqx.upstream; 1 package cn.iocoder.yudao.module.iot.plugin.emqx.upstream;
2 2
  3 +import cn.hutool.core.date.DateUtil;
  4 +import cn.hutool.json.JSONObject;
  5 +import cn.hutool.json.JSONUtil;
3 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 6 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  7 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
4 import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties; 8 import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
5 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer; 9 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
  10 +import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
6 import cn.iocoder.yudao.module.iot.plugin.emqx.config.IotPluginEmqxProperties; 11 import cn.iocoder.yudao.module.iot.plugin.emqx.config.IotPluginEmqxProperties;
7 import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceAuthVertxHandler; 12 import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceAuthVertxHandler;
8 import io.vertx.core.Vertx; 13 import io.vertx.core.Vertx;
9 import io.vertx.core.http.HttpServer; 14 import io.vertx.core.http.HttpServer;
10 import io.vertx.ext.web.Router; 15 import io.vertx.ext.web.Router;
11 import io.vertx.ext.web.handler.BodyHandler; 16 import io.vertx.ext.web.handler.BodyHandler;
  17 +import io.vertx.mqtt.MqttClient;
  18 +import io.vertx.mqtt.MqttClientOptions;
12 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
13 20
  21 +import java.time.LocalDateTime;
  22 +import java.util.UUID;
  23 +
14 /** 24 /**
15 * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器 25 * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
16 * <p> 26 * <p>
17 - * 协议:HTTP 27 + * 协议:HTTP、MQTT
18 * 28 *
19 * @author haohao 29 * @author haohao
20 */ 30 */
@@ -23,13 +33,16 @@ public class IotDeviceUpstreamServer { @@ -23,13 +33,16 @@ public class IotDeviceUpstreamServer {
23 33
24 private final Vertx vertx; 34 private final Vertx vertx;
25 private final HttpServer server; 35 private final HttpServer server;
  36 + private final MqttClient client;
26 private final IotPluginEmqxProperties emqxProperties; 37 private final IotPluginEmqxProperties emqxProperties;
  38 + private final IotDeviceUpstreamApi deviceUpstreamApi;
27 39
28 public IotDeviceUpstreamServer(IotPluginCommonProperties commonProperties, 40 public IotDeviceUpstreamServer(IotPluginCommonProperties commonProperties,
29 IotPluginEmqxProperties emqxProperties, 41 IotPluginEmqxProperties emqxProperties,
30 IotDeviceUpstreamApi deviceUpstreamApi, 42 IotDeviceUpstreamApi deviceUpstreamApi,
31 IotDeviceDownstreamServer deviceDownstreamServer) { 43 IotDeviceDownstreamServer deviceDownstreamServer) {
32 this.emqxProperties = emqxProperties; 44 this.emqxProperties = emqxProperties;
  45 + this.deviceUpstreamApi = deviceUpstreamApi;
33 // 创建 Vertx 实例 46 // 创建 Vertx 实例
34 this.vertx = Vertx.vertx(); 47 this.vertx = Vertx.vertx();
35 // 创建 Router 实例 48 // 创建 Router 实例
@@ -39,18 +52,104 @@ public class IotDeviceUpstreamServer { @@ -39,18 +52,104 @@ public class IotDeviceUpstreamServer {
39 .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi)); 52 .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi));
40 // 创建 HttpServer 实例 53 // 创建 HttpServer 实例
41 this.server = vertx.createHttpServer().requestHandler(router); 54 this.server = vertx.createHttpServer().requestHandler(router);
  55 +
  56 + // 创建 MQTT 客户端
  57 + MqttClientOptions options = new MqttClientOptions()
  58 + .setClientId("yudao-iot-server-" + UUID.randomUUID())
  59 + .setUsername(emqxProperties.getMqttUsername())
  60 + .setPassword(emqxProperties.getMqttPassword())
  61 + .setSsl(emqxProperties.isMqttSsl());
  62 + client = MqttClient.create(vertx, options);
42 } 63 }
43 64
44 /** 65 /**
45 - * 启动 HTTP 服务器 66 + * 启动 HTTP 服务器、MQTT 客户端
46 */ 67 */
47 public void start() { 68 public void start() {
  69 + // 1. 启动 HTTP 服务器
48 log.info("[start][开始启动]"); 70 log.info("[start][开始启动]");
49 server.listen(emqxProperties.getAuthPort()) 71 server.listen(emqxProperties.getAuthPort())
50 .toCompletionStage() 72 .toCompletionStage()
51 .toCompletableFuture() 73 .toCompletableFuture()
52 .join(); 74 .join();
53 - log.info("[start][启动完成,端口({})]", this.server.actualPort()); 75 + log.info("[start][HTTP服务器启动完成,端口({})]", this.server.actualPort());
  76 +
  77 + // 2. 连接 MQTT Broker
  78 + connectMqtt();
  79 +
  80 + // 3. 添加 MQTT 断开重连监听器
  81 + client.closeHandler(v -> {
  82 + log.warn("[closeHandler][MQTT 连接已断开,准备重连]");
  83 + // 等待 5 秒后重连,避免频繁重连
  84 + vertx.setTimer(5000, id -> {
  85 + log.info("[closeHandler][开始重新连接 MQTT]");
  86 + connectMqtt();
  87 + });
  88 + });
  89 +
  90 + // 4. 设置 MQTT 消息处理器
  91 + client.publishHandler(message -> {
  92 + String topic = message.topicName();
  93 + String payload = message.payload().toString();
  94 + log.info("[messageHandler][接收到消息][topic: {}][payload: {}]", topic, payload);
  95 +
  96 + try {
  97 + // 4.1 处理设备属性上报消息: /{productKey}/{deviceName}/event/property/post
  98 + if (topic.contains("/event/property/post")) {
  99 + // 4.2 解析消息内容
  100 + JSONObject jsonObject = JSONUtil.parseObj(payload);
  101 + String requestId = jsonObject.getStr("id");
  102 + Long timestamp = jsonObject.getLong("timestamp");
  103 +
  104 + // 4.3 从 topic 中解析设备标识
  105 + String[] topicParts = topic.split("/");
  106 + String productKey = topicParts[1];
  107 + String deviceName = topicParts[2];
  108 +
  109 + // 4.4 构建设备属性上报请求对象
  110 + IotDevicePropertyReportReqDTO devicePropertyReportReqDTO = ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO()
  111 + .setRequestId(requestId)
  112 + .setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now())
  113 + .setProductKey(productKey).setDeviceName(deviceName))
  114 + .setProperties(jsonObject.getJSONObject("params"));
  115 +
  116 + // 4.5 调用上游 API 处理设备上报数据
  117 + deviceUpstreamApi.reportDeviceProperty(devicePropertyReportReqDTO);
  118 + log.info("[messageHandler][处理设备上行消息成功][topic: {}][devicePropertyReportReqDTO: {}]",
  119 + topic, JSONUtil.toJsonStr(devicePropertyReportReqDTO));
  120 + }
  121 + } catch (Exception e) {
  122 + log.error("[messageHandler][处理消息失败][topic: {}][payload: {}]", topic, payload, e);
  123 + }
  124 + });
  125 + }
  126 +
  127 + /**
  128 + * 连接 MQTT Broker 并订阅主题
  129 + */
  130 + private void connectMqtt() {
  131 + // 连接 MQTT Broker
  132 + client.connect(emqxProperties.getMqttPort(), emqxProperties.getMqttHost())
  133 + .onSuccess(connAck -> {
  134 + log.info("[connectMqtt][MQTT客户端连接成功]");
  135 + // 连接成功后订阅主题
  136 + String mqttTopics = emqxProperties.getMqttTopics();
  137 + String[] topics = mqttTopics.split(",");
  138 + for (String topic : topics) {
  139 + client.subscribe(topic, 1)
  140 + .onSuccess(v -> log.info("[connectMqtt][成功订阅主题: {}]", topic))
  141 + .onFailure(err -> log.error("[connectMqtt][订阅主题失败: {}]", topic, err));
  142 + }
  143 + log.info("[connectMqtt][开始订阅设备上行消息主题]");
  144 + })
  145 + .onFailure(err -> {
  146 + log.error("[connectMqtt][连接 MQTT Broker 失败]", err);
  147 + // 连接失败后,等待 5 秒重试
  148 + vertx.setTimer(5000, id -> {
  149 + log.info("[connectMqtt][准备重新连接 MQTT]");
  150 + connectMqtt();
  151 + });
  152 + });
54 } 153 }
55 154
56 /** 155 /**
@@ -67,6 +166,14 @@ public class IotDeviceUpstreamServer { @@ -67,6 +166,14 @@ public class IotDeviceUpstreamServer {
67 .join(); 166 .join();
68 } 167 }
69 168
  169 + // 关闭 MQTT 客户端
  170 + if (client != null) {
  171 + client.disconnect()
  172 + .toCompletionStage()
  173 + .toCompletableFuture()
  174 + .join();
  175 + }
  176 +
70 // 关闭 Vertx 实例 177 // 关闭 Vertx 实例
71 if (vertx != null) { 178 if (vertx != null) {
72 vertx.close() 179 vertx.close()
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/resources/application.yml
@@ -10,8 +10,10 @@ yudao: @@ -10,8 +10,10 @@ yudao:
10 downstream-port: 8100 10 downstream-port: 8100
11 plugin-key: yudao-module-iot-plugin-emqx 11 plugin-key: yudao-module-iot-plugin-emqx
12 emqx: 12 emqx:
13 - host: 127.0.0.1  
14 - port: 1883  
15 - ssl: false  
16 - topics: "/sys/#" 13 + mqtt-host: 127.0.0.1
  14 + mqtt-port: 1883
  15 + mqtt-ssl: false
  16 + mqtt-username: yudao
  17 + mqtt-password: yudao
  18 + mqtt-topics: "/+/#"
17 auth-port: 8101 19 auth-port: 8101