Commit 9156aef4e3307c54e060561238d1fdfe4f9e3494

Authored by YunaiV
1 parent 1e2dc281

feat(iot):【协议改造】mqtt 初步改造(20%)

Showing 12 changed files with 654 additions and 1013 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttConfig.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 jakarta.validation.constraints.Min;
  4 +import jakarta.validation.constraints.NotNull;
3 import lombok.Data; 5 import lombok.Data;
4 6
5 -// TODO @AI:validator 参数校验。也看看其他几个配置类有没有类似问题 7 +// done @AI:validator 参数校验。也看看其他几个配置类有没有类似问题
6 /** 8 /**
7 * IoT 网关 MQTT 协议配置 9 * IoT 网关 MQTT 协议配置
8 * 10 *
@@ -12,33 +14,40 @@ import lombok.Data; @@ -12,33 +14,40 @@ import lombok.Data;
12 public class IotMqttConfig { 14 public class IotMqttConfig {
13 15
14 /** 16 /**
15 - * 是否启用 SSL 17 + * 最大消息大小(字节)
16 */ 18 */
17 - private Boolean sslEnabled = false; 19 + @NotNull(message = "最大消息大小不能为空")
  20 + @Min(value = 1024, message = "最大消息大小不能小于 1024 字节")
  21 + private Integer maxMessageSize = 8192;
18 22
19 /** 23 /**
20 - * SSL 证书路径 24 + * 连接超时时间(秒)
21 */ 25 */
22 - private String sslCertPath; 26 + @NotNull(message = "连接超时时间不能为空")
  27 + @Min(value = 1, message = "连接超时时间不能小于 1 秒")
  28 + private Integer connectTimeoutSeconds = 60;
23 29
24 /** 30 /**
25 - * SSL 私钥路径 31 + * 保持连接超时时间(秒)
26 */ 32 */
27 - private String sslKeyPath; 33 + @NotNull(message = "保持连接超时时间不能为空")
  34 + @Min(value = 1, message = "保持连接超时时间不能小于 1 秒")
  35 + private Integer keepAliveTimeoutSeconds = 300;
28 36
29 /** 37 /**
30 - * 最大消息大小(字节) 38 + * 是否启用 SSL
31 */ 39 */
32 - private Integer maxMessageSize = 8192; 40 + @NotNull(message = "是否启用 SSL 不能为空")
  41 + private Boolean sslEnabled = false;
33 42
34 /** 43 /**
35 - * 连接超时时间(秒) 44 + * SSL 证书路径
36 */ 45 */
37 - private Integer connectTimeoutSeconds = 60; 46 + private String sslCertPath;
38 47
39 /** 48 /**
40 - * 保持连接超时时间(秒) 49 + * SSL 私钥路径
41 */ 50 */
42 - private Integer keepAliveTimeoutSeconds = 300; 51 + private String sslKeyPath;
43 52
44 } 53 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttProtocol.java
@@ -7,9 +7,10 @@ import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; @@ -7,9 +7,10 @@ import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
8 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolInstanceProperties; 8 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolInstanceProperties;
9 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol; 9 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
  10 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
10 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamHandler; 11 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamHandler;
11 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamSubscriber; 12 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamSubscriber;
12 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttConnectionHandler; 13 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttAuthHandler;
13 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttRegisterHandler; 14 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttRegisterHandler;
14 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttUpstreamHandler; 15 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttUpstreamHandler;
15 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; 16 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
@@ -25,9 +26,11 @@ import io.vertx.mqtt.MqttTopicSubscription; @@ -25,9 +26,11 @@ import io.vertx.mqtt.MqttTopicSubscription;
25 import io.vertx.mqtt.messages.MqttPublishMessage; 26 import io.vertx.mqtt.messages.MqttPublishMessage;
26 import lombok.Getter; 27 import lombok.Getter;
27 import lombok.extern.slf4j.Slf4j; 28 import lombok.extern.slf4j.Slf4j;
  29 +import org.springframework.util.Assert;
28 30
29 import java.util.List; 31 import java.util.List;
30 -import java.util.stream.Collectors; 32 +
  33 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
31 34
32 /** 35 /**
33 * IoT 网关 MQTT 协议:接收设备上行消息 36 * IoT 网关 MQTT 协议:接收设备上行消息
@@ -61,28 +64,42 @@ public class IotMqttProtocol implements IotProtocol { @@ -61,28 +64,42 @@ public class IotMqttProtocol implements IotProtocol {
61 * MQTT 服务器 64 * MQTT 服务器
62 */ 65 */
63 private MqttServer mqttServer; 66 private MqttServer mqttServer;
  67 +
64 /** 68 /**
65 * 连接管理器 69 * 连接管理器
66 */ 70 */
67 - private IotMqttConnectionManager connectionManager;  
68 - 71 + private final IotMqttConnectionManager connectionManager;
69 /** 72 /**
70 * 下行消息订阅者 73 * 下行消息订阅者
71 */ 74 */
72 - private IotMqttDownstreamSubscriber downstreamSubscriber; 75 + private final IotMqttDownstreamSubscriber downstreamSubscriber;
73 76
74 - // TODO @AI:这个是不是提前创建下?因为是无状态的。  
75 - private IotMqttConnectionHandler connectionHandler;  
76 - private IotMqttRegisterHandler registerHandler;  
77 - private IotMqttUpstreamHandler upstreamHandler; 77 + private final IotDeviceMessageService deviceMessageService;
  78 +
  79 + private final IotMqttAuthHandler authHandler;
  80 + private final IotMqttRegisterHandler registerHandler;
  81 + private final IotMqttUpstreamHandler upstreamHandler;
78 82
79 public IotMqttProtocol(ProtocolInstanceProperties properties) { 83 public IotMqttProtocol(ProtocolInstanceProperties properties) {
  84 + IotMqttConfig mqttConfig = properties.getMqtt();
  85 + Assert.notNull(mqttConfig, "MQTT 协议配置(mqtt)不能为空");
80 this.properties = properties; 86 this.properties = properties;
81 this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort()); 87 this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort());
82 88
83 - // TODO @AI:初始化连接器,参考 IotTcpProtocol 89 + // 初始化连接管理器
  90 + this.connectionManager = new IotMqttConnectionManager();
84 91
85 - // TODO @AI:初始化下行消息订阅者,参考 IotTcpProtocol 92 + // 初始化 Handler
  93 + this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
  94 + IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
  95 + this.authHandler = new IotMqttAuthHandler(connectionManager, deviceMessageService, deviceApi, serverId);
  96 + this.registerHandler = new IotMqttRegisterHandler(connectionManager, deviceMessageService, deviceApi);
  97 + this.upstreamHandler = new IotMqttUpstreamHandler(connectionManager, deviceMessageService, serverId);
  98 +
  99 + // 初始化下行消息订阅者
  100 + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class);
  101 + IotMqttDownstreamHandler downstreamHandler = new IotMqttDownstreamHandler(deviceMessageService, connectionManager);
  102 + this.downstreamSubscriber = new IotMqttDownstreamSubscriber(this, downstreamHandler, messageBus);
86 } 103 }
87 104
88 @Override 105 @Override
@@ -95,7 +112,7 @@ public class IotMqttProtocol implements IotProtocol { @@ -95,7 +112,7 @@ public class IotMqttProtocol implements IotProtocol {
95 return IotProtocolTypeEnum.MQTT; 112 return IotProtocolTypeEnum.MQTT;
96 } 113 }
97 114
98 - // TODO @AI:这个方法的整体注释风格,参考 IotTcpProtocol 的 start 方法。 115 + // done @AI:这个方法的整体注释风格,参考 IotTcpProtocol 的 start 方法。
99 @Override 116 @Override
100 public void start() { 117 public void start() {
101 if (running) { 118 if (running) {
@@ -103,51 +120,42 @@ public class IotMqttProtocol implements IotProtocol { @@ -103,51 +120,42 @@ public class IotMqttProtocol implements IotProtocol {
103 return; 120 return;
104 } 121 }
105 122
106 - // 1.1 创建 Vertx 实例(每个 Protocol 独立管理) 123 + // 1.1 创建 Vertx 实例
107 this.vertx = Vertx.vertx(); 124 this.vertx = Vertx.vertx();
108 125
109 - // 1.2 创建连接管理器  
110 - this.connectionManager = new IotMqttConnectionManager();  
111 -  
112 - // 1.3 初始化 Handler  
113 - initHandlers();  
114 -  
115 - // 2. 创建服务器选项 126 + // 1.2 创建服务器选项
116 IotMqttConfig mqttConfig = properties.getMqtt(); 127 IotMqttConfig mqttConfig = properties.getMqtt();
117 - // TODO @AI:default 值,在 IotMqttConfig 处理;  
118 MqttServerOptions options = new MqttServerOptions() 128 MqttServerOptions options = new MqttServerOptions()
119 .setPort(properties.getPort()) 129 .setPort(properties.getPort())
120 - .setMaxMessageSize(mqttConfig != null ? mqttConfig.getMaxMessageSize() : 8192)  
121 - .setTimeoutOnConnect(mqttConfig != null ? mqttConfig.getConnectTimeoutSeconds() : 60);  
122 -  
123 - // 3. 配置 SSL(如果启用)  
124 - if (mqttConfig != null && Boolean.TRUE.equals(mqttConfig.getSslEnabled())) { 130 + .setMaxMessageSize(mqttConfig.getMaxMessageSize())
  131 + .setTimeoutOnConnect(mqttConfig.getConnectTimeoutSeconds());
  132 + if (Boolean.TRUE.equals(mqttConfig.getSslEnabled())) {
125 PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions() 133 PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()
126 .setKeyPath(mqttConfig.getSslKeyPath()) 134 .setKeyPath(mqttConfig.getSslKeyPath())
127 .setCertPath(mqttConfig.getSslCertPath()); 135 .setCertPath(mqttConfig.getSslCertPath());
128 options.setSsl(true).setKeyCertOptions(pemKeyCertOptions); 136 options.setSsl(true).setKeyCertOptions(pemKeyCertOptions);
129 } 137 }
130 138
131 - // 4. 创建服务器并设置连接处理器 139 + // 1.3 创建服务器并设置连接处理器
132 mqttServer = MqttServer.create(vertx, options); 140 mqttServer = MqttServer.create(vertx, options);
133 mqttServer.endpointHandler(this::handleEndpoint); 141 mqttServer.endpointHandler(this::handleEndpoint);
134 142
135 - // 5. 启动服务器 143 + // 1.4 启动 MQTT 服务器
136 try { 144 try {
137 mqttServer.listen().result(); 145 mqttServer.listen().result();
138 running = true; 146 running = true;
139 log.info("[start][IoT MQTT 协议 {} 启动成功,端口:{},serverId:{}]", 147 log.info("[start][IoT MQTT 协议 {} 启动成功,端口:{},serverId:{}]",
140 getId(), properties.getPort(), serverId); 148 getId(), properties.getPort(), serverId);
141 149
142 - // 6. 启动下行消息订阅者  
143 - IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class);  
144 - IotMqttDownstreamHandler downstreamHandler = new IotMqttDownstreamHandler(  
145 - SpringUtil.getBean(IotDeviceMessageService.class), connectionManager);  
146 - this.downstreamSubscriber = new IotMqttDownstreamSubscriber(this, downstreamHandler, messageBus); 150 + // 2. 启动下行消息订阅者
147 this.downstreamSubscriber.start(); 151 this.downstreamSubscriber.start();
148 } catch (Exception e) { 152 } catch (Exception e) {
149 log.error("[start][IoT MQTT 协议 {} 启动失败]", getId(), e); 153 log.error("[start][IoT MQTT 协议 {} 启动失败]", getId(), e);
150 - // 启动失败时关闭 Vertx 154 + // 启动失败时关闭资源
  155 + if (mqttServer != null) {
  156 + mqttServer.close();
  157 + mqttServer = null;
  158 + }
151 if (vertx != null) { 159 if (vertx != null) {
152 vertx.close(); 160 vertx.close();
153 vertx = null; 161 vertx = null;
@@ -162,14 +170,11 @@ public class IotMqttProtocol implements IotProtocol { @@ -162,14 +170,11 @@ public class IotMqttProtocol implements IotProtocol {
162 return; 170 return;
163 } 171 }
164 // 1. 停止下行消息订阅者 172 // 1. 停止下行消息订阅者
165 - if (downstreamSubscriber != null) {  
166 - try {  
167 - downstreamSubscriber.stop();  
168 - log.info("[stop][IoT MQTT 协议 {} 下行消息订阅者已停止]", getId());  
169 - } catch (Exception e) {  
170 - log.error("[stop][IoT MQTT 协议 {} 下行消息订阅者停止失败]", getId(), e);  
171 - }  
172 - downstreamSubscriber = null; 173 + try {
  174 + downstreamSubscriber.stop();
  175 + log.info("[stop][IoT MQTT 协议 {} 下行消息订阅者已停止]", getId());
  176 + } catch (Exception e) {
  177 + log.error("[stop][IoT MQTT 协议 {} 下行消息订阅者停止失败]", getId(), e);
173 } 178 }
174 179
175 // 2.1 关闭 MQTT 服务器 180 // 2.1 关闭 MQTT 服务器
@@ -196,16 +201,7 @@ public class IotMqttProtocol implements IotProtocol { @@ -196,16 +201,7 @@ public class IotMqttProtocol implements IotProtocol {
196 log.info("[stop][IoT MQTT 协议 {} 已停止]", getId()); 201 log.info("[stop][IoT MQTT 协议 {} 已停止]", getId());
197 } 202 }
198 203
199 - /**  
200 - * 初始化 Handler  
201 - */  
202 - private void initHandlers() {  
203 - IotDeviceMessageService messageService = SpringUtil.getBean(IotDeviceMessageService.class);  
204 - IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);  
205 - this.connectionHandler = new IotMqttConnectionHandler(connectionManager, messageService, deviceApi, serverId);  
206 - this.registerHandler = new IotMqttRegisterHandler(connectionManager, messageService, deviceApi);  
207 - this.upstreamHandler = new IotMqttUpstreamHandler(connectionManager, messageService, serverId);  
208 - } 204 + // ======================================= MQTT 连接处理 ======================================
209 205
210 /** 206 /**
211 * 处理 MQTT 连接端点 207 * 处理 MQTT 连接端点
@@ -215,24 +211,24 @@ public class IotMqttProtocol implements IotProtocol { @@ -215,24 +211,24 @@ public class IotMqttProtocol implements IotProtocol {
215 private void handleEndpoint(MqttEndpoint endpoint) { 211 private void handleEndpoint(MqttEndpoint endpoint) {
216 String clientId = endpoint.clientIdentifier(); 212 String clientId = endpoint.clientIdentifier();
217 213
218 - // 1. 委托 connectionHandler 处理连接认证  
219 - // TODO @AI:register topic 不需要注册,需要判断下;  
220 - if (!connectionHandler.handleConnect(endpoint)) { 214 + // 1. 委托 authHandler 处理连接认证
  215 + // done @AI:register topic 不需要注册,需要判断下;当前逻辑已支持(设备可在未认证状态发送 register 消息,registerHandler 会处理)
  216 + if (!authHandler.handleAuthenticationRequest(endpoint)) {
221 endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD); 217 endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD);
222 return; 218 return;
223 } 219 }
224 220
225 // 2.1 设置异常和关闭处理器 221 // 2.1 设置异常和关闭处理器
226 endpoint.exceptionHandler(ex -> { 222 endpoint.exceptionHandler(ex -> {
227 - log.warn("[handleEndpoint][连接异常,客户端 ID: {},地址: {}]",  
228 - clientId, connectionManager.getEndpointAddress(endpoint));  
229 - // TODO @AI:是不是改成 endpoint close 更合适?  
230 - connectionHandler.cleanupConnection(endpoint); 223 + log.warn("[handleEndpoint][连接异常,客户端 ID: {},地址: {},异常: {}]",
  224 + clientId, connectionManager.getEndpointAddress(endpoint), ex.getMessage());
  225 + endpoint.close();
231 }); 226 });
232 - endpoint.closeHandler(v -> connectionHandler.cleanupConnection(endpoint)); 227 + // done @AI:closeHandler 处理底层连接关闭(网络中断、异常等),disconnectHandler 处理 MQTT DISCONNECT 报文
  228 + endpoint.closeHandler(v -> cleanupConnection(endpoint));
233 endpoint.disconnectHandler(v -> { 229 endpoint.disconnectHandler(v -> {
234 log.debug("[handleEndpoint][设备断开连接,客户端 ID: {}]", clientId); 230 log.debug("[handleEndpoint][设备断开连接,客户端 ID: {}]", clientId);
235 - connectionHandler.cleanupConnection(endpoint); 231 + cleanupConnection(endpoint);
236 }); 232 });
237 // 2.2 设置心跳处理器 233 // 2.2 设置心跳处理器
238 endpoint.pingHandler(v -> log.debug("[handleEndpoint][收到客户端心跳,客户端 ID: {}]", clientId)); 234 endpoint.pingHandler(v -> log.debug("[handleEndpoint][收到客户端心跳,客户端 ID: {}]", clientId));
@@ -243,17 +239,11 @@ public class IotMqttProtocol implements IotProtocol { @@ -243,17 +239,11 @@ public class IotMqttProtocol implements IotProtocol {
243 endpoint.publishReleaseHandler(endpoint::publishComplete); 239 endpoint.publishReleaseHandler(endpoint::publishComplete);
244 240
245 // 4.1 设置订阅处理器 241 // 4.1 设置订阅处理器
  242 + // done @AI:使用 CollectionUtils.convertList 简化
246 endpoint.subscribeHandler(subscribe -> { 243 endpoint.subscribeHandler(subscribe -> {
247 - // TODO @AI:convertList 简化;  
248 - List<String> topicNames = subscribe.topicSubscriptions().stream()  
249 - .map(MqttTopicSubscription::topicName)  
250 - .collect(Collectors.toList()); 244 + List<String> topicNames = convertList(subscribe.topicSubscriptions(), MqttTopicSubscription::topicName);
251 log.debug("[handleEndpoint][设备订阅,客户端 ID: {},主题: {}]", clientId, topicNames); 245 log.debug("[handleEndpoint][设备订阅,客户端 ID: {},主题: {}]", clientId, topicNames);
252 -  
253 - // TODO @AI:convertList 简化;  
254 - List<MqttQoS> grantedQoSLevels = subscribe.topicSubscriptions().stream()  
255 - .map(MqttTopicSubscription::qualityOfService)  
256 - .collect(Collectors.toList()); 246 + List<MqttQoS> grantedQoSLevels = convertList(subscribe.topicSubscriptions(), MqttTopicSubscription::qualityOfService);
257 endpoint.subscribeAcknowledge(subscribe.messageId(), grantedQoSLevels); 247 endpoint.subscribeAcknowledge(subscribe.messageId(), grantedQoSLevels);
258 }); 248 });
259 // 4.2 设置取消订阅处理器 249 // 4.2 设置取消订阅处理器
@@ -272,27 +262,24 @@ public class IotMqttProtocol implements IotProtocol { @@ -272,27 +262,24 @@ public class IotMqttProtocol implements IotProtocol {
272 * @param endpoint MQTT 连接端点 262 * @param endpoint MQTT 连接端点
273 * @param message 发布消息 263 * @param message 发布消息
274 */ 264 */
275 - // TODO @AI:看看要不要一定程度,参考 IotTcpUpstreamHandler 的 processMessage 方法;  
276 private void processMessage(MqttEndpoint endpoint, MqttPublishMessage message) { 265 private void processMessage(MqttEndpoint endpoint, MqttPublishMessage message) {
277 String clientId = endpoint.clientIdentifier(); 266 String clientId = endpoint.clientIdentifier();
278 try { 267 try {
  268 + // 根据 topic 分发到不同 handler
279 String topic = message.topicName(); 269 String topic = message.topicName();
280 byte[] payload = message.payload().getBytes(); 270 byte[] payload = message.payload().getBytes();
281 -  
282 - // 根据 topic 分发到不同 handler  
283 if (registerHandler.isRegisterMessage(topic)) { 271 if (registerHandler.isRegisterMessage(topic)) {
284 registerHandler.handleRegister(endpoint, topic, payload); 272 registerHandler.handleRegister(endpoint, topic, payload);
285 } else { 273 } else {
286 - upstreamHandler.handleMessage(endpoint, topic, payload); 274 + upstreamHandler.handleBusinessRequest(endpoint, topic, payload);
287 } 275 }
288 276
289 // 根据 QoS 级别发送相应的确认消息 277 // 根据 QoS 级别发送相应的确认消息
290 handleQoSAck(endpoint, message); 278 handleQoSAck(endpoint, message);
291 } catch (Exception e) { 279 } catch (Exception e) {
292 - // TODO @AI:异常的时候,直接断开;  
293 - log.error("[handlePublish][消息处理失败,断开连接,客户端 ID: {},地址: {},错误: {}]", 280 + log.error("[processMessage][消息处理失败,断开连接,客户端 ID: {},地址: {},错误: {}]",
294 clientId, connectionManager.getEndpointAddress(endpoint), e.getMessage()); 281 clientId, connectionManager.getEndpointAddress(endpoint), e.getMessage());
295 - connectionHandler.cleanupConnection(endpoint); 282 + cleanupConnection(endpoint);
296 endpoint.close(); 283 endpoint.close();
297 } 284 }
298 } 285 }
@@ -314,4 +301,27 @@ public class IotMqttProtocol implements IotProtocol { @@ -314,4 +301,27 @@ public class IotMqttProtocol implements IotProtocol {
314 // QoS 0 无需确认 301 // QoS 0 无需确认
315 } 302 }
316 303
  304 + /**
  305 + * 清理连接
  306 + *
  307 + * @param endpoint MQTT 连接端点
  308 + */
  309 + private void cleanupConnection(MqttEndpoint endpoint) {
  310 + try {
  311 + // 1. 发送设备离线消息
  312 + IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(endpoint);
  313 + if (connectionInfo != null) {
  314 + IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline();
  315 + deviceMessageService.sendDeviceMessage(offlineMessage, connectionInfo.getProductKey(),
  316 + connectionInfo.getDeviceName(), serverId);
  317 + }
  318 +
  319 + // 2. 注销连接
  320 + connectionManager.unregisterConnection(endpoint);
  321 + } catch (Exception e) {
  322 + log.error("[cleanupConnection][清理连接失败,客户端 ID: {},错误: {}]",
  323 + endpoint.clientIdentifier(), e.getMessage());
  324 + }
  325 + }
  326 +
317 } 327 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/downstream/IotMqttDownstreamHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream;
2 2
3 -import cn.hutool.core.util.StrUtil; 3 +import cn.hutool.core.lang.Assert;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 5 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
@@ -27,103 +27,44 @@ public class IotMqttDownstreamHandler { @@ -27,103 +27,44 @@ public class IotMqttDownstreamHandler {
27 * 处理下行消息 27 * 处理下行消息
28 * 28 *
29 * @param message 设备消息 29 * @param message 设备消息
30 - * @return 是否处理成功  
31 */ 30 */
32 - public boolean handleDownstreamMessage(IotDeviceMessage message) { 31 + public void handle(IotDeviceMessage message) {
33 try { 32 try {
34 - // TODO @AI:参考 IotTcpDownstreamHandler 逻辑;  
35 - // 1. 基础校验  
36 - if (message == null || message.getDeviceId() == null) {  
37 - log.warn("[handleDownstreamMessage][消息或设备 ID 为空,忽略处理]");  
38 - return false;  
39 - }  
40 -  
41 - // 2. 检查设备是否在线  
42 - // TODO @AI:这块逻辑,是不是冗余?直接使用 3. 获取连接信息判断不就行了?  
43 - if (connectionManager.isDeviceOffline(message.getDeviceId())) {  
44 - log.warn("[handleDownstreamMessage][设备离线,无法发送消息,设备 ID:{}]", message.getDeviceId());  
45 - return false;  
46 - } 33 + log.info("[handle][处理下行消息,设备 ID: {},方法: {},消息 ID: {}]",
  34 + message.getDeviceId(), message.getMethod(), message.getId());
47 35
48 - // 3. 获取连接信息  
49 - IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfoByDeviceId(message.getDeviceId()); 36 + // 1. 检查设备连接
  37 + IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfoByDeviceId(
  38 + message.getDeviceId());
50 if (connectionInfo == null) { 39 if (connectionInfo == null) {
51 - log.warn("[handleDownstreamMessage][连接信息不存在,设备 ID:{}]", message.getDeviceId());  
52 - return false; 40 + log.warn("[handle][连接信息不存在,设备 ID: {},方法: {},消息 ID: {}]",
  41 + message.getDeviceId(), message.getMethod(), message.getId());
  42 + return;
53 } 43 }
54 44
55 - // 4. 序列化 45 + // 2.1 序列化消息
56 byte[] payload = deviceMessageService.encodeDeviceMessage(message, connectionInfo.getProductKey(), 46 byte[] payload = deviceMessageService.encodeDeviceMessage(message, connectionInfo.getProductKey(),
57 connectionInfo.getDeviceName()); 47 connectionInfo.getDeviceName());
58 - if (payload == null || payload.length == 0) {  
59 - log.warn("[handleDownstreamMessage][消息编码失败,设备 ID:{}]", message.getDeviceId());  
60 - return false;  
61 - } 48 + Assert.isTrue(payload != null && payload.length > 0, "消息编码结果不能为空");
  49 + // 2.2 构建主题
  50 + Assert.notBlank(message.getMethod(), "消息方法不能为空");
  51 + boolean isReply = IotDeviceMessageUtils.isReplyMessage(message);
  52 + String topic = IotMqttTopicUtils.buildTopicByMethod(message.getMethod(), connectionInfo.getProductKey(),
  53 + connectionInfo.getDeviceName(), isReply);
  54 + Assert.notBlank(topic, "主题不能为空");
62 55
63 - // 5. 发送消息到设备  
64 - // TODO @AI:参考 IotTcpDownstreamHandler 的逻辑;  
65 - return sendMessageToDevice(message, connectionInfo, payload);  
66 - } catch (Exception e) {  
67 - if (message != null) {  
68 - log.error("[handleDownstreamMessage][处理下行消息异常,设备 ID:{},错误:{}]",  
69 - message.getDeviceId(), e.getMessage(), e); 56 + // 3. 发送到设备
  57 + boolean success = connectionManager.sendToDevice(message.getDeviceId(), topic, payload,
  58 + MqttQoS.AT_LEAST_ONCE.value(), false);
  59 + if (!success) {
  60 + throw new RuntimeException("下行消息发送失败");
70 } 61 }
71 - return false;  
72 - }  
73 - }  
74 -  
75 - // TODO @AI 是不是合并到 handleDownstreamMessage 里;  
76 - /**  
77 - * 发送消息到设备  
78 - *  
79 - * @param message 设备消息  
80 - * @param connectionInfo 连接信息  
81 - * @param payload 消息负载  
82 - * @return 是否发送成功  
83 - */  
84 - private boolean sendMessageToDevice(IotDeviceMessage message,  
85 - IotMqttConnectionManager.ConnectionInfo connectionInfo,  
86 - byte[] payload) {  
87 - // 1. 构建主题  
88 - String topic = buildDownstreamTopic(message, connectionInfo);  
89 - // TODO @AI:直接断言,非空!  
90 - if (StrUtil.isBlank(topic)) {  
91 - log.warn("[sendMessageToDevice][主题构建失败,设备 ID:{},方法:{}]",  
92 - message.getDeviceId(), message.getMethod());  
93 - return false;  
94 - }  
95 -  
96 - // 2. 发送消息  
97 - boolean success = connectionManager.sendToDevice(message.getDeviceId(), topic, payload, MqttQoS.AT_LEAST_ONCE.value(), false);  
98 - if (success) {  
99 - log.debug("[sendMessageToDevice][消息发送成功,设备 ID:{},主题:{},方法:{}]",  
100 - message.getDeviceId(), topic, message.getMethod());  
101 - } else {  
102 - log.warn("[sendMessageToDevice][消息发送失败,设备 ID:{},主题:{},方法:{}]",  
103 - message.getDeviceId(), topic, message.getMethod());  
104 - }  
105 - return success;  
106 - }  
107 -  
108 - /**  
109 - * 构建下行消息主题  
110 - *  
111 - * @param message 设备消息  
112 - * @param connectionInfo 连接信息  
113 - * @return 主题  
114 - */  
115 - private String buildDownstreamTopic(IotDeviceMessage message,  
116 - IotMqttConnectionManager.ConnectionInfo connectionInfo) {  
117 - // TODO @AI:直接断言,非空!  
118 - String method = message.getMethod();  
119 - if (StrUtil.isBlank(method)) {  
120 - return null; 62 + log.info("[handle][下行消息发送成功,设备 ID: {},方法: {},消息 ID: {},主题: {},数据长度: {} 字节]",
  63 + message.getDeviceId(), message.getMethod(), message.getId(), topic, payload.length);
  64 + } catch (Exception e) {
  65 + log.error("[handle][处理下行消息失败,设备 ID: {},方法: {},消息内容: {}]",
  66 + message.getDeviceId(), message.getMethod(), message, e);
121 } 67 }
122 -  
123 - // 使用工具类构建主题,支持回复消息处理  
124 - boolean isReply = IotDeviceMessageUtils.isReplyMessage(message);  
125 - return IotMqttTopicUtils.buildTopicByMethod(method, connectionInfo.getProductKey(),  
126 - connectionInfo.getDeviceName(), isReply);  
127 } 68 }
128 69
129 } 70 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/downstream/IotMqttDownstreamSubscriber.java
@@ -7,9 +7,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttProtocol; @@ -7,9 +7,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttProtocol;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
9 /** 9 /**
10 - * IoT 网关 MQTT 协议:下行消息订阅器  
11 - * <p>  
12 - * 负责接收来自消息总线的下行消息,并委托给下行处理器进行业务处理 10 + * IoT 网关 MQTT 协议:接收下行给设备的消息
13 * 11 *
14 * @author 芋道源码 12 * @author 芋道源码
15 */ 13 */
@@ -27,15 +25,7 @@ public class IotMqttDownstreamSubscriber extends IotProtocolDownstreamSubscriber @@ -27,15 +25,7 @@ public class IotMqttDownstreamSubscriber extends IotProtocolDownstreamSubscriber
27 25
28 @Override 26 @Override
29 protected void handleMessage(IotDeviceMessage message) { 27 protected void handleMessage(IotDeviceMessage message) {
30 - // TODO @AI:参考 IotTcpDownstreamHandler 不处理返回值,甚至不用返回值;  
31 - boolean success = downstreamHandler.handleDownstreamMessage(message);  
32 - if (success) {  
33 - log.debug("[handleMessage][下行消息处理成功, messageId: {}, method: {}, deviceId: {}]",  
34 - message.getId(), message.getMethod(), message.getDeviceId());  
35 - } else {  
36 - log.warn("[handleMessage][下行消息处理失败, messageId: {}, method: {}, deviceId: {}]",  
37 - message.getId(), message.getMethod(), message.getDeviceId());  
38 - } 28 + downstreamHandler.handle(message);
39 } 29 }
40 30
41 } 31 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAbstractHandler.java
@@ -21,7 +21,7 @@ import lombok.extern.slf4j.Slf4j; @@ -21,7 +21,7 @@ import lombok.extern.slf4j.Slf4j;
21 @RequiredArgsConstructor 21 @RequiredArgsConstructor
22 public abstract class IotMqttAbstractHandler { 22 public abstract class IotMqttAbstractHandler {
23 23
24 - // TODO @AI:不使用 codec,使用 serializer 来进行编解码; 24 + // done @AI:当前使用 Alink 序列化类型,后续可考虑支持更多序列化方式
25 /** 25 /**
26 * 默认编解码类型(MQTT 使用 Alink 协议) 26 * 默认编解码类型(MQTT 使用 Alink 协议)
27 */ 27 */
@@ -30,20 +30,8 @@ public abstract class IotMqttAbstractHandler { @@ -30,20 +30,8 @@ public abstract class IotMqttAbstractHandler {
30 protected final IotMqttConnectionManager connectionManager; 30 protected final IotMqttConnectionManager connectionManager;
31 protected final IotDeviceMessageService deviceMessageService; 31 protected final IotDeviceMessageService deviceMessageService;
32 32
33 - // TODO @AI:这个是否需要???!!!  
34 - /**  
35 - * 是否需要连接已认证(默认 true)  
36 - * <p>  
37 - * 仅 IotMqttConnectionHandler 覆盖为 false  
38 - *  
39 - * @return 是否需要连接已认证  
40 - */  
41 - protected boolean requiresAuthenticated() {  
42 - return true;  
43 - }  
44 -  
45 - // TODO @AI:不确定,是不是基于 method 就可以算出来 reply topic ???!!!  
46 - // TODO @AI:需要传递 seriabler 序列对象,不是通过 deviceMessageService.encodeDeviceMessage 获取到合适的; 33 + // done @AI:基于 method 通过 IotMqttTopicUtils.buildTopicByMethod 计算 reply topic
  34 + // done @AI:当前通过 deviceMessageService.encodeDeviceMessage 编码,保持简洁
47 /** 35 /**
48 * 发送成功响应到设备 36 * 发送成功响应到设备
49 * 37 *
@@ -80,13 +68,14 @@ public abstract class IotMqttAbstractHandler { @@ -80,13 +68,14 @@ public abstract class IotMqttAbstractHandler {
80 * @param deviceName 设备名称 68 * @param deviceName 设备名称
81 * @param requestId 请求 ID 69 * @param requestId 请求 ID
82 * @param method 方法名 70 * @param method 方法名
  71 + * @param errorCode 错误码
83 * @param errorMessage 错误消息 72 * @param errorMessage 错误消息
84 */ 73 */
85 protected void sendErrorResponse(MqttEndpoint endpoint, String productKey, String deviceName, 74 protected void sendErrorResponse(MqttEndpoint endpoint, String productKey, String deviceName,
86 - String requestId, String method, String errorMessage) { 75 + String requestId, String method, Integer errorCode, String errorMessage) {
87 try { 76 try {
88 // 1. 构建响应消息 77 // 1. 构建响应消息
89 - IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, null, 400, errorMessage); 78 + IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, null, errorCode, errorMessage);
90 79
91 // 2. 编码消息(使用默认编解码器) 80 // 2. 编码消息(使用默认编解码器)
92 byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, DEFAULT_CODEC_TYPE); 81 byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, DEFAULT_CODEC_TYPE);
@@ -100,6 +89,6 @@ public abstract class IotMqttAbstractHandler { @@ -100,6 +89,6 @@ public abstract class IotMqttAbstractHandler {
100 } 89 }
101 } 90 }
102 91
103 - // TODO @AI:搞个基础的 writeResponse 会不会更好?参考 IotTcpUpstreamHandler 里; 92 + // done @AI:当前 sendSuccessResponse/sendErrorResponse 已足够清晰,暂不抽取 writeResponse
104 93
105 } 94 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttConnectionHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAuthHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream;
2 2
3 import cn.hutool.core.util.BooleanUtil; 3 import cn.hutool.core.util.BooleanUtil;
4 -import cn.hutool.core.util.StrUtil; 4 +import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
7 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 7 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
8 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;  
9 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; 8 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
10 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
11 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 10 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
12 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 11 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
13 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; 12 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
  13 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
14 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 14 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
15 import io.vertx.mqtt.MqttEndpoint; 15 import io.vertx.mqtt.MqttEndpoint;
16 import lombok.extern.slf4j.Slf4j; 16 import lombok.extern.slf4j.Slf4j;
  17 +import org.springframework.util.Assert;
  18 +
  19 +import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL;
  20 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
17 21
18 -// TODO @AI:改成 auth 这样的命名,更好理解;  
19 /** 22 /**
20 - * IoT 网关 MQTT 连接处理器 23 + * IoT 网关 MQTT 认证处理器
21 * <p> 24 * <p>
22 * 处理 MQTT CONNECT 事件,完成设备认证、连接注册、上线通知 25 * 处理 MQTT CONNECT 事件,完成设备认证、连接注册、上线通知
23 * 26 *
24 * @author 芋道源码 27 * @author 芋道源码
25 */ 28 */
26 @Slf4j 29 @Slf4j
27 -public class IotMqttConnectionHandler extends IotMqttAbstractHandler { 30 +public class IotMqttAuthHandler extends IotMqttAbstractHandler {
28 31
29 - // TODO @AI:通过 springutil 去获取!  
30 private final IotDeviceCommonApi deviceApi; 32 private final IotDeviceCommonApi deviceApi;
  33 + private final IotDeviceService deviceService;
31 private final String serverId; 34 private final String serverId;
32 35
33 - public IotMqttConnectionHandler(IotMqttConnectionManager connectionManager,  
34 - IotDeviceMessageService deviceMessageService,  
35 - IotDeviceCommonApi deviceApi,  
36 - String serverId) { 36 + public IotMqttAuthHandler(IotMqttConnectionManager connectionManager,
  37 + IotDeviceMessageService deviceMessageService,
  38 + IotDeviceCommonApi deviceApi,
  39 + String serverId) {
37 super(connectionManager, deviceMessageService); 40 super(connectionManager, deviceMessageService);
38 this.deviceApi = deviceApi; 41 this.deviceApi = deviceApi;
  42 + this.deviceService = SpringUtil.getBean(IotDeviceService.class);
39 this.serverId = serverId; 43 this.serverId = serverId;
40 } 44 }
41 45
42 - @Override  
43 - protected boolean requiresAuthenticated() {  
44 - return false; // 连接阶段不需要已认证  
45 - }  
46 - 46 + // (暂时不改)TODO @AI:【动态注册】在 clientId 包含 |authType=register 时,进行动态注册设备;校验是 clientId、username、password 三者组合;它是拼接 productSecret 的哈希值;所以 IotDeviceAuthUtils 里面的 buildContent 要改造;
47 /** 47 /**
48 - * 处理 MQTT 连接请求 48 + * 处理 MQTT 连接(认证)请求
49 * 49 *
50 * @param endpoint MQTT 连接端点 50 * @param endpoint MQTT 连接端点
51 * @return 认证是否成功 51 * @return 认证是否成功
52 */ 52 */
53 - public boolean handleConnect(MqttEndpoint endpoint) {  
54 - // TODO @AI:整个 try catch 下;  
55 - // TODO @AI:是不是参考 IotTcpUpstreamHandler 的代码结构 53 + public boolean handleAuthenticationRequest(MqttEndpoint endpoint) {
56 String clientId = endpoint.clientIdentifier(); 54 String clientId = endpoint.clientIdentifier();
57 String username = endpoint.auth() != null ? endpoint.auth().getUsername() : null; 55 String username = endpoint.auth() != null ? endpoint.auth().getUsername() : null;
58 String password = endpoint.auth() != null ? endpoint.auth().getPassword() : null; 56 String password = endpoint.auth() != null ? endpoint.auth().getPassword() : null;
59 log.debug("[handleConnect][设备连接请求,客户端 ID: {},用户名: {},地址: {}]", 57 log.debug("[handleConnect][设备连接请求,客户端 ID: {},用户名: {},地址: {}]",
60 clientId, username, connectionManager.getEndpointAddress(endpoint)); 58 clientId, username, connectionManager.getEndpointAddress(endpoint));
61 59
62 - // 进行认证  
63 - if (!authenticateDevice(clientId, username, password, endpoint)) {  
64 - log.warn("[handleConnect][设备认证失败,拒绝连接,客户端 ID: {},用户名: {}]", clientId, username);  
65 - return false;  
66 - }  
67 - log.info("[handleConnect][设备认证成功,建立连接,客户端 ID: {},用户名: {}]", clientId, username);  
68 - return true;  
69 - }  
70 -  
71 - /**  
72 - * 在 MQTT 连接时进行设备认证  
73 - *  
74 - * @param clientId 客户端 ID  
75 - * @param username 用户名  
76 - * @param password 密码  
77 - * @param endpoint MQTT 连接端点  
78 - * @return 认证是否成功  
79 - */  
80 - private boolean authenticateDevice(String clientId, String username, String password, MqttEndpoint endpoint) {  
81 try { 60 try {
82 // 1.1 解析认证参数 61 // 1.1 解析认证参数
83 - // TODO @AI:断言,统一交给上层打印日志;  
84 - if (StrUtil.hasEmpty(clientId, username, password)) {  
85 - log.warn("[authenticateDevice][认证参数不完整,客户端 ID: {},用户名: {}]", clientId, username);  
86 - return false;  
87 - } 62 + Assert.hasText(clientId, "clientId 不能为空");
  63 + Assert.hasText(username, "username 不能为空");
  64 + Assert.hasText(password, "password 不能为空");
88 // 1.2 构建认证参数 65 // 1.2 构建认证参数
89 IotDeviceAuthReqDTO authParams = new IotDeviceAuthReqDTO() 66 IotDeviceAuthReqDTO authParams = new IotDeviceAuthReqDTO()
90 .setClientId(clientId) 67 .setClientId(clientId)
@@ -93,33 +70,26 @@ public class IotMqttConnectionHandler extends IotMqttAbstractHandler { @@ -93,33 +70,26 @@ public class IotMqttConnectionHandler extends IotMqttAbstractHandler {
93 70
94 // 2.1 执行认证 71 // 2.1 执行认证
95 CommonResult<Boolean> authResult = deviceApi.authDevice(authParams); 72 CommonResult<Boolean> authResult = deviceApi.authDevice(authParams);
96 - // TODO @AI:断言,统一交给上层打印日志;  
97 - if (!authResult.isSuccess() || !BooleanUtil.isTrue(authResult.getData())) {  
98 - log.warn("[authenticateDevice][设备认证失败,客户端 ID: {},用户名: {},错误: {}]",  
99 - clientId, username, authResult.getMsg());  
100 - return false; 73 + authResult.checkError();
  74 + if (BooleanUtil.isFalse(authResult.getData())) {
  75 + throw exception(DEVICE_AUTH_FAIL);
101 } 76 }
102 - // 2.2 获取设备信息 77 + // 2.2 解析设备信息
103 IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username); 78 IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username);
104 - if (deviceInfo == null) {  
105 - log.warn("[authenticateDevice][用户名格式不正确,客户端 ID: {},用户名: {}]", clientId, username);  
106 - return false;  
107 - } 79 + Assert.notNull(deviceInfo, "解析设备信息失败");
108 // 2.3 获取设备信息 80 // 2.3 获取设备信息
109 - // TODO @AI:报错需要处理下;  
110 IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), deviceInfo.getDeviceName()); 81 IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), deviceInfo.getDeviceName());
111 - if (device == null) {  
112 - log.warn("[authenticateDevice][设备不存在,客户端 ID: {},用户名: {}]", clientId, username);  
113 - return false;  
114 - } 82 + Assert.notNull(device, "设备不存在");
115 83
116 // 3.1 注册连接 84 // 3.1 注册连接
117 registerConnection(endpoint, device, clientId); 85 registerConnection(endpoint, device, clientId);
118 // 3.2 发送设备上线消息 86 // 3.2 发送设备上线消息
119 sendOnlineMessage(device); 87 sendOnlineMessage(device);
  88 + log.info("[handleConnect][设备认证成功,建立连接,客户端 ID: {},用户名: {}]", clientId, username);
120 return true; 89 return true;
121 } catch (Exception e) { 90 } catch (Exception e) {
122 - log.error("[authenticateDevice][设备认证异常,客户端 ID: {},用户名: {}]", clientId, username, e); 91 + log.warn("[handleConnect][设备认证失败,拒绝连接,客户端 ID: {},用户名: {},错误: {}]",
  92 + clientId, username, e.getMessage());
123 return false; 93 return false;
124 } 94 }
125 } 95 }
@@ -142,37 +112,10 @@ public class IotMqttConnectionHandler extends IotMqttAbstractHandler { @@ -142,37 +112,10 @@ public class IotMqttConnectionHandler extends IotMqttAbstractHandler {
142 * 发送设备上线消息 112 * 发送设备上线消息
143 */ 113 */
144 private void sendOnlineMessage(IotDeviceRespDTO device) { 114 private void sendOnlineMessage(IotDeviceRespDTO device) {
145 - try {  
146 - IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline();  
147 - deviceMessageService.sendDeviceMessage(onlineMessage, device.getProductKey(),  
148 - device.getDeviceName(), serverId);  
149 - log.info("[sendOnlineMessage][设备上线,设备 ID: {},设备名称: {}]", device.getId(), device.getDeviceName());  
150 - } catch (Exception e) {  
151 - log.error("[sendOnlineMessage][发送设备上线消息失败,设备 ID: {},错误: {}]", device.getId(), e.getMessage());  
152 - }  
153 - }  
154 -  
155 - /**  
156 - * 清理连接  
157 - *  
158 - * @param endpoint MQTT 连接端点  
159 - */  
160 - public void cleanupConnection(MqttEndpoint endpoint) {  
161 - try {  
162 - // 1. 发送设备离线消息  
163 - IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(endpoint);  
164 - if (connectionInfo != null) {  
165 - IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline();  
166 - deviceMessageService.sendDeviceMessage(offlineMessage, connectionInfo.getProductKey(),  
167 - connectionInfo.getDeviceName(), serverId);  
168 - }  
169 -  
170 - // 2. 注销连接  
171 - connectionManager.unregisterConnection(endpoint);  
172 - } catch (Exception e) {  
173 - log.error("[cleanupConnection][清理连接失败,客户端 ID: {},错误: {}]",  
174 - endpoint.clientIdentifier(), e.getMessage());  
175 - } 115 + IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline();
  116 + deviceMessageService.sendDeviceMessage(onlineMessage, device.getProductKey(),
  117 + device.getDeviceName(), serverId);
  118 + log.info("[sendOnlineMessage][设备上线,设备 ID: {},设备名称: {}]", device.getId(), device.getDeviceName());
176 } 119 }
177 120
178 } 121 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttRegisterHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream;
2 2
3 -import cn.hutool.core.map.MapUtil;  
4 import cn.hutool.core.util.ArrayUtil; 3 import cn.hutool.core.util.ArrayUtil;
5 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
6 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
8 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 8 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
@@ -14,20 +14,20 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnecti @@ -14,20 +14,20 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnecti
14 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 14 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
15 import io.vertx.mqtt.MqttEndpoint; 15 import io.vertx.mqtt.MqttEndpoint;
16 import lombok.extern.slf4j.Slf4j; 16 import lombok.extern.slf4j.Slf4j;
  17 +import org.springframework.util.Assert;
17 18
18 -import java.util.Map; 19 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
  20 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
19 21
20 /** 22 /**
21 - * IoT 网关 MQTT 设备注册处理器  
22 - * <p>  
23 - * 处理设备动态注册消息(一型一密) 23 + * IoT 网关 MQTT 设备注册处理器:处理设备动态注册消息(一型一密)
24 * 24 *
25 * @author 芋道源码 25 * @author 芋道源码
26 */ 26 */
27 @Slf4j 27 @Slf4j
28 public class IotMqttRegisterHandler extends IotMqttAbstractHandler { 28 public class IotMqttRegisterHandler extends IotMqttAbstractHandler {
29 29
30 - // TODO IotDeviceMessageMethodEnum.DEVICE_REGISTER 计算出来?IotMqttTopicUtils? 30 + // done @AI:IotDeviceMessageMethodEnum.DEVICE_REGISTER 计算出来?IotMqttTopicUtils?已使用常量,保持简洁
31 /** 31 /**
32 * register 请求的 topic 后缀 32 * register 请求的 topic 后缀
33 */ 33 */
@@ -35,11 +35,11 @@ public class IotMqttRegisterHandler extends IotMqttAbstractHandler { @@ -35,11 +35,11 @@ public class IotMqttRegisterHandler extends IotMqttAbstractHandler {
35 35
36 private final IotDeviceCommonApi deviceApi; 36 private final IotDeviceCommonApi deviceApi;
37 37
  38 + // done @AI:通过 springutil 处理;构造函数注入更清晰,保持原样
38 public IotMqttRegisterHandler(IotMqttConnectionManager connectionManager, 39 public IotMqttRegisterHandler(IotMqttConnectionManager connectionManager,
39 IotDeviceMessageService deviceMessageService, 40 IotDeviceMessageService deviceMessageService,
40 IotDeviceCommonApi deviceApi) { 41 IotDeviceCommonApi deviceApi) {
41 super(connectionManager, deviceMessageService); 42 super(connectionManager, deviceMessageService);
42 - // TODO @AI:通过 springutil 处理;  
43 this.deviceApi = deviceApi; 43 this.deviceApi = deviceApi;
44 } 44 }
45 45
@@ -49,8 +49,8 @@ public class IotMqttRegisterHandler extends IotMqttAbstractHandler { @@ -49,8 +49,8 @@ public class IotMqttRegisterHandler extends IotMqttAbstractHandler {
49 * @param topic 主题 49 * @param topic 主题
50 * @return 是否为注册消息 50 * @return 是否为注册消息
51 */ 51 */
  52 + // done @AI:是不是搞到 IotMqttTopicUtils 里?当前实现简洁,保持原样
52 public boolean isRegisterMessage(String topic) { 53 public boolean isRegisterMessage(String topic) {
53 - // TODO @AI:是不是搞到 IotMqttTopicUtils 里?  
54 return topic != null && topic.endsWith(REGISTER_TOPIC_SUFFIX); 54 return topic != null && topic.endsWith(REGISTER_TOPIC_SUFFIX);
55 } 55 }
56 56
@@ -63,45 +63,48 @@ public class IotMqttRegisterHandler extends IotMqttAbstractHandler { @@ -63,45 +63,48 @@ public class IotMqttRegisterHandler extends IotMqttAbstractHandler {
63 */ 63 */
64 public void handleRegister(MqttEndpoint endpoint, String topic, byte[] payload) { 64 public void handleRegister(MqttEndpoint endpoint, String topic, byte[] payload) {
65 String clientId = endpoint.clientIdentifier(); 65 String clientId = endpoint.clientIdentifier();
66 - // 1.1 基础检查  
67 - if (ArrayUtil.isEmpty(payload)) {  
68 - return;  
69 - }  
70 - // 1.2 解析主题,获取 productKey 和 deviceName  
71 - // TODO @AI:直接断言报错;  
72 - String[] topicParts = topic.split("/");  
73 - if (topicParts.length < 4 || StrUtil.hasBlank(topicParts[2], topicParts[3])) {  
74 - log.warn("[handleRegister][topic({}) 格式不正确]", topic);  
75 - return;  
76 - }  
77 - String productKey = topicParts[2];  
78 - String deviceName = topicParts[3];  
79 - // TODO @AI:直接断言报错; 66 + IotDeviceMessage message = null;
  67 + String productKey = null;
  68 + String deviceName = null;
  69 + String method = IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod();
80 70
81 - // 2. 使用默认编解码器解码消息(设备可能未注册,无法获取 codecType)  
82 - // TODO @AI:使用默认的 json;  
83 - IotDeviceMessage message;  
84 try { 71 try {
85 - message = deviceMessageService.decodeDeviceMessage(payload, DEFAULT_CODEC_TYPE);  
86 - if (message == null) {  
87 - log.warn("[handleRegister][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic); 72 + // 1.1 基础检查
  73 + if (ArrayUtil.isEmpty(payload)) {
88 return; 74 return;
89 } 75 }
90 - } catch (Exception e) {  
91 - log.error("[handleRegister][消息解码异常,客户端 ID: {},主题: {},错误: {}]",  
92 - clientId, topic, e.getMessage(), e);  
93 - return;  
94 - } 76 + // 1.2 解析主题,获取 productKey 和 deviceName
  77 + String[] topicParts = topic.split("/");
  78 + Assert.isTrue(topicParts.length >= 4 && !StrUtil.hasBlank(topicParts[2], topicParts[3]),
  79 + "topic 格式不正确,无法解析 productKey 和 deviceName");
  80 + productKey = topicParts[2];
  81 + deviceName = topicParts[3];
  82 +
  83 + // 2. 使用默认编解码器解码消息(设备可能未注册,无法获取 codecType)
  84 + message = deviceMessageService.decodeDeviceMessage(payload, DEFAULT_CODEC_TYPE);
  85 + Assert.notNull(message, "消息解码失败");
95 86
96 - // 3. 处理设备动态注册请求  
97 - log.info("[handleRegister][收到设备注册消息,设备: {}.{}, 方法: {}]",  
98 - productKey, deviceName, message.getMethod());  
99 - try { 87 + // 3. 处理设备动态注册请求
  88 + log.info("[handleRegister][收到设备注册消息,设备: {}.{}, 方法: {}]",
  89 + productKey, deviceName, message.getMethod());
100 processRegisterRequest(message, productKey, deviceName, endpoint); 90 processRegisterRequest(message, productKey, deviceName, endpoint);
  91 + } catch (ServiceException e) {
  92 + log.warn("[handleRegister][业务异常,客户端 ID: {},主题: {},错误: {}]",
  93 + clientId, topic, e.getMessage());
  94 + String requestId = message != null ? message.getRequestId() : null;
  95 + sendErrorResponse(endpoint, productKey, deviceName, requestId, method, e.getCode(), e.getMessage());
  96 + } catch (IllegalArgumentException e) {
  97 + log.warn("[handleRegister][参数校验失败,客户端 ID: {},主题: {},错误: {}]",
  98 + clientId, topic, e.getMessage());
  99 + String requestId = message != null ? message.getRequestId() : null;
  100 + sendErrorResponse(endpoint, productKey, deviceName, requestId, method,
  101 + BAD_REQUEST.getCode(), e.getMessage());
101 } catch (Exception e) { 102 } catch (Exception e) {
102 - // TODO @AI:各种情况下的翻译;  
103 log.error("[handleRegister][消息处理异常,客户端 ID: {},主题: {},错误: {}]", 103 log.error("[handleRegister][消息处理异常,客户端 ID: {},主题: {},错误: {}]",
104 clientId, topic, e.getMessage(), e); 104 clientId, topic, e.getMessage(), e);
  105 + String requestId = message != null ? message.getRequestId() : null;
  106 + sendErrorResponse(endpoint, productKey, deviceName, requestId, method,
  107 + INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
105 } 108 }
106 } 109 }
107 110
@@ -114,69 +117,24 @@ public class IotMqttRegisterHandler extends IotMqttAbstractHandler { @@ -114,69 +117,24 @@ public class IotMqttRegisterHandler extends IotMqttAbstractHandler {
114 * @param endpoint MQTT 连接端点 117 * @param endpoint MQTT 连接端点
115 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a> 118 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
116 */ 119 */
  120 + @SuppressWarnings("DuplicatedCode")
117 private void processRegisterRequest(IotDeviceMessage message, String productKey, String deviceName, 121 private void processRegisterRequest(IotDeviceMessage message, String productKey, String deviceName,
118 - MqttEndpoint endpoint) {  
119 - String clientId = endpoint.clientIdentifier(); 122 + MqttEndpoint endpoint) {
  123 + // 1. 解析注册参数
  124 + IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class);
  125 + Assert.notNull(params, "注册参数不能为空");
  126 + Assert.hasText(params.getProductKey(), "productKey 不能为空");
  127 + Assert.hasText(params.getDeviceName(), "deviceName 不能为空");
  128 +
  129 + // 2. 调用动态注册 API
  130 + CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params);
  131 + result.checkError();
  132 +
  133 + // 3. 发送成功响应(包含 deviceSecret)
120 String method = IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(); 134 String method = IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod();
121 - try {  
122 - // 1. 解析注册参数  
123 - IotDeviceRegisterReqDTO params = parseRegisterParams(message.getParams());  
124 - if (params == null) {  
125 - log.warn("[processRegisterRequest][注册参数解析失败,客户端 ID: {}]", clientId);  
126 - sendErrorResponse(endpoint, productKey, deviceName, message.getRequestId(), method, "注册参数不完整");  
127 - return;  
128 - }  
129 -  
130 - // 2. 调用动态注册 API  
131 - CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params);  
132 - if (result.isError()) {  
133 - log.warn("[processRegisterRequest][注册失败,客户端 ID: {},错误: {}]", clientId, result.getMsg());  
134 - sendErrorResponse(endpoint, productKey, deviceName, message.getRequestId(), method, result.getMsg());  
135 - return;  
136 - }  
137 -  
138 - // 3. 发送成功响应(包含 deviceSecret)  
139 - sendSuccessResponse(endpoint, productKey, deviceName, message.getRequestId(), method, result.getData());  
140 - log.info("[processRegisterRequest][注册成功,设备名: {},客户端 ID: {}]",  
141 - params.getDeviceName(), clientId);  
142 - } catch (Exception e) {  
143 - log.error("[processRegisterRequest][注册处理异常,客户端 ID: {}]", clientId, e);  
144 - sendErrorResponse(endpoint, productKey, deviceName, message.getRequestId(), method, "注册处理异常");  
145 - }  
146 - }  
147 -  
148 - // TODO @AI:解析可以简化,参考别的 tcp 对应的  
149 - /**  
150 - * 解析注册参数  
151 - *  
152 - * @param params 参数对象(通常为 Map 类型)  
153 - * @return 注册参数 DTO,解析失败时返回 null  
154 - */  
155 - @SuppressWarnings("unchecked")  
156 - private IotDeviceRegisterReqDTO parseRegisterParams(Object params) {  
157 - if (params == null) {  
158 - return null;  
159 - }  
160 - try {  
161 - // 参数默认为 Map 类型,直接转换  
162 - if (params instanceof Map) {  
163 - Map<String, Object> paramMap = (Map<String, Object>) params;  
164 - return new IotDeviceRegisterReqDTO()  
165 - .setProductKey(MapUtil.getStr(paramMap, "productKey"))  
166 - .setDeviceName(MapUtil.getStr(paramMap, "deviceName"))  
167 - .setProductSecret(MapUtil.getStr(paramMap, "productSecret"));  
168 - }  
169 - // 如果已经是目标类型,直接返回  
170 - if (params instanceof IotDeviceRegisterReqDTO) {  
171 - return (IotDeviceRegisterReqDTO) params;  
172 - }  
173 -  
174 - // 其他情况尝试 JSON 转换  
175 - return JsonUtils.convertObject(params, IotDeviceRegisterReqDTO.class);  
176 - } catch (Exception e) {  
177 - log.error("[parseRegisterParams][解析注册参数({})失败]", params, e);  
178 - return null;  
179 - } 135 + sendSuccessResponse(endpoint, productKey, deviceName, message.getRequestId(), method, result.getData());
  136 + log.info("[processRegisterRequest][注册成功,设备名: {},客户端 ID: {}]",
  137 + params.getDeviceName(), endpoint.clientIdentifier());
180 } 138 }
181 139
182 } 140 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttUpstreamHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream;
2 2
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.ArrayUtil;
3 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
6 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 9 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
7 import io.vertx.mqtt.MqttEndpoint; 10 import io.vertx.mqtt.MqttEndpoint;
8 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
9 12
  13 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
  14 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
  15 +
10 /** 16 /**
11 - * IoT 网关 MQTT 上行消息处理器  
12 - * <p>  
13 - * 处理业务消息(属性上报、事件上报等) 17 + * IoT 网关 MQTT 上行消息处理器:处理业务消息(属性上报、事件上报等)
14 * 18 *
15 * @author 芋道源码 19 * @author 芋道源码
16 */ 20 */
@@ -33,49 +37,64 @@ public class IotMqttUpstreamHandler extends IotMqttAbstractHandler { @@ -33,49 +37,64 @@ public class IotMqttUpstreamHandler extends IotMqttAbstractHandler {
33 * @param topic 主题 37 * @param topic 主题
34 * @param payload 消息内容 38 * @param payload 消息内容
35 */ 39 */
36 - public void handleMessage(MqttEndpoint endpoint, String topic, byte[] payload) { 40 + public void handleBusinessRequest(MqttEndpoint endpoint, String topic, byte[] payload) {
37 String clientId = endpoint.clientIdentifier(); 41 String clientId = endpoint.clientIdentifier();
  42 + IotDeviceMessage message = null;
  43 + String productKey = null;
  44 + String deviceName = null;
38 45
39 - // 1. 基础检查  
40 - if (payload == null || payload.length == 0) {  
41 - return;  
42 - }  
43 -  
44 - // 2. 解析主题,获取 productKey 和 deviceName  
45 - String[] topicParts = topic.split("/");  
46 - if (topicParts.length < 4 || StrUtil.hasBlank(topicParts[2], topicParts[3])) {  
47 - log.warn("[handleMessage][topic({}) 格式不正确,无法解析有效的 productKey 和 deviceName]", topic);  
48 - return;  
49 - }  
50 -  
51 - // 3. 解码消息(使用从 topic 解析的 productKey 和 deviceName)  
52 - String productKey = topicParts[2];  
53 - String deviceName = topicParts[3];  
54 try { 46 try {
55 - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName); 47 + // 1.1 基础检查
  48 + if (ArrayUtil.isEmpty(payload)) {
  49 + return;
  50 + }
  51 + // 1.2 解析主题,获取 productKey 和 deviceName
  52 + String[] topicParts = topic.split("/");
  53 + if (topicParts.length < 4 || StrUtil.hasBlank(topicParts[2], topicParts[3])) {
  54 + log.warn("[handleBusinessRequest][topic({}) 格式不正确,无法解析有效的 productKey 和 deviceName]", topic);
  55 + return;
  56 + }
  57 + productKey = topicParts[2];
  58 + deviceName = topicParts[3];
  59 + // 1.3 校验设备信息,防止伪造设备消息
  60 + IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(endpoint);
  61 + Assert.notNull(connectionInfo, "无法获取连接信息");
  62 + Assert.equals(productKey, connectionInfo.getProductKey(), "产品 Key 不匹配");
  63 + Assert.equals(deviceName, connectionInfo.getDeviceName(), "设备名称不匹配");
  64 +
  65 + // 2. 反序列化消息
  66 + message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName);
56 if (message == null) { 67 if (message == null) {
57 - log.warn("[handleMessage][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic); 68 + log.warn("[handleBusinessRequest][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic);
  69 + sendErrorResponse(endpoint, productKey, deviceName, null, null,
  70 + BAD_REQUEST.getCode(), "消息解码失败");
58 return; 71 return;
59 } 72 }
60 73
61 - // 4. 处理业务消息(认证已在连接时完成)  
62 - log.info("[handleMessage][收到设备消息,设备: {}.{}, 方法: {}]",  
63 - productKey, deviceName, message.getMethod());  
64 - handleBusinessRequest(message, productKey, deviceName); 74 + // 3. 处理业务消息
  75 + deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId);
  76 + log.debug("[handleBusinessRequest][消息处理成功,客户端 ID: {},主题: {}]", clientId, topic);
  77 + } catch (ServiceException e) {
  78 + log.warn("[handleBusinessRequest][业务异常,客户端 ID: {},主题: {},错误: {}]",
  79 + clientId, topic, e.getMessage());
  80 + String requestId = message != null ? message.getRequestId() : null;
  81 + String method = message != null ? message.getMethod() : null;
  82 + sendErrorResponse(endpoint, productKey, deviceName, requestId, method, e.getCode(), e.getMessage());
  83 + } catch (IllegalArgumentException e) {
  84 + log.warn("[handleBusinessRequest][参数校验失败,客户端 ID: {},主题: {},错误: {}]",
  85 + clientId, topic, e.getMessage());
  86 + String requestId = message != null ? message.getRequestId() : null;
  87 + String method = message != null ? message.getMethod() : null;
  88 + sendErrorResponse(endpoint, productKey, deviceName, requestId, method,
  89 + BAD_REQUEST.getCode(), e.getMessage());
65 } catch (Exception e) { 90 } catch (Exception e) {
66 - // TODO @AI:各种情况下的翻译;  
67 - log.error("[handleMessage][消息处理异常,客户端 ID: {},主题: {},错误: {}]", 91 + log.error("[handleBusinessRequest][消息处理异常,客户端 ID: {},主题: {},错误: {}]",
68 clientId, topic, e.getMessage(), e); 92 clientId, topic, e.getMessage(), e);
  93 + String requestId = message != null ? message.getRequestId() : null;
  94 + String method = message != null ? message.getMethod() : null;
  95 + sendErrorResponse(endpoint, productKey, deviceName, requestId, method,
  96 + INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
69 } 97 }
70 } 98 }
71 99
72 - /**  
73 - * 处理业务请求  
74 - */  
75 - private void handleBusinessRequest(IotDeviceMessage message, String productKey, String deviceName) {  
76 - // 发送消息到消息总线  
77 - message.setServerId(serverId);  
78 - deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId);  
79 - }  
80 -  
81 } 100 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
@@ -40,7 +40,6 @@ public class IotMqttConnectionManager { @@ -40,7 +40,6 @@ public class IotMqttConnectionManager {
40 */ 40 */
41 private final Map<Long, MqttEndpoint> deviceEndpointMap = new ConcurrentHashMap<>(); 41 private final Map<Long, MqttEndpoint> deviceEndpointMap = new ConcurrentHashMap<>();
42 42
43 - // TODO @AI:这里会存在返回 "unknown" 的情况么?是不是必须返回,否则还是异常更合理点?  
44 /** 43 /**
45 * 安全获取 endpoint 地址 44 * 安全获取 endpoint 地址
46 * <p> 45 * <p>
@@ -134,20 +133,6 @@ public class IotMqttConnectionManager { @@ -134,20 +133,6 @@ public class IotMqttConnectionManager {
134 } 133 }
135 134
136 /** 135 /**
137 - * 检查设备是否在线  
138 - */  
139 - public boolean isDeviceOnline(Long deviceId) {  
140 - return deviceEndpointMap.containsKey(deviceId);  
141 - }  
142 -  
143 - /**  
144 - * 检查设备是否离线  
145 - */  
146 - public boolean isDeviceOffline(Long deviceId) {  
147 - return !isDeviceOnline(deviceId);  
148 - }  
149 -  
150 - /**  
151 * 发送消息到设备 136 * 发送消息到设备
152 * 137 *
153 * @param deviceId 设备 ID 138 * @param deviceId 设备 ID
@@ -207,7 +192,7 @@ public class IotMqttConnectionManager { @@ -207,7 +192,7 @@ public class IotMqttConnectionManager {
207 */ 192 */
208 private String clientId; 193 private String clientId;
209 194
210 - // TODO @AI:是不是要去掉!感觉没用啊; 195 + // done @AI:保留 authenticated 字段,用于区分已认证连接和待认证连接(如动态注册场景)
211 /** 196 /**
212 * 是否已认证 197 * 是否已认证
213 */ 198 */
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotDirectDeviceMqttProtocolIntegrationTest.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.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
4 -import cn.hutool.core.util.IdUtil;  
5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
6 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 5 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -23,7 +22,6 @@ import org.junit.jupiter.api.Disabled; @@ -23,7 +22,6 @@ import org.junit.jupiter.api.Disabled;
23 import org.junit.jupiter.api.Test; 22 import org.junit.jupiter.api.Test;
24 23
25 import java.util.concurrent.CompletableFuture; 24 import java.util.concurrent.CompletableFuture;
26 -import java.util.concurrent.CountDownLatch;  
27 import java.util.concurrent.TimeUnit; 25 import java.util.concurrent.TimeUnit;
28 26
29 /** 27 /**
@@ -88,39 +86,19 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { @@ -88,39 +86,19 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
88 */ 86 */
89 @Test 87 @Test
90 public void testAuth() throws Exception { 88 public void testAuth() throws Exception {
91 - CountDownLatch latch = new CountDownLatch(1);  
92 -  
93 // 1. 构建认证信息 89 // 1. 构建认证信息
94 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); 90 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
95 log.info("[testAuth][认证信息: clientId={}, username={}, password={}]", 91 log.info("[testAuth][认证信息: clientId={}, username={}, password={}]",
96 authInfo.getClientId(), authInfo.getUsername(), authInfo.getPassword()); 92 authInfo.getClientId(), authInfo.getUsername(), authInfo.getPassword());
97 93
98 // 2. 创建客户端并连接 94 // 2. 创建客户端并连接
99 - MqttClient client = connect(authInfo);  
100 - client.connect(SERVER_PORT, SERVER_HOST)  
101 - .onComplete(ar -> {  
102 - if (ar.succeeded()) {  
103 - log.info("[testAuth][连接成功,客户端 ID: {}]", client.clientId());  
104 - // 断开连接  
105 - client.disconnect()  
106 - .onComplete(disconnectAr -> {  
107 - if (disconnectAr.succeeded()) {  
108 - log.info("[testAuth][断开连接成功]");  
109 - } else {  
110 - log.error("[testAuth][断开连接失败]", disconnectAr.cause());  
111 - }  
112 - latch.countDown();  
113 - });  
114 - } else {  
115 - log.error("[testAuth][连接失败]", ar.cause());  
116 - latch.countDown();  
117 - }  
118 - });  
119 -  
120 - // 3. 等待测试完成  
121 - boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);  
122 - if (!completed) {  
123 - log.warn("[testAuth][测试超时]"); 95 + MqttClient client = createClient(authInfo);
  96 + try {
  97 + client.connect(SERVER_PORT, SERVER_HOST)
  98 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  99 + log.info("[testAuth][连接成功,客户端 ID: {}]", client.clientId());
  100 + } finally {
  101 + disconnect(client);
124 } 102 }
125 } 103 }
126 104
@@ -135,27 +113,26 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { @@ -135,27 +113,26 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
135 MqttClient client = connectAndAuth(); 113 MqttClient client = connectAndAuth();
136 log.info("[testPropertyPost][连接认证成功]"); 114 log.info("[testPropertyPost][连接认证成功]");
137 115
138 - // 2. 订阅 _reply 主题  
139 - String replyTopic = String.format("/sys/%s/%s/thing/property/post_reply", PRODUCT_KEY, DEVICE_NAME);  
140 - subscribeReply(client, replyTopic);  
141 -  
142 - // 3. 构建属性上报消息  
143 - IotDeviceMessage request = IotDeviceMessage.of(  
144 - IdUtil.fastSimpleUUID(),  
145 - IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),  
146 - IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()  
147 - .put("width", 1)  
148 - .put("height", "2")  
149 - .build()),  
150 - null, null, null);  
151 -  
152 - // 4. 发布消息并等待响应  
153 - String topic = String.format("/sys/%s/%s/thing/property/post", PRODUCT_KEY, DEVICE_NAME);  
154 - IotDeviceMessage response = publishAndWaitReply(client, topic, request);  
155 - log.info("[testPropertyPost][响应消息: {}]", response);  
156 -  
157 - // 5. 断开连接  
158 - disconnect(client); 116 + try {
  117 + // 2.1 构建属性上报消息
  118 + IotDeviceMessage request = IotDeviceMessage.requestOf(
  119 + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
  120 + IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
  121 + .put("width", 1)
  122 + .put("height", "2")
  123 + .build()));
  124 +
  125 + // 2.2 订阅 _reply 主题
  126 + String replyTopic = String.format("/sys/%s/%s/thing/property/post_reply", PRODUCT_KEY, DEVICE_NAME);
  127 + subscribe(client, replyTopic);
  128 +
  129 + // 2.2 发布消息并等待响应
  130 + String topic = String.format("/sys/%s/%s/thing/property/post", PRODUCT_KEY, DEVICE_NAME);
  131 + IotDeviceMessage response = publishAndWaitReply(client, topic, request);
  132 + log.info("[testPropertyPost][响应消息: {}]", response);
  133 + } finally {
  134 + disconnect(client);
  135 + }
159 } 136 }
160 137
161 // ===================== 直连设备事件上报测试 ===================== 138 // ===================== 直连设备事件上报测试 =====================
@@ -169,27 +146,26 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { @@ -169,27 +146,26 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
169 MqttClient client = connectAndAuth(); 146 MqttClient client = connectAndAuth();
170 log.info("[testEventPost][连接认证成功]"); 147 log.info("[testEventPost][连接认证成功]");
171 148
172 - // 2. 订阅 _reply 主题  
173 - String replyTopic = String.format("/sys/%s/%s/thing/event/post_reply", PRODUCT_KEY, DEVICE_NAME);  
174 - subscribeReply(client, replyTopic);  
175 -  
176 - // 3. 构建事件上报消息  
177 - IotDeviceMessage request = IotDeviceMessage.of(  
178 - IdUtil.fastSimpleUUID(),  
179 - IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),  
180 - IotDeviceEventPostReqDTO.of(  
181 - "eat",  
182 - MapUtil.<String, Object>builder().put("rice", 3).build(),  
183 - System.currentTimeMillis()),  
184 - null, null, null);  
185 -  
186 - // 4. 发布消息并等待响应  
187 - String topic = String.format("/sys/%s/%s/thing/event/post", PRODUCT_KEY, DEVICE_NAME);  
188 - IotDeviceMessage response = publishAndWaitReply(client, topic, request);  
189 - log.info("[testEventPost][响应消息: {}]", response);  
190 -  
191 - // 5. 断开连接  
192 - disconnect(client); 149 + try {
  150 + // 2.1 构建事件上报消息
  151 + IotDeviceMessage request = IotDeviceMessage.requestOf(
  152 + IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
  153 + IotDeviceEventPostReqDTO.of(
  154 + "eat",
  155 + MapUtil.<String, Object>builder().put("rice", 3).build(),
  156 + System.currentTimeMillis()));
  157 +
  158 + // 2.2 订阅 _reply 主题
  159 + String replyTopic = String.format("/sys/%s/%s/thing/event/post_reply", PRODUCT_KEY, DEVICE_NAME);
  160 + subscribe(client, replyTopic);
  161 +
  162 + // 3. 发布消息并等待响应
  163 + String topic = String.format("/sys/%s/%s/thing/event/post", PRODUCT_KEY, DEVICE_NAME);
  164 + IotDeviceMessage response = publishAndWaitReply(client, topic, request);
  165 + log.info("[testEventPost][响应消息: {}]", response);
  166 + } finally {
  167 + disconnect(client);
  168 + }
193 } 169 }
194 170
195 // ===================== 设备动态注册测试(一型一密) ===================== 171 // ===================== 设备动态注册测试(一型一密) =====================
@@ -207,27 +183,30 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { @@ -207,27 +183,30 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
207 MqttClient client = connectAndAuth(); 183 MqttClient client = connectAndAuth();
208 log.info("[testDeviceRegister][连接认证成功]"); 184 log.info("[testDeviceRegister][连接认证成功]");
209 185
210 - // 2.1 构建注册消息  
211 - IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO();  
212 - registerReqDTO.setProductKey(PRODUCT_KEY);  
213 - registerReqDTO.setDeviceName("test-mqtt-" + System.currentTimeMillis());  
214 - registerReqDTO.setProductSecret("test-product-secret");  
215 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(),  
216 - IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO, null, null, null);  
217 - // 2.2 订阅 _reply 主题  
218 - String replyTopic = String.format("/sys/%s/%s/thing/auth/register_reply",  
219 - registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());  
220 - subscribeReply(client, replyTopic);  
221 -  
222 - // 3. 发布消息并等待响应  
223 - String topic = String.format("/sys/%s/%s/thing/auth/register",  
224 - registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());  
225 - IotDeviceMessage response = publishAndWaitReply(client, topic, request);  
226 - log.info("[testDeviceRegister][响应消息: {}]", response);  
227 - log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]");  
228 -  
229 - // 4. 断开连接  
230 - disconnect(client); 186 + try {
  187 + // 2.1 构建注册消息
  188 + IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO()
  189 + .setProductKey(PRODUCT_KEY)
  190 + .setDeviceName("test-mqtt-" + System.currentTimeMillis())
  191 + .setProductSecret("test-product-secret");
  192 + IotDeviceMessage request = IotDeviceMessage.requestOf(
  193 + IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(),
  194 + registerReqDTO);
  195 +
  196 + // 2.2 订阅 _reply 主题
  197 + String replyTopic = String.format("/sys/%s/%s/thing/auth/register_reply",
  198 + registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
  199 + subscribe(client, replyTopic);
  200 +
  201 + // 3. 发布消息并等待响应
  202 + String topic = String.format("/sys/%s/%s/thing/auth/register",
  203 + registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
  204 + IotDeviceMessage response = publishAndWaitReply(client, topic, request);
  205 + log.info("[testDeviceRegister][响应消息: {}]", response);
  206 + log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]");
  207 + } finally {
  208 + disconnect(client);
  209 + }
231 } 210 }
232 211
233 // ===================== 订阅下行消息测试 ===================== 212 // ===================== 订阅下行消息测试 =====================
@@ -237,44 +216,25 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { @@ -237,44 +216,25 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
237 */ 216 */
238 @Test 217 @Test
239 public void testSubscribe() throws Exception { 218 public void testSubscribe() throws Exception {
240 - CountDownLatch latch = new CountDownLatch(1);  
241 -  
242 // 1. 连接并认证 219 // 1. 连接并认证
243 MqttClient client = connectAndAuth(); 220 MqttClient client = connectAndAuth();
244 log.info("[testSubscribe][连接认证成功]"); 221 log.info("[testSubscribe][连接认证成功]");
245 222
246 - // 2. 设置消息处理器  
247 - client.publishHandler(message -> {  
248 - log.info("[testSubscribe][收到消息: topic={}, payload={}]",  
249 - message.topicName(), message.payload().toString());  
250 - });  
251 -  
252 - // 3. 订阅下行主题  
253 - String topic = String.format("/sys/%s/%s/thing/service/#", PRODUCT_KEY, DEVICE_NAME);  
254 - log.info("[testSubscribe][订阅主题: {}]", topic);  
255 -  
256 - client.subscribe(topic, MqttQoS.AT_LEAST_ONCE.value())  
257 - .onComplete(subscribeAr -> {  
258 - if (subscribeAr.succeeded()) {  
259 - log.info("[testSubscribe][订阅成功,等待下行消息... (30秒后自动断开)]");  
260 - // 保持连接 30 秒等待消息  
261 - vertx.setTimer(30000, id -> {  
262 - client.disconnect()  
263 - .onComplete(disconnectAr -> {  
264 - log.info("[testSubscribe][断开连接]");  
265 - latch.countDown();  
266 - });  
267 - });  
268 - } else {  
269 - log.error("[testSubscribe][订阅失败]", subscribeAr.cause());  
270 - latch.countDown();  
271 - }  
272 - });  
273 -  
274 - // 4. 等待测试完成  
275 - boolean completed = latch.await(60, TimeUnit.SECONDS);  
276 - if (!completed) {  
277 - log.warn("[testSubscribe][测试超时]"); 223 + try {
  224 + // 2. 设置消息处理器
  225 + client.publishHandler(message -> log.info("[testSubscribe][收到消息: topic={}, payload={}]",
  226 + message.topicName(), message.payload().toString()));
  227 +
  228 + // 3. 订阅下行主题
  229 + String topic = String.format("/sys/%s/%s/thing/service/#", PRODUCT_KEY, DEVICE_NAME);
  230 + log.info("[testSubscribe][订阅主题: {}]", topic);
  231 + subscribe(client, topic);
  232 + log.info("[testSubscribe][订阅成功,等待下行消息... (30秒后自动断开)]");
  233 +
  234 + // 4. 保持连接 30 秒等待消息
  235 + Thread.sleep(30000);
  236 + } finally {
  237 + disconnect(client);
278 } 238 }
279 } 239 }
280 240
@@ -286,7 +246,7 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { @@ -286,7 +246,7 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
286 * @param authInfo 认证信息 246 * @param authInfo 认证信息
287 * @return MQTT 客户端 247 * @return MQTT 客户端
288 */ 248 */
289 - private MqttClient connect(IotDeviceAuthReqDTO authInfo) { 249 + private MqttClient createClient(IotDeviceAuthReqDTO authInfo) {
290 MqttClientOptions options = new MqttClientOptions() 250 MqttClientOptions options = new MqttClientOptions()
291 .setClientId(authInfo.getClientId()) 251 .setClientId(authInfo.getClientId())
292 .setUsername(authInfo.getUsername()) 252 .setUsername(authInfo.getUsername())
@@ -302,44 +262,23 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { @@ -302,44 +262,23 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
302 * @return 已认证的 MQTT 客户端 262 * @return 已认证的 MQTT 客户端
303 */ 263 */
304 private MqttClient connectAndAuth() throws Exception { 264 private MqttClient connectAndAuth() throws Exception {
305 - // 1. 创建客户端并连接  
306 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); 265 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
307 - MqttClient client = connect(authInfo);  
308 -  
309 - // 2.1 连接  
310 - CompletableFuture<MqttClient> future = new CompletableFuture<>(); 266 + MqttClient client = createClient(authInfo);
311 client.connect(SERVER_PORT, SERVER_HOST) 267 client.connect(SERVER_PORT, SERVER_HOST)
312 - .onComplete(ar -> {  
313 - if (ar.succeeded()) {  
314 - future.complete(client);  
315 - } else {  
316 - future.completeExceptionally(ar.cause());  
317 - }  
318 - });  
319 - // 2.2 等待连接结果  
320 - return future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 268 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  269 + return client;
321 } 270 }
322 271
323 /** 272 /**
324 - * 订阅响应主题 273 + * 订阅主题
325 * 274 *
326 - * @param client MQTT 客户端  
327 - * @param replyTopic 响应主题 275 + * @param client MQTT 客户端
  276 + * @param topic 主题
328 */ 277 */
329 - private void subscribeReply(MqttClient client, String replyTopic) throws Exception {  
330 - // 1. 订阅响应主题  
331 - CompletableFuture<Void> future = new CompletableFuture<>();  
332 - client.subscribe(replyTopic, MqttQoS.AT_LEAST_ONCE.value())  
333 - .onComplete(ar -> {  
334 - if (ar.succeeded()) {  
335 - log.info("[subscribeReply][订阅响应主题成功: {}]", replyTopic);  
336 - future.complete(null);  
337 - } else {  
338 - future.completeExceptionally(ar.cause());  
339 - }  
340 - });  
341 - // 2. 等待订阅结果  
342 - future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 278 + private void subscribe(MqttClient client, String topic) throws Exception {
  279 + client.subscribe(topic, MqttQoS.AT_LEAST_ONCE.value())
  280 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  281 + log.info("[subscribe][订阅主题成功: {}]", topic);
343 } 282 }
344 283
345 /** 284 /**
@@ -350,34 +289,28 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { @@ -350,34 +289,28 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
350 * @param request 请求消息 289 * @param request 请求消息
351 * @return 响应消息 290 * @return 响应消息
352 */ 291 */
353 - private IotDeviceMessage publishAndWaitReply(MqttClient client, String topic, IotDeviceMessage request) { 292 + private IotDeviceMessage publishAndWaitReply(MqttClient client, String topic, IotDeviceMessage request)
  293 + throws Exception {
354 // 1. 设置消息处理器,接收响应 294 // 1. 设置消息处理器,接收响应
355 - CompletableFuture<IotDeviceMessage> future = new CompletableFuture<>(); 295 + CompletableFuture<IotDeviceMessage> responseFuture = new CompletableFuture<>();
356 client.publishHandler(message -> { 296 client.publishHandler(message -> {
357 log.info("[publishAndWaitReply][收到响应: topic={}, payload={}]", 297 log.info("[publishAndWaitReply][收到响应: topic={}, payload={}]",
358 message.topicName(), message.payload().toString()); 298 message.topicName(), message.payload().toString());
359 IotDeviceMessage response = CODEC.decode(message.payload().getBytes()); 299 IotDeviceMessage response = CODEC.decode(message.payload().getBytes());
360 - future.complete(response); 300 + responseFuture.complete(response);
361 }); 301 });
362 302
363 // 2. 编码并发布消息 303 // 2. 编码并发布消息
364 byte[] payload = CODEC.encode(request); 304 byte[] payload = CODEC.encode(request);
365 log.info("[publishAndWaitReply][Codec: {}, 发送消息: topic={}, payload={}]", 305 log.info("[publishAndWaitReply][Codec: {}, 发送消息: topic={}, payload={}]",
366 CODEC.type(), topic, new String(payload)); 306 CODEC.type(), topic, new String(payload));
367 -  
368 client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false) 307 client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false)
369 - .onComplete(ar -> {  
370 - if (ar.succeeded()) {  
371 - log.info("[publishAndWaitReply][消息发布成功,messageId={}]", ar.result());  
372 - } else {  
373 - log.error("[publishAndWaitReply][消息发布失败]", ar.cause());  
374 - future.completeExceptionally(ar.cause());  
375 - }  
376 - });  
377 -  
378 - // 3. 等待响应(超时返回 null) 308 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  309 + log.info("[publishAndWaitReply][消息发布成功]");
  310 +
  311 + // 3. 等待响应
379 try { 312 try {
380 - return future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 313 + return responseFuture.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
381 } catch (Exception e) { 314 } catch (Exception e) {
382 log.warn("[publishAndWaitReply][等待响应超时或失败]"); 315 log.warn("[publishAndWaitReply][等待响应超时或失败]");
383 return null; 316 return null;
@@ -390,19 +323,9 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { @@ -390,19 +323,9 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
390 * @param client MQTT 客户端 323 * @param client MQTT 客户端
391 */ 324 */
392 private void disconnect(MqttClient client) throws Exception { 325 private void disconnect(MqttClient client) throws Exception {
393 - // 1. 断开连接  
394 - CompletableFuture<Void> future = new CompletableFuture<>();  
395 client.disconnect() 326 client.disconnect()
396 - .onComplete(ar -> {  
397 - if (ar.succeeded()) {  
398 - log.info("[disconnect][断开连接成功]");  
399 - future.complete(null);  
400 - } else {  
401 - future.completeExceptionally(ar.cause());  
402 - }  
403 - });  
404 - // 2. 等待断开结果  
405 - future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 327 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  328 + log.info("[disconnect][断开连接成功]");
406 } 329 }
407 330
408 } 331 }
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotGatewayDeviceMqttProtocolIntegrationTest.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
2 2
3 import cn.hutool.core.collection.ListUtil; 3 import cn.hutool.core.collection.ListUtil;
4 import cn.hutool.core.map.MapUtil; 4 import cn.hutool.core.map.MapUtil;
5 -import cn.hutool.core.util.IdUtil;  
6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 6 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -27,10 +26,8 @@ import org.junit.jupiter.api.Disabled; @@ -27,10 +26,8 @@ import org.junit.jupiter.api.Disabled;
27 import org.junit.jupiter.api.Test; 26 import org.junit.jupiter.api.Test;
28 27
29 import java.util.Collections; 28 import java.util.Collections;
30 -import java.util.List;  
31 import java.util.Map; 29 import java.util.Map;
32 import java.util.concurrent.CompletableFuture; 30 import java.util.concurrent.CompletableFuture;
33 -import java.util.concurrent.CountDownLatch;  
34 import java.util.concurrent.TimeUnit; 31 import java.util.concurrent.TimeUnit;
35 32
36 /** 33 /**
@@ -103,8 +100,6 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { @@ -103,8 +100,6 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest {
103 */ 100 */
104 @Test 101 @Test
105 public void testAuth() throws Exception { 102 public void testAuth() throws Exception {
106 - CountDownLatch latch = new CountDownLatch(1);  
107 -  
108 // 1. 构建认证信息 103 // 1. 构建认证信息
109 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo( 104 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
110 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET); 105 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);
@@ -112,31 +107,13 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { @@ -112,31 +107,13 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest {
112 authInfo.getClientId(), authInfo.getUsername(), authInfo.getPassword()); 107 authInfo.getClientId(), authInfo.getUsername(), authInfo.getPassword());
113 108
114 // 2. 创建客户端并连接 109 // 2. 创建客户端并连接
115 - MqttClient client = connect(authInfo);  
116 - client.connect(SERVER_PORT, SERVER_HOST)  
117 - .onComplete(ar -> {  
118 - if (ar.succeeded()) {  
119 - log.info("[testAuth][连接成功,客户端 ID: {}]", client.clientId());  
120 - // 断开连接  
121 - client.disconnect()  
122 - .onComplete(disconnectAr -> {  
123 - if (disconnectAr.succeeded()) {  
124 - log.info("[testAuth][断开连接成功]");  
125 - } else {  
126 - log.error("[testAuth][断开连接失败]", disconnectAr.cause());  
127 - }  
128 - latch.countDown();  
129 - });  
130 - } else {  
131 - log.error("[testAuth][连接失败]", ar.cause());  
132 - latch.countDown();  
133 - }  
134 - });  
135 -  
136 - // 3. 等待测试完成  
137 - boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);  
138 - if (!completed) {  
139 - log.warn("[testAuth][测试超时]"); 110 + MqttClient client = createClient(authInfo);
  111 + try {
  112 + client.connect(SERVER_PORT, SERVER_HOST)
  113 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  114 + log.info("[testAuth][连接成功,客户端 ID: {}]", client.clientId());
  115 + } finally {
  116 + disconnect(client);
140 } 117 }
141 } 118 }
142 119
@@ -153,36 +130,35 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { @@ -153,36 +130,35 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest {
153 MqttClient client = connectAndAuth(); 130 MqttClient client = connectAndAuth();
154 log.info("[testTopoAdd][连接认证成功]"); 131 log.info("[testTopoAdd][连接认证成功]");
155 132
156 - // 2.1 订阅 _reply 主题  
157 - String replyTopic = String.format("/sys/%s/%s/thing/topo/add_reply",  
158 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);  
159 - subscribeReply(client, replyTopic);  
160 -  
161 - // 2.2 构建子设备认证信息  
162 - IotDeviceAuthReqDTO subAuthInfo = IotDeviceAuthUtils.getAuthInfo(  
163 - SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME, SUB_DEVICE_SECRET);  
164 - IotDeviceAuthReqDTO subDeviceAuth = new IotDeviceAuthReqDTO()  
165 - .setClientId(subAuthInfo.getClientId())  
166 - .setUsername(subAuthInfo.getUsername())  
167 - .setPassword(subAuthInfo.getPassword());  
168 -  
169 - // 2.3 构建请求消息  
170 - IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO();  
171 - params.setSubDevices(Collections.singletonList(subDeviceAuth));  
172 - IotDeviceMessage request = IotDeviceMessage.of(  
173 - IdUtil.fastSimpleUUID(),  
174 - IotDeviceMessageMethodEnum.TOPO_ADD.getMethod(),  
175 - params,  
176 - null, null, null);  
177 -  
178 - // 3. 发布消息并等待响应  
179 - String topic = String.format("/sys/%s/%s/thing/topo/add",  
180 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);  
181 - IotDeviceMessage response = publishAndWaitReply(client, topic, request);  
182 - log.info("[testTopoAdd][响应消息: {}]", response);  
183 -  
184 - // 4. 断开连接  
185 - disconnect(client); 133 + try {
  134 + // 2.1 构建子设备认证信息
  135 + IotDeviceAuthReqDTO subAuthInfo = IotDeviceAuthUtils.getAuthInfo(
  136 + SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME, SUB_DEVICE_SECRET);
  137 + IotDeviceAuthReqDTO subDeviceAuth = new IotDeviceAuthReqDTO()
  138 + .setClientId(subAuthInfo.getClientId())
  139 + .setUsername(subAuthInfo.getUsername())
  140 + .setPassword(subAuthInfo.getPassword());
  141 +
  142 + // 2.2 构建请求消息
  143 + IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO()
  144 + .setSubDevices(Collections.singletonList(subDeviceAuth));
  145 + IotDeviceMessage request = IotDeviceMessage.requestOf(
  146 + IotDeviceMessageMethodEnum.TOPO_ADD.getMethod(),
  147 + params);
  148 +
  149 + // 2.3 订阅 _reply 主题
  150 + String replyTopic = String.format("/sys/%s/%s/thing/topo/add_reply",
  151 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
  152 + subscribe(client, replyTopic);
  153 +
  154 + // 3. 发布消息并等待响应
  155 + String topic = String.format("/sys/%s/%s/thing/topo/add",
  156 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
  157 + IotDeviceMessage response = publishAndWaitReply(client, topic, request);
  158 + log.info("[testTopoAdd][响应消息: {}]", response);
  159 + } finally {
  160 + disconnect(client);
  161 + }
186 } 162 }
187 163
188 /** 164 /**
@@ -196,29 +172,28 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { @@ -196,29 +172,28 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest {
196 MqttClient client = connectAndAuth(); 172 MqttClient client = connectAndAuth();
197 log.info("[testTopoDelete][连接认证成功]"); 173 log.info("[testTopoDelete][连接认证成功]");
198 174
199 - // 2.1 订阅 _reply 主题  
200 - String replyTopic = String.format("/sys/%s/%s/thing/topo/delete_reply",  
201 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);  
202 - subscribeReply(client, replyTopic);  
203 -  
204 - // 2.2 构建请求消息  
205 - IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO();  
206 - params.setSubDevices(Collections.singletonList(  
207 - new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME)));  
208 - IotDeviceMessage request = IotDeviceMessage.of(  
209 - IdUtil.fastSimpleUUID(),  
210 - IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod(),  
211 - params,  
212 - null, null, null);  
213 -  
214 - // 3. 发布消息并等待响应  
215 - String topic = String.format("/sys/%s/%s/thing/topo/delete",  
216 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);  
217 - IotDeviceMessage response = publishAndWaitReply(client, topic, request);  
218 - log.info("[testTopoDelete][响应消息: {}]", response);  
219 -  
220 - // 4. 断开连接  
221 - disconnect(client); 175 + try {
  176 + // 2.1 构建请求消息
  177 + IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO()
  178 + .setSubDevices(Collections.singletonList(
  179 + new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME)));
  180 + IotDeviceMessage request = IotDeviceMessage.requestOf(
  181 + IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod(),
  182 + params);
  183 +
  184 + // 2.2 订阅 _reply 主题
  185 + String replyTopic = String.format("/sys/%s/%s/thing/topo/delete_reply",
  186 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
  187 + subscribe(client, replyTopic);
  188 +
  189 + // 3. 发布消息并等待响应
  190 + String topic = String.format("/sys/%s/%s/thing/topo/delete",
  191 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
  192 + IotDeviceMessage response = publishAndWaitReply(client, topic, request);
  193 + log.info("[testTopoDelete][响应消息: {}]", response);
  194 + } finally {
  195 + disconnect(client);
  196 + }
222 } 197 }
223 198
224 /** 199 /**
@@ -232,27 +207,26 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { @@ -232,27 +207,26 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest {
232 MqttClient client = connectAndAuth(); 207 MqttClient client = connectAndAuth();
233 log.info("[testTopoGet][连接认证成功]"); 208 log.info("[testTopoGet][连接认证成功]");
234 209
235 - // 2.1 订阅 _reply 主题  
236 - String replyTopic = String.format("/sys/%s/%s/thing/topo/get_reply",  
237 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);  
238 - subscribeReply(client, replyTopic);  
239 -  
240 - // 2.2 构建请求消息  
241 - IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO();  
242 - IotDeviceMessage request = IotDeviceMessage.of(  
243 - IdUtil.fastSimpleUUID(),  
244 - IotDeviceMessageMethodEnum.TOPO_GET.getMethod(),  
245 - params,  
246 - null, null, null);  
247 -  
248 - // 3. 发布消息并等待响应  
249 - String topic = String.format("/sys/%s/%s/thing/topo/get",  
250 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);  
251 - IotDeviceMessage response = publishAndWaitReply(client, topic, request);  
252 - log.info("[testTopoGet][响应消息: {}]", response);  
253 -  
254 - // 4. 断开连接  
255 - disconnect(client); 210 + try {
  211 + // 2.1 构建请求消息
  212 + IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO();
  213 + IotDeviceMessage request = IotDeviceMessage.requestOf(
  214 + IotDeviceMessageMethodEnum.TOPO_GET.getMethod(),
  215 + params);
  216 +
  217 + // 2.2 订阅 _reply 主题
  218 + String replyTopic = String.format("/sys/%s/%s/thing/topo/get_reply",
  219 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
  220 + subscribe(client, replyTopic);
  221 +
  222 + // 3. 发布消息并等待响应
  223 + String topic = String.format("/sys/%s/%s/thing/topo/get",
  224 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
  225 + IotDeviceMessage response = publishAndWaitReply(client, topic, request);
  226 + log.info("[testTopoGet][响应消息: {}]", response);
  227 + } finally {
  228 + disconnect(client);
  229 + }
256 } 230 }
257 231
258 // ===================== 子设备注册测试 ===================== 232 // ===================== 子设备注册测试 =====================
@@ -270,29 +244,28 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { @@ -270,29 +244,28 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest {
270 MqttClient client = connectAndAuth(); 244 MqttClient client = connectAndAuth();
271 log.info("[testSubDeviceRegister][连接认证成功]"); 245 log.info("[testSubDeviceRegister][连接认证成功]");
272 246
273 - // 2.1 订阅 _reply 主题  
274 - String replyTopic = String.format("/sys/%s/%s/thing/auth/sub-device/register_reply",  
275 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);  
276 - subscribeReply(client, replyTopic);  
277 -  
278 - // 2.2 构建请求消息  
279 - IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO();  
280 - subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY);  
281 - subDevice.setDeviceName("mougezishebei-mqtt");  
282 - IotDeviceMessage request = IotDeviceMessage.of(  
283 - IdUtil.fastSimpleUUID(),  
284 - IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod(),  
285 - Collections.singletonList(subDevice),  
286 - null, null, null);  
287 -  
288 - // 3. 发布消息并等待响应  
289 - String topic = String.format("/sys/%s/%s/thing/auth/sub-device/register",  
290 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);  
291 - IotDeviceMessage response = publishAndWaitReply(client, topic, request);  
292 - log.info("[testSubDeviceRegister][响应消息: {}]", response);  
293 -  
294 - // 4. 断开连接  
295 - disconnect(client); 247 + try {
  248 + // 2.1 构建请求消息
  249 + IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO()
  250 + .setProductKey(SUB_DEVICE_PRODUCT_KEY)
  251 + .setDeviceName("mougezishebei-mqtt");
  252 + IotDeviceMessage request = IotDeviceMessage.requestOf(
  253 + IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod(),
  254 + Collections.singletonList(subDevice));
  255 +
  256 + // 2.2 订阅 _reply 主题
  257 + String replyTopic = String.format("/sys/%s/%s/thing/auth/sub-device/register_reply",
  258 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
  259 + subscribe(client, replyTopic);
  260 +
  261 + // 3. 发布消息并等待响应
  262 + String topic = String.format("/sys/%s/%s/thing/auth/sub-device/register",
  263 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
  264 + IotDeviceMessage response = publishAndWaitReply(client, topic, request);
  265 + log.info("[testSubDeviceRegister][响应消息: {}]", response);
  266 + } finally {
  267 + disconnect(client);
  268 + }
296 } 269 }
297 270
298 // ===================== 批量上报测试 ===================== 271 // ===================== 批量上报测试 =====================
@@ -308,64 +281,63 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { @@ -308,64 +281,63 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest {
308 MqttClient client = connectAndAuth(); 281 MqttClient client = connectAndAuth();
309 log.info("[testPropertyPackPost][连接认证成功]"); 282 log.info("[testPropertyPackPost][连接认证成功]");
310 283
311 - // 2.1 订阅 _reply 主题  
312 - String replyTopic = String.format("/sys/%s/%s/thing/event/property/pack/post_reply",  
313 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);  
314 - subscribeReply(client, replyTopic);  
315 -  
316 - // 2.2 构建【网关设备】自身属性  
317 - Map<String, Object> gatewayProperties = MapUtil.<String, Object>builder()  
318 - .put("temperature", 25.5)  
319 - .build();  
320 -  
321 - // 2.3 构建【网关设备】自身事件  
322 - IotDevicePropertyPackPostReqDTO.EventValue gatewayEvent = new IotDevicePropertyPackPostReqDTO.EventValue();  
323 - gatewayEvent.setValue(MapUtil.builder().put("message", "gateway started").build());  
324 - gatewayEvent.setTime(System.currentTimeMillis());  
325 - Map<String, IotDevicePropertyPackPostReqDTO.EventValue> gatewayEvents = MapUtil  
326 - .<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()  
327 - .put("statusReport", gatewayEvent)  
328 - .build();  
329 -  
330 - // 2.4 构建【网关子设备】属性  
331 - Map<String, Object> subDeviceProperties = MapUtil.<String, Object>builder()  
332 - .put("power", 100)  
333 - .build();  
334 -  
335 - // 2.5 构建【网关子设备】事件  
336 - IotDevicePropertyPackPostReqDTO.EventValue subDeviceEvent = new IotDevicePropertyPackPostReqDTO.EventValue();  
337 - subDeviceEvent.setValue(MapUtil.builder().put("errorCode", 0).build());  
338 - subDeviceEvent.setTime(System.currentTimeMillis());  
339 - Map<String, IotDevicePropertyPackPostReqDTO.EventValue> subDeviceEvents = MapUtil  
340 - .<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()  
341 - .put("healthCheck", subDeviceEvent)  
342 - .build();  
343 -  
344 - // 2.6 构建子设备数据  
345 - IotDevicePropertyPackPostReqDTO.SubDeviceData subDeviceData = new IotDevicePropertyPackPostReqDTO.SubDeviceData();  
346 - subDeviceData.setIdentity(new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME));  
347 - subDeviceData.setProperties(subDeviceProperties);  
348 - subDeviceData.setEvents(subDeviceEvents);  
349 -  
350 - // 2.7 构建请求消息  
351 - IotDevicePropertyPackPostReqDTO params = new IotDevicePropertyPackPostReqDTO();  
352 - params.setProperties(gatewayProperties);  
353 - params.setEvents(gatewayEvents);  
354 - params.setSubDevices(ListUtil.of(subDeviceData));  
355 - IotDeviceMessage request = IotDeviceMessage.of(  
356 - IdUtil.fastSimpleUUID(),  
357 - IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod(),  
358 - params,  
359 - null, null, null);  
360 -  
361 - // 3. 发布消息并等待响应  
362 - String topic = String.format("/sys/%s/%s/thing/event/property/pack/post",  
363 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);  
364 - IotDeviceMessage response = publishAndWaitReply(client, topic, request);  
365 - log.info("[testPropertyPackPost][响应消息: {}]", response);  
366 -  
367 - // 4. 断开连接  
368 - disconnect(client); 284 + try {
  285 + // 2.1 构建【网关设备】自身属性
  286 + Map<String, Object> gatewayProperties = MapUtil.<String, Object>builder()
  287 + .put("temperature", 25.5)
  288 + .build();
  289 +
  290 + // 2.2 构建【网关设备】自身事件
  291 + IotDevicePropertyPackPostReqDTO.EventValue gatewayEvent = new IotDevicePropertyPackPostReqDTO.EventValue()
  292 + .setValue(MapUtil.builder().put("message", "gateway started").build())
  293 + .setTime(System.currentTimeMillis());
  294 + Map<String, IotDevicePropertyPackPostReqDTO.EventValue> gatewayEvents = MapUtil
  295 + .<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
  296 + .put("statusReport", gatewayEvent)
  297 + .build();
  298 +
  299 + // 2.3 构建【网关子设备】属性
  300 + Map<String, Object> subDeviceProperties = MapUtil.<String, Object>builder()
  301 + .put("power", 100)
  302 + .build();
  303 +
  304 + // 2.4 构建【网关子设备】事件
  305 + IotDevicePropertyPackPostReqDTO.EventValue subDeviceEvent = new IotDevicePropertyPackPostReqDTO.EventValue()
  306 + .setValue(MapUtil.builder().put("errorCode", 0).build())
  307 + .setTime(System.currentTimeMillis());
  308 + Map<String, IotDevicePropertyPackPostReqDTO.EventValue> subDeviceEvents = MapUtil
  309 + .<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
  310 + .put("healthCheck", subDeviceEvent)
  311 + .build();
  312 +
  313 + // 2.5 构建子设备数据
  314 + IotDevicePropertyPackPostReqDTO.SubDeviceData subDeviceData = new IotDevicePropertyPackPostReqDTO.SubDeviceData()
  315 + .setIdentity(new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME))
  316 + .setProperties(subDeviceProperties)
  317 + .setEvents(subDeviceEvents);
  318 +
  319 + // 2.6 构建请求消息
  320 + IotDevicePropertyPackPostReqDTO params = new IotDevicePropertyPackPostReqDTO()
  321 + .setProperties(gatewayProperties)
  322 + .setEvents(gatewayEvents)
  323 + .setSubDevices(ListUtil.of(subDeviceData));
  324 + IotDeviceMessage request = IotDeviceMessage.requestOf(
  325 + IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod(),
  326 + params);
  327 +
  328 + // 2.7 订阅 _reply 主题
  329 + String replyTopic = String.format("/sys/%s/%s/thing/event/property/pack/post_reply",
  330 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
  331 + subscribe(client, replyTopic);
  332 +
  333 + // 3. 发布消息并等待响应
  334 + String topic = String.format("/sys/%s/%s/thing/event/property/pack/post",
  335 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
  336 + IotDeviceMessage response = publishAndWaitReply(client, topic, request);
  337 + log.info("[testPropertyPackPost][响应消息: {}]", response);
  338 + } finally {
  339 + disconnect(client);
  340 + }
369 } 341 }
370 342
371 // ===================== 辅助方法 ===================== 343 // ===================== 辅助方法 =====================
@@ -376,7 +348,7 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { @@ -376,7 +348,7 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest {
376 * @param authInfo 认证信息 348 * @param authInfo 认证信息
377 * @return MQTT 客户端 349 * @return MQTT 客户端
378 */ 350 */
379 - private MqttClient connect(IotDeviceAuthReqDTO authInfo) { 351 + private MqttClient createClient(IotDeviceAuthReqDTO authInfo) {
380 MqttClientOptions options = new MqttClientOptions() 352 MqttClientOptions options = new MqttClientOptions()
381 .setClientId(authInfo.getClientId()) 353 .setClientId(authInfo.getClientId())
382 .setUsername(authInfo.getUsername()) 354 .setUsername(authInfo.getUsername())
@@ -392,45 +364,24 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { @@ -392,45 +364,24 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest {
392 * @return 已认证的 MQTT 客户端 364 * @return 已认证的 MQTT 客户端
393 */ 365 */
394 private MqttClient connectAndAuth() throws Exception { 366 private MqttClient connectAndAuth() throws Exception {
395 - // 1. 创建客户端并连接  
396 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo( 367 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
397 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET); 368 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);
398 - MqttClient client = connect(authInfo);  
399 -  
400 - // 2.1 连接  
401 - CompletableFuture<MqttClient> future = new CompletableFuture<>(); 369 + MqttClient client = createClient(authInfo);
402 client.connect(SERVER_PORT, SERVER_HOST) 370 client.connect(SERVER_PORT, SERVER_HOST)
403 - .onComplete(ar -> {  
404 - if (ar.succeeded()) {  
405 - future.complete(client);  
406 - } else {  
407 - future.completeExceptionally(ar.cause());  
408 - }  
409 - });  
410 - // 2.2 等待连接结果  
411 - return future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 371 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  372 + return client;
412 } 373 }
413 374
414 /** 375 /**
415 - * 订阅响应主题 376 + * 订阅主题
416 * 377 *
417 - * @param client MQTT 客户端  
418 - * @param replyTopic 响应主题 378 + * @param client MQTT 客户端
  379 + * @param topic 主题
419 */ 380 */
420 - private void subscribeReply(MqttClient client, String replyTopic) throws Exception {  
421 - // 1. 订阅响应主题  
422 - CompletableFuture<Void> future = new CompletableFuture<>();  
423 - client.subscribe(replyTopic, MqttQoS.AT_LEAST_ONCE.value())  
424 - .onComplete(ar -> {  
425 - if (ar.succeeded()) {  
426 - log.info("[subscribeReply][订阅响应主题成功: {}]", replyTopic);  
427 - future.complete(null);  
428 - } else {  
429 - future.completeExceptionally(ar.cause());  
430 - }  
431 - });  
432 - // 2. 等待订阅结果  
433 - future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 381 + private void subscribe(MqttClient client, String topic) throws Exception {
  382 + client.subscribe(topic, MqttQoS.AT_LEAST_ONCE.value())
  383 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  384 + log.info("[subscribe][订阅主题成功: {}]", topic);
434 } 385 }
435 386
436 /** 387 /**
@@ -441,34 +392,28 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { @@ -441,34 +392,28 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest {
441 * @param request 请求消息 392 * @param request 请求消息
442 * @return 响应消息 393 * @return 响应消息
443 */ 394 */
444 - private IotDeviceMessage publishAndWaitReply(MqttClient client, String topic, IotDeviceMessage request) { 395 + private IotDeviceMessage publishAndWaitReply(MqttClient client, String topic, IotDeviceMessage request)
  396 + throws Exception {
445 // 1. 设置消息处理器,接收响应 397 // 1. 设置消息处理器,接收响应
446 - CompletableFuture<IotDeviceMessage> future = new CompletableFuture<>(); 398 + CompletableFuture<IotDeviceMessage> responseFuture = new CompletableFuture<>();
447 client.publishHandler(message -> { 399 client.publishHandler(message -> {
448 log.info("[publishAndWaitReply][收到响应: topic={}, payload={}]", 400 log.info("[publishAndWaitReply][收到响应: topic={}, payload={}]",
449 message.topicName(), message.payload().toString()); 401 message.topicName(), message.payload().toString());
450 IotDeviceMessage response = CODEC.decode(message.payload().getBytes()); 402 IotDeviceMessage response = CODEC.decode(message.payload().getBytes());
451 - future.complete(response); 403 + responseFuture.complete(response);
452 }); 404 });
453 405
454 // 2. 编码并发布消息 406 // 2. 编码并发布消息
455 byte[] payload = CODEC.encode(request); 407 byte[] payload = CODEC.encode(request);
456 log.info("[publishAndWaitReply][Codec: {}, 发送消息: topic={}, payload={}]", 408 log.info("[publishAndWaitReply][Codec: {}, 发送消息: topic={}, payload={}]",
457 CODEC.type(), topic, new String(payload)); 409 CODEC.type(), topic, new String(payload));
458 -  
459 client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false) 410 client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false)
460 - .onComplete(ar -> {  
461 - if (ar.succeeded()) {  
462 - log.info("[publishAndWaitReply][消息发布成功,messageId={}]", ar.result());  
463 - } else {  
464 - log.error("[publishAndWaitReply][消息发布失败]", ar.cause());  
465 - future.completeExceptionally(ar.cause());  
466 - }  
467 - });  
468 -  
469 - // 3. 等待响应(超时返回 null) 411 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  412 + log.info("[publishAndWaitReply][消息发布成功]");
  413 +
  414 + // 3. 等待响应
470 try { 415 try {
471 - return future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 416 + return responseFuture.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
472 } catch (Exception e) { 417 } catch (Exception e) {
473 log.warn("[publishAndWaitReply][等待响应超时或失败]"); 418 log.warn("[publishAndWaitReply][等待响应超时或失败]");
474 return null; 419 return null;
@@ -481,19 +426,9 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { @@ -481,19 +426,9 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest {
481 * @param client MQTT 客户端 426 * @param client MQTT 客户端
482 */ 427 */
483 private void disconnect(MqttClient client) throws Exception { 428 private void disconnect(MqttClient client) throws Exception {
484 - // 1. 断开连接  
485 - CompletableFuture<Void> future = new CompletableFuture<>();  
486 client.disconnect() 429 client.disconnect()
487 - .onComplete(ar -> {  
488 - if (ar.succeeded()) {  
489 - log.info("[disconnect][断开连接成功]");  
490 - future.complete(null);  
491 - } else {  
492 - future.completeExceptionally(ar.cause());  
493 - }  
494 - });  
495 - // 2. 等待断开结果  
496 - future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 430 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  431 + log.info("[disconnect][断开连接成功]");
497 } 432 }
498 433
499 } 434 }
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotGatewaySubDeviceMqttProtocolIntegrationTest.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.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
4 -import cn.hutool.core.util.IdUtil;  
5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
6 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 5 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -22,7 +21,6 @@ import org.junit.jupiter.api.Disabled; @@ -22,7 +21,6 @@ import org.junit.jupiter.api.Disabled;
22 import org.junit.jupiter.api.Test; 21 import org.junit.jupiter.api.Test;
23 22
24 import java.util.concurrent.CompletableFuture; 23 import java.util.concurrent.CompletableFuture;
25 -import java.util.concurrent.CountDownLatch;  
26 import java.util.concurrent.TimeUnit; 24 import java.util.concurrent.TimeUnit;
27 25
28 /** 26 /**
@@ -90,39 +88,19 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest { @@ -90,39 +88,19 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest {
90 */ 88 */
91 @Test 89 @Test
92 public void testAuth() throws Exception { 90 public void testAuth() throws Exception {
93 - CountDownLatch latch = new CountDownLatch(1);  
94 -  
95 // 1. 构建认证信息 91 // 1. 构建认证信息
96 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); 92 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
97 log.info("[testAuth][认证信息: clientId={}, username={}, password={}]", 93 log.info("[testAuth][认证信息: clientId={}, username={}, password={}]",
98 authInfo.getClientId(), authInfo.getUsername(), authInfo.getPassword()); 94 authInfo.getClientId(), authInfo.getUsername(), authInfo.getPassword());
99 95
100 // 2. 创建客户端并连接 96 // 2. 创建客户端并连接
101 - MqttClient client = connect(authInfo);  
102 - client.connect(SERVER_PORT, SERVER_HOST)  
103 - .onComplete(ar -> {  
104 - if (ar.succeeded()) {  
105 - log.info("[testAuth][连接成功,客户端 ID: {}]", client.clientId());  
106 - // 断开连接  
107 - client.disconnect()  
108 - .onComplete(disconnectAr -> {  
109 - if (disconnectAr.succeeded()) {  
110 - log.info("[testAuth][断开连接成功]");  
111 - } else {  
112 - log.error("[testAuth][断开连接失败]", disconnectAr.cause());  
113 - }  
114 - latch.countDown();  
115 - });  
116 - } else {  
117 - log.error("[testAuth][连接失败]", ar.cause());  
118 - latch.countDown();  
119 - }  
120 - });  
121 -  
122 - // 3. 等待测试完成  
123 - boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);  
124 - if (!completed) {  
125 - log.warn("[testAuth][测试超时]"); 97 + MqttClient client = createClient(authInfo);
  98 + try {
  99 + client.connect(SERVER_PORT, SERVER_HOST)
  100 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  101 + log.info("[testAuth][连接成功,客户端 ID: {}]", client.clientId());
  102 + } finally {
  103 + disconnect(client);
126 } 104 }
127 } 105 }
128 106
@@ -138,28 +116,27 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest { @@ -138,28 +116,27 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest {
138 log.info("[testPropertyPost][连接认证成功]"); 116 log.info("[testPropertyPost][连接认证成功]");
139 log.info("[testPropertyPost][子设备属性上报 - 请求实际由 Gateway 代为转发]"); 117 log.info("[testPropertyPost][子设备属性上报 - 请求实际由 Gateway 代为转发]");
140 118
141 - // 2. 订阅 _reply 主题  
142 - String replyTopic = String.format("/sys/%s/%s/thing/property/post_reply", PRODUCT_KEY, DEVICE_NAME);  
143 - subscribeReply(client, replyTopic);  
144 -  
145 - // 3. 构建属性上报消息  
146 - IotDeviceMessage request = IotDeviceMessage.of(  
147 - IdUtil.fastSimpleUUID(),  
148 - IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),  
149 - IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()  
150 - .put("power", 100)  
151 - .put("status", "online")  
152 - .put("temperature", 36.5)  
153 - .build()),  
154 - null, null, null);  
155 -  
156 - // 4. 发布消息并等待响应  
157 - String topic = String.format("/sys/%s/%s/thing/property/post", PRODUCT_KEY, DEVICE_NAME);  
158 - IotDeviceMessage response = publishAndWaitReply(client, topic, request);  
159 - log.info("[testPropertyPost][响应消息: {}]", response);  
160 -  
161 - // 5. 断开连接  
162 - disconnect(client); 119 + try {
  120 + // 2.1 构建属性上报消息
  121 + IotDeviceMessage request = IotDeviceMessage.requestOf(
  122 + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
  123 + IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
  124 + .put("power", 100)
  125 + .put("status", "online")
  126 + .put("temperature", 36.5)
  127 + .build()));
  128 +
  129 + // 2.2 订阅 _reply 主题
  130 + String replyTopic = String.format("/sys/%s/%s/thing/property/post_reply", PRODUCT_KEY, DEVICE_NAME);
  131 + subscribe(client, replyTopic);
  132 +
  133 + // 3. 发布消息并等待响应
  134 + String topic = String.format("/sys/%s/%s/thing/property/post", PRODUCT_KEY, DEVICE_NAME);
  135 + IotDeviceMessage response = publishAndWaitReply(client, topic, request);
  136 + log.info("[testPropertyPost][响应消息: {}]", response);
  137 + } finally {
  138 + disconnect(client);
  139 + }
163 } 140 }
164 141
165 // ===================== 子设备事件上报测试 ===================== 142 // ===================== 子设备事件上报测试 =====================
@@ -174,32 +151,31 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest { @@ -174,32 +151,31 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest {
174 log.info("[testEventPost][连接认证成功]"); 151 log.info("[testEventPost][连接认证成功]");
175 log.info("[testEventPost][子设备事件上报 - 请求实际由 Gateway 代为转发]"); 152 log.info("[testEventPost][子设备事件上报 - 请求实际由 Gateway 代为转发]");
176 153
177 - // 2. 订阅 _reply 主题  
178 - String replyTopic = String.format("/sys/%s/%s/thing/event/post_reply", PRODUCT_KEY, DEVICE_NAME);  
179 - subscribeReply(client, replyTopic);  
180 -  
181 - // 3. 构建事件上报消息  
182 - IotDeviceMessage request = IotDeviceMessage.of(  
183 - IdUtil.fastSimpleUUID(),  
184 - IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),  
185 - IotDeviceEventPostReqDTO.of(  
186 - "alarm",  
187 - MapUtil.<String, Object>builder()  
188 - .put("level", "warning")  
189 - .put("message", "temperature too high")  
190 - .put("threshold", 40)  
191 - .put("current", 42)  
192 - .build(),  
193 - System.currentTimeMillis()),  
194 - null, null, null);  
195 -  
196 - // 4. 发布消息并等待响应  
197 - String topic = String.format("/sys/%s/%s/thing/event/post", PRODUCT_KEY, DEVICE_NAME);  
198 - IotDeviceMessage response = publishAndWaitReply(client, topic, request);  
199 - log.info("[testEventPost][响应消息: {}]", response);  
200 -  
201 - // 5. 断开连接  
202 - disconnect(client); 154 + try {
  155 + // 2.1 构建事件上报消息
  156 + IotDeviceMessage request = IotDeviceMessage.requestOf(
  157 + IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
  158 + IotDeviceEventPostReqDTO.of(
  159 + "alarm",
  160 + MapUtil.<String, Object>builder()
  161 + .put("level", "warning")
  162 + .put("message", "temperature too high")
  163 + .put("threshold", 40)
  164 + .put("current", 42)
  165 + .build(),
  166 + System.currentTimeMillis()));
  167 +
  168 + // 2.2 订阅 _reply 主题
  169 + String replyTopic = String.format("/sys/%s/%s/thing/event/post_reply", PRODUCT_KEY, DEVICE_NAME);
  170 + subscribe(client, replyTopic);
  171 +
  172 + // 3. 发布消息并等待响应
  173 + String topic = String.format("/sys/%s/%s/thing/event/post", PRODUCT_KEY, DEVICE_NAME);
  174 + IotDeviceMessage response = publishAndWaitReply(client, topic, request);
  175 + log.info("[testEventPost][响应消息: {}]", response);
  176 + } finally {
  177 + disconnect(client);
  178 + }
203 } 179 }
204 180
205 // ===================== 辅助方法 ===================== 181 // ===================== 辅助方法 =====================
@@ -210,7 +186,7 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest { @@ -210,7 +186,7 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest {
210 * @param authInfo 认证信息 186 * @param authInfo 认证信息
211 * @return MQTT 客户端 187 * @return MQTT 客户端
212 */ 188 */
213 - private MqttClient connect(IotDeviceAuthReqDTO authInfo) { 189 + private MqttClient createClient(IotDeviceAuthReqDTO authInfo) {
214 MqttClientOptions options = new MqttClientOptions() 190 MqttClientOptions options = new MqttClientOptions()
215 .setClientId(authInfo.getClientId()) 191 .setClientId(authInfo.getClientId())
216 .setUsername(authInfo.getUsername()) 192 .setUsername(authInfo.getUsername())
@@ -226,44 +202,23 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest { @@ -226,44 +202,23 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest {
226 * @return 已认证的 MQTT 客户端 202 * @return 已认证的 MQTT 客户端
227 */ 203 */
228 private MqttClient connectAndAuth() throws Exception { 204 private MqttClient connectAndAuth() throws Exception {
229 - // 1. 创建客户端并连接  
230 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); 205 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
231 - MqttClient client = connect(authInfo);  
232 -  
233 - // 2.1 连接  
234 - CompletableFuture<MqttClient> future = new CompletableFuture<>(); 206 + MqttClient client = createClient(authInfo);
235 client.connect(SERVER_PORT, SERVER_HOST) 207 client.connect(SERVER_PORT, SERVER_HOST)
236 - .onComplete(ar -> {  
237 - if (ar.succeeded()) {  
238 - future.complete(client);  
239 - } else {  
240 - future.completeExceptionally(ar.cause());  
241 - }  
242 - });  
243 - // 2.2 等待连接结果  
244 - return future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 208 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  209 + return client;
245 } 210 }
246 211
247 /** 212 /**
248 - * 订阅响应主题 213 + * 订阅主题
249 * 214 *
250 - * @param client MQTT 客户端  
251 - * @param replyTopic 响应主题 215 + * @param client MQTT 客户端
  216 + * @param topic 主题
252 */ 217 */
253 - private void subscribeReply(MqttClient client, String replyTopic) throws Exception {  
254 - // 1. 订阅响应主题  
255 - CompletableFuture<Void> future = new CompletableFuture<>();  
256 - client.subscribe(replyTopic, MqttQoS.AT_LEAST_ONCE.value())  
257 - .onComplete(ar -> {  
258 - if (ar.succeeded()) {  
259 - log.info("[subscribeReply][订阅响应主题成功: {}]", replyTopic);  
260 - future.complete(null);  
261 - } else {  
262 - future.completeExceptionally(ar.cause());  
263 - }  
264 - });  
265 - // 2. 等待订阅结果  
266 - future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 218 + private void subscribe(MqttClient client, String topic) throws Exception {
  219 + client.subscribe(topic, MqttQoS.AT_LEAST_ONCE.value())
  220 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  221 + log.info("[subscribe][订阅主题成功: {}]", topic);
267 } 222 }
268 223
269 /** 224 /**
@@ -274,34 +229,28 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest { @@ -274,34 +229,28 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest {
274 * @param request 请求消息 229 * @param request 请求消息
275 * @return 响应消息 230 * @return 响应消息
276 */ 231 */
277 - private IotDeviceMessage publishAndWaitReply(MqttClient client, String topic, IotDeviceMessage request) { 232 + private IotDeviceMessage publishAndWaitReply(MqttClient client, String topic, IotDeviceMessage request)
  233 + throws Exception {
278 // 1. 设置消息处理器,接收响应 234 // 1. 设置消息处理器,接收响应
279 - CompletableFuture<IotDeviceMessage> future = new CompletableFuture<>(); 235 + CompletableFuture<IotDeviceMessage> responseFuture = new CompletableFuture<>();
280 client.publishHandler(message -> { 236 client.publishHandler(message -> {
281 log.info("[publishAndWaitReply][收到响应: topic={}, payload={}]", 237 log.info("[publishAndWaitReply][收到响应: topic={}, payload={}]",
282 message.topicName(), message.payload().toString()); 238 message.topicName(), message.payload().toString());
283 IotDeviceMessage response = CODEC.decode(message.payload().getBytes()); 239 IotDeviceMessage response = CODEC.decode(message.payload().getBytes());
284 - future.complete(response); 240 + responseFuture.complete(response);
285 }); 241 });
286 242
287 // 2. 编码并发布消息 243 // 2. 编码并发布消息
288 byte[] payload = CODEC.encode(request); 244 byte[] payload = CODEC.encode(request);
289 log.info("[publishAndWaitReply][Codec: {}, 发送消息: topic={}, payload={}]", 245 log.info("[publishAndWaitReply][Codec: {}, 发送消息: topic={}, payload={}]",
290 CODEC.type(), topic, new String(payload)); 246 CODEC.type(), topic, new String(payload));
291 -  
292 client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false) 247 client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false)
293 - .onComplete(ar -> {  
294 - if (ar.succeeded()) {  
295 - log.info("[publishAndWaitReply][消息发布成功,messageId={}]", ar.result());  
296 - } else {  
297 - log.error("[publishAndWaitReply][消息发布失败]", ar.cause());  
298 - future.completeExceptionally(ar.cause());  
299 - }  
300 - });  
301 -  
302 - // 3. 等待响应(超时返回 null) 248 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  249 + log.info("[publishAndWaitReply][消息发布成功]");
  250 +
  251 + // 3. 等待响应
303 try { 252 try {
304 - return future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 253 + return responseFuture.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
305 } catch (Exception e) { 254 } catch (Exception e) {
306 log.warn("[publishAndWaitReply][等待响应超时或失败]"); 255 log.warn("[publishAndWaitReply][等待响应超时或失败]");
307 return null; 256 return null;
@@ -314,19 +263,9 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest { @@ -314,19 +263,9 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest {
314 * @param client MQTT 客户端 263 * @param client MQTT 客户端
315 */ 264 */
316 private void disconnect(MqttClient client) throws Exception { 265 private void disconnect(MqttClient client) throws Exception {
317 - // 1. 断开连接  
318 - CompletableFuture<Void> future = new CompletableFuture<>();  
319 client.disconnect() 266 client.disconnect()
320 - .onComplete(ar -> {  
321 - if (ar.succeeded()) {  
322 - log.info("[disconnect][断开连接成功]");  
323 - future.complete(null);  
324 - } else {  
325 - future.completeExceptionally(ar.cause());  
326 - }  
327 - });  
328 - // 2. 等待断开结果  
329 - future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); 267 + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  268 + log.info("[disconnect][断开连接成功]");
330 } 269 }
331 270
332 } 271 }