Commit 0072482af8faf113b5ed663d15eaae90b5d15dd5

Authored by YunaiV
1 parent 610ae6d5

feat(iot): 完善 WebSocket 协议实现,优化代码质量

1. 配置属性校验完善
   - CoAP 配置添加 @NotNull 校验注解,替换 TODO 注释

2. WebSocket 协议核心优化
   - ConnectionInfo 新增 codecType 字段,支持动态编解码类型
   - 上行/下行处理器根据连接的 codecType 进行消息编解码
   - 使用 StrUtil.utf8Str/utf8Bytes 替换 StandardCharsets 硬编码

3. 包注释完善
   - http/tcp package-info.java 添加规范的包级注释

4. 单元测试重构
   - 使用 WebSocketClient.connect() 替换废弃的 HttpClient.webSocket()
   - 提取公共方法,简化测试代码结构
Showing 10 changed files with 680 additions and 610 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
@@ -568,25 +568,25 @@ public class IotGatewayProperties { @@ -568,25 +568,25 @@ public class IotGatewayProperties {
568 /** 568 /**
569 * 服务端口(CoAP 默认端口 5683) 569 * 服务端口(CoAP 默认端口 5683)
570 */ 570 */
571 - // TODO @AI:默认不为空 571 + @NotNull(message = "服务端口不能为空")
572 private Integer port = 5683; 572 private Integer port = 5683;
573 573
574 /** 574 /**
575 * 最大消息大小(字节) 575 * 最大消息大小(字节)
576 */ 576 */
577 - // TODO @AI:默认不为空 577 + @NotNull(message = "最大消息大小不能为空")
578 private Integer maxMessageSize = 1024; 578 private Integer maxMessageSize = 1024;
579 579
580 /** 580 /**
581 * ACK 超时时间(毫秒) 581 * ACK 超时时间(毫秒)
582 */ 582 */
583 - // TODO @AI:默认不为空 583 + @NotNull(message = "ACK 超时时间不能为空")
584 private Integer ackTimeout = 2000; 584 private Integer ackTimeout = 2000;
585 585
586 /** 586 /**
587 * 最大重传次数 587 * 最大重传次数
588 */ 588 */
589 - // TODO @AI:默认不为空 589 + @NotNull(message = "最大重传次数不能为空")
590 private Integer maxRetransmit = 4; 590 private Integer maxRetransmit = 4;
591 591
592 } 592 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/package-info.java
1 -// TODO @AI:参考 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/package-info.java 完善注释;  
2 -package cn.iocoder.yudao.module.iot.gateway.protocol.http;  
3 \ No newline at end of file 1 \ No newline at end of file
  2 +/**
  3 + * HTTP 协议实现包
  4 + * <p>
  5 + * 提供基于 Vert.x HTTP Server 的 IoT 设备连接和消息处理功能
  6 + */
  7 +package cn.iocoder.yudao.module.iot.gateway.protocol.http;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/package-info.java
1 -// TODO @AI:参考 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/package-info.java  
2 -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;  
3 \ No newline at end of file 1 \ No newline at end of file
  2 +/**
  3 + * TCP 协议实现包
  4 + * <p>
  5 + * 提供基于 Vert.x TCP Server 的 IoT 设备连接和消息处理功能
  6 + */
  7 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketUpstreamProtocol.java
@@ -53,6 +53,7 @@ public class IotWebSocketUpstreamProtocol { @@ -53,6 +53,7 @@ public class IotWebSocketUpstreamProtocol {
53 } 53 }
54 54
55 @PostConstruct 55 @PostConstruct
  56 + @SuppressWarnings("deprecation")
56 public void start() { 57 public void start() {
57 // 1.1 创建服务器选项 58 // 1.1 创建服务器选项
58 HttpServerOptions options = new HttpServerOptions() 59 HttpServerOptions options = new HttpServerOptions()
@@ -73,15 +74,14 @@ public class IotWebSocketUpstreamProtocol { @@ -73,15 +74,14 @@ public class IotWebSocketUpstreamProtocol {
73 httpServer.webSocketHandler(socket -> { 74 httpServer.webSocketHandler(socket -> {
74 // 验证路径 75 // 验证路径
75 if (ObjUtil.notEqual(wsProperties.getPath(), socket.path())) { 76 if (ObjUtil.notEqual(wsProperties.getPath(), socket.path())) {
76 - log.warn("[webSocketHandler][WebSocket 路径不匹配,拒绝连接,路径: {},期望: {}]", socket.path(), wsProperties.getPath());  
77 - // TODO @AI:已经被废弃,看看换什么其他方法; 77 + log.warn("[webSocketHandler][WebSocket 路径不匹配,拒绝连接,路径: {},期望: {}]",
  78 + socket.path(), wsProperties.getPath());
78 socket.reject(); 79 socket.reject();
79 return; 80 return;
80 } 81 }
81 -  
82 // 创建上行处理器 82 // 创建上行处理器
83 - IotWebSocketUpstreamHandler handler = new IotWebSocketUpstreamHandler(  
84 - this, messageService, deviceService, connectionManager); 83 + IotWebSocketUpstreamHandler handler = new IotWebSocketUpstreamHandler(this,
  84 + messageService, deviceService, connectionManager);
85 handler.handle(socket); 85 handler.handle(socket);
86 }); 86 });
87 87
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/manager/IotWebSocketConnectionManager.java
@@ -139,8 +139,10 @@ public class IotWebSocketConnectionManager { @@ -139,8 +139,10 @@ public class IotWebSocketConnectionManager {
139 * 客户端 ID 139 * 客户端 ID
140 */ 140 */
141 private String clientId; 141 private String clientId;
142 -  
143 - // TODO @AI:增加有个 codecType 字段;后续可以使用,参考 tcp、udp;然后下行的时候,也基于这个 codeType 去获取; 142 + /**
  143 + * 消息编解码类型(认证后确定)
  144 + */
  145 + private String codecType;
144 146
145 } 147 }
146 148
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/router/IotWebSocketDownstreamHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
4 -import cn.iocoder.yudao.module.iot.gateway.codec.websocket.IotWebSocketJsonDeviceMessageCodec;  
5 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;
6 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 6 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
7 import lombok.RequiredArgsConstructor; 7 import lombok.RequiredArgsConstructor;
8 import lombok.extern.slf4j.Slf4j; 8 import lombok.extern.slf4j.Slf4j;
9 9
10 -import java.nio.charset.StandardCharsets;  
11 -  
12 /** 10 /**
13 * IoT 网关 WebSocket 下行消息处理器 11 * IoT 网关 WebSocket 下行消息处理器
14 * 12 *
@@ -18,9 +16,6 @@ import java.nio.charset.StandardCharsets; @@ -18,9 +16,6 @@ import java.nio.charset.StandardCharsets;
18 @RequiredArgsConstructor 16 @RequiredArgsConstructor
19 public class IotWebSocketDownstreamHandler { 17 public class IotWebSocketDownstreamHandler {
20 18
21 - // TODO @芋艿:codeType 的处理;  
22 - private static final String CODEC_TYPE = IotWebSocketJsonDeviceMessageCodec.TYPE;  
23 -  
24 private final IotDeviceMessageService deviceMessageService; 19 private final IotDeviceMessageService deviceMessageService;
25 20
26 private final IotWebSocketConnectionManager connectionManager; 21 private final IotWebSocketConnectionManager connectionManager;
@@ -42,8 +37,8 @@ public class IotWebSocketDownstreamHandler { @@ -42,8 +37,8 @@ public class IotWebSocketDownstreamHandler {
42 } 37 }
43 38
44 // 2. 编码消息并发送到设备 39 // 2. 编码消息并发送到设备
45 - byte[] bytes = deviceMessageService.encodeDeviceMessage(message, CODEC_TYPE);  
46 - String jsonMessage = new String(bytes, StandardCharsets.UTF_8); 40 + byte[] bytes = deviceMessageService.encodeDeviceMessage(message, connectionInfo.getCodecType());
  41 + String jsonMessage = StrUtil.utf8Str(bytes);
47 boolean success = connectionManager.sendToDevice(message.getDeviceId(), jsonMessage); 42 boolean success = connectionManager.sendToDevice(message.getDeviceId(), jsonMessage);
48 if (success) { 43 if (success) {
49 log.info("[handle][下行消息发送成功,设备 ID: {},方法: {},消息 ID: {},数据长度: {} 字节]", 44 log.info("[handle][下行消息发送成功,设备 ID: {},方法: {},消息 ID: {},数据长度: {} 字节]",
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/router/IotWebSocketUpstreamHandler.java
@@ -25,7 +25,6 @@ import io.vertx.core.Handler; @@ -25,7 +25,6 @@ import io.vertx.core.Handler;
25 import io.vertx.core.http.ServerWebSocket; 25 import io.vertx.core.http.ServerWebSocket;
26 import lombok.extern.slf4j.Slf4j; 26 import lombok.extern.slf4j.Slf4j;
27 27
28 -import java.nio.charset.StandardCharsets;  
29 28
30 /** 29 /**
31 * WebSocket 上行消息处理器 30 * WebSocket 上行消息处理器
@@ -35,7 +34,9 @@ import java.nio.charset.StandardCharsets; @@ -35,7 +34,9 @@ import java.nio.charset.StandardCharsets;
35 @Slf4j 34 @Slf4j
36 public class IotWebSocketUpstreamHandler implements Handler<ServerWebSocket> { 35 public class IotWebSocketUpstreamHandler implements Handler<ServerWebSocket> {
37 36
38 - // TODO @芋艿:codeType 的处理; 37 + /**
  38 + * 默认消息编解码类型
  39 + */
39 private static final String CODEC_TYPE = IotWebSocketJsonDeviceMessageCodec.TYPE; 40 private static final String CODEC_TYPE = IotWebSocketJsonDeviceMessageCodec.TYPE;
40 41
41 private static final String AUTH_METHOD = "auth"; 42 private static final String AUTH_METHOD = "auth";
@@ -63,13 +64,10 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; { @@ -63,13 +64,10 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
63 64
64 @Override 65 @Override
65 public void handle(ServerWebSocket socket) { 66 public void handle(ServerWebSocket socket) {
66 - // 1. 接受 WebSocket 连接  
67 String clientId = IdUtil.simpleUUID(); 67 String clientId = IdUtil.simpleUUID();
68 log.debug("[handle][设备连接,客户端 ID: {},地址: {}]", clientId, socket.remoteAddress()); 68 log.debug("[handle][设备连接,客户端 ID: {},地址: {}]", clientId, socket.remoteAddress());
69 - // TODO @AI:这个方法已经废弃,看看有没其他替换的  
70 - socket.accept();  
71 69
72 - // 2.1 设置异常和关闭处理器 70 + // 1. 设置异常和关闭处理器
73 socket.exceptionHandler(ex -> { 71 socket.exceptionHandler(ex -> {
74 log.warn("[handle][连接异常,客户端 ID: {},地址: {}]", clientId, socket.remoteAddress()); 72 log.warn("[handle][连接异常,客户端 ID: {},地址: {}]", clientId, socket.remoteAddress());
75 cleanupConnection(socket); 73 cleanupConnection(socket);
@@ -79,7 +77,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; { @@ -79,7 +77,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
79 cleanupConnection(socket); 77 cleanupConnection(socket);
80 }); 78 });
81 79
82 - // 2.2 设置文本消息处理器 80 + // 2. 设置文本消息处理器
83 socket.textMessageHandler(message -> { 81 socket.textMessageHandler(message -> {
84 try { 82 try {
85 processMessage(clientId, message, socket); 83 processMessage(clientId, message, socket);
@@ -105,12 +103,13 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; { @@ -105,12 +103,13 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
105 if (StrUtil.isBlank(message)) { 103 if (StrUtil.isBlank(message)) {
106 return; 104 return;
107 } 105 }
108 - // 1.2 解码消息  
109 - // TODO @AI:应该只有初始使用 CODEC_TYPE 解析,后续基于 106 + // 1.2 解码消息(已认证连接使用其 codecType,未认证连接使用默认 CODEC_TYPE)
  107 + IotWebSocketConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket);
  108 + String codecType = connectionInfo != null ? connectionInfo.getCodecType() : CODEC_TYPE;
110 IotDeviceMessage deviceMessage; 109 IotDeviceMessage deviceMessage;
111 try { 110 try {
112 deviceMessage = deviceMessageService.decodeDeviceMessage( 111 deviceMessage = deviceMessageService.decodeDeviceMessage(
113 - message.getBytes(StandardCharsets.UTF_8), CODEC_TYPE); 112 + StrUtil.utf8Bytes(message), codecType);
114 if (deviceMessage == null) { 113 if (deviceMessage == null) {
115 throw new Exception("解码后消息为空"); 114 throw new Exception("解码后消息为空");
116 } 115 }
@@ -269,7 +268,8 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; { @@ -269,7 +268,8 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
269 .setDeviceId(device.getId()) 268 .setDeviceId(device.getId())
270 .setProductKey(device.getProductKey()) 269 .setProductKey(device.getProductKey())
271 .setDeviceName(device.getDeviceName()) 270 .setDeviceName(device.getDeviceName())
272 - .setClientId(clientId); 271 + .setClientId(clientId)
  272 + .setCodecType(CODEC_TYPE);
273 // 注册连接 273 // 注册连接
274 connectionManager.registerConnection(socket, device.getId(), connectionInfo); 274 connectionManager.registerConnection(socket, device.getId(), connectionInfo);
275 } 275 }
@@ -330,7 +330,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; { @@ -330,7 +330,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
330 IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, responseData, code, message); 330 IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, responseData, code, message);
331 331
332 byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, CODEC_TYPE); 332 byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, CODEC_TYPE);
333 - socket.writeTextMessage(new String(encodedData, StandardCharsets.UTF_8)); 333 + socket.writeTextMessage(StrUtil.utf8Str(encodedData));
334 } catch (Exception e) { 334 } catch (Exception e) {
335 log.error("[sendResponse][发送响应失败,requestId: {}]", requestId, e); 335 log.error("[sendResponse][发送响应失败,requestId: {}]", requestId, e);
336 } 336 }
@@ -472,7 +472,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; { @@ -472,7 +472,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
472 IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerResp, 0, null); 472 IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerResp, 0, null);
473 // 2. 发送响应 473 // 2. 发送响应
474 byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, CODEC_TYPE); 474 byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, CODEC_TYPE);
475 - socket.writeTextMessage(new String(encodedData, StandardCharsets.UTF_8)); 475 + socket.writeTextMessage(StrUtil.utf8Str(encodedData));
476 } catch (Exception e) { 476 } catch (Exception e) {
477 log.error("[sendRegisterSuccessResponse][发送注册成功响应失败,requestId: {}]", requestId, e); 477 log.error("[sendRegisterSuccessResponse][发送注册成功响应失败,requestId: {}]", requestId, e);
478 } 478 }
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotDirectDeviceWebSocketProtocolIntegrationTest.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.websocket; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.websocket;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
4 import cn.hutool.core.util.IdUtil; 4 import cn.hutool.core.util.IdUtil;
  5 +import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
6 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -12,15 +13,14 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; @@ -12,15 +13,14 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
12 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; 13 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
13 import cn.iocoder.yudao.module.iot.gateway.codec.websocket.IotWebSocketJsonDeviceMessageCodec; 14 import cn.iocoder.yudao.module.iot.gateway.codec.websocket.IotWebSocketJsonDeviceMessageCodec;
14 import io.vertx.core.Vertx; 15 import io.vertx.core.Vertx;
15 -import io.vertx.core.http.HttpClient;  
16 import io.vertx.core.http.WebSocket; 16 import io.vertx.core.http.WebSocket;
  17 +import io.vertx.core.http.WebSocketClient;
17 import io.vertx.core.http.WebSocketConnectOptions; 18 import io.vertx.core.http.WebSocketConnectOptions;
18 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
19 import org.junit.jupiter.api.AfterAll; 20 import org.junit.jupiter.api.AfterAll;
20 import org.junit.jupiter.api.BeforeAll; 21 import org.junit.jupiter.api.BeforeAll;
21 import org.junit.jupiter.api.Test; 22 import org.junit.jupiter.api.Test;
22 23
23 -import java.nio.charset.StandardCharsets;  
24 import java.util.concurrent.CountDownLatch; 24 import java.util.concurrent.CountDownLatch;
25 import java.util.concurrent.TimeUnit; 25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.atomic.AtomicReference; 26 import java.util.concurrent.atomic.AtomicReference;
@@ -81,119 +81,83 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest { @@ -81,119 +81,83 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
81 81
82 // ===================== 认证测试 ===================== 82 // ===================== 认证测试 =====================
83 83
84 - // TODO @AI:参考 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java 或 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotDirectDeviceMqttProtocolIntegrationTest.java 类,优化代码结构  
85 -  
86 /** 84 /**
87 * 认证测试:获取设备 Token 85 * 认证测试:获取设备 Token
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 - AtomicReference<String> responseRef = new AtomicReference<>();  
93 -  
94 - // 1. 创建 WebSocket 连接  
95 - HttpClient client = vertx.createHttpClient();  
96 - WebSocketConnectOptions options = new WebSocketConnectOptions()  
97 - .setHost(SERVER_HOST)  
98 - .setPort(SERVER_PORT)  
99 - .setURI(WS_PATH);  
100 -  
101 - // TODO @AI:这里有告警;Deprecate /instead use WebSocketClient.connect(WebSocketConnectOptions)  
102 - client.webSocket(options).onComplete(ar -> {  
103 - if (ar.succeeded()) {  
104 - WebSocket ws = ar.result();  
105 - log.info("[testAuth][WebSocket 连接成功]");  
106 -  
107 - // 设置消息处理器  
108 - ws.textMessageHandler(message -> {  
109 - log.info("[testAuth][收到响应: {}]", message);  
110 - responseRef.set(message);  
111 - ws.close();  
112 - latch.countDown();  
113 - });  
114 -  
115 - // 2. 构建认证消息  
116 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);  
117 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
118 - .setClientId(authInfo.getClientId())  
119 - .setUsername(authInfo.getUsername())  
120 - .setPassword(authInfo.getPassword());  
121 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
122 -  
123 - // 3. 编码并发送  
124 - byte[] payload = CODEC.encode(request);  
125 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
126 - log.info("[testAuth][发送认证请求: {}]", jsonMessage);  
127 - ws.writeTextMessage(jsonMessage);  
128 - } else {  
129 - log.error("[testAuth][WebSocket 连接失败]", ar.cause());  
130 - latch.countDown();  
131 - }  
132 - });  
133 -  
134 - // 4. 等待响应  
135 - boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);  
136 - if (completed && responseRef.get() != null) {  
137 - IotDeviceMessage response = CODEC.decode(responseRef.get().getBytes(StandardCharsets.UTF_8));  
138 - log.info("[testAuth][解码响应: {}]", response); 89 + // 1.1 构建认证消息
  90 + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
  91 + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
  92 + .setClientId(authInfo.getClientId())
  93 + .setUsername(authInfo.getUsername())
  94 + .setPassword(authInfo.getPassword());
  95 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  96 + // 1.2 编码
  97 + byte[] payload = CODEC.encode(request);
  98 + String jsonMessage = StrUtil.utf8Str(payload);
  99 + log.info("[testAuth][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  100 +
  101 + // 2.1 创建 WebSocket 连接(同步)
  102 + WebSocket ws = createWebSocketConnection();
  103 + log.info("[testAuth][WebSocket 连接成功]");
  104 +
  105 + // 2.2 发送并等待响应
  106 + String response = sendAndReceive(ws, jsonMessage);
  107 +
  108 + // 3. 解码响应
  109 + if (response != null) {
  110 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  111 + log.info("[testAuth][响应消息: {}]", responseMessage);
139 } else { 112 } else {
140 - log.warn("[testAuth][测试超时或未收到响应]"); 113 + log.warn("[testAuth][未收到响应]");
141 } 114 }
  115 +
  116 + // 4. 关闭连接
  117 + ws.close();
142 } 118 }
143 119
144 // ===================== 动态注册测试 ===================== 120 // ===================== 动态注册测试 =====================
145 121
146 /** 122 /**
147 * 直连设备动态注册测试(一型一密) 123 * 直连设备动态注册测试(一型一密)
  124 + * <p>
  125 + * 使用产品密钥(productSecret)验证身份,成功后返回设备密钥(deviceSecret)
  126 + * <p>
  127 + * 注意:此接口不需要认证
148 */ 128 */
149 @Test 129 @Test
150 public void testDeviceRegister() throws Exception { 130 public void testDeviceRegister() throws Exception {
151 - CountDownLatch latch = new CountDownLatch(1);  
152 - AtomicReference<String> responseRef = new AtomicReference<>();  
153 -  
154 - HttpClient client = vertx.createHttpClient();  
155 - WebSocketConnectOptions options = new WebSocketConnectOptions()  
156 - .setHost(SERVER_HOST)  
157 - .setPort(SERVER_PORT)  
158 - .setURI(WS_PATH);  
159 -  
160 - client.webSocket(options).onComplete(ar -> {  
161 - if (ar.succeeded()) {  
162 - WebSocket ws = ar.result();  
163 - log.info("[testDeviceRegister][WebSocket 连接成功]");  
164 -  
165 - ws.textMessageHandler(message -> {  
166 - log.info("[testDeviceRegister][收到响应: {}]", message);  
167 - responseRef.set(message);  
168 - ws.close();  
169 - latch.countDown();  
170 - });  
171 -  
172 - // 构建注册消息  
173 - IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO();  
174 - registerReqDTO.setProductKey(PRODUCT_KEY);  
175 - registerReqDTO.setDeviceName("test-ws-" + System.currentTimeMillis());  
176 - registerReqDTO.setProductSecret("test-product-secret");  
177 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(),  
178 - IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO, null, null, null);  
179 -  
180 - byte[] payload = CODEC.encode(request);  
181 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
182 - log.info("[testDeviceRegister][发送注册请求: {}]", jsonMessage);  
183 - ws.writeTextMessage(jsonMessage);  
184 - } else {  
185 - log.error("[testDeviceRegister][WebSocket 连接失败]", ar.cause());  
186 - latch.countDown();  
187 - }  
188 - });  
189 -  
190 - boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);  
191 - if (completed && responseRef.get() != null) {  
192 - IotDeviceMessage response = CODEC.decode(responseRef.get().getBytes(StandardCharsets.UTF_8));  
193 - log.info("[testDeviceRegister][解码响应: {}]", response); 131 + // 1.1 构建注册消息
  132 + IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO();
  133 + registerReqDTO.setProductKey(PRODUCT_KEY);
  134 + registerReqDTO.setDeviceName("test-ws-" + System.currentTimeMillis());
  135 + registerReqDTO.setProductSecret("test-product-secret");
  136 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(),
  137 + IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO, null, null, null);
  138 + // 1.2 编码
  139 + byte[] payload = CODEC.encode(request);
  140 + String jsonMessage = StrUtil.utf8Str(payload);
  141 + log.info("[testDeviceRegister][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  142 +
  143 + // 2.1 创建 WebSocket 连接(同步)
  144 + WebSocket ws = createWebSocketConnection();
  145 + log.info("[testDeviceRegister][WebSocket 连接成功]");
  146 +
  147 + // 2.2 发送并等待响应
  148 + String response = sendAndReceive(ws, jsonMessage);
  149 +
  150 + // 3. 解码响应
  151 + if (response != null) {
  152 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  153 + log.info("[testDeviceRegister][响应消息: {}]", responseMessage);
  154 + log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]");
194 } else { 155 } else {
195 - log.warn("[testDeviceRegister][测试超时或未收到响应]"); 156 + log.warn("[testDeviceRegister][未收到响应]");
196 } 157 }
  158 +
  159 + // 4. 关闭连接
  160 + ws.close();
197 } 161 }
198 162
199 // ===================== 直连设备属性上报测试 ===================== 163 // ===================== 直连设备属性上报测试 =====================
@@ -203,82 +167,40 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest { @@ -203,82 +167,40 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
203 */ 167 */
204 @Test 168 @Test
205 public void testPropertyPost() throws Exception { 169 public void testPropertyPost() throws Exception {
206 - CountDownLatch latch = new CountDownLatch(2); // 认证 + 属性上报  
207 - AtomicReference<String> authResponseRef = new AtomicReference<>();  
208 - AtomicReference<String> propertyResponseRef = new AtomicReference<>();  
209 -  
210 - HttpClient client = vertx.createHttpClient();  
211 - WebSocketConnectOptions options = new WebSocketConnectOptions()  
212 - .setHost(SERVER_HOST)  
213 - .setPort(SERVER_PORT)  
214 - .setURI(WS_PATH);  
215 -  
216 - client.webSocket(options).onComplete(ar -> {  
217 - if (ar.succeeded()) {  
218 - WebSocket ws = ar.result();  
219 - log.info("[testPropertyPost][WebSocket 连接成功]");  
220 -  
221 - final boolean[] authenticated = {false};  
222 -  
223 - ws.textMessageHandler(message -> {  
224 - log.info("[testPropertyPost][收到响应: {}]", message);  
225 - if (!authenticated[0]) {  
226 - authResponseRef.set(message);  
227 - authenticated[0] = true;  
228 - latch.countDown();  
229 -  
230 - // 认证成功后发送属性上报  
231 - IotDeviceMessage propertyRequest = IotDeviceMessage.of(  
232 - IdUtil.fastSimpleUUID(),  
233 - IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),  
234 - IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()  
235 - .put("width", 1)  
236 - .put("height", "2")  
237 - .build()),  
238 - null, null, null);  
239 - byte[] payload = CODEC.encode(propertyRequest);  
240 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
241 - log.info("[testPropertyPost][发送属性上报请求: {}]", jsonMessage);  
242 - ws.writeTextMessage(jsonMessage);  
243 - } else {  
244 - propertyResponseRef.set(message);  
245 - ws.close();  
246 - latch.countDown();  
247 - }  
248 - });  
249 -  
250 - // 先发送认证请求  
251 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);  
252 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
253 - .setClientId(authInfo.getClientId())  
254 - .setUsername(authInfo.getUsername())  
255 - .setPassword(authInfo.getPassword());  
256 - IotDeviceMessage authRequest = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
257 -  
258 - byte[] payload = CODEC.encode(authRequest);  
259 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
260 - log.info("[testPropertyPost][发送认证请求: {}]", jsonMessage);  
261 - ws.writeTextMessage(jsonMessage);  
262 - } else {  
263 - log.error("[testPropertyPost][WebSocket 连接失败]", ar.cause());  
264 - latch.countDown();  
265 - latch.countDown();  
266 - }  
267 - });  
268 -  
269 - boolean completed = latch.await(TIMEOUT_SECONDS * 2, TimeUnit.SECONDS);  
270 - if (completed) {  
271 - if (authResponseRef.get() != null) {  
272 - IotDeviceMessage authResponse = CODEC.decode(authResponseRef.get().getBytes(StandardCharsets.UTF_8));  
273 - log.info("[testPropertyPost][认证响应: {}]", authResponse);  
274 - }  
275 - if (propertyResponseRef.get() != null) {  
276 - IotDeviceMessage propertyResponse = CODEC.decode(propertyResponseRef.get().getBytes(StandardCharsets.UTF_8));  
277 - log.info("[testPropertyPost][属性上报响应: {}]", propertyResponse);  
278 - } 170 + // 1.1 创建 WebSocket 连接(同步)
  171 + WebSocket ws = createWebSocketConnection();
  172 + log.info("[testPropertyPost][WebSocket 连接成功]");
  173 +
  174 + // 1.2 先进行认证
  175 + IotDeviceMessage authResponse = authenticate(ws);
  176 + log.info("[testPropertyPost][认证响应: {}]", authResponse);
  177 +
  178 + // 2.1 构建属性上报消息
  179 + IotDeviceMessage request = IotDeviceMessage.of(
  180 + IdUtil.fastSimpleUUID(),
  181 + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
  182 + IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
  183 + .put("width", 1)
  184 + .put("height", "2")
  185 + .build()),
  186 + null, null, null);
  187 + // 2.2 编码
  188 + byte[] payload = CODEC.encode(request);
  189 + String jsonMessage = StrUtil.utf8Str(payload);
  190 + log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  191 +
  192 + // 3.1 发送并等待响应
  193 + String response = sendAndReceive(ws, jsonMessage);
  194 + // 3.2 解码响应
  195 + if (response != null) {
  196 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  197 + log.info("[testPropertyPost][响应消息: {}]", responseMessage);
279 } else { 198 } else {
280 - log.warn("[testPropertyPost][测试超时]"); 199 + log.warn("[testPropertyPost][未收到响应]");
281 } 200 }
  201 +
  202 + // 4. 关闭连接
  203 + ws.close();
282 } 204 }
283 205
284 // ===================== 直连设备事件上报测试 ===================== 206 // ===================== 直连设备事件上报测试 =====================
@@ -288,82 +210,111 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest { @@ -288,82 +210,111 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
288 */ 210 */
289 @Test 211 @Test
290 public void testEventPost() throws Exception { 212 public void testEventPost() throws Exception {
291 - CountDownLatch latch = new CountDownLatch(2); // 认证 + 事件上报  
292 - AtomicReference<String> authResponseRef = new AtomicReference<>();  
293 - AtomicReference<String> eventResponseRef = new AtomicReference<>(); 213 + // 1.1 创建 WebSocket 连接(同步)
  214 + WebSocket ws = createWebSocketConnection();
  215 + log.info("[testEventPost][WebSocket 连接成功]");
  216 +
  217 + // 1.2 先进行认证
  218 + IotDeviceMessage authResponse = authenticate(ws);
  219 + log.info("[testEventPost][认证响应: {}]", authResponse);
  220 +
  221 + // 2.1 构建事件上报消息
  222 + IotDeviceMessage request = IotDeviceMessage.of(
  223 + IdUtil.fastSimpleUUID(),
  224 + IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
  225 + IotDeviceEventPostReqDTO.of(
  226 + "eat",
  227 + MapUtil.<String, Object>builder().put("rice", 3).build(),
  228 + System.currentTimeMillis()),
  229 + null, null, null);
  230 + // 2.2 编码
  231 + byte[] payload = CODEC.encode(request);
  232 + String jsonMessage = StrUtil.utf8Str(payload);
  233 + log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  234 +
  235 + // 3.1 发送并等待响应
  236 + String response = sendAndReceive(ws, jsonMessage);
  237 + // 3.2 解码响应
  238 + if (response != null) {
  239 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  240 + log.info("[testEventPost][响应消息: {}]", responseMessage);
  241 + } else {
  242 + log.warn("[testEventPost][未收到响应]");
  243 + }
  244 +
  245 + // 4. 关闭连接
  246 + ws.close();
  247 + }
  248 +
  249 + // ===================== 辅助方法 =====================
294 250
295 - HttpClient client = vertx.createHttpClient(); 251 + /**
  252 + * 创建 WebSocket 连接(同步)
  253 + *
  254 + * @return WebSocket 连接
  255 + */
  256 + private WebSocket createWebSocketConnection() throws Exception {
  257 + WebSocketClient wsClient = vertx.createWebSocketClient();
296 WebSocketConnectOptions options = new WebSocketConnectOptions() 258 WebSocketConnectOptions options = new WebSocketConnectOptions()
297 .setHost(SERVER_HOST) 259 .setHost(SERVER_HOST)
298 .setPort(SERVER_PORT) 260 .setPort(SERVER_PORT)
299 .setURI(WS_PATH); 261 .setURI(WS_PATH);
  262 + return wsClient.connect(options).toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  263 + }
300 264
301 - client.webSocket(options).onComplete(ar -> {  
302 - if (ar.succeeded()) {  
303 - WebSocket ws = ar.result();  
304 - log.info("[testEventPost][WebSocket 连接成功]");  
305 -  
306 - final boolean[] authenticated = {false};  
307 -  
308 - ws.textMessageHandler(message -> {  
309 - log.info("[testEventPost][收到响应: {}]", message);  
310 - if (!authenticated[0]) {  
311 - authResponseRef.set(message);  
312 - authenticated[0] = true;  
313 - latch.countDown();  
314 -  
315 - // 认证成功后发送事件上报  
316 - IotDeviceMessage eventRequest = IotDeviceMessage.of(  
317 - IdUtil.fastSimpleUUID(),  
318 - IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),  
319 - IotDeviceEventPostReqDTO.of(  
320 - "eat",  
321 - MapUtil.<String, Object>builder().put("rice", 3).build(),  
322 - System.currentTimeMillis()),  
323 - null, null, null);  
324 - byte[] payload = CODEC.encode(eventRequest);  
325 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
326 - log.info("[testEventPost][发送事件上报请求: {}]", jsonMessage);  
327 - ws.writeTextMessage(jsonMessage);  
328 - } else {  
329 - eventResponseRef.set(message);  
330 - ws.close();  
331 - latch.countDown();  
332 - }  
333 - });  
334 -  
335 - // 先发送认证请求  
336 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);  
337 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
338 - .setClientId(authInfo.getClientId())  
339 - .setUsername(authInfo.getUsername())  
340 - .setPassword(authInfo.getPassword());  
341 - IotDeviceMessage authRequest = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
342 -  
343 - byte[] payload = CODEC.encode(authRequest);  
344 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
345 - log.info("[testEventPost][发送认证请求: {}]", jsonMessage);  
346 - ws.writeTextMessage(jsonMessage);  
347 - } else {  
348 - log.error("[testEventPost][WebSocket 连接失败]", ar.cause());  
349 - latch.countDown();  
350 - latch.countDown();  
351 - } 265 + /**
  266 + * 发送消息并等待响应(同步)
  267 + *
  268 + * @param ws WebSocket 连接
  269 + * @param message 请求消息
  270 + * @return 响应消息
  271 + */
  272 + public static String sendAndReceive(WebSocket ws, String message) throws Exception {
  273 + CountDownLatch latch = new CountDownLatch(1);
  274 + AtomicReference<String> responseRef = new AtomicReference<>();
  275 +
  276 + // 设置消息处理器
  277 + ws.textMessageHandler(response -> {
  278 + log.info("[sendAndReceive][收到响应: {}]", response);
  279 + responseRef.set(response);
  280 + latch.countDown();
352 }); 281 });
353 282
354 - boolean completed = latch.await(TIMEOUT_SECONDS * 2, TimeUnit.SECONDS);  
355 - if (completed) {  
356 - if (authResponseRef.get() != null) {  
357 - IotDeviceMessage authResponse = CODEC.decode(authResponseRef.get().getBytes(StandardCharsets.UTF_8));  
358 - log.info("[testEventPost][认证响应: {}]", authResponse);  
359 - }  
360 - if (eventResponseRef.get() != null) {  
361 - IotDeviceMessage eventResponse = CODEC.decode(eventResponseRef.get().getBytes(StandardCharsets.UTF_8));  
362 - log.info("[testEventPost][事件上报响应: {}]", eventResponse);  
363 - }  
364 - } else {  
365 - log.warn("[testEventPost][测试超时]"); 283 + // 发送请求
  284 + log.info("[sendAndReceive][发送请求: {}]", message);
  285 + ws.writeTextMessage(message);
  286 +
  287 + // 等待响应
  288 + boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  289 + if (!completed) {
  290 + log.warn("[sendAndReceive][等待响应超时]");
  291 + }
  292 + return responseRef.get();
  293 + }
  294 +
  295 + /**
  296 + * 执行设备认证(同步)
  297 + *
  298 + * @param ws WebSocket 连接
  299 + * @return 认证响应消息
  300 + */
  301 + private IotDeviceMessage authenticate(WebSocket ws) throws Exception {
  302 + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
  303 + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
  304 + .setClientId(authInfo.getClientId())
  305 + .setUsername(authInfo.getUsername())
  306 + .setPassword(authInfo.getPassword());
  307 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  308 +
  309 + byte[] payload = CODEC.encode(request);
  310 + String jsonMessage = StrUtil.utf8Str(payload);
  311 + log.info("[authenticate][发送认证请求: {}]", jsonMessage);
  312 +
  313 + String response = sendAndReceive(ws, jsonMessage);
  314 + if (response != null) {
  315 + return CODEC.decode(StrUtil.utf8Bytes(response));
366 } 316 }
  317 + return null;
367 } 318 }
368 319
369 } 320 }
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotGatewayDeviceWebSocketProtocolIntegrationTest.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.websocket; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.websocket;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
4 import cn.hutool.core.util.IdUtil; 4 import cn.hutool.core.util.IdUtil;
  5 +import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
6 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -15,15 +16,14 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; @@ -15,15 +16,14 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
15 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; 16 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
16 import cn.iocoder.yudao.module.iot.gateway.codec.websocket.IotWebSocketJsonDeviceMessageCodec; 17 import cn.iocoder.yudao.module.iot.gateway.codec.websocket.IotWebSocketJsonDeviceMessageCodec;
17 import io.vertx.core.Vertx; 18 import io.vertx.core.Vertx;
18 -import io.vertx.core.http.HttpClient;  
19 import io.vertx.core.http.WebSocket; 19 import io.vertx.core.http.WebSocket;
  20 +import io.vertx.core.http.WebSocketClient;
20 import io.vertx.core.http.WebSocketConnectOptions; 21 import io.vertx.core.http.WebSocketConnectOptions;
21 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
22 import org.junit.jupiter.api.AfterAll; 23 import org.junit.jupiter.api.AfterAll;
23 import org.junit.jupiter.api.BeforeAll; 24 import org.junit.jupiter.api.BeforeAll;
24 import org.junit.jupiter.api.Test; 25 import org.junit.jupiter.api.Test;
25 26
26 -import java.nio.charset.StandardCharsets;  
27 import java.util.Collections; 27 import java.util.Collections;
28 import java.util.List; 28 import java.util.List;
29 import java.util.Map; 29 import java.util.Map;
@@ -100,53 +100,36 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest { @@ -100,53 +100,36 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
100 */ 100 */
101 @Test 101 @Test
102 public void testAuth() throws Exception { 102 public void testAuth() throws Exception {
103 - CountDownLatch latch = new CountDownLatch(1);  
104 - AtomicReference<String> responseRef = new AtomicReference<>();  
105 -  
106 - HttpClient client = vertx.createHttpClient();  
107 - WebSocketConnectOptions options = new WebSocketConnectOptions()  
108 - .setHost(SERVER_HOST)  
109 - .setPort(SERVER_PORT)  
110 - .setURI(WS_PATH);  
111 -  
112 - client.webSocket(options).onComplete(ar -> {  
113 - if (ar.succeeded()) {  
114 - WebSocket ws = ar.result();  
115 - log.info("[testAuth][WebSocket 连接成功]");  
116 -  
117 - ws.textMessageHandler(message -> {  
118 - log.info("[testAuth][收到响应: {}]", message);  
119 - responseRef.set(message);  
120 - ws.close();  
121 - latch.countDown();  
122 - });  
123 -  
124 - // 构建认证消息  
125 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(  
126 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);  
127 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
128 - .setClientId(authInfo.getClientId())  
129 - .setUsername(authInfo.getUsername())  
130 - .setPassword(authInfo.getPassword());  
131 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
132 -  
133 - byte[] payload = CODEC.encode(request);  
134 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
135 - log.info("[testAuth][发送认证请求: {}]", jsonMessage);  
136 - ws.writeTextMessage(jsonMessage);  
137 - } else {  
138 - log.error("[testAuth][WebSocket 连接失败]", ar.cause());  
139 - latch.countDown();  
140 - }  
141 - });  
142 -  
143 - boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);  
144 - if (completed && responseRef.get() != null) {  
145 - IotDeviceMessage response = CODEC.decode(responseRef.get().getBytes(StandardCharsets.UTF_8));  
146 - log.info("[testAuth][解码响应: {}]", response); 103 + // 1.1 构建认证消息
  104 + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
  105 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);
  106 + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
  107 + .setClientId(authInfo.getClientId())
  108 + .setUsername(authInfo.getUsername())
  109 + .setPassword(authInfo.getPassword());
  110 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  111 + // 1.2 编码
  112 + byte[] payload = CODEC.encode(request);
  113 + String jsonMessage = StrUtil.utf8Str(payload);
  114 + log.info("[testAuth][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  115 +
  116 + // 2.1 创建 WebSocket 连接(同步)
  117 + WebSocket ws = createWebSocketConnection();
  118 + log.info("[testAuth][WebSocket 连接成功]");
  119 +
  120 + // 2.2 发送并等待响应
  121 + String response = sendAndReceive(ws, jsonMessage);
  122 +
  123 + // 3. 解码响应
  124 + if (response != null) {
  125 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  126 + log.info("[testAuth][响应消息: {}]", responseMessage);
147 } else { 127 } else {
148 - log.warn("[testAuth][测试超时或未收到响应]"); 128 + log.warn("[testAuth][未收到响应]");
149 } 129 }
  130 +
  131 + // 4. 关闭连接
  132 + ws.close();
150 } 133 }
151 134
152 // ===================== 拓扑管理测试 ===================== 135 // ===================== 拓扑管理测试 =====================
@@ -156,23 +139,46 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest { @@ -156,23 +139,46 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
156 */ 139 */
157 @Test 140 @Test
158 public void testTopoAdd() throws Exception { 141 public void testTopoAdd() throws Exception {
159 - executeAuthenticatedRequest("testTopoAdd", ws -> {  
160 - // 构建子设备认证信息  
161 - IotDeviceAuthReqDTO subAuthInfo = IotDeviceAuthUtils.getAuthInfo(  
162 - SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME, SUB_DEVICE_SECRET);  
163 - IotDeviceAuthReqDTO subDeviceAuth = new IotDeviceAuthReqDTO()  
164 - .setClientId(subAuthInfo.getClientId())  
165 - .setUsername(subAuthInfo.getUsername())  
166 - .setPassword(subAuthInfo.getPassword());  
167 - // 构建请求参数  
168 - IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO();  
169 - params.setSubDevices(Collections.singletonList(subDeviceAuth));  
170 - return IotDeviceMessage.of(  
171 - IdUtil.fastSimpleUUID(),  
172 - IotDeviceMessageMethodEnum.TOPO_ADD.getMethod(),  
173 - params,  
174 - null, null, null);  
175 - }); 142 + // 1.1 创建 WebSocket 连接(同步)
  143 + WebSocket ws = createWebSocketConnection();
  144 + log.info("[testTopoAdd][WebSocket 连接成功]");
  145 +
  146 + // 1.2 先进行认证
  147 + IotDeviceMessage authResponse = authenticate(ws);
  148 + log.info("[testTopoAdd][认证响应: {}]", authResponse);
  149 +
  150 + // 2.1 构建子设备认证信息
  151 + IotDeviceAuthReqDTO subAuthInfo = IotDeviceAuthUtils.getAuthInfo(
  152 + SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME, SUB_DEVICE_SECRET);
  153 + IotDeviceAuthReqDTO subDeviceAuth = new IotDeviceAuthReqDTO()
  154 + .setClientId(subAuthInfo.getClientId())
  155 + .setUsername(subAuthInfo.getUsername())
  156 + .setPassword(subAuthInfo.getPassword());
  157 + // 2.2 构建请求参数
  158 + IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO();
  159 + params.setSubDevices(Collections.singletonList(subDeviceAuth));
  160 + IotDeviceMessage request = IotDeviceMessage.of(
  161 + IdUtil.fastSimpleUUID(),
  162 + IotDeviceMessageMethodEnum.TOPO_ADD.getMethod(),
  163 + params,
  164 + null, null, null);
  165 + // 2.3 编码
  166 + byte[] payload = CODEC.encode(request);
  167 + String jsonMessage = StrUtil.utf8Str(payload);
  168 + log.info("[testTopoAdd][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  169 +
  170 + // 3.1 发送并等待响应
  171 + String response = sendAndReceive(ws, jsonMessage);
  172 + // 3.2 解码响应
  173 + if (response != null) {
  174 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  175 + log.info("[testTopoAdd][响应消息: {}]", responseMessage);
  176 + } else {
  177 + log.warn("[testTopoAdd][未收到响应]");
  178 + }
  179 +
  180 + // 4. 关闭连接
  181 + ws.close();
176 } 182 }
177 183
178 /** 184 /**
@@ -180,16 +186,40 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest { @@ -180,16 +186,40 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
180 */ 186 */
181 @Test 187 @Test
182 public void testTopoDelete() throws Exception { 188 public void testTopoDelete() throws Exception {
183 - executeAuthenticatedRequest("testTopoDelete", ws -> {  
184 - IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO();  
185 - params.setSubDevices(Collections.singletonList(  
186 - new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME)));  
187 - return IotDeviceMessage.of(  
188 - IdUtil.fastSimpleUUID(),  
189 - IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod(),  
190 - params,  
191 - null, null, null);  
192 - }); 189 + // 1.1 创建 WebSocket 连接(同步)
  190 + WebSocket ws = createWebSocketConnection();
  191 + log.info("[testTopoDelete][WebSocket 连接成功]");
  192 +
  193 + // 1.2 先进行认证
  194 + IotDeviceMessage authResponse = authenticate(ws);
  195 + log.info("[testTopoDelete][认证响应: {}]", authResponse);
  196 +
  197 + // 2.1 构建请求参数
  198 + IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO();
  199 + params.setSubDevices(Collections.singletonList(
  200 + new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME)));
  201 + IotDeviceMessage request = IotDeviceMessage.of(
  202 + IdUtil.fastSimpleUUID(),
  203 + IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod(),
  204 + params,
  205 + null, null, null);
  206 + // 2.2 编码
  207 + byte[] payload = CODEC.encode(request);
  208 + String jsonMessage = StrUtil.utf8Str(payload);
  209 + log.info("[testTopoDelete][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  210 +
  211 + // 3.1 发送并等待响应
  212 + String response = sendAndReceive(ws, jsonMessage);
  213 + // 3.2 解码响应
  214 + if (response != null) {
  215 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  216 + log.info("[testTopoDelete][响应消息: {}]", responseMessage);
  217 + } else {
  218 + log.warn("[testTopoDelete][未收到响应]");
  219 + }
  220 +
  221 + // 4. 关闭连接
  222 + ws.close();
193 } 223 }
194 224
195 /** 225 /**
@@ -197,14 +227,38 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest { @@ -197,14 +227,38 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
197 */ 227 */
198 @Test 228 @Test
199 public void testTopoGet() throws Exception { 229 public void testTopoGet() throws Exception {
200 - executeAuthenticatedRequest("testTopoGet", ws -> {  
201 - IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO();  
202 - return IotDeviceMessage.of(  
203 - IdUtil.fastSimpleUUID(),  
204 - IotDeviceMessageMethodEnum.TOPO_GET.getMethod(),  
205 - params,  
206 - null, null, null);  
207 - }); 230 + // 1.1 创建 WebSocket 连接(同步)
  231 + WebSocket ws = createWebSocketConnection();
  232 + log.info("[testTopoGet][WebSocket 连接成功]");
  233 +
  234 + // 1.2 先进行认证
  235 + IotDeviceMessage authResponse = authenticate(ws);
  236 + log.info("[testTopoGet][认证响应: {}]", authResponse);
  237 +
  238 + // 2.1 构建请求参数
  239 + IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO();
  240 + IotDeviceMessage request = IotDeviceMessage.of(
  241 + IdUtil.fastSimpleUUID(),
  242 + IotDeviceMessageMethodEnum.TOPO_GET.getMethod(),
  243 + params,
  244 + null, null, null);
  245 + // 2.2 编码
  246 + byte[] payload = CODEC.encode(request);
  247 + String jsonMessage = StrUtil.utf8Str(payload);
  248 + log.info("[testTopoGet][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  249 +
  250 + // 3.1 发送并等待响应
  251 + String response = sendAndReceive(ws, jsonMessage);
  252 + // 3.2 解码响应
  253 + if (response != null) {
  254 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  255 + log.info("[testTopoGet][响应消息: {}]", responseMessage);
  256 + } else {
  257 + log.warn("[testTopoGet][未收到响应]");
  258 + }
  259 +
  260 + // 4. 关闭连接
  261 + ws.close();
208 } 262 }
209 263
210 // ===================== 子设备注册测试 ===================== 264 // ===================== 子设备注册测试 =====================
@@ -214,16 +268,40 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest { @@ -214,16 +268,40 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
214 */ 268 */
215 @Test 269 @Test
216 public void testSubDeviceRegister() throws Exception { 270 public void testSubDeviceRegister() throws Exception {
217 - executeAuthenticatedRequest("testSubDeviceRegister", ws -> {  
218 - IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO();  
219 - subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY);  
220 - subDevice.setDeviceName("mougezishebei-ws");  
221 - return IotDeviceMessage.of(  
222 - IdUtil.fastSimpleUUID(),  
223 - IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod(),  
224 - Collections.singletonList(subDevice),  
225 - null, null, null);  
226 - }); 271 + // 1.1 创建 WebSocket 连接(同步)
  272 + WebSocket ws = createWebSocketConnection();
  273 + log.info("[testSubDeviceRegister][WebSocket 连接成功]");
  274 +
  275 + // 1.2 先进行认证
  276 + IotDeviceMessage authResponse = authenticate(ws);
  277 + log.info("[testSubDeviceRegister][认证响应: {}]", authResponse);
  278 +
  279 + // 2.1 构建请求参数
  280 + IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO();
  281 + subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY);
  282 + subDevice.setDeviceName("mougezishebei-ws");
  283 + IotDeviceMessage request = IotDeviceMessage.of(
  284 + IdUtil.fastSimpleUUID(),
  285 + IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod(),
  286 + Collections.singletonList(subDevice),
  287 + null, null, null);
  288 + // 2.2 编码
  289 + byte[] payload = CODEC.encode(request);
  290 + String jsonMessage = StrUtil.utf8Str(payload);
  291 + log.info("[testSubDeviceRegister][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  292 +
  293 + // 3.1 发送并等待响应
  294 + String response = sendAndReceive(ws, jsonMessage);
  295 + // 3.2 解码响应
  296 + if (response != null) {
  297 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  298 + log.info("[testSubDeviceRegister][响应消息: {}]", responseMessage);
  299 + } else {
  300 + log.warn("[testSubDeviceRegister][未收到响应]");
  301 + }
  302 +
  303 + // 4. 关闭连接
  304 + ws.close();
227 } 305 }
228 306
229 // ===================== 批量上报测试 ===================== 307 // ===================== 批量上报测试 =====================
@@ -233,126 +311,140 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest { @@ -233,126 +311,140 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
233 */ 311 */
234 @Test 312 @Test
235 public void testPropertyPackPost() throws Exception { 313 public void testPropertyPackPost() throws Exception {
236 - executeAuthenticatedRequest("testPropertyPackPost", ws -> {  
237 - // 构建【网关设备】自身属性  
238 - Map<String, Object> gatewayProperties = MapUtil.<String, Object>builder()  
239 - .put("temperature", 25.5)  
240 - .build();  
241 - // 构建【网关设备】自身事件  
242 - IotDevicePropertyPackPostReqDTO.EventValue gatewayEvent = new IotDevicePropertyPackPostReqDTO.EventValue();  
243 - gatewayEvent.setValue(MapUtil.builder().put("message", "gateway started").build());  
244 - gatewayEvent.setTime(System.currentTimeMillis());  
245 - Map<String, IotDevicePropertyPackPostReqDTO.EventValue> gatewayEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()  
246 - .put("statusReport", gatewayEvent)  
247 - .build();  
248 - // 构建【网关子设备】属性  
249 - Map<String, Object> subDeviceProperties = MapUtil.<String, Object>builder()  
250 - .put("power", 100)  
251 - .build();  
252 - // 构建【网关子设备】事件  
253 - IotDevicePropertyPackPostReqDTO.EventValue subDeviceEvent = new IotDevicePropertyPackPostReqDTO.EventValue();  
254 - subDeviceEvent.setValue(MapUtil.builder().put("errorCode", 0).build());  
255 - subDeviceEvent.setTime(System.currentTimeMillis());  
256 - Map<String, IotDevicePropertyPackPostReqDTO.EventValue> subDeviceEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()  
257 - .put("healthCheck", subDeviceEvent)  
258 - .build();  
259 - // 构建子设备数据  
260 - IotDevicePropertyPackPostReqDTO.SubDeviceData subDeviceData = new IotDevicePropertyPackPostReqDTO.SubDeviceData();  
261 - subDeviceData.setIdentity(new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME));  
262 - subDeviceData.setProperties(subDeviceProperties);  
263 - subDeviceData.setEvents(subDeviceEvents);  
264 - // 构建请求参数  
265 - IotDevicePropertyPackPostReqDTO params = new IotDevicePropertyPackPostReqDTO();  
266 - params.setProperties(gatewayProperties);  
267 - params.setEvents(gatewayEvents);  
268 - params.setSubDevices(List.of(subDeviceData));  
269 - return IotDeviceMessage.of(  
270 - IdUtil.fastSimpleUUID(),  
271 - IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod(),  
272 - params,  
273 - null, null, null);  
274 - }); 314 + // 1.1 创建 WebSocket 连接(同步)
  315 + WebSocket ws = createWebSocketConnection();
  316 + log.info("[testPropertyPackPost][WebSocket 连接成功]");
  317 +
  318 + // 1.2 先进行认证
  319 + IotDeviceMessage authResponse = authenticate(ws);
  320 + log.info("[testPropertyPackPost][认证响应: {}]", authResponse);
  321 +
  322 + // 2.1 构建【网关设备】自身属性
  323 + Map<String, Object> gatewayProperties = MapUtil.<String, Object>builder()
  324 + .put("temperature", 25.5)
  325 + .build();
  326 + // 2.2 构建【网关设备】自身事件
  327 + IotDevicePropertyPackPostReqDTO.EventValue gatewayEvent = new IotDevicePropertyPackPostReqDTO.EventValue();
  328 + gatewayEvent.setValue(MapUtil.builder().put("message", "gateway started").build());
  329 + gatewayEvent.setTime(System.currentTimeMillis());
  330 + Map<String, IotDevicePropertyPackPostReqDTO.EventValue> gatewayEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
  331 + .put("statusReport", gatewayEvent)
  332 + .build();
  333 + // 2.3 构建【网关子设备】属性
  334 + Map<String, Object> subDeviceProperties = MapUtil.<String, Object>builder()
  335 + .put("power", 100)
  336 + .build();
  337 + // 2.4 构建【网关子设备】事件
  338 + IotDevicePropertyPackPostReqDTO.EventValue subDeviceEvent = new IotDevicePropertyPackPostReqDTO.EventValue();
  339 + subDeviceEvent.setValue(MapUtil.builder().put("errorCode", 0).build());
  340 + subDeviceEvent.setTime(System.currentTimeMillis());
  341 + Map<String, IotDevicePropertyPackPostReqDTO.EventValue> subDeviceEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
  342 + .put("healthCheck", subDeviceEvent)
  343 + .build();
  344 + // 2.5 构建子设备数据
  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 + // 2.6 构建请求参数
  350 + IotDevicePropertyPackPostReqDTO params = new IotDevicePropertyPackPostReqDTO();
  351 + params.setProperties(gatewayProperties);
  352 + params.setEvents(gatewayEvents);
  353 + params.setSubDevices(List.of(subDeviceData));
  354 + IotDeviceMessage request = IotDeviceMessage.of(
  355 + IdUtil.fastSimpleUUID(),
  356 + IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod(),
  357 + params,
  358 + null, null, null);
  359 + // 2.7 编码
  360 + byte[] payload = CODEC.encode(request);
  361 + String jsonMessage = StrUtil.utf8Str(payload);
  362 + log.info("[testPropertyPackPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  363 +
  364 + // 3.1 发送并等待响应
  365 + String response = sendAndReceive(ws, jsonMessage);
  366 + // 3.2 解码响应
  367 + if (response != null) {
  368 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  369 + log.info("[testPropertyPackPost][响应消息: {}]", responseMessage);
  370 + } else {
  371 + log.warn("[testPropertyPackPost][未收到响应]");
  372 + }
  373 +
  374 + // 4. 关闭连接
  375 + ws.close();
275 } 376 }
276 377
277 // ===================== 辅助方法 ===================== 378 // ===================== 辅助方法 =====================
278 379
279 /** 380 /**
280 - * 执行需要认证的请求 381 + * 创建 WebSocket 连接(同步)
281 * 382 *
282 - * @param testName 测试名称  
283 - * @param requestSupplier 请求消息提供者 383 + * @return WebSocket 连接
284 */ 384 */
285 - private void executeAuthenticatedRequest(String testName, java.util.function.Function<WebSocket, IotDeviceMessage> requestSupplier) throws Exception {  
286 - CountDownLatch latch = new CountDownLatch(2);  
287 - AtomicReference<String> authResponseRef = new AtomicReference<>();  
288 - AtomicReference<String> businessResponseRef = new AtomicReference<>();  
289 -  
290 - HttpClient client = vertx.createHttpClient(); 385 + private WebSocket createWebSocketConnection() throws Exception {
  386 + WebSocketClient wsClient = vertx.createWebSocketClient();
291 WebSocketConnectOptions options = new WebSocketConnectOptions() 387 WebSocketConnectOptions options = new WebSocketConnectOptions()
292 .setHost(SERVER_HOST) 388 .setHost(SERVER_HOST)
293 .setPort(SERVER_PORT) 389 .setPort(SERVER_PORT)
294 .setURI(WS_PATH); 390 .setURI(WS_PATH);
  391 + return wsClient.connect(options).toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  392 + }
  393 +
  394 + /**
  395 + * 发送消息并等待响应(同步)
  396 + *
  397 + * @param ws WebSocket 连接
  398 + * @param message 请求消息
  399 + * @return 响应消息
  400 + */
  401 + private String sendAndReceive(WebSocket ws, String message) throws Exception {
  402 + CountDownLatch latch = new CountDownLatch(1);
  403 + AtomicReference<String> responseRef = new AtomicReference<>();
295 404
296 - client.webSocket(options).onComplete(ar -> {  
297 - if (ar.succeeded()) {  
298 - WebSocket ws = ar.result();  
299 - log.info("[{}][WebSocket 连接成功]", testName);  
300 -  
301 - final boolean[] authenticated = {false};  
302 -  
303 - ws.textMessageHandler(message -> {  
304 - log.info("[{}][收到响应: {}]", testName, message);  
305 - if (!authenticated[0]) {  
306 - authResponseRef.set(message);  
307 - authenticated[0] = true;  
308 - latch.countDown();  
309 -  
310 - // 认证成功后发送业务请求  
311 - IotDeviceMessage businessRequest = requestSupplier.apply(ws);  
312 - byte[] payload = CODEC.encode(businessRequest);  
313 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
314 - log.info("[{}][发送业务请求: {}]", testName, jsonMessage);  
315 - ws.writeTextMessage(jsonMessage);  
316 - } else {  
317 - businessResponseRef.set(message);  
318 - ws.close();  
319 - latch.countDown();  
320 - }  
321 - });  
322 -  
323 - // 先发送认证请求  
324 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(  
325 - GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);  
326 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
327 - .setClientId(authInfo.getClientId())  
328 - .setUsername(authInfo.getUsername())  
329 - .setPassword(authInfo.getPassword());  
330 - IotDeviceMessage authRequest = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
331 -  
332 - byte[] payload = CODEC.encode(authRequest);  
333 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
334 - log.info("[{}][发送认证请求: {}]", testName, jsonMessage);  
335 - ws.writeTextMessage(jsonMessage);  
336 - } else {  
337 - log.error("[{}][WebSocket 连接失败]", testName, ar.cause());  
338 - latch.countDown();  
339 - latch.countDown();  
340 - } 405 + // 设置消息处理器
  406 + ws.textMessageHandler(response -> {
  407 + log.info("[sendAndReceive][收到响应: {}]", response);
  408 + responseRef.set(response);
  409 + latch.countDown();
341 }); 410 });
342 411
343 - boolean completed = latch.await(TIMEOUT_SECONDS * 2, TimeUnit.SECONDS);  
344 - if (completed) {  
345 - if (authResponseRef.get() != null) {  
346 - IotDeviceMessage authResponse = CODEC.decode(authResponseRef.get().getBytes(StandardCharsets.UTF_8));  
347 - log.info("[{}][认证响应: {}]", testName, authResponse);  
348 - }  
349 - if (businessResponseRef.get() != null) {  
350 - IotDeviceMessage businessResponse = CODEC.decode(businessResponseRef.get().getBytes(StandardCharsets.UTF_8));  
351 - log.info("[{}][业务响应: {}]", testName, businessResponse);  
352 - }  
353 - } else {  
354 - log.warn("[{}][测试超时]", testName); 412 + // 发送请求
  413 + log.info("[sendAndReceive][发送请求: {}]", message);
  414 + ws.writeTextMessage(message);
  415 +
  416 + // 等待响应
  417 + boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  418 + if (!completed) {
  419 + log.warn("[sendAndReceive][等待响应超时]");
  420 + }
  421 + return responseRef.get();
  422 + }
  423 +
  424 + /**
  425 + * 执行网关设备认证(同步)
  426 + *
  427 + * @param ws WebSocket 连接
  428 + * @return 认证响应消息
  429 + */
  430 + private IotDeviceMessage authenticate(WebSocket ws) throws Exception {
  431 + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
  432 + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);
  433 + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
  434 + .setClientId(authInfo.getClientId())
  435 + .setUsername(authInfo.getUsername())
  436 + .setPassword(authInfo.getPassword());
  437 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  438 +
  439 + byte[] payload = CODEC.encode(request);
  440 + String jsonMessage = StrUtil.utf8Str(payload);
  441 + log.info("[authenticate][发送认证请求: {}]", jsonMessage);
  442 +
  443 + String response = sendAndReceive(ws, jsonMessage);
  444 + if (response != null) {
  445 + return CODEC.decode(StrUtil.utf8Bytes(response));
355 } 446 }
  447 + return null;
356 } 448 }
357 449
358 } 450 }
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotGatewaySubDeviceWebSocketProtocolIntegrationTest.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.websocket; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.websocket;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
4 import cn.hutool.core.util.IdUtil; 4 import cn.hutool.core.util.IdUtil;
  5 +import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
6 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -11,15 +12,14 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; @@ -11,15 +12,14 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
11 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; 12 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
12 import cn.iocoder.yudao.module.iot.gateway.codec.websocket.IotWebSocketJsonDeviceMessageCodec; 13 import cn.iocoder.yudao.module.iot.gateway.codec.websocket.IotWebSocketJsonDeviceMessageCodec;
13 import io.vertx.core.Vertx; 14 import io.vertx.core.Vertx;
14 -import io.vertx.core.http.HttpClient;  
15 import io.vertx.core.http.WebSocket; 15 import io.vertx.core.http.WebSocket;
  16 +import io.vertx.core.http.WebSocketClient;
16 import io.vertx.core.http.WebSocketConnectOptions; 17 import io.vertx.core.http.WebSocketConnectOptions;
17 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
18 import org.junit.jupiter.api.AfterAll; 19 import org.junit.jupiter.api.AfterAll;
19 import org.junit.jupiter.api.BeforeAll; 20 import org.junit.jupiter.api.BeforeAll;
20 import org.junit.jupiter.api.Test; 21 import org.junit.jupiter.api.Test;
21 22
22 -import java.nio.charset.StandardCharsets;  
23 import java.util.concurrent.CountDownLatch; 23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.TimeUnit; 24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.atomic.AtomicReference; 25 import java.util.concurrent.atomic.AtomicReference;
@@ -87,52 +87,35 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest { @@ -87,52 +87,35 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest {
87 */ 87 */
88 @Test 88 @Test
89 public void testAuth() throws Exception { 89 public void testAuth() throws Exception {
90 - CountDownLatch latch = new CountDownLatch(1);  
91 - AtomicReference<String> responseRef = new AtomicReference<>();  
92 -  
93 - HttpClient client = vertx.createHttpClient();  
94 - WebSocketConnectOptions options = new WebSocketConnectOptions()  
95 - .setHost(SERVER_HOST)  
96 - .setPort(SERVER_PORT)  
97 - .setURI(WS_PATH);  
98 -  
99 - client.webSocket(options).onComplete(ar -> {  
100 - if (ar.succeeded()) {  
101 - WebSocket ws = ar.result();  
102 - log.info("[testAuth][WebSocket 连接成功]");  
103 -  
104 - ws.textMessageHandler(message -> {  
105 - log.info("[testAuth][收到响应: {}]", message);  
106 - responseRef.set(message);  
107 - ws.close();  
108 - latch.countDown();  
109 - });  
110 -  
111 - // 构建认证消息  
112 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);  
113 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
114 - .setClientId(authInfo.getClientId())  
115 - .setUsername(authInfo.getUsername())  
116 - .setPassword(authInfo.getPassword());  
117 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
118 -  
119 - byte[] payload = CODEC.encode(request);  
120 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
121 - log.info("[testAuth][发送认证请求: {}]", jsonMessage);  
122 - ws.writeTextMessage(jsonMessage);  
123 - } else {  
124 - log.error("[testAuth][WebSocket 连接失败]", ar.cause());  
125 - latch.countDown();  
126 - }  
127 - });  
128 -  
129 - boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);  
130 - if (completed && responseRef.get() != null) {  
131 - IotDeviceMessage response = CODEC.decode(responseRef.get().getBytes(StandardCharsets.UTF_8));  
132 - log.info("[testAuth][解码响应: {}]", response); 90 + // 1.1 构建认证消息
  91 + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
  92 + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
  93 + .setClientId(authInfo.getClientId())
  94 + .setUsername(authInfo.getUsername())
  95 + .setPassword(authInfo.getPassword());
  96 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  97 + // 1.2 编码
  98 + byte[] payload = CODEC.encode(request);
  99 + String jsonMessage = StrUtil.utf8Str(payload);
  100 + log.info("[testAuth][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  101 +
  102 + // 2.1 创建 WebSocket 连接(同步)
  103 + WebSocket ws = createWebSocketConnection();
  104 + log.info("[testAuth][WebSocket 连接成功]");
  105 +
  106 + // 2.2 发送并等待响应
  107 + String response = sendAndReceive(ws, jsonMessage);
  108 +
  109 + // 3. 解码响应
  110 + if (response != null) {
  111 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  112 + log.info("[testAuth][响应消息: {}]", responseMessage);
133 } else { 113 } else {
134 - log.warn("[testAuth][测试超时或未收到响应]"); 114 + log.warn("[testAuth][未收到响应]");
135 } 115 }
  116 +
  117 + // 4. 关闭连接
  118 + ws.close();
136 } 119 }
137 120
138 // ===================== 子设备属性上报测试 ===================== 121 // ===================== 子设备属性上报测试 =====================
@@ -142,18 +125,42 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest { @@ -142,18 +125,42 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest {
142 */ 125 */
143 @Test 126 @Test
144 public void testPropertyPost() throws Exception { 127 public void testPropertyPost() throws Exception {
145 - executeAuthenticatedRequest("testPropertyPost", ws -> {  
146 - log.info("[testPropertyPost][子设备属性上报 - 请求实际由 Gateway 代为转发]");  
147 - return IotDeviceMessage.of(  
148 - IdUtil.fastSimpleUUID(),  
149 - IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),  
150 - IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()  
151 - .put("power", 100)  
152 - .put("status", "online")  
153 - .put("temperature", 36.5)  
154 - .build()),  
155 - null, null, null);  
156 - }); 128 + // 1.1 创建 WebSocket 连接(同步)
  129 + WebSocket ws = createWebSocketConnection();
  130 + log.info("[testPropertyPost][WebSocket 连接成功]");
  131 +
  132 + // 1.2 先进行认证
  133 + IotDeviceMessage authResponse = authenticate(ws);
  134 + log.info("[testPropertyPost][认证响应: {}]", authResponse);
  135 + log.info("[testPropertyPost][子设备属性上报 - 请求实际由 Gateway 代为转发]");
  136 +
  137 + // 2.1 构建属性上报消息
  138 + IotDeviceMessage request = IotDeviceMessage.of(
  139 + IdUtil.fastSimpleUUID(),
  140 + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
  141 + IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
  142 + .put("power", 100)
  143 + .put("status", "online")
  144 + .put("temperature", 36.5)
  145 + .build()),
  146 + null, null, null);
  147 + // 2.2 编码
  148 + byte[] payload = CODEC.encode(request);
  149 + String jsonMessage = StrUtil.utf8Str(payload);
  150 + log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  151 +
  152 + // 3.1 发送并等待响应
  153 + String response = sendAndReceive(ws, jsonMessage);
  154 + // 3.2 解码响应
  155 + if (response != null) {
  156 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  157 + log.info("[testPropertyPost][响应消息: {}]", responseMessage);
  158 + } else {
  159 + log.warn("[testPropertyPost][未收到响应]");
  160 + }
  161 +
  162 + // 4. 关闭连接
  163 + ws.close();
157 } 164 }
158 165
159 // ===================== 子设备事件上报测试 ===================== 166 // ===================== 子设备事件上报测试 =====================
@@ -163,102 +170,117 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest { @@ -163,102 +170,117 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest {
163 */ 170 */
164 @Test 171 @Test
165 public void testEventPost() throws Exception { 172 public void testEventPost() throws Exception {
166 - executeAuthenticatedRequest("testEventPost", ws -> {  
167 - log.info("[testEventPost][子设备事件上报 - 请求实际由 Gateway 代为转发]");  
168 - return IotDeviceMessage.of(  
169 - IdUtil.fastSimpleUUID(),  
170 - IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),  
171 - IotDeviceEventPostReqDTO.of(  
172 - "alarm",  
173 - MapUtil.<String, Object>builder()  
174 - .put("level", "warning")  
175 - .put("message", "temperature too high")  
176 - .put("threshold", 40)  
177 - .put("current", 42)  
178 - .build(),  
179 - System.currentTimeMillis()),  
180 - null, null, null);  
181 - }); 173 + // 1.1 创建 WebSocket 连接(同步)
  174 + WebSocket ws = createWebSocketConnection();
  175 + log.info("[testEventPost][WebSocket 连接成功]");
  176 +
  177 + // 1.2 先进行认证
  178 + IotDeviceMessage authResponse = authenticate(ws);
  179 + log.info("[testEventPost][认证响应: {}]", authResponse);
  180 + log.info("[testEventPost][子设备事件上报 - 请求实际由 Gateway 代为转发]");
  181 +
  182 + // 2.1 构建事件上报消息
  183 + IotDeviceMessage request = IotDeviceMessage.of(
  184 + IdUtil.fastSimpleUUID(),
  185 + IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
  186 + IotDeviceEventPostReqDTO.of(
  187 + "alarm",
  188 + MapUtil.<String, Object>builder()
  189 + .put("level", "warning")
  190 + .put("message", "temperature too high")
  191 + .put("threshold", 40)
  192 + .put("current", 42)
  193 + .build(),
  194 + System.currentTimeMillis()),
  195 + null, null, null);
  196 + // 2.2 编码
  197 + byte[] payload = CODEC.encode(request);
  198 + String jsonMessage = StrUtil.utf8Str(payload);
  199 + log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  200 +
  201 + // 3.1 发送并等待响应
  202 + String response = sendAndReceive(ws, jsonMessage);
  203 + // 3.2 解码响应
  204 + if (response != null) {
  205 + IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  206 + log.info("[testEventPost][响应消息: {}]", responseMessage);
  207 + } else {
  208 + log.warn("[testEventPost][未收到响应]");
  209 + }
  210 +
  211 + // 4. 关闭连接
  212 + ws.close();
182 } 213 }
183 214
184 // ===================== 辅助方法 ===================== 215 // ===================== 辅助方法 =====================
185 216
186 /** 217 /**
187 - * 执行需要认证的请求 218 + * 创建 WebSocket 连接(同步)
188 * 219 *
189 - * @param testName 测试名称  
190 - * @param requestSupplier 请求消息提供者 220 + * @return WebSocket 连接
191 */ 221 */
192 - private void executeAuthenticatedRequest(String testName, java.util.function.Function<WebSocket, IotDeviceMessage> requestSupplier) throws Exception {  
193 - CountDownLatch latch = new CountDownLatch(2);  
194 - AtomicReference<String> authResponseRef = new AtomicReference<>();  
195 - AtomicReference<String> businessResponseRef = new AtomicReference<>();  
196 -  
197 - HttpClient client = vertx.createHttpClient(); 222 + private WebSocket createWebSocketConnection() throws Exception {
  223 + WebSocketClient wsClient = vertx.createWebSocketClient();
198 WebSocketConnectOptions options = new WebSocketConnectOptions() 224 WebSocketConnectOptions options = new WebSocketConnectOptions()
199 .setHost(SERVER_HOST) 225 .setHost(SERVER_HOST)
200 .setPort(SERVER_PORT) 226 .setPort(SERVER_PORT)
201 .setURI(WS_PATH); 227 .setURI(WS_PATH);
  228 + return wsClient.connect(options).toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  229 + }
  230 +
  231 + /**
  232 + * 发送消息并等待响应(同步)
  233 + *
  234 + * @param ws WebSocket 连接
  235 + * @param message 请求消息
  236 + * @return 响应消息
  237 + */
  238 + private String sendAndReceive(WebSocket ws, String message) throws Exception {
  239 + CountDownLatch latch = new CountDownLatch(1);
  240 + AtomicReference<String> responseRef = new AtomicReference<>();
202 241
203 - client.webSocket(options).onComplete(ar -> {  
204 - if (ar.succeeded()) {  
205 - WebSocket ws = ar.result();  
206 - log.info("[{}][WebSocket 连接成功]", testName);  
207 -  
208 - final boolean[] authenticated = {false};  
209 -  
210 - ws.textMessageHandler(message -> {  
211 - log.info("[{}][收到响应: {}]", testName, message);  
212 - if (!authenticated[0]) {  
213 - authResponseRef.set(message);  
214 - authenticated[0] = true;  
215 - latch.countDown();  
216 -  
217 - // 认证成功后发送业务请求  
218 - IotDeviceMessage businessRequest = requestSupplier.apply(ws);  
219 - byte[] payload = CODEC.encode(businessRequest);  
220 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
221 - log.info("[{}][发送业务请求: {}]", testName, jsonMessage);  
222 - ws.writeTextMessage(jsonMessage);  
223 - } else {  
224 - businessResponseRef.set(message);  
225 - ws.close();  
226 - latch.countDown();  
227 - }  
228 - });  
229 -  
230 - // 先发送认证请求  
231 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);  
232 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
233 - .setClientId(authInfo.getClientId())  
234 - .setUsername(authInfo.getUsername())  
235 - .setPassword(authInfo.getPassword());  
236 - IotDeviceMessage authRequest = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
237 -  
238 - byte[] payload = CODEC.encode(authRequest);  
239 - String jsonMessage = new String(payload, StandardCharsets.UTF_8);  
240 - log.info("[{}][发送认证请求: {}]", testName, jsonMessage);  
241 - ws.writeTextMessage(jsonMessage);  
242 - } else {  
243 - log.error("[{}][WebSocket 连接失败]", testName, ar.cause());  
244 - latch.countDown();  
245 - latch.countDown();  
246 - } 242 + // 设置消息处理器
  243 + ws.textMessageHandler(response -> {
  244 + log.info("[sendAndReceive][收到响应: {}]", response);
  245 + responseRef.set(response);
  246 + latch.countDown();
247 }); 247 });
248 248
249 - boolean completed = latch.await(TIMEOUT_SECONDS * 2, TimeUnit.SECONDS);  
250 - if (completed) {  
251 - if (authResponseRef.get() != null) {  
252 - IotDeviceMessage authResponse = CODEC.decode(authResponseRef.get().getBytes(StandardCharsets.UTF_8));  
253 - log.info("[{}][认证响应: {}]", testName, authResponse);  
254 - }  
255 - if (businessResponseRef.get() != null) {  
256 - IotDeviceMessage businessResponse = CODEC.decode(businessResponseRef.get().getBytes(StandardCharsets.UTF_8));  
257 - log.info("[{}][业务响应: {}]", testName, businessResponse);  
258 - }  
259 - } else {  
260 - log.warn("[{}][测试超时]", testName); 249 + // 发送请求
  250 + log.info("[sendAndReceive][发送请求: {}]", message);
  251 + ws.writeTextMessage(message);
  252 +
  253 + // 等待响应
  254 + boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
  255 + if (!completed) {
  256 + log.warn("[sendAndReceive][等待响应超时]");
  257 + }
  258 + return responseRef.get();
  259 + }
  260 +
  261 + /**
  262 + * 执行子设备认证(同步)
  263 + *
  264 + * @param ws WebSocket 连接
  265 + * @return 认证响应消息
  266 + */
  267 + private IotDeviceMessage authenticate(WebSocket ws) throws Exception {
  268 + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
  269 + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
  270 + .setClientId(authInfo.getClientId())
  271 + .setUsername(authInfo.getUsername())
  272 + .setPassword(authInfo.getPassword());
  273 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  274 +
  275 + byte[] payload = CODEC.encode(request);
  276 + String jsonMessage = StrUtil.utf8Str(payload);
  277 + log.info("[authenticate][发送认证请求: {}]", jsonMessage);
  278 +
  279 + String response = sendAndReceive(ws, jsonMessage);
  280 + if (response != null) {
  281 + return CODEC.decode(StrUtil.utf8Bytes(response));
261 } 282 }
  283 + return null;
262 } 284 }
263 285
264 } 286 }