Commit 1d8ab8ff3d88bcade12371af3c3f08499f24cd89

Authored by YunaiV
1 parent 44b1950e

feat(iot):【协议改造】优化 tcp、http 单测类的实现

yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotDirectDeviceHttpProtocolIntegrationTest.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.http; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.http;
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.hutool.http.HttpResponse; 4 import cn.hutool.http.HttpResponse;
6 import cn.hutool.http.HttpUtil; 5 import cn.hutool.http.HttpUtil;
7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@@ -92,9 +91,7 @@ public class IotDirectDeviceHttpProtocolIntegrationTest { @@ -92,9 +91,7 @@ public class IotDirectDeviceHttpProtocolIntegrationTest {
92 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/property/post", 91 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/property/post",
93 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME); 92 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME);
94 String payload = JsonUtils.toJsonString(MapUtil.builder() 93 String payload = JsonUtils.toJsonString(MapUtil.builder()
95 - .put("id", IdUtil.fastSimpleUUID())  
96 .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()) 94 .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())
97 - .put("version", "1.0")  
98 .put("params", IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder() 95 .put("params", IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
99 .put("width", 1) 96 .put("width", 1)
100 .put("height", "2") 97 .put("height", "2")
@@ -126,9 +123,7 @@ public class IotDirectDeviceHttpProtocolIntegrationTest { @@ -126,9 +123,7 @@ public class IotDirectDeviceHttpProtocolIntegrationTest {
126 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/event/post", 123 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/event/post",
127 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME); 124 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME);
128 String payload = JsonUtils.toJsonString(MapUtil.builder() 125 String payload = JsonUtils.toJsonString(MapUtil.builder()
129 - .put("id", IdUtil.fastSimpleUUID())  
130 .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod()) 126 .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod())
131 - .put("version", "1.0")  
132 .put("params", IotDeviceEventPostReqDTO.of( 127 .put("params", IotDeviceEventPostReqDTO.of(
133 "eat", 128 "eat",
134 MapUtil.<String, Object>builder().put("rice", 3).build(), 129 MapUtil.<String, Object>builder().put("rice", 3).build(),
@@ -163,10 +158,10 @@ public class IotDirectDeviceHttpProtocolIntegrationTest { @@ -163,10 +158,10 @@ public class IotDirectDeviceHttpProtocolIntegrationTest {
163 // 1.1 构建请求 158 // 1.1 构建请求
164 String url = String.format("http://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT); 159 String url = String.format("http://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT);
165 // 1.2 构建请求参数 160 // 1.2 构建请求参数
166 - IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO();  
167 - reqDTO.setProductKey(PRODUCT_KEY);  
168 - reqDTO.setDeviceName("test-" + System.currentTimeMillis());  
169 - reqDTO.setProductSecret("test-product-secret"); 161 + IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO()
  162 + .setProductKey(PRODUCT_KEY)
  163 + .setDeviceName("test-" + System.currentTimeMillis())
  164 + .setProductSecret("test-product-secret");
170 String payload = JsonUtils.toJsonString(reqDTO); 165 String payload = JsonUtils.toJsonString(reqDTO);
171 // 1.3 输出请求 166 // 1.3 输出请求
172 log.info("[testDeviceRegister][请求 URL: {}]", url); 167 log.info("[testDeviceRegister][请求 URL: {}]", url);
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotGatewayDeviceHttpProtocolIntegrationTest.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.http; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.http;
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.hutool.http.HttpResponse; 5 import cn.hutool.http.HttpResponse;
7 import cn.hutool.http.HttpUtil; 6 import cn.hutool.http.HttpUtil;
8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@@ -20,7 +19,6 @@ import org.junit.jupiter.api.Disabled; @@ -20,7 +19,6 @@ import org.junit.jupiter.api.Disabled;
20 import org.junit.jupiter.api.Test; 19 import org.junit.jupiter.api.Test;
21 20
22 import java.util.Collections; 21 import java.util.Collections;
23 -import java.util.List;  
24 import java.util.Map; 22 import java.util.Map;
25 23
26 24
@@ -121,9 +119,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { @@ -121,9 +119,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
121 IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO(); 119 IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO();
122 params.setSubDevices(Collections.singletonList(subDeviceAuth)); 120 params.setSubDevices(Collections.singletonList(subDeviceAuth));
123 String payload = JsonUtils.toJsonString(MapUtil.builder() 121 String payload = JsonUtils.toJsonString(MapUtil.builder()
124 - .put("id", IdUtil.fastSimpleUUID())  
125 .put("method", IotDeviceMessageMethodEnum.TOPO_ADD.getMethod()) 122 .put("method", IotDeviceMessageMethodEnum.TOPO_ADD.getMethod())
126 - .put("version", "1.0")  
127 .put("params", params) 123 .put("params", params)
128 .build()); 124 .build());
129 // 1.4 输出请求 125 // 1.4 输出请求
@@ -155,9 +151,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { @@ -155,9 +151,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
155 params.setSubDevices(Collections.singletonList( 151 params.setSubDevices(Collections.singletonList(
156 new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME))); 152 new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME)));
157 String payload = JsonUtils.toJsonString(MapUtil.builder() 153 String payload = JsonUtils.toJsonString(MapUtil.builder()
158 - .put("id", IdUtil.fastSimpleUUID())  
159 .put("method", IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod()) 154 .put("method", IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod())
160 - .put("version", "1.0")  
161 .put("params", params) 155 .put("params", params)
162 .build()); 156 .build());
163 // 1.3 输出请求 157 // 1.3 输出请求
@@ -187,9 +181,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { @@ -187,9 +181,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
187 // 1.2 构建请求参数(目前为空,预留扩展) 181 // 1.2 构建请求参数(目前为空,预留扩展)
188 IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO(); 182 IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO();
189 String payload = JsonUtils.toJsonString(MapUtil.builder() 183 String payload = JsonUtils.toJsonString(MapUtil.builder()
190 - .put("id", IdUtil.fastSimpleUUID())  
191 .put("method", IotDeviceMessageMethodEnum.TOPO_GET.getMethod()) 184 .put("method", IotDeviceMessageMethodEnum.TOPO_GET.getMethod())
192 - .put("version", "1.0")  
193 .put("params", params) 185 .put("params", params)
194 .build()); 186 .build());
195 // 1.3 输出请求 187 // 1.3 输出请求
@@ -208,8 +200,6 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { @@ -208,8 +200,6 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
208 200
209 // ===================== 子设备注册测试 ===================== 201 // ===================== 子设备注册测试 =====================
210 202
211 - // TODO @芋艿:待测试  
212 -  
213 /** 203 /**
214 * 子设备动态注册测试 204 * 子设备动态注册测试
215 * <p> 205 * <p>
@@ -227,9 +217,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { @@ -227,9 +217,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
227 subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY); 217 subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY);
228 subDevice.setDeviceName("mougezishebei"); 218 subDevice.setDeviceName("mougezishebei");
229 String payload = JsonUtils.toJsonString(MapUtil.builder() 219 String payload = JsonUtils.toJsonString(MapUtil.builder()
230 - .put("id", IdUtil.fastSimpleUUID())  
231 .put("method", IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod()) 220 .put("method", IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod())
232 - .put("version", "1.0")  
233 .put("params", Collections.singletonList(subDevice)) 221 .put("params", Collections.singletonList(subDevice))
234 .build()); 222 .build());
235 // 1.3 输出请求 223 // 1.3 输出请求
@@ -263,9 +251,9 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { @@ -263,9 +251,9 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
263 .put("temperature", 25.5) 251 .put("temperature", 25.5)
264 .build(); 252 .build();
265 // 1.3 构建【网关设备】自身事件 253 // 1.3 构建【网关设备】自身事件
266 - IotDevicePropertyPackPostReqDTO.EventValue gatewayEvent = new IotDevicePropertyPackPostReqDTO.EventValue();  
267 - gatewayEvent.setValue(MapUtil.builder().put("message", "gateway started").build());  
268 - gatewayEvent.setTime(System.currentTimeMillis()); 254 + IotDevicePropertyPackPostReqDTO.EventValue gatewayEvent = new IotDevicePropertyPackPostReqDTO.EventValue()
  255 + .setValue(MapUtil.builder().put("message", "gateway started").build())
  256 + .setTime(System.currentTimeMillis());
269 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> gatewayEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder() 257 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> gatewayEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
270 .put("statusReport", gatewayEvent) 258 .put("statusReport", gatewayEvent)
271 .build(); 259 .build();
@@ -274,26 +262,24 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { @@ -274,26 +262,24 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
274 .put("power", 100) 262 .put("power", 100)
275 .build(); 263 .build();
276 // 1.5 构建【网关子设备】事件 264 // 1.5 构建【网关子设备】事件
277 - IotDevicePropertyPackPostReqDTO.EventValue subDeviceEvent = new IotDevicePropertyPackPostReqDTO.EventValue();  
278 - subDeviceEvent.setValue(MapUtil.builder().put("errorCode", 0).build());  
279 - subDeviceEvent.setTime(System.currentTimeMillis()); 265 + IotDevicePropertyPackPostReqDTO.EventValue subDeviceEvent = new IotDevicePropertyPackPostReqDTO.EventValue()
  266 + .setValue(MapUtil.builder().put("errorCode", 0).build())
  267 + .setTime(System.currentTimeMillis());
280 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> subDeviceEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder() 268 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> subDeviceEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
281 .put("healthCheck", subDeviceEvent) 269 .put("healthCheck", subDeviceEvent)
282 .build(); 270 .build();
283 // 1.6 构建子设备数据 271 // 1.6 构建子设备数据
284 - IotDevicePropertyPackPostReqDTO.SubDeviceData subDeviceData = new IotDevicePropertyPackPostReqDTO.SubDeviceData();  
285 - subDeviceData.setIdentity(new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME));  
286 - subDeviceData.setProperties(subDeviceProperties);  
287 - subDeviceData.setEvents(subDeviceEvents); 272 + IotDevicePropertyPackPostReqDTO.SubDeviceData subDeviceData = new IotDevicePropertyPackPostReqDTO.SubDeviceData()
  273 + .setIdentity(new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME))
  274 + .setProperties(subDeviceProperties)
  275 + .setEvents(subDeviceEvents);
288 // 1.7 构建请求参数 276 // 1.7 构建请求参数
289 IotDevicePropertyPackPostReqDTO params = new IotDevicePropertyPackPostReqDTO(); 277 IotDevicePropertyPackPostReqDTO params = new IotDevicePropertyPackPostReqDTO();
290 params.setProperties(gatewayProperties); 278 params.setProperties(gatewayProperties);
291 params.setEvents(gatewayEvents); 279 params.setEvents(gatewayEvents);
292 params.setSubDevices(ListUtil.of(subDeviceData)); 280 params.setSubDevices(ListUtil.of(subDeviceData));
293 String payload = JsonUtils.toJsonString(MapUtil.builder() 281 String payload = JsonUtils.toJsonString(MapUtil.builder()
294 - .put("id", IdUtil.fastSimpleUUID())  
295 .put("method", IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod()) 282 .put("method", IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod())
296 - .put("version", "1.0")  
297 .put("params", params) 283 .put("params", params)
298 .build()); 284 .build());
299 // 1.8 输出请求 285 // 1.8 输出请求
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotGatewaySubDeviceHttpProtocolIntegrationTest.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.http; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.http;
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.hutool.http.HttpResponse; 4 import cn.hutool.http.HttpResponse;
6 import cn.hutool.http.HttpUtil; 5 import cn.hutool.http.HttpUtil;
7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@@ -94,9 +93,7 @@ public class IotGatewaySubDeviceHttpProtocolIntegrationTest { @@ -94,9 +93,7 @@ public class IotGatewaySubDeviceHttpProtocolIntegrationTest {
94 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/property/post", 93 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/property/post",
95 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME); 94 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME);
96 String payload = JsonUtils.toJsonString(MapUtil.builder() 95 String payload = JsonUtils.toJsonString(MapUtil.builder()
97 - .put("id", IdUtil.fastSimpleUUID())  
98 .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()) 96 .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())
99 - .put("version", "1.0")  
100 .put("params", IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder() 97 .put("params", IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
101 .put("power", 100) 98 .put("power", 100)
102 .put("status", "online") 99 .put("status", "online")
@@ -130,9 +127,7 @@ public class IotGatewaySubDeviceHttpProtocolIntegrationTest { @@ -130,9 +127,7 @@ public class IotGatewaySubDeviceHttpProtocolIntegrationTest {
130 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/event/post", 127 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/event/post",
131 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME); 128 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME);
132 String payload = JsonUtils.toJsonString(MapUtil.builder() 129 String payload = JsonUtils.toJsonString(MapUtil.builder()
133 - .put("id", IdUtil.fastSimpleUUID())  
134 .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod()) 130 .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod())
135 - .put("version", "1.0")  
136 .put("params", IotDeviceEventPostReqDTO.of( 131 .put("params", IotDeviceEventPostReqDTO.of(
137 "alarm", 132 "alarm",
138 MapUtil.<String, Object>builder() 133 MapUtil.<String, Object>builder()
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
4 -import cn.hutool.core.util.HexUtil;  
5 -import cn.hutool.core.util.IdUtil;  
6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 5 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -10,40 +8,32 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; @@ -10,40 +8,32 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
10 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; 8 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
11 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; 9 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
12 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
13 import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; 12 import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
14 -import cn.iocoder.yudao.module.iot.gateway.serialize.binary.IotBinarySerializer;  
15 import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer; 13 import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer;
  14 +import io.vertx.core.Vertx;
  15 +import io.vertx.core.buffer.Buffer;
  16 +import io.vertx.core.net.NetClient;
  17 +import io.vertx.core.net.NetClientOptions;
  18 +import io.vertx.core.net.NetSocket;
  19 +import io.vertx.core.parsetools.RecordParser;
16 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
  21 +import org.junit.jupiter.api.AfterAll;
  22 +import org.junit.jupiter.api.BeforeAll;
17 import org.junit.jupiter.api.Disabled; 23 import org.junit.jupiter.api.Disabled;
18 import org.junit.jupiter.api.Test; 24 import org.junit.jupiter.api.Test;
19 25
20 -import java.io.BufferedReader;  
21 -import java.io.InputStreamReader;  
22 -import java.io.OutputStream;  
23 -import java.net.Socket;  
24 -import java.nio.charset.StandardCharsets; 26 +import java.util.concurrent.CompletableFuture;
  27 +import java.util.concurrent.TimeUnit;
25 28
26 /** 29 /**
27 * IoT 直连设备 TCP 协议集成测试(手动测试) 30 * IoT 直连设备 TCP 协议集成测试(手动测试)
28 * 31 *
29 * <p>测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 TCP 协议直接连接平台 32 * <p>测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 TCP 协议直接连接平台
30 * 33 *
31 - * <p>支持两种序列化格式:  
32 - * <ul>  
33 - * <li>{@link IotJsonSerializer} - JSON 格式</li>  
34 - * <li>{@link IotBinarySerializer} - 二进制格式</li>  
35 - * </ul>  
36 - *  
37 - * <p>TCP 拆包配置(需与 application.yaml 中的 codec 配置一致):  
38 - * <ul>  
39 - * <li>type: delimiter - 基于分隔符拆包</li>  
40 - * <li>delimiter: \n - 换行符作为分隔符</li>  
41 - * </ul>  
42 - *  
43 * <p>使用步骤: 34 * <p>使用步骤:
44 * <ol> 35 * <ol>
45 * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li> 36 * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li>
46 - * <li>修改 {@link #SERIALIZER} 选择测试的序列化格式(Delimiter 模式只支持 JSON)</li>  
47 * <li>运行以下测试方法: 37 * <li>运行以下测试方法:
48 * <ul> 38 * <ul>
49 * <li>{@link #testAuth()} - 设备认证</li> 39 * <li>{@link #testAuth()} - 设备认证</li>
@@ -66,16 +56,25 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -66,16 +56,25 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
66 private static final int SERVER_PORT = 8091; 56 private static final int SERVER_PORT = 8091;
67 private static final int TIMEOUT_MS = 5000; 57 private static final int TIMEOUT_MS = 5000;
68 58
69 - // 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/tcp/codec 么?例如说:使用 vertx vertx tcp client???从而更好的复用解码逻辑;  
70 - /**  
71 - * 分隔符(需与 application.yaml 中的 delimiter 配置一致)  
72 - */  
73 - private static final String DELIMITER = "\n"; 59 + private static Vertx vertx;
  60 + private static NetClient netClient;
74 61
75 - // ===================== 序列化器选择(Delimiter 模式推荐使用 JSON) ===================== 62 + // ===================== 编解码器 =====================
76 63
  64 + /**
  65 + * 消息序列化器
  66 + */
77 private static final IotMessageSerializer SERIALIZER = new IotJsonSerializer(); 67 private static final IotMessageSerializer SERIALIZER = new IotJsonSerializer();
78 -// private static final IotMessageSerializer SERIALIZER = new IotBinarySerializer(); 68 +
  69 + /**
  70 + * TCP 帧编解码器
  71 + */
  72 + private static final IotTcpFrameCodec FRAME_CODEC = IotTcpFrameCodec.create(
  73 + new IotTcpConfig.CodecConfig() {{
  74 + setType("delimiter");
  75 + setDelimiter("\\n");
  76 + }}
  77 + );
79 78
80 // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) ===================== 79 // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) =====================
81 80
@@ -83,6 +82,25 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -83,6 +82,25 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
83 private static final String DEVICE_NAME = "small"; 82 private static final String DEVICE_NAME = "small";
84 private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; 83 private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3";
85 84
  85 + @BeforeAll
  86 + static void setUp() {
  87 + vertx = Vertx.vertx();
  88 + NetClientOptions options = new NetClientOptions()
  89 + .setConnectTimeout(TIMEOUT_MS)
  90 + .setIdleTimeout(TIMEOUT_MS);
  91 + netClient = vertx.createNetClient(options);
  92 + }
  93 +
  94 + @AfterAll
  95 + static void tearDown() {
  96 + if (netClient != null) {
  97 + netClient.close();
  98 + }
  99 + if (vertx != null) {
  100 + vertx.close();
  101 + }
  102 + }
  103 +
86 // ===================== 认证测试 ===================== 104 // ===================== 认证测试 =====================
87 105
88 /** 106 /**
@@ -90,29 +108,21 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -90,29 +108,21 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
90 */ 108 */
91 @Test 109 @Test
92 public void testAuth() throws Exception { 110 public void testAuth() throws Exception {
93 - // 1.1 构建认证消息 111 + // 1. 构建认证消息
94 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); 112 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
95 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() 113 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
96 .setClientId(authInfo.getClientId()) 114 .setClientId(authInfo.getClientId())
97 .setUsername(authInfo.getUsername()) 115 .setUsername(authInfo.getUsername())
98 .setPassword(authInfo.getPassword()); 116 .setPassword(authInfo.getPassword());
99 IotDeviceMessage request = IotDeviceMessage.requestOf("auth", authReqDTO); 117 IotDeviceMessage request = IotDeviceMessage.requestOf("auth", authReqDTO);
100 - // 1.2 序列化  
101 - // TODO @AI:是不是把 SERIALIZER 放到 sendAndReceive 里;  
102 - byte[] payload = SERIALIZER.serialize(request);  
103 - log.info("[testAuth][Serializer: {}, 请求消息: {}, 数据包长度: {} 字节]", SERIALIZER.getType(), request, payload.length);  
104 -  
105 - // 2.1 发送请求  
106 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
107 - socket.setSoTimeout(TIMEOUT_MS);  
108 - byte[] responseBytes = sendAndReceive(socket, payload);  
109 - // 2.2 反序列化响应  
110 - if (responseBytes != null) {  
111 - IotDeviceMessage response = SERIALIZER.deserialize(responseBytes);  
112 - log.info("[testAuth][响应消息: {}]", response);  
113 - } else {  
114 - log.warn("[testAuth][未收到响应]");  
115 - } 118 +
  119 + // 2. 发送并接收响应
  120 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  121 + try {
  122 + IotDeviceMessage response = sendAndReceive(socket, request);
  123 + log.info("[testAuth][响应消息: {}]", response);
  124 + } finally {
  125 + socket.close();
116 } 126 }
117 } 127 }
118 128
@@ -127,29 +137,22 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -127,29 +137,22 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
127 */ 137 */
128 @Test 138 @Test
129 public void testDeviceRegister() throws Exception { 139 public void testDeviceRegister() throws Exception {
130 - // 1.1 构建注册消息  
131 - IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO();  
132 - registerReqDTO.setProductKey(PRODUCT_KEY);  
133 - registerReqDTO.setDeviceName("test-tcp-" + System.currentTimeMillis());  
134 - registerReqDTO.setProductSecret("test-product-secret");  
135 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(),  
136 - IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO, null, null, null);  
137 - // 1.2 序列化  
138 - byte[] payload = SERIALIZER.serialize(request);  
139 - log.info("[testDeviceRegister][Serializer: {}, 请求消息: {}, 数据包长度: {} 字节]", SERIALIZER.getType(), request, payload.length);  
140 -  
141 - // 2.1 发送请求  
142 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
143 - socket.setSoTimeout(TIMEOUT_MS);  
144 - byte[] responseBytes = sendAndReceive(socket, payload);  
145 - // 2.2 反序列化响应  
146 - if (responseBytes != null) {  
147 - IotDeviceMessage response = SERIALIZER.deserialize(responseBytes);  
148 - log.info("[testDeviceRegister][响应消息: {}]", response);  
149 - log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]");  
150 - } else {  
151 - log.warn("[testDeviceRegister][未收到响应]");  
152 - } 140 + // 1. 构建注册消息
  141 + IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO()
  142 + .setProductKey(PRODUCT_KEY)
  143 + .setDeviceName("test-tcp-" + System.currentTimeMillis())
  144 + .setProductSecret("test-product-secret");
  145 + IotDeviceMessage request = IotDeviceMessage.requestOf(
  146 + IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO);
  147 +
  148 + // 2. 发送并接收响应
  149 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  150 + try {
  151 + IotDeviceMessage response = sendAndReceive(socket, request);
  152 + log.info("[testDeviceRegister][响应消息: {}]", response);
  153 + log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]");
  154 + } finally {
  155 + socket.close();
153 } 156 }
154 } 157 }
155 158
@@ -160,35 +163,25 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -160,35 +163,25 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
160 */ 163 */
161 @Test 164 @Test
162 public void testPropertyPost() throws Exception { 165 public void testPropertyPost() throws Exception {
163 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
164 - socket.setSoTimeout(TIMEOUT_MS);  
165 - 166 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  167 + try {
166 // 1. 先进行认证 168 // 1. 先进行认证
167 IotDeviceMessage authResponse = authenticate(socket); 169 IotDeviceMessage authResponse = authenticate(socket);
168 log.info("[testPropertyPost][认证响应: {}]", authResponse); 170 log.info("[testPropertyPost][认证响应: {}]", authResponse);
169 171
170 - // 2.1 构建属性上报消息  
171 - IotDeviceMessage request = IotDeviceMessage.of(  
172 - IdUtil.fastSimpleUUID(), 172 + // 2. 构建属性上报消息
  173 + IotDeviceMessage request = IotDeviceMessage.requestOf(
173 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(), 174 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
174 IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder() 175 IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
175 .put("width", 1) 176 .put("width", 1)
176 .put("height", "2") 177 .put("height", "2")
177 - .build()),  
178 - null, null, null);  
179 - // 2.2 序列化  
180 - byte[] payload = SERIALIZER.serialize(request);  
181 - log.info("[testPropertyPost][Serializer: {}, 请求消息: {}]", SERIALIZER.getType(), request);  
182 -  
183 - // 3.1 发送请求  
184 - byte[] responseBytes = sendAndReceive(socket, payload);  
185 - // 3.2 反序列化响应  
186 - if (responseBytes != null) {  
187 - IotDeviceMessage response = SERIALIZER.deserialize(responseBytes);  
188 - log.info("[testPropertyPost][响应消息: {}]", response);  
189 - } else {  
190 - log.warn("[testPropertyPost][未收到响应]");  
191 - } 178 + .build()));
  179 +
  180 + // 3. 发送并接收响应
  181 + IotDeviceMessage response = sendAndReceive(socket, request);
  182 + log.info("[testPropertyPost][响应消息: {}]", response);
  183 + } finally {
  184 + socket.close();
192 } 185 }
193 } 186 }
194 187
@@ -199,98 +192,87 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -199,98 +192,87 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
199 */ 192 */
200 @Test 193 @Test
201 public void testEventPost() throws Exception { 194 public void testEventPost() throws Exception {
202 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
203 - socket.setSoTimeout(TIMEOUT_MS);  
204 - 195 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  196 + try {
205 // 1. 先进行认证 197 // 1. 先进行认证
206 IotDeviceMessage authResponse = authenticate(socket); 198 IotDeviceMessage authResponse = authenticate(socket);
207 log.info("[testEventPost][认证响应: {}]", authResponse); 199 log.info("[testEventPost][认证响应: {}]", authResponse);
208 200
209 - // 2.1 构建事件上报消息  
210 - IotDeviceMessage request = IotDeviceMessage.of(  
211 - IdUtil.fastSimpleUUID(), 201 + // 2. 构建事件上报消息
  202 + IotDeviceMessage request = IotDeviceMessage.requestOf(
212 IotDeviceMessageMethodEnum.EVENT_POST.getMethod(), 203 IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
213 IotDeviceEventPostReqDTO.of( 204 IotDeviceEventPostReqDTO.of(
214 "eat", 205 "eat",
215 MapUtil.<String, Object>builder().put("rice", 3).build(), 206 MapUtil.<String, Object>builder().put("rice", 3).build(),
216 - System.currentTimeMillis()),  
217 - null, null, null);  
218 - // 2.2 序列化  
219 - byte[] payload = SERIALIZER.serialize(request);  
220 - log.info("[testEventPost][Serializer: {}, 请求消息: {}]", SERIALIZER.getType(), request);  
221 -  
222 - // 3.1 发送请求  
223 - byte[] responseBytes = sendAndReceive(socket, payload);  
224 - // 3.2 反序列化响应  
225 - if (responseBytes != null) {  
226 - IotDeviceMessage response = SERIALIZER.deserialize(responseBytes);  
227 - log.info("[testEventPost][响应消息: {}]", response);  
228 - } else {  
229 - log.warn("[testEventPost][未收到响应]");  
230 - } 207 + System.currentTimeMillis()));
  208 +
  209 + // 3. 发送并接收响应
  210 + IotDeviceMessage response = sendAndReceive(socket, request);
  211 + log.info("[testEventPost][响应消息: {}]", response);
  212 + } finally {
  213 + socket.close();
231 } 214 }
232 } 215 }
233 216
234 // ===================== 辅助方法 ===================== 217 // ===================== 辅助方法 =====================
235 218
236 /** 219 /**
  220 + * 建立 TCP 连接
  221 + *
  222 + * @return 连接 Future
  223 + */
  224 + private CompletableFuture<NetSocket> connect() {
  225 + CompletableFuture<NetSocket> future = new CompletableFuture<>();
  226 + netClient.connect(SERVER_PORT, SERVER_HOST)
  227 + .onSuccess(future::complete)
  228 + .onFailure(future::completeExceptionally);
  229 + return future;
  230 + }
  231 +
  232 + /**
237 * 执行设备认证 233 * 执行设备认证
238 * 234 *
239 * @param socket TCP 连接 235 * @param socket TCP 连接
240 * @return 认证响应消息 236 * @return 认证响应消息
241 */ 237 */
242 - private IotDeviceMessage authenticate(Socket socket) throws Exception { 238 + private IotDeviceMessage authenticate(NetSocket socket) throws Exception {
243 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); 239 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
244 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
245 - .setClientId(authInfo.getClientId())  
246 - .setUsername(authInfo.getUsername())  
247 - .setPassword(authInfo.getPassword());  
248 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
249 - byte[] payload = SERIALIZER.serialize(request);  
250 - byte[] responseBytes = sendAndReceive(socket, payload);  
251 - if (responseBytes != null) {  
252 - log.info("[authenticate][响应数据长度: {} 字节,首字节: 0x{}, HEX: {}]",  
253 - responseBytes.length,  
254 - String.format("%02X", responseBytes[0]),  
255 - HexUtil.encodeHexStr(responseBytes));  
256 - return SERIALIZER.deserialize(responseBytes);  
257 - }  
258 - return null; 240 + IotDeviceMessage request = IotDeviceMessage.requestOf("auth", authInfo);
  241 + return sendAndReceive(socket, request);
259 } 242 }
260 243
261 /** 244 /**
262 - * 发送 TCP 请求并接收响应(支持 Delimiter 分隔符协议)  
263 - * <p>  
264 - * 发送格式:[消息体][分隔符]  
265 - * 接收格式:[消息体][分隔符] 245 + * 发送消息并接收响应
266 * 246 *
267 - * @param socket TCP Socket  
268 - * @param payload 请求数据(消息体,不含分隔符)  
269 - * @return 响应数据(消息体,不含分隔符) 247 + * @param socket TCP 连接
  248 + * @param request 请求消息
  249 + * @return 响应消息
270 */ 250 */
271 - private byte[] sendAndReceive(Socket socket, byte[] payload) throws Exception {  
272 - OutputStream out = socket.getOutputStream();  
273 - BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); 251 + private IotDeviceMessage sendAndReceive(NetSocket socket, IotDeviceMessage request) throws Exception {
  252 + // 1. 使用 FRAME_CODEC 创建解码器
  253 + CompletableFuture<IotDeviceMessage> responseFuture = new CompletableFuture<>();
  254 + RecordParser parser = FRAME_CODEC.createDecodeParser(buffer -> {
  255 + try {
  256 + // 反序列化响应
  257 + IotDeviceMessage response = SERIALIZER.deserialize(buffer.getBytes());
  258 + responseFuture.complete(response);
  259 + } catch (Exception e) {
  260 + responseFuture.completeExceptionally(e);
  261 + }
  262 + });
  263 + socket.handler(parser);
274 264
275 - // 1. 发送请求(添加分隔符后缀)  
276 - out.write(payload);  
277 - out.write(DELIMITER.getBytes(StandardCharsets.UTF_8));  
278 - out.flush();  
279 - log.info("[sendAndReceive][发送数据: {} 字节(不含分隔符)]", payload.length); 265 + // 2.1 序列化 + 帧编码(复用 gateway 的编码逻辑)
  266 + byte[] serializedData = SERIALIZER.serialize(request);
  267 + Buffer frameData = FRAME_CODEC.encode(serializedData);
  268 + log.info("[sendAndReceive][发送消息: {},数据长度: {} 字节]", request.getMethod(), frameData.length());
  269 + // 2.2 发送请求
  270 + socket.write(frameData);
280 271
281 - // 2. 接收响应(读取到分隔符为止)  
282 - try {  
283 - String responseLine = in.readLine();  
284 - if (responseLine != null) {  
285 - byte[] response = responseLine.getBytes(StandardCharsets.UTF_8);  
286 - log.info("[sendAndReceive][接收数据: {} 字节]", response.length);  
287 - return response;  
288 - }  
289 - return null;  
290 - } catch (java.net.SocketTimeoutException e) {  
291 - log.warn("[sendAndReceive][接收响应超时]");  
292 - return null;  
293 - } 272 + // 3. 等待响应
  273 + IotDeviceMessage response = responseFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  274 + log.info("[sendAndReceive][收到响应,数据长度: {} 字节]", SERIALIZER.serialize(response).length);
  275 + return response;
294 } 276 }
295 277
296 } 278 }
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotGatewayDeviceTcpProtocolIntegrationTest.java
@@ -13,35 +13,34 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO; @@ -13,35 +13,34 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO;
13 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO; 13 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO;
14 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetReqDTO; 14 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetReqDTO;
15 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 15 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
16 -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;  
17 -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpBinaryDeviceMessageCodec;  
18 -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec; 16 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
  17 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
  18 +import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer;
  19 +import io.vertx.core.Vertx;
  20 +import io.vertx.core.buffer.Buffer;
  21 +import io.vertx.core.net.NetClient;
  22 +import io.vertx.core.net.NetClientOptions;
  23 +import io.vertx.core.net.NetSocket;
  24 +import io.vertx.core.parsetools.RecordParser;
19 import lombok.extern.slf4j.Slf4j; 25 import lombok.extern.slf4j.Slf4j;
  26 +import org.junit.jupiter.api.AfterAll;
  27 +import org.junit.jupiter.api.BeforeAll;
20 import org.junit.jupiter.api.Disabled; 28 import org.junit.jupiter.api.Disabled;
21 import org.junit.jupiter.api.Test; 29 import org.junit.jupiter.api.Test;
22 30
23 -import java.io.InputStream;  
24 -import java.io.OutputStream;  
25 -import java.net.Socket;  
26 import java.util.Collections; 31 import java.util.Collections;
27 -import java.util.List;  
28 import java.util.Map; 32 import java.util.Map;
  33 +import java.util.concurrent.CompletableFuture;
  34 +import java.util.concurrent.TimeUnit;
29 35
30 /** 36 /**
31 * IoT 网关设备 TCP 协议集成测试(手动测试) 37 * IoT 网关设备 TCP 协议集成测试(手动测试)
32 * 38 *
33 * <p>测试场景:网关设备(IotProductDeviceTypeEnum 的 GATEWAY 类型)通过 TCP 协议管理子设备拓扑关系 39 * <p>测试场景:网关设备(IotProductDeviceTypeEnum 的 GATEWAY 类型)通过 TCP 协议管理子设备拓扑关系
34 * 40 *
35 - * <p>支持两种编解码格式:  
36 - * <ul>  
37 - * <li>{@link IotTcpJsonDeviceMessageCodec} - JSON 格式</li>  
38 - * <li>{@link IotTcpBinaryDeviceMessageCodec} - 二进制格式</li>  
39 - * </ul>  
40 - *  
41 * <p>使用步骤: 41 * <p>使用步骤:
42 * <ol> 42 * <ol>
43 * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li> 43 * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li>
44 - * <li>修改 {@link #CODEC} 选择测试的编解码格式</li>  
45 * <li>运行以下测试方法: 44 * <li>运行以下测试方法:
46 * <ul> 45 * <ul>
47 * <li>{@link #testAuth()} - 网关设备认证</li> 46 * <li>{@link #testAuth()} - 网关设备认证</li>
@@ -66,10 +65,25 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { @@ -66,10 +65,25 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
66 private static final int SERVER_PORT = 8091; 65 private static final int SERVER_PORT = 8091;
67 private static final int TIMEOUT_MS = 5000; 66 private static final int TIMEOUT_MS = 5000;
68 67
69 - // ===================== 编解码器选择(修改此处切换 JSON / Binary) ===================== 68 + private static Vertx vertx;
  69 + private static NetClient netClient;
  70 +
  71 + // ===================== 编解码器 =====================
70 72
71 - private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec();  
72 -// private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec(); 73 + /**
  74 + * 消息序列化器
  75 + */
  76 + private static final IotMessageSerializer SERIALIZER = new IotJsonSerializer();
  77 +
  78 + /**
  79 + * TCP 帧编解码器
  80 + */
  81 + private static final IotTcpFrameCodec FRAME_CODEC = IotTcpFrameCodec.create(
  82 + new IotTcpConfig.CodecConfig() {{
  83 + setType("delimiter");
  84 + setDelimiter("\\n");
  85 + }}
  86 + );
73 87
74 // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) ===================== 88 // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) =====================
75 89
@@ -83,6 +97,25 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { @@ -83,6 +97,25 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
83 private static final String SUB_DEVICE_NAME = "chazuo-it"; 97 private static final String SUB_DEVICE_NAME = "chazuo-it";
84 private static final String SUB_DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; 98 private static final String SUB_DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af";
85 99
  100 + @BeforeAll
  101 + static void setUp() {
  102 + vertx = Vertx.vertx();
  103 + NetClientOptions options = new NetClientOptions()
  104 + .setConnectTimeout(TIMEOUT_MS)
  105 + .setIdleTimeout(TIMEOUT_MS);
  106 + netClient = vertx.createNetClient(options);
  107 + }
  108 +
  109 + @AfterAll
  110 + static void tearDown() {
  111 + if (netClient != null) {
  112 + netClient.close();
  113 + }
  114 + if (vertx != null) {
  115 + vertx.close();
  116 + }
  117 + }
  118 +
86 // ===================== 认证测试 ===================== 119 // ===================== 认证测试 =====================
87 120
88 /** 121 /**
@@ -90,29 +123,22 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { @@ -90,29 +123,22 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
90 */ 123 */
91 @Test 124 @Test
92 public void testAuth() throws Exception { 125 public void testAuth() throws Exception {
93 - // 1.1 构建认证消息 126 + // 1. 构建认证消息
94 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo( 127 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
95 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET); 128 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);
96 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() 129 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
97 .setClientId(authInfo.getClientId()) 130 .setClientId(authInfo.getClientId())
98 .setUsername(authInfo.getUsername()) 131 .setUsername(authInfo.getUsername())
99 .setPassword(authInfo.getPassword()); 132 .setPassword(authInfo.getPassword());
100 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
101 - // 1.2 编码  
102 - byte[] payload = CODEC.encode(request);  
103 - log.info("[testAuth][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length);  
104 -  
105 - // 2.1 发送请求  
106 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
107 - socket.setSoTimeout(TIMEOUT_MS);  
108 - byte[] responseBytes = sendAndReceive(socket, payload);  
109 - // 2.2 解码响应  
110 - if (responseBytes != null) {  
111 - IotDeviceMessage response = CODEC.decode(responseBytes);  
112 - log.info("[testAuth][响应消息: {}]", response);  
113 - } else {  
114 - log.warn("[testAuth][未收到响应]");  
115 - } 133 + IotDeviceMessage request = IotDeviceMessage.requestOf("auth", authReqDTO);
  134 +
  135 + // 2. 发送并接收响应
  136 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  137 + try {
  138 + IotDeviceMessage response = sendAndReceive(socket, request);
  139 + log.info("[testAuth][响应消息: {}]", response);
  140 + } finally {
  141 + socket.close();
116 } 142 }
117 } 143 }
118 144
@@ -123,9 +149,8 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { @@ -123,9 +149,8 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
123 */ 149 */
124 @Test 150 @Test
125 public void testTopoAdd() throws Exception { 151 public void testTopoAdd() throws Exception {
126 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
127 - socket.setSoTimeout(TIMEOUT_MS);  
128 - 152 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  153 + try {
129 // 1. 先进行认证 154 // 1. 先进行认证
130 IotDeviceMessage authResponse = authenticate(socket); 155 IotDeviceMessage authResponse = authenticate(socket);
131 log.info("[testTopoAdd][认证响应: {}]", authResponse); 156 log.info("[testTopoAdd][认证响应: {}]", authResponse);
@@ -140,24 +165,16 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { @@ -140,24 +165,16 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
140 // 2.2 构建请求参数 165 // 2.2 构建请求参数
141 IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO(); 166 IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO();
142 params.setSubDevices(Collections.singletonList(subDeviceAuth)); 167 params.setSubDevices(Collections.singletonList(subDeviceAuth));
143 - IotDeviceMessage request = IotDeviceMessage.of( 168 + IotDeviceMessage request = IotDeviceMessage.requestOf(
144 IdUtil.fastSimpleUUID(), 169 IdUtil.fastSimpleUUID(),
145 IotDeviceMessageMethodEnum.TOPO_ADD.getMethod(), 170 IotDeviceMessageMethodEnum.TOPO_ADD.getMethod(),
146 - params,  
147 - null, null, null);  
148 - // 2.3 编码  
149 - byte[] payload = CODEC.encode(request);  
150 - log.info("[testTopoAdd][Codec: {}, 请求消息: {}]", CODEC.type(), request);  
151 -  
152 - // 3.1 发送请求  
153 - byte[] responseBytes = sendAndReceive(socket, payload);  
154 - // 3.2 解码响应  
155 - if (responseBytes != null) {  
156 - IotDeviceMessage response = CODEC.decode(responseBytes);  
157 - log.info("[testTopoAdd][响应消息: {}]", response);  
158 - } else {  
159 - log.warn("[testTopoAdd][未收到响应]");  
160 - } 171 + params);
  172 +
  173 + // 3. 发送并接收响应
  174 + IotDeviceMessage response = sendAndReceive(socket, request);
  175 + log.info("[testTopoAdd][响应消息: {}]", response);
  176 + } finally {
  177 + socket.close();
161 } 178 }
162 } 179 }
163 180
@@ -166,35 +183,25 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { @@ -166,35 +183,25 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
166 */ 183 */
167 @Test 184 @Test
168 public void testTopoDelete() throws Exception { 185 public void testTopoDelete() throws Exception {
169 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
170 - socket.setSoTimeout(TIMEOUT_MS);  
171 - 186 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  187 + try {
172 // 1. 先进行认证 188 // 1. 先进行认证
173 IotDeviceMessage authResponse = authenticate(socket); 189 IotDeviceMessage authResponse = authenticate(socket);
174 log.info("[testTopoDelete][认证响应: {}]", authResponse); 190 log.info("[testTopoDelete][认证响应: {}]", authResponse);
175 191
176 - // 2.1 构建请求参数 192 + // 2. 构建请求参数
177 IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO(); 193 IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO();
178 params.setSubDevices(Collections.singletonList( 194 params.setSubDevices(Collections.singletonList(
179 new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME))); 195 new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME)));
180 - IotDeviceMessage request = IotDeviceMessage.of(  
181 - IdUtil.fastSimpleUUID(), 196 + IotDeviceMessage request = IotDeviceMessage.requestOf(
182 IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod(), 197 IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod(),
183 - params,  
184 - null, null, null);  
185 - // 2.2 编码  
186 - byte[] payload = CODEC.encode(request);  
187 - log.info("[testTopoDelete][Codec: {}, 请求消息: {}]", CODEC.type(), request);  
188 -  
189 - // 3.1 发送请求  
190 - byte[] responseBytes = sendAndReceive(socket, payload);  
191 - // 3.2 解码响应  
192 - if (responseBytes != null) {  
193 - IotDeviceMessage response = CODEC.decode(responseBytes);  
194 - log.info("[testTopoDelete][响应消息: {}]", response);  
195 - } else {  
196 - log.warn("[testTopoDelete][未收到响应]");  
197 - } 198 + params);
  199 +
  200 + // 3. 发送并接收响应
  201 + IotDeviceMessage response = sendAndReceive(socket, request);
  202 + log.info("[testTopoDelete][响应消息: {}]", response);
  203 + } finally {
  204 + socket.close();
198 } 205 }
199 } 206 }
200 207
@@ -203,33 +210,23 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { @@ -203,33 +210,23 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
203 */ 210 */
204 @Test 211 @Test
205 public void testTopoGet() throws Exception { 212 public void testTopoGet() throws Exception {
206 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
207 - socket.setSoTimeout(TIMEOUT_MS);  
208 - 213 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  214 + try {
209 // 1. 先进行认证 215 // 1. 先进行认证
210 IotDeviceMessage authResponse = authenticate(socket); 216 IotDeviceMessage authResponse = authenticate(socket);
211 log.info("[testTopoGet][认证响应: {}]", authResponse); 217 log.info("[testTopoGet][认证响应: {}]", authResponse);
212 218
213 - // 2.1 构建请求参数 219 + // 2. 构建请求参数
214 IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO(); 220 IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO();
215 - IotDeviceMessage request = IotDeviceMessage.of(  
216 - IdUtil.fastSimpleUUID(), 221 + IotDeviceMessage request = IotDeviceMessage.requestOf(
217 IotDeviceMessageMethodEnum.TOPO_GET.getMethod(), 222 IotDeviceMessageMethodEnum.TOPO_GET.getMethod(),
218 - params,  
219 - null, null, null);  
220 - // 2.2 编码  
221 - byte[] payload = CODEC.encode(request);  
222 - log.info("[testTopoGet][Codec: {}, 请求消息: {}]", CODEC.type(), request);  
223 -  
224 - // 3.1 发送请求  
225 - byte[] responseBytes = sendAndReceive(socket, payload);  
226 - // 3.2 解码响应  
227 - if (responseBytes != null) {  
228 - IotDeviceMessage response = CODEC.decode(responseBytes);  
229 - log.info("[testTopoGet][响应消息: {}]", response);  
230 - } else {  
231 - log.warn("[testTopoGet][未收到响应]");  
232 - } 223 + params);
  224 +
  225 + // 3. 发送并接收响应
  226 + IotDeviceMessage response = sendAndReceive(socket, request);
  227 + log.info("[testTopoGet][响应消息: {}]", response);
  228 + } finally {
  229 + socket.close();
233 } 230 }
234 } 231 }
235 232
@@ -240,35 +237,25 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { @@ -240,35 +237,25 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
240 */ 237 */
241 @Test 238 @Test
242 public void testSubDeviceRegister() throws Exception { 239 public void testSubDeviceRegister() throws Exception {
243 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
244 - socket.setSoTimeout(TIMEOUT_MS);  
245 - 240 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  241 + try {
246 // 1. 先进行认证 242 // 1. 先进行认证
247 IotDeviceMessage authResponse = authenticate(socket); 243 IotDeviceMessage authResponse = authenticate(socket);
248 log.info("[testSubDeviceRegister][认证响应: {}]", authResponse); 244 log.info("[testSubDeviceRegister][认证响应: {}]", authResponse);
249 245
250 - // 2.1 构建请求参数  
251 - IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO();  
252 - subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY);  
253 - subDevice.setDeviceName("mougezishebei");  
254 - IotDeviceMessage request = IotDeviceMessage.of(  
255 - IdUtil.fastSimpleUUID(), 246 + // 2. 构建请求参数
  247 + IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO()
  248 + .setProductKey(SUB_DEVICE_PRODUCT_KEY)
  249 + .setDeviceName("mougezishebei");
  250 + IotDeviceMessage request = IotDeviceMessage.requestOf(
256 IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod(), 251 IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod(),
257 - Collections.singletonList(subDevice),  
258 - null, null, null);  
259 - // 2.2 编码  
260 - byte[] payload = CODEC.encode(request);  
261 - log.info("[testSubDeviceRegister][Codec: {}, 请求消息: {}]", CODEC.type(), request);  
262 -  
263 - // 3.1 发送请求  
264 - byte[] responseBytes = sendAndReceive(socket, payload);  
265 - // 3.2 解码响应  
266 - if (responseBytes != null) {  
267 - IotDeviceMessage response = CODEC.decode(responseBytes);  
268 - log.info("[testSubDeviceRegister][响应消息: {}]", response);  
269 - } else {  
270 - log.warn("[testSubDeviceRegister][未收到响应]");  
271 - } 252 + Collections.singletonList(subDevice));
  253 +
  254 + // 3. 发送并接收响应
  255 + IotDeviceMessage response = sendAndReceive(socket, request);
  256 + log.info("[testSubDeviceRegister][响应消息: {}]", response);
  257 + } finally {
  258 + socket.close();
272 } 259 }
273 } 260 }
274 261
@@ -279,9 +266,8 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { @@ -279,9 +266,8 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
279 */ 266 */
280 @Test 267 @Test
281 public void testPropertyPackPost() throws Exception { 268 public void testPropertyPackPost() throws Exception {
282 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
283 - socket.setSoTimeout(TIMEOUT_MS);  
284 - 269 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  270 + try {
285 // 1. 先进行认证 271 // 1. 先进行认证
286 IotDeviceMessage authResponse = authenticate(socket); 272 IotDeviceMessage authResponse = authenticate(socket);
287 log.info("[testPropertyPackPost][认证响应: {}]", authResponse); 273 log.info("[testPropertyPackPost][认证响应: {}]", authResponse);
@@ -291,9 +277,9 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { @@ -291,9 +277,9 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
291 .put("temperature", 25.5) 277 .put("temperature", 25.5)
292 .build(); 278 .build();
293 // 2.2 构建【网关设备】自身事件 279 // 2.2 构建【网关设备】自身事件
294 - IotDevicePropertyPackPostReqDTO.EventValue gatewayEvent = new IotDevicePropertyPackPostReqDTO.EventValue();  
295 - gatewayEvent.setValue(MapUtil.builder().put("message", "gateway started").build());  
296 - gatewayEvent.setTime(System.currentTimeMillis()); 280 + IotDevicePropertyPackPostReqDTO.EventValue gatewayEvent = new IotDevicePropertyPackPostReqDTO.EventValue()
  281 + .setValue(MapUtil.builder().put("message", "gateway started").build())
  282 + .setTime(System.currentTimeMillis());
297 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> gatewayEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder() 283 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> gatewayEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
298 .put("statusReport", gatewayEvent) 284 .put("statusReport", gatewayEvent)
299 .build(); 285 .build();
@@ -302,97 +288,95 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { @@ -302,97 +288,95 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
302 .put("power", 100) 288 .put("power", 100)
303 .build(); 289 .build();
304 // 2.4 构建【网关子设备】事件 290 // 2.4 构建【网关子设备】事件
305 - IotDevicePropertyPackPostReqDTO.EventValue subDeviceEvent = new IotDevicePropertyPackPostReqDTO.EventValue();  
306 - subDeviceEvent.setValue(MapUtil.builder().put("errorCode", 0).build());  
307 - subDeviceEvent.setTime(System.currentTimeMillis()); 291 + IotDevicePropertyPackPostReqDTO.EventValue subDeviceEvent = new IotDevicePropertyPackPostReqDTO.EventValue()
  292 + .setValue(MapUtil.builder().put("errorCode", 0).build())
  293 + .setTime(System.currentTimeMillis());
308 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> subDeviceEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder() 294 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> subDeviceEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
309 .put("healthCheck", subDeviceEvent) 295 .put("healthCheck", subDeviceEvent)
310 .build(); 296 .build();
311 // 2.5 构建子设备数据 297 // 2.5 构建子设备数据
312 - IotDevicePropertyPackPostReqDTO.SubDeviceData subDeviceData = new IotDevicePropertyPackPostReqDTO.SubDeviceData();  
313 - subDeviceData.setIdentity(new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME));  
314 - subDeviceData.setProperties(subDeviceProperties);  
315 - subDeviceData.setEvents(subDeviceEvents); 298 + IotDevicePropertyPackPostReqDTO.SubDeviceData subDeviceData = new IotDevicePropertyPackPostReqDTO.SubDeviceData()
  299 + .setIdentity(new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME))
  300 + .setProperties(subDeviceProperties)
  301 + .setEvents(subDeviceEvents);
316 // 2.6 构建请求参数 302 // 2.6 构建请求参数
317 IotDevicePropertyPackPostReqDTO params = new IotDevicePropertyPackPostReqDTO(); 303 IotDevicePropertyPackPostReqDTO params = new IotDevicePropertyPackPostReqDTO();
318 params.setProperties(gatewayProperties); 304 params.setProperties(gatewayProperties);
319 params.setEvents(gatewayEvents); 305 params.setEvents(gatewayEvents);
320 params.setSubDevices(ListUtil.of(subDeviceData)); 306 params.setSubDevices(ListUtil.of(subDeviceData));
321 - IotDeviceMessage request = IotDeviceMessage.of(  
322 - IdUtil.fastSimpleUUID(), 307 + IotDeviceMessage request = IotDeviceMessage.requestOf(
323 IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod(), 308 IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod(),
324 - params,  
325 - null, null, null);  
326 - // 2.7 编码  
327 - byte[] payload = CODEC.encode(request);  
328 - log.info("[testPropertyPackPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);  
329 -  
330 - // 3.1 发送请求  
331 - byte[] responseBytes = sendAndReceive(socket, payload);  
332 - // 3.2 解码响应  
333 - if (responseBytes != null) {  
334 - IotDeviceMessage response = CODEC.decode(responseBytes);  
335 - log.info("[testPropertyPackPost][响应消息: {}]", response);  
336 - } else {  
337 - log.warn("[testPropertyPackPost][未收到响应]");  
338 - } 309 + params);
  310 +
  311 + // 3. 发送并接收响应
  312 + IotDeviceMessage response = sendAndReceive(socket, request);
  313 + log.info("[testPropertyPackPost][响应消息: {}]", response);
  314 + } finally {
  315 + socket.close();
339 } 316 }
340 } 317 }
341 318
342 // ===================== 辅助方法 ===================== 319 // ===================== 辅助方法 =====================
343 320
344 /** 321 /**
  322 + * 建立 TCP 连接
  323 + *
  324 + * @return 连接 Future
  325 + */
  326 + private CompletableFuture<NetSocket> connect() {
  327 + CompletableFuture<NetSocket> future = new CompletableFuture<>();
  328 + netClient.connect(SERVER_PORT, SERVER_HOST)
  329 + .onSuccess(future::complete)
  330 + .onFailure(future::completeExceptionally);
  331 + return future;
  332 + }
  333 +
  334 + /**
345 * 执行网关设备认证 335 * 执行网关设备认证
346 * 336 *
347 * @param socket TCP 连接 337 * @param socket TCP 连接
348 * @return 认证响应消息 338 * @return 认证响应消息
349 */ 339 */
350 - private IotDeviceMessage authenticate(Socket socket) throws Exception { 340 + private IotDeviceMessage authenticate(NetSocket socket) throws Exception {
351 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo( 341 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
352 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET); 342 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);
353 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
354 - .setClientId(authInfo.getClientId())  
355 - .setUsername(authInfo.getUsername())  
356 - .setPassword(authInfo.getPassword());  
357 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
358 - byte[] payload = CODEC.encode(request);  
359 - byte[] responseBytes = sendAndReceive(socket, payload);  
360 - if (responseBytes != null) {  
361 - return CODEC.decode(responseBytes);  
362 - }  
363 - return null; 343 + IotDeviceMessage request = IotDeviceMessage.requestOf("auth", authInfo);
  344 + return sendAndReceive(socket, request);
364 } 345 }
365 346
366 /** 347 /**
367 - * 发送 TCP 请求并接收响应 348 + * 发送消息并接收响应(复用 IotTcpFrameCodec 编解码逻辑)
368 * 349 *
369 - * @param socket TCP Socket  
370 - * @param payload 请求数据  
371 - * @return 响应数据 350 + * @param socket TCP 连接
  351 + * @param request 请求消息
  352 + * @return 响应消息
372 */ 353 */
373 - private byte[] sendAndReceive(Socket socket, byte[] payload) throws Exception {  
374 - // 1. 发送请求  
375 - OutputStream out = socket.getOutputStream();  
376 - InputStream in = socket.getInputStream();  
377 - out.write(payload);  
378 - out.flush();  
379 -  
380 - // 2.1 等待一小段时间让服务器处理  
381 - Thread.sleep(100);  
382 - // 2.2 接收响应  
383 - byte[] buffer = new byte[4096];  
384 - try {  
385 - int length = in.read(buffer);  
386 - if (length > 0) {  
387 - byte[] response = new byte[length];  
388 - System.arraycopy(buffer, 0, response, 0, length);  
389 - return response; 354 + private IotDeviceMessage sendAndReceive(NetSocket socket, IotDeviceMessage request) throws Exception {
  355 + // 1. 使用 FRAME_CODEC 创建解码器(复用 gateway 的拆包逻辑)
  356 + CompletableFuture<IotDeviceMessage> responseFuture = new CompletableFuture<>();
  357 + RecordParser parser = FRAME_CODEC.createDecodeParser(buffer -> {
  358 + try {
  359 + // 反序列化响应
  360 + IotDeviceMessage response = SERIALIZER.deserialize(buffer.getBytes());
  361 + responseFuture.complete(response);
  362 + } catch (Exception e) {
  363 + responseFuture.completeExceptionally(e);
390 } 364 }
391 - return null;  
392 - } catch (java.net.SocketTimeoutException e) {  
393 - log.warn("[sendAndReceive][接收响应超时]");  
394 - return null;  
395 - } 365 + });
  366 + socket.handler(parser);
  367 +
  368 + // 2.1 序列化 + 帧编码(复用 gateway 的编码逻辑)
  369 + byte[] serializedData = SERIALIZER.serialize(request);
  370 + Buffer frameData = FRAME_CODEC.encode(serializedData);
  371 + log.info("[sendAndReceive][发送消息: {},数据长度: {} 字节]", request.getMethod(), frameData.length());
  372 + // 2.2 发送请求
  373 + socket.write(frameData);
  374 +
  375 + // 3. 等待响应
  376 + IotDeviceMessage response = responseFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  377 + log.info("[sendAndReceive][收到响应,数据长度: {} 字节]",
  378 + SERIALIZER.serialize(response).length);
  379 + return response;
396 } 380 }
397 381
398 } 382 }
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotGatewaySubDeviceTcpProtocolIntegrationTest.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
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;
8 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; 7 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
9 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; 8 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 9 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.tcp.IotTcpBinaryDeviceMessageCodec;  
13 -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec; 10 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
  11 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
  12 +import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer;
  13 +import io.vertx.core.Vertx;
  14 +import io.vertx.core.buffer.Buffer;
  15 +import io.vertx.core.net.NetClient;
  16 +import io.vertx.core.net.NetClientOptions;
  17 +import io.vertx.core.net.NetSocket;
  18 +import io.vertx.core.parsetools.RecordParser;
14 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.junit.jupiter.api.AfterAll;
  21 +import org.junit.jupiter.api.BeforeAll;
15 import org.junit.jupiter.api.Disabled; 22 import org.junit.jupiter.api.Disabled;
16 import org.junit.jupiter.api.Test; 23 import org.junit.jupiter.api.Test;
17 24
18 -import java.io.InputStream;  
19 -import java.io.OutputStream;  
20 -import java.net.Socket; 25 +import java.util.concurrent.CompletableFuture;
  26 +import java.util.concurrent.TimeUnit;
21 27
22 /** 28 /**
23 * IoT 网关子设备 TCP 协议集成测试(手动测试) 29 * IoT 网关子设备 TCP 协议集成测试(手动测试)
@@ -26,17 +32,10 @@ import java.net.Socket; @@ -26,17 +32,10 @@ import java.net.Socket;
26 * 32 *
27 * <p><b>重要说明:子设备无法直接连接平台,所有请求均由网关设备(Gateway)代为转发。</b> 33 * <p><b>重要说明:子设备无法直接连接平台,所有请求均由网关设备(Gateway)代为转发。</b>
28 * 34 *
29 - * <p>支持两种编解码格式:  
30 - * <ul>  
31 - * <li>{@link IotTcpJsonDeviceMessageCodec} - JSON 格式</li>  
32 - * <li>{@link IotTcpBinaryDeviceMessageCodec} - 二进制格式</li>  
33 - * </ul>  
34 - *  
35 * <p>使用步骤: 35 * <p>使用步骤:
36 * <ol> 36 * <ol>
37 * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li> 37 * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li>
38 * <li>确保子设备已通过 {@link IotGatewayDeviceTcpProtocolIntegrationTest#testTopoAdd()} 绑定到网关</li> 38 * <li>确保子设备已通过 {@link IotGatewayDeviceTcpProtocolIntegrationTest#testTopoAdd()} 绑定到网关</li>
39 - * <li>修改 {@link #CODEC} 选择测试的编解码格式</li>  
40 * <li>运行以下测试方法: 39 * <li>运行以下测试方法:
41 * <ul> 40 * <ul>
42 * <li>{@link #testAuth()} - 子设备认证</li> 41 * <li>{@link #testAuth()} - 子设备认证</li>
@@ -58,10 +57,25 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest { @@ -58,10 +57,25 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest {
58 private static final int SERVER_PORT = 8091; 57 private static final int SERVER_PORT = 8091;
59 private static final int TIMEOUT_MS = 5000; 58 private static final int TIMEOUT_MS = 5000;
60 59
61 - // ===================== 编解码器选择(修改此处切换 JSON / Binary) ===================== 60 + private static Vertx vertx;
  61 + private static NetClient netClient;
  62 +
  63 + // ===================== 编解码器 =====================
  64 +
  65 + /**
  66 + * 消息序列化器
  67 + */
  68 + private static final IotMessageSerializer SERIALIZER = new IotJsonSerializer();
62 69
63 - private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec();  
64 -// private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec(); 70 + /**
  71 + * TCP 帧编解码器
  72 + */
  73 + private static final IotTcpFrameCodec FRAME_CODEC = IotTcpFrameCodec.create(
  74 + new IotTcpConfig.CodecConfig() {{
  75 + setType("delimiter");
  76 + setDelimiter("\\n");
  77 + }}
  78 + );
65 79
66 // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== 80 // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) =====================
67 81
@@ -69,6 +83,25 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest { @@ -69,6 +83,25 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest {
69 private static final String DEVICE_NAME = "chazuo-it"; 83 private static final String DEVICE_NAME = "chazuo-it";
70 private static final String DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; 84 private static final String DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af";
71 85
  86 + @BeforeAll
  87 + static void setUp() {
  88 + vertx = Vertx.vertx();
  89 + NetClientOptions options = new NetClientOptions()
  90 + .setConnectTimeout(TIMEOUT_MS)
  91 + .setIdleTimeout(TIMEOUT_MS);
  92 + netClient = vertx.createNetClient(options);
  93 + }
  94 +
  95 + @AfterAll
  96 + static void tearDown() {
  97 + if (netClient != null) {
  98 + netClient.close();
  99 + }
  100 + if (vertx != null) {
  101 + vertx.close();
  102 + }
  103 + }
  104 +
72 // ===================== 认证测试 ===================== 105 // ===================== 认证测试 =====================
73 106
74 /** 107 /**
@@ -76,28 +109,21 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest { @@ -76,28 +109,21 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest {
76 */ 109 */
77 @Test 110 @Test
78 public void testAuth() throws Exception { 111 public void testAuth() throws Exception {
79 - // 1.1 构建认证消息 112 + // 1. 构建认证消息
80 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); 113 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
81 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() 114 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
82 .setClientId(authInfo.getClientId()) 115 .setClientId(authInfo.getClientId())
83 .setUsername(authInfo.getUsername()) 116 .setUsername(authInfo.getUsername())
84 .setPassword(authInfo.getPassword()); 117 .setPassword(authInfo.getPassword());
85 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
86 - // 1.2 编码  
87 - byte[] payload = CODEC.encode(request);  
88 - log.info("[testAuth][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length);  
89 -  
90 - // 2.1 发送请求  
91 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
92 - socket.setSoTimeout(TIMEOUT_MS);  
93 - byte[] responseBytes = sendAndReceive(socket, payload);  
94 - // 2.2 解码响应  
95 - if (responseBytes != null) {  
96 - IotDeviceMessage response = CODEC.decode(responseBytes);  
97 - log.info("[testAuth][响应消息: {}]", response);  
98 - } else {  
99 - log.warn("[testAuth][未收到响应]");  
100 - } 118 + IotDeviceMessage request = IotDeviceMessage.requestOf("auth", authReqDTO);
  119 +
  120 + // 2. 发送并接收响应
  121 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  122 + try {
  123 + IotDeviceMessage response = sendAndReceive(socket, request);
  124 + log.info("[testAuth][响应消息: {}]", response);
  125 + } finally {
  126 + socket.close();
101 } 127 }
102 } 128 }
103 129
@@ -108,37 +134,27 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest { @@ -108,37 +134,27 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest {
108 */ 134 */
109 @Test 135 @Test
110 public void testPropertyPost() throws Exception { 136 public void testPropertyPost() throws Exception {
111 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
112 - socket.setSoTimeout(TIMEOUT_MS);  
113 - 137 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  138 + try {
114 // 1. 先进行认证 139 // 1. 先进行认证
115 IotDeviceMessage authResponse = authenticate(socket); 140 IotDeviceMessage authResponse = authenticate(socket);
116 log.info("[testPropertyPost][认证响应: {}]", authResponse); 141 log.info("[testPropertyPost][认证响应: {}]", authResponse);
117 142
118 - // 2.1 构建属性上报消息  
119 - IotDeviceMessage request = IotDeviceMessage.of(  
120 - IdUtil.fastSimpleUUID(), 143 + // 2. 构建属性上报消息
  144 + IotDeviceMessage request = IotDeviceMessage.requestOf(
121 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(), 145 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
122 IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder() 146 IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
123 .put("power", 100) 147 .put("power", 100)
124 .put("status", "online") 148 .put("status", "online")
125 .put("temperature", 36.5) 149 .put("temperature", 36.5)
126 - .build()),  
127 - null, null, null);  
128 - // 2.2 编码  
129 - byte[] payload = CODEC.encode(request); 150 + .build()));
130 log.info("[testPropertyPost][子设备属性上报 - 请求实际由 Gateway 代为转发]"); 151 log.info("[testPropertyPost][子设备属性上报 - 请求实际由 Gateway 代为转发]");
131 - log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);  
132 -  
133 - // 3.1 发送请求  
134 - byte[] responseBytes = sendAndReceive(socket, payload);  
135 - // 3.2 解码响应  
136 - if (responseBytes != null) {  
137 - IotDeviceMessage response = CODEC.decode(responseBytes);  
138 - log.info("[testPropertyPost][响应消息: {}]", response);  
139 - } else {  
140 - log.warn("[testPropertyPost][未收到响应]");  
141 - } 152 +
  153 + // 3. 发送并接收响应
  154 + IotDeviceMessage response = sendAndReceive(socket, request);
  155 + log.info("[testPropertyPost][响应消息: {}]", response);
  156 + } finally {
  157 + socket.close();
142 } 158 }
143 } 159 }
144 160
@@ -149,16 +165,14 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest { @@ -149,16 +165,14 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest {
149 */ 165 */
150 @Test 166 @Test
151 public void testEventPost() throws Exception { 167 public void testEventPost() throws Exception {
152 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
153 - socket.setSoTimeout(TIMEOUT_MS);  
154 - 168 + NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  169 + try {
155 // 1. 先进行认证 170 // 1. 先进行认证
156 IotDeviceMessage authResponse = authenticate(socket); 171 IotDeviceMessage authResponse = authenticate(socket);
157 log.info("[testEventPost][认证响应: {}]", authResponse); 172 log.info("[testEventPost][认证响应: {}]", authResponse);
158 173
159 - // 2.1 构建事件上报消息  
160 - IotDeviceMessage request = IotDeviceMessage.of(  
161 - IdUtil.fastSimpleUUID(), 174 + // 2. 构建事件上报消息
  175 + IotDeviceMessage request = IotDeviceMessage.requestOf(
162 IotDeviceMessageMethodEnum.EVENT_POST.getMethod(), 176 IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
163 IotDeviceEventPostReqDTO.of( 177 IotDeviceEventPostReqDTO.of(
164 "alarm", 178 "alarm",
@@ -168,78 +182,77 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest { @@ -168,78 +182,77 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest {
168 .put("threshold", 40) 182 .put("threshold", 40)
169 .put("current", 42) 183 .put("current", 42)
170 .build(), 184 .build(),
171 - System.currentTimeMillis()),  
172 - null, null, null);  
173 - // 2.2 编码  
174 - byte[] payload = CODEC.encode(request); 185 + System.currentTimeMillis()));
175 log.info("[testEventPost][子设备事件上报 - 请求实际由 Gateway 代为转发]"); 186 log.info("[testEventPost][子设备事件上报 - 请求实际由 Gateway 代为转发]");
176 - log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);  
177 -  
178 - // 3.1 发送请求  
179 - byte[] responseBytes = sendAndReceive(socket, payload);  
180 - // 3.2 解码响应  
181 - if (responseBytes != null) {  
182 - IotDeviceMessage response = CODEC.decode(responseBytes);  
183 - log.info("[testEventPost][响应消息: {}]", response);  
184 - } else {  
185 - log.warn("[testEventPost][未收到响应]");  
186 - } 187 +
  188 + // 3. 发送并接收响应
  189 + IotDeviceMessage response = sendAndReceive(socket, request);
  190 + log.info("[testEventPost][响应消息: {}]", response);
  191 + } finally {
  192 + socket.close();
187 } 193 }
188 } 194 }
189 195
190 // ===================== 辅助方法 ===================== 196 // ===================== 辅助方法 =====================
191 197
192 /** 198 /**
  199 + * 建立 TCP 连接
  200 + *
  201 + * @return 连接 Future
  202 + */
  203 + private CompletableFuture<NetSocket> connect() {
  204 + CompletableFuture<NetSocket> future = new CompletableFuture<>();
  205 + netClient.connect(SERVER_PORT, SERVER_HOST)
  206 + .onSuccess(future::complete)
  207 + .onFailure(future::completeExceptionally);
  208 + return future;
  209 + }
  210 +
  211 + /**
193 * 执行子设备认证 212 * 执行子设备认证
194 * 213 *
195 * @param socket TCP 连接 214 * @param socket TCP 连接
196 * @return 认证响应消息 215 * @return 认证响应消息
197 */ 216 */
198 - private IotDeviceMessage authenticate(Socket socket) throws Exception { 217 + private IotDeviceMessage authenticate(NetSocket socket) throws Exception {
199 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); 218 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
200 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
201 - .setClientId(authInfo.getClientId())  
202 - .setUsername(authInfo.getUsername())  
203 - .setPassword(authInfo.getPassword());  
204 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
205 - byte[] payload = CODEC.encode(request);  
206 - byte[] responseBytes = sendAndReceive(socket, payload);  
207 - if (responseBytes != null) {  
208 - return CODEC.decode(responseBytes);  
209 - }  
210 - return null; 219 + IotDeviceMessage request = IotDeviceMessage.requestOf("auth", authInfo);
  220 + return sendAndReceive(socket, request);
211 } 221 }
212 222
213 /** 223 /**
214 - * 发送 TCP 请求并接收响应 224 + * 发送消息并接收响应(复用 IotTcpFrameCodec 编解码逻辑)
215 * 225 *
216 - * @param socket TCP Socket  
217 - * @param payload 请求数据  
218 - * @return 响应数据 226 + * @param socket TCP 连接
  227 + * @param request 请求消息
  228 + * @return 响应消息
219 */ 229 */
220 - private byte[] sendAndReceive(Socket socket, byte[] payload) throws Exception {  
221 - // 1. 发送请求  
222 - OutputStream out = socket.getOutputStream();  
223 - InputStream in = socket.getInputStream();  
224 - out.write(payload);  
225 - out.flush();  
226 -  
227 - // 2.1 等待一小段时间让服务器处理  
228 - Thread.sleep(100);  
229 - // 2.2 接收响应  
230 - byte[] buffer = new byte[4096];  
231 - try {  
232 - int length = in.read(buffer);  
233 - if (length > 0) {  
234 - byte[] response = new byte[length];  
235 - System.arraycopy(buffer, 0, response, 0, length);  
236 - return response; 230 + private IotDeviceMessage sendAndReceive(NetSocket socket, IotDeviceMessage request) throws Exception {
  231 + // 1. 使用 FRAME_CODEC 创建解码器(复用 gateway 的拆包逻辑)
  232 + CompletableFuture<IotDeviceMessage> responseFuture = new CompletableFuture<>();
  233 + RecordParser parser = FRAME_CODEC.createDecodeParser(buffer -> {
  234 + try {
  235 + // 反序列化响应
  236 + IotDeviceMessage response = SERIALIZER.deserialize(buffer.getBytes());
  237 + responseFuture.complete(response);
  238 + } catch (Exception e) {
  239 + responseFuture.completeExceptionally(e);
237 } 240 }
238 - return null;  
239 - } catch (java.net.SocketTimeoutException e) {  
240 - log.warn("[sendAndReceive][接收响应超时]");  
241 - return null;  
242 - } 241 + });
  242 + socket.handler(parser);
  243 +
  244 + // 2.1 序列化 + 帧编码(复用 gateway 的编码逻辑)
  245 + byte[] serializedData = SERIALIZER.serialize(request);
  246 + Buffer frameData = FRAME_CODEC.encode(serializedData);
  247 + log.info("[sendAndReceive][发送消息: {},数据长度: {} 字节]", request.getMethod(), frameData.length());
  248 + // 2.2 发送请求
  249 + socket.write(frameData);
  250 +
  251 + // 3. 等待响应
  252 + IotDeviceMessage response = responseFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
  253 + log.info("[sendAndReceive][收到响应,数据长度: {} 字节]",
  254 + SERIALIZER.serialize(response).length);
  255 + return response;
243 } 256 }
244 257
245 } 258 }