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 1 package cn.iocoder.yudao.module.iot.gateway.protocol.http;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
4   -import cn.hutool.core.util.IdUtil;
5 4 import cn.hutool.http.HttpResponse;
6 5 import cn.hutool.http.HttpUtil;
7 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
... ... @@ -92,9 +91,7 @@ public class IotDirectDeviceHttpProtocolIntegrationTest {
92 91 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/property/post",
93 92 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME);
94 93 String payload = JsonUtils.toJsonString(MapUtil.builder()
95   - .put("id", IdUtil.fastSimpleUUID())
96 94 .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())
97   - .put("version", "1.0")
98 95 .put("params", IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
99 96 .put("width", 1)
100 97 .put("height", "2")
... ... @@ -126,9 +123,7 @@ public class IotDirectDeviceHttpProtocolIntegrationTest {
126 123 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/event/post",
127 124 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME);
128 125 String payload = JsonUtils.toJsonString(MapUtil.builder()
129   - .put("id", IdUtil.fastSimpleUUID())
130 126 .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod())
131   - .put("version", "1.0")
132 127 .put("params", IotDeviceEventPostReqDTO.of(
133 128 "eat",
134 129 MapUtil.<String, Object>builder().put("rice", 3).build(),
... ... @@ -163,10 +158,10 @@ public class IotDirectDeviceHttpProtocolIntegrationTest {
163 158 // 1.1 构建请求
164 159 String url = String.format("http://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT);
165 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 165 String payload = JsonUtils.toJsonString(reqDTO);
171 166 // 1.3 输出请求
172 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 2  
3 3 import cn.hutool.core.collection.ListUtil;
4 4 import cn.hutool.core.map.MapUtil;
5   -import cn.hutool.core.util.IdUtil;
6 5 import cn.hutool.http.HttpResponse;
7 6 import cn.hutool.http.HttpUtil;
8 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
... ... @@ -20,7 +19,6 @@ import org.junit.jupiter.api.Disabled;
20 19 import org.junit.jupiter.api.Test;
21 20  
22 21 import java.util.Collections;
23   -import java.util.List;
24 22 import java.util.Map;
25 23  
26 24  
... ... @@ -121,9 +119,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
121 119 IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO();
122 120 params.setSubDevices(Collections.singletonList(subDeviceAuth));
123 121 String payload = JsonUtils.toJsonString(MapUtil.builder()
124   - .put("id", IdUtil.fastSimpleUUID())
125 122 .put("method", IotDeviceMessageMethodEnum.TOPO_ADD.getMethod())
126   - .put("version", "1.0")
127 123 .put("params", params)
128 124 .build());
129 125 // 1.4 输出请求
... ... @@ -155,9 +151,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
155 151 params.setSubDevices(Collections.singletonList(
156 152 new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME)));
157 153 String payload = JsonUtils.toJsonString(MapUtil.builder()
158   - .put("id", IdUtil.fastSimpleUUID())
159 154 .put("method", IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod())
160   - .put("version", "1.0")
161 155 .put("params", params)
162 156 .build());
163 157 // 1.3 输出请求
... ... @@ -187,9 +181,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
187 181 // 1.2 构建请求参数(目前为空,预留扩展)
188 182 IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO();
189 183 String payload = JsonUtils.toJsonString(MapUtil.builder()
190   - .put("id", IdUtil.fastSimpleUUID())
191 184 .put("method", IotDeviceMessageMethodEnum.TOPO_GET.getMethod())
192   - .put("version", "1.0")
193 185 .put("params", params)
194 186 .build());
195 187 // 1.3 输出请求
... ... @@ -208,8 +200,6 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
208 200  
209 201 // ===================== 子设备注册测试 =====================
210 202  
211   - // TODO @芋艿:待测试
212   -
213 203 /**
214 204 * 子设备动态注册测试
215 205 * <p>
... ... @@ -227,9 +217,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
227 217 subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY);
228 218 subDevice.setDeviceName("mougezishebei");
229 219 String payload = JsonUtils.toJsonString(MapUtil.builder()
230   - .put("id", IdUtil.fastSimpleUUID())
231 220 .put("method", IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod())
232   - .put("version", "1.0")
233 221 .put("params", Collections.singletonList(subDevice))
234 222 .build());
235 223 // 1.3 输出请求
... ... @@ -263,9 +251,9 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
263 251 .put("temperature", 25.5)
264 252 .build();
265 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 257 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> gatewayEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
270 258 .put("statusReport", gatewayEvent)
271 259 .build();
... ... @@ -274,26 +262,24 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
274 262 .put("power", 100)
275 263 .build();
276 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 268 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> subDeviceEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
281 269 .put("healthCheck", subDeviceEvent)
282 270 .build();
283 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 276 // 1.7 构建请求参数
289 277 IotDevicePropertyPackPostReqDTO params = new IotDevicePropertyPackPostReqDTO();
290 278 params.setProperties(gatewayProperties);
291 279 params.setEvents(gatewayEvents);
292 280 params.setSubDevices(ListUtil.of(subDeviceData));
293 281 String payload = JsonUtils.toJsonString(MapUtil.builder()
294   - .put("id", IdUtil.fastSimpleUUID())
295 282 .put("method", IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod())
296   - .put("version", "1.0")
297 283 .put("params", params)
298 284 .build());
299 285 // 1.8 输出请求
... ...
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotGatewaySubDeviceHttpProtocolIntegrationTest.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.http;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
4   -import cn.hutool.core.util.IdUtil;
5 4 import cn.hutool.http.HttpResponse;
6 5 import cn.hutool.http.HttpUtil;
7 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
... ... @@ -94,9 +93,7 @@ public class IotGatewaySubDeviceHttpProtocolIntegrationTest {
94 93 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/property/post",
95 94 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME);
96 95 String payload = JsonUtils.toJsonString(MapUtil.builder()
97   - .put("id", IdUtil.fastSimpleUUID())
98 96 .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())
99   - .put("version", "1.0")
100 97 .put("params", IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
101 98 .put("power", 100)
102 99 .put("status", "online")
... ... @@ -130,9 +127,7 @@ public class IotGatewaySubDeviceHttpProtocolIntegrationTest {
130 127 String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/event/post",
131 128 SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME);
132 129 String payload = JsonUtils.toJsonString(MapUtil.builder()
133   - .put("id", IdUtil.fastSimpleUUID())
134 130 .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod())
135   - .put("version", "1.0")
136 131 .put("params", IotDeviceEventPostReqDTO.of(
137 132 "alarm",
138 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 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
4   -import cn.hutool.core.util.HexUtil;
5   -import cn.hutool.core.util.IdUtil;
6 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
7 5 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
8 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 8 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
11 9 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
12 10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
13 12 import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
14   -import cn.iocoder.yudao.module.iot.gateway.serialize.binary.IotBinarySerializer;
15 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 20 import lombok.extern.slf4j.Slf4j;
  21 +import org.junit.jupiter.api.AfterAll;
  22 +import org.junit.jupiter.api.BeforeAll;
17 23 import org.junit.jupiter.api.Disabled;
18 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 30 * IoT 直连设备 TCP 协议集成测试(手动测试)
28 31 *
29 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 34 * <p>使用步骤:
44 35 * <ol>
45 36 * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li>
46   - * <li>修改 {@link #SERIALIZER} 选择测试的序列化格式(Delimiter 模式只支持 JSON)</li>
47 37 * <li>运行以下测试方法:
48 38 * <ul>
49 39 * <li>{@link #testAuth()} - 设备认证</li>
... ... @@ -66,16 +56,25 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
66 56 private static final int SERVER_PORT = 8091;
67 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 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 79 // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) =====================
81 80  
... ... @@ -83,6 +82,25 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
83 82 private static final String DEVICE_NAME = "small";
84 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 108 */
91 109 @Test
92 110 public void testAuth() throws Exception {
93   - // 1.1 构建认证消息
  111 + // 1. 构建认证消息
94 112 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
95 113 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
96 114 .setClientId(authInfo.getClientId())
97 115 .setUsername(authInfo.getUsername())
98 116 .setPassword(authInfo.getPassword());
99 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 137 */
128 138 @Test
129 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 163 */
161 164 @Test
162 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 168 // 1. 先进行认证
167 169 IotDeviceMessage authResponse = authenticate(socket);
168 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 174 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
174 175 IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
175 176 .put("width", 1)
176 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 192 */
200 193 @Test
201 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 197 // 1. 先进行认证
206 198 IotDeviceMessage authResponse = authenticate(socket);
207 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 203 IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
213 204 IotDeviceEventPostReqDTO.of(
214 205 "eat",
215 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 235 * @param socket TCP 连接
240 236 * @return 认证响应消息
241 237 */
242   - private IotDeviceMessage authenticate(Socket socket) throws Exception {
  238 + private IotDeviceMessage authenticate(NetSocket socket) throws Exception {
243 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 13 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO;
14 14 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetReqDTO;
15 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 25 import lombok.extern.slf4j.Slf4j;
  26 +import org.junit.jupiter.api.AfterAll;
  27 +import org.junit.jupiter.api.BeforeAll;
20 28 import org.junit.jupiter.api.Disabled;
21 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 31 import java.util.Collections;
27   -import java.util.List;
28 32 import java.util.Map;
  33 +import java.util.concurrent.CompletableFuture;
  34 +import java.util.concurrent.TimeUnit;
29 35  
30 36 /**
31 37 * IoT 网关设备 TCP 协议集成测试(手动测试)
32 38 *
33 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 41 * <p>使用步骤:
42 42 * <ol>
43 43 * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li>
44   - * <li>修改 {@link #CODEC} 选择测试的编解码格式</li>
45 44 * <li>运行以下测试方法:
46 45 * <ul>
47 46 * <li>{@link #testAuth()} - 网关设备认证</li>
... ... @@ -66,10 +65,25 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
66 65 private static final int SERVER_PORT = 8091;
67 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 88 // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) =====================
75 89  
... ... @@ -83,6 +97,25 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
83 97 private static final String SUB_DEVICE_NAME = "chazuo-it";
84 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 123 */
91 124 @Test
92 125 public void testAuth() throws Exception {
93   - // 1.1 构建认证消息
  126 + // 1. 构建认证消息
94 127 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
95 128 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);
96 129 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
97 130 .setClientId(authInfo.getClientId())
98 131 .setUsername(authInfo.getUsername())
99 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 149 */
124 150 @Test
125 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 154 // 1. 先进行认证
130 155 IotDeviceMessage authResponse = authenticate(socket);
131 156 log.info("[testTopoAdd][认证响应: {}]", authResponse);
... ... @@ -140,24 +165,16 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
140 165 // 2.2 构建请求参数
141 166 IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO();
142 167 params.setSubDevices(Collections.singletonList(subDeviceAuth));
143   - IotDeviceMessage request = IotDeviceMessage.of(
  168 + IotDeviceMessage request = IotDeviceMessage.requestOf(
144 169 IdUtil.fastSimpleUUID(),
145 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 183 */
167 184 @Test
168 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 188 // 1. 先进行认证
173 189 IotDeviceMessage authResponse = authenticate(socket);
174 190 log.info("[testTopoDelete][认证响应: {}]", authResponse);
175 191  
176   - // 2.1 构建请求参数
  192 + // 2. 构建请求参数
177 193 IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO();
178 194 params.setSubDevices(Collections.singletonList(
179 195 new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME)));
180   - IotDeviceMessage request = IotDeviceMessage.of(
181   - IdUtil.fastSimpleUUID(),
  196 + IotDeviceMessage request = IotDeviceMessage.requestOf(
182 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 210 */
204 211 @Test
205 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 215 // 1. 先进行认证
210 216 IotDeviceMessage authResponse = authenticate(socket);
211 217 log.info("[testTopoGet][认证响应: {}]", authResponse);
212 218  
213   - // 2.1 构建请求参数
  219 + // 2. 构建请求参数
214 220 IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO();
215   - IotDeviceMessage request = IotDeviceMessage.of(
216   - IdUtil.fastSimpleUUID(),
  221 + IotDeviceMessage request = IotDeviceMessage.requestOf(
217 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 237 */
241 238 @Test
242 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 242 // 1. 先进行认证
247 243 IotDeviceMessage authResponse = authenticate(socket);
248 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 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 266 */
280 267 @Test
281 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 271 // 1. 先进行认证
286 272 IotDeviceMessage authResponse = authenticate(socket);
287 273 log.info("[testPropertyPackPost][认证响应: {}]", authResponse);
... ... @@ -291,9 +277,9 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
291 277 .put("temperature", 25.5)
292 278 .build();
293 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 283 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> gatewayEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
298 284 .put("statusReport", gatewayEvent)
299 285 .build();
... ... @@ -302,97 +288,95 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
302 288 .put("power", 100)
303 289 .build();
304 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 294 Map<String, IotDevicePropertyPackPostReqDTO.EventValue> subDeviceEvents = MapUtil.<String, IotDevicePropertyPackPostReqDTO.EventValue>builder()
309 295 .put("healthCheck", subDeviceEvent)
310 296 .build();
311 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 302 // 2.6 构建请求参数
317 303 IotDevicePropertyPackPostReqDTO params = new IotDevicePropertyPackPostReqDTO();
318 304 params.setProperties(gatewayProperties);
319 305 params.setEvents(gatewayEvents);
320 306 params.setSubDevices(ListUtil.of(subDeviceData));
321   - IotDeviceMessage request = IotDeviceMessage.of(
322   - IdUtil.fastSimpleUUID(),
  307 + IotDeviceMessage request = IotDeviceMessage.requestOf(
323 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 337 * @param socket TCP 连接
348 338 * @return 认证响应消息
349 339 */
350   - private IotDeviceMessage authenticate(Socket socket) throws Exception {
  340 + private IotDeviceMessage authenticate(NetSocket socket) throws Exception {
351 341 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
352 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 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
4   -import cn.hutool.core.util.IdUtil;
5 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
6 5 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
7 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
8 7 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
9 8 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
10 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 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.junit.jupiter.api.AfterAll;
  21 +import org.junit.jupiter.api.BeforeAll;
15 22 import org.junit.jupiter.api.Disabled;
16 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 29 * IoT 网关子设备 TCP 协议集成测试(手动测试)
... ... @@ -26,17 +32,10 @@ import java.net.Socket;
26 32 *
27 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 35 * <p>使用步骤:
36 36 * <ol>
37 37 * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li>
38 38 * <li>确保子设备已通过 {@link IotGatewayDeviceTcpProtocolIntegrationTest#testTopoAdd()} 绑定到网关</li>
39   - * <li>修改 {@link #CODEC} 选择测试的编解码格式</li>
40 39 * <li>运行以下测试方法:
41 40 * <ul>
42 41 * <li>{@link #testAuth()} - 子设备认证</li>
... ... @@ -58,10 +57,25 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest {
58 57 private static final int SERVER_PORT = 8091;
59 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 80 // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) =====================
67 81  
... ... @@ -69,6 +83,25 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest {
69 83 private static final String DEVICE_NAME = "chazuo-it";
70 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 109 */
77 110 @Test
78 111 public void testAuth() throws Exception {
79   - // 1.1 构建认证消息
  112 + // 1. 构建认证消息
80 113 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
81 114 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
82 115 .setClientId(authInfo.getClientId())
83 116 .setUsername(authInfo.getUsername())
84 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 134 */
109 135 @Test
110 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 139 // 1. 先进行认证
115 140 IotDeviceMessage authResponse = authenticate(socket);
116 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 145 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
122 146 IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
123 147 .put("power", 100)
124 148 .put("status", "online")
125 149 .put("temperature", 36.5)
126   - .build()),
127   - null, null, null);
128   - // 2.2 编码
129   - byte[] payload = CODEC.encode(request);
  150 + .build()));
130 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 165 */
150 166 @Test
151 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 170 // 1. 先进行认证
156 171 IotDeviceMessage authResponse = authenticate(socket);
157 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 176 IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
163 177 IotDeviceEventPostReqDTO.of(
164 178 "alarm",
... ... @@ -168,78 +182,77 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest {
168 182 .put("threshold", 40)
169 183 .put("current", 42)
170 184 .build(),
171   - System.currentTimeMillis()),
172   - null, null, null);
173   - // 2.2 编码
174   - byte[] payload = CODEC.encode(request);
  185 + System.currentTimeMillis()));
175 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 214 * @param socket TCP 连接
196 215 * @return 认证响应消息
197 216 */
198   - private IotDeviceMessage authenticate(Socket socket) throws Exception {
  217 + private IotDeviceMessage authenticate(NetSocket socket) throws Exception {
199 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 }
... ...