Commit 4003f4b0280adcbec03fe6e1a43ad1f1de891759

Authored by YunaiV
1 parent b0ab37cc

feat:【iot】TCP 协议:1)合并单测,通过 codec 切换;2)兼容下行的时候,基于连接的 codec 处理

Showing 10 changed files with 255 additions and 633 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
@@ -115,11 +115,9 @@ public class IotGatewayConfiguration { @@ -115,11 +115,9 @@ public class IotGatewayConfiguration {
115 @Bean 115 @Bean
116 public IotTcpDownstreamSubscriber iotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocolHandler, 116 public IotTcpDownstreamSubscriber iotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocolHandler,
117 IotDeviceMessageService messageService, 117 IotDeviceMessageService messageService,
118 - IotDeviceService deviceService,  
119 IotTcpConnectionManager connectionManager, 118 IotTcpConnectionManager connectionManager,
120 IotMessageBus messageBus) { 119 IotMessageBus messageBus) {
121 - return new IotTcpDownstreamSubscriber(protocolHandler, messageService, deviceService, connectionManager,  
122 - messageBus); 120 + return new IotTcpDownstreamSubscriber(protocolHandler, messageService, connectionManager, messageBus);
123 } 121 }
124 122
125 } 123 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java
@@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 6 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager; 7 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler;
9 -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;  
10 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 9 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
11 import jakarta.annotation.PostConstruct; 10 import jakarta.annotation.PostConstruct;
12 import lombok.RequiredArgsConstructor; 11 import lombok.RequiredArgsConstructor;
@@ -25,8 +24,6 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDevic @@ -25,8 +24,6 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDevic
25 24
26 private final IotDeviceMessageService messageService; 25 private final IotDeviceMessageService messageService;
27 26
28 - private final IotDeviceService deviceService;  
29 -  
30 private final IotTcpConnectionManager connectionManager; 27 private final IotTcpConnectionManager connectionManager;
31 28
32 private final IotMessageBus messageBus; 29 private final IotMessageBus messageBus;
@@ -36,7 +33,7 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDevic @@ -36,7 +33,7 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDevic
36 @PostConstruct 33 @PostConstruct
37 public void init() { 34 public void init() {
38 // 初始化下游处理器 35 // 初始化下游处理器
39 - this.downstreamHandler = new IotTcpDownstreamHandler(messageService, deviceService, connectionManager); 36 + this.downstreamHandler = new IotTcpDownstreamHandler(messageService, connectionManager);
40 37
41 messageBus.register(this); 38 messageBus.register(this);
42 log.info("[init][TCP 下游订阅者初始化完成,服务器 ID: {},Topic: {}]", 39 log.info("[init][TCP 下游订阅者初始化完成,服务器 ID: {},Topic: {}]",
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpConnectionManager.java
@@ -78,7 +78,7 @@ public class IotTcpConnectionManager { @@ -78,7 +78,7 @@ public class IotTcpConnectionManager {
78 */ 78 */
79 public boolean isAuthenticated(NetSocket socket) { 79 public boolean isAuthenticated(NetSocket socket) {
80 ConnectionInfo info = connectionMap.get(socket); 80 ConnectionInfo info = connectionMap.get(socket);
81 - return info != null && info.isAuthenticated(); 81 + return info != null;
82 } 82 }
83 83
84 /** 84 /**
@@ -96,17 +96,11 @@ public class IotTcpConnectionManager { @@ -96,17 +96,11 @@ public class IotTcpConnectionManager {
96 } 96 }
97 97
98 /** 98 /**
99 - * 检查设备是否在线 99 + * 根据设备 ID 获取连接信息
100 */ 100 */
101 - public boolean isDeviceOnline(Long deviceId) {  
102 - return deviceSocketMap.containsKey(deviceId);  
103 - }  
104 -  
105 - /**  
106 - * 检查设备是否离线  
107 - */  
108 - public boolean isDeviceOffline(Long deviceId) {  
109 - return !isDeviceOnline(deviceId); 101 + public ConnectionInfo getConnectionInfoByDeviceId(Long deviceId) {
  102 + NetSocket socket = deviceSocketMap.get(deviceId);
  103 + return socket != null ? connectionMap.get(socket) : null;
110 } 104 }
111 105
112 /** 106 /**
@@ -158,11 +152,6 @@ public class IotTcpConnectionManager { @@ -158,11 +152,6 @@ public class IotTcpConnectionManager {
158 * 消息编解码类型(认证后确定) 152 * 消息编解码类型(认证后确定)
159 */ 153 */
160 private String codecType; 154 private String codecType;
161 - // TODO @haohao:有没可能不要 authenticated 字段,通过 deviceId 或者其他的?进一步简化,想的是哈。  
162 - /**  
163 - * 是否已认证  
164 - */  
165 - private boolean authenticated;  
166 155
167 } 156 }
168 157
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpDownstreamHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router;
2 2
3 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;  
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager; 4 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;
6 -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;  
7 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 5 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
8 import lombok.RequiredArgsConstructor; 6 import lombok.RequiredArgsConstructor;
9 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
@@ -19,8 +17,6 @@ public class IotTcpDownstreamHandler { @@ -19,8 +17,6 @@ public class IotTcpDownstreamHandler {
19 17
20 private final IotDeviceMessageService deviceMessageService; 18 private final IotDeviceMessageService deviceMessageService;
21 19
22 - private final IotDeviceService deviceService;  
23 -  
24 private final IotTcpConnectionManager connectionManager; 20 private final IotTcpConnectionManager connectionManager;
25 21
26 /** 22 /**
@@ -31,21 +27,16 @@ public class IotTcpDownstreamHandler { @@ -31,21 +27,16 @@ public class IotTcpDownstreamHandler {
31 log.info("[handle][处理下行消息,设备 ID: {},方法: {},消息 ID: {}]", 27 log.info("[handle][处理下行消息,设备 ID: {},方法: {},消息 ID: {}]",
32 message.getDeviceId(), message.getMethod(), message.getId()); 28 message.getDeviceId(), message.getMethod(), message.getId());
33 29
34 - // 1.1 获取设备信息  
35 - IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId());  
36 - if (deviceInfo == null) {  
37 - log.error("[handle][设备不存在,设备 ID: {}]", message.getDeviceId());  
38 - return;  
39 - }  
40 - // 1.2 检查设备是否在线  
41 - if (connectionManager.isDeviceOffline(message.getDeviceId())) {  
42 - log.warn("[handle][设备不在线,设备 ID: {}]", message.getDeviceId()); 30 + // 1. 获取连接信息(包含 codecType)
  31 + IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfoByDeviceId(
  32 + message.getDeviceId());
  33 + if (connectionInfo == null) {
  34 + log.error("[handle][连接信息不存在,设备 ID: {}]", message.getDeviceId());
43 return; 35 return;
44 } 36 }
45 37
46 - // 2. 根据产品 Key 和设备名称编码消息,并发送到设备  
47 - byte[] bytes = deviceMessageService.encodeDeviceMessage(message, deviceInfo.getProductKey(),  
48 - deviceInfo.getDeviceName()); 38 + // 2. 使用连接时的 codecType 编码消息,并发送到设备
  39 + byte[] bytes = deviceMessageService.encodeDeviceMessage(message, connectionInfo.getCodecType());
49 boolean success = connectionManager.sendToDevice(message.getDeviceId(), bytes); 40 boolean success = connectionManager.sendToDevice(message.getDeviceId(), bytes);
50 if (success) { 41 if (success) {
51 log.info("[handle][下行消息发送成功,设备 ID: {},方法: {},消息 ID: {},数据长度: {} 字节]", 42 log.info("[handle][下行消息发送成功,设备 ID: {},方法: {},消息 ID: {},数据长度: {} 字节]",
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpUpstreamHandler.java
@@ -230,8 +230,8 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { @@ -230,8 +230,8 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> {
230 private String getMessageCodecType(Buffer buffer, NetSocket socket) { 230 private String getMessageCodecType(Buffer buffer, NetSocket socket) {
231 // 1. 如果已认证,优先使用缓存的编解码类型 231 // 1. 如果已认证,优先使用缓存的编解码类型
232 IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket); 232 IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket);
233 - if (connectionInfo != null && connectionInfo.isAuthenticated() &&  
234 - StrUtil.isNotBlank(connectionInfo.getCodecType())) { 233 + if (connectionInfo != null
  234 + && StrUtil.isNotBlank(connectionInfo.getCodecType())) {
235 return connectionInfo.getCodecType(); 235 return connectionInfo.getCodecType();
236 } 236 }
237 237
@@ -255,8 +255,7 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { @@ -255,8 +255,7 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> {
255 .setProductKey(device.getProductKey()) 255 .setProductKey(device.getProductKey())
256 .setDeviceName(device.getDeviceName()) 256 .setDeviceName(device.getDeviceName())
257 .setClientId(clientId) 257 .setClientId(clientId)
258 - .setCodecType(codecType)  
259 - .setAuthenticated(true); 258 + .setCodecType(codecType);
260 // 注册连接 259 // 注册连接
261 connectionManager.registerConnection(socket, device.getId(), connectionInfo); 260 connectionManager.registerConnection(socket, device.getId(), connectionInfo);
262 } 261 }
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
  2 +
  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;
  7 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  8 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  9 +import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
  10 +import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
  11 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  12 +import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
  13 +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpBinaryDeviceMessageCodec;
  14 +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec;
  15 +import lombok.extern.slf4j.Slf4j;
  16 +import org.junit.jupiter.api.Test;
  17 +
  18 +import java.io.InputStream;
  19 +import java.io.OutputStream;
  20 +import java.net.Socket;
  21 +
  22 +/**
  23 + * IoT 直连设备 TCP 协议集成测试(手动测试)
  24 + *
  25 + * <p>测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 TCP 协议直接连接平台
  26 + *
  27 + * <p>支持两种编解码格式:
  28 + * <ul>
  29 + * <li>{@link IotTcpJsonDeviceMessageCodec} - JSON 格式</li>
  30 + * <li>{@link IotTcpBinaryDeviceMessageCodec} - 二进制格式</li>
  31 + * </ul>
  32 + *
  33 + * <p>使用步骤:
  34 + * <ol>
  35 + * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li>
  36 + * <li>修改 {@link #CODEC} 选择测试的编解码格式</li>
  37 + * <li>运行以下测试方法:
  38 + * <ul>
  39 + * <li>{@link #testAuth()} - 设备认证</li>
  40 + * <li>{@link #testPropertyPost()} - 设备属性上报</li>
  41 + * <li>{@link #testEventPost()} - 设备事件上报</li>
  42 + * </ul>
  43 + * </li>
  44 + * </ol>
  45 + *
  46 + * <p>注意:TCP 协议是有状态的长连接,认证成功后同一连接上的后续请求无需再携带认证信息
  47 + *
  48 + * @author 芋道源码
  49 + */
  50 +@Slf4j
  51 +public class IotDirectDeviceTcpProtocolIntegrationTest {
  52 +
  53 + private static final String SERVER_HOST = "127.0.0.1";
  54 + private static final int SERVER_PORT = 8091;
  55 + private static final int TIMEOUT_MS = 5000;
  56 +
  57 + // ===================== 编解码器选择(修改此处切换 JSON / Binary) =====================
  58 +// private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec();
  59 + private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec();
  60 +
  61 + // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) =====================
  62 + private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT";
  63 + private static final String DEVICE_NAME = "small";
  64 + private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3";
  65 +
  66 + // ===================== 认证测试 =====================
  67 +
  68 + /**
  69 + * 认证测试:设备认证
  70 + */
  71 + @Test
  72 + public void testAuth() throws Exception {
  73 + // 1.1 构建认证消息
  74 + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
  75 + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
  76 + .setClientId(authInfo.getClientId())
  77 + .setUsername(authInfo.getUsername())
  78 + .setPassword(authInfo.getPassword());
  79 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  80 + // 1.2 编码
  81 + byte[] payload = CODEC.encode(request);
  82 + log.info("[testAuth][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length);
  83 + if (CODEC instanceof IotTcpBinaryDeviceMessageCodec) {
  84 + log.info("[testAuth][二进制数据包(HEX): {}]", HexUtil.encodeHexStr(payload));
  85 + }
  86 +
  87 + // 2.1 发送请求
  88 + try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {
  89 + socket.setSoTimeout(TIMEOUT_MS);
  90 + byte[] responseBytes = sendAndReceive(socket, payload);
  91 + // 2.2 解码响应
  92 + if (responseBytes != null) {
  93 + IotDeviceMessage response = CODEC.decode(responseBytes);
  94 + log.info("[testAuth][响应消息: {}]", response);
  95 + } else {
  96 + log.warn("[testAuth][未收到响应]");
  97 + }
  98 + }
  99 + }
  100 +
  101 + // ===================== 直连设备属性上报测试 =====================
  102 +
  103 + /**
  104 + * 属性上报测试
  105 + */
  106 + @Test
  107 + public void testPropertyPost() throws Exception {
  108 + try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {
  109 + socket.setSoTimeout(TIMEOUT_MS);
  110 +
  111 + // 1. 先进行认证
  112 + IotDeviceMessage authResponse = authenticate(socket);
  113 + log.info("[testPropertyPost][认证响应: {}]", authResponse);
  114 +
  115 + // 2.1 构建属性上报消息
  116 + IotDeviceMessage request = IotDeviceMessage.of(
  117 + IdUtil.fastSimpleUUID(),
  118 + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
  119 + IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
  120 + .put("width", 1)
  121 + .put("height", "2")
  122 + .build()),
  123 + null, null, null);
  124 + // 2.2 编码
  125 + byte[] payload = CODEC.encode(request);
  126 + log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  127 +
  128 + // 3.1 发送请求
  129 + byte[] responseBytes = sendAndReceive(socket, payload);
  130 + // 3.2 解码响应
  131 + if (responseBytes != null) {
  132 + IotDeviceMessage response = CODEC.decode(responseBytes);
  133 + log.info("[testPropertyPost][响应消息: {}]", response);
  134 + } else {
  135 + log.warn("[testPropertyPost][未收到响应]");
  136 + }
  137 + }
  138 + }
  139 +
  140 + // ===================== 直连设备事件上报测试 =====================
  141 +
  142 + /**
  143 + * 事件上报测试
  144 + */
  145 + @Test
  146 + public void testEventPost() throws Exception {
  147 + try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {
  148 + socket.setSoTimeout(TIMEOUT_MS);
  149 +
  150 + // 1. 先进行认证
  151 + IotDeviceMessage authResponse = authenticate(socket);
  152 + log.info("[testEventPost][认证响应: {}]", authResponse);
  153 +
  154 + // 2.1 构建事件上报消息
  155 + IotDeviceMessage request = IotDeviceMessage.of(
  156 + IdUtil.fastSimpleUUID(),
  157 + IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
  158 + IotDeviceEventPostReqDTO.of(
  159 + "eat",
  160 + MapUtil.<String, Object>builder().put("rice", 3).build(),
  161 + System.currentTimeMillis()),
  162 + null, null, null);
  163 + // 2.2 编码
  164 + byte[] payload = CODEC.encode(request);
  165 + log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  166 +
  167 + // 3.1 发送请求
  168 + byte[] responseBytes = sendAndReceive(socket, payload);
  169 + // 3.2 解码响应
  170 + if (responseBytes != null) {
  171 + IotDeviceMessage response = CODEC.decode(responseBytes);
  172 + log.info("[testEventPost][响应消息: {}]", response);
  173 + } else {
  174 + log.warn("[testEventPost][未收到响应]");
  175 + }
  176 + }
  177 + }
  178 +
  179 + // ===================== 辅助方法 =====================
  180 +
  181 + /**
  182 + * 执行设备认证
  183 + *
  184 + * @param socket TCP 连接
  185 + * @return 认证响应消息
  186 + */
  187 + private IotDeviceMessage authenticate(Socket socket) throws Exception {
  188 + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
  189 + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
  190 + .setClientId(authInfo.getClientId())
  191 + .setUsername(authInfo.getUsername())
  192 + .setPassword(authInfo.getPassword());
  193 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  194 + byte[] payload = CODEC.encode(request);
  195 + byte[] responseBytes = sendAndReceive(socket, payload);
  196 + if (responseBytes != null) {
  197 + log.info("[authenticate][响应数据长度: {} 字节,首字节: 0x{}, HEX: {}]",
  198 + responseBytes.length,
  199 + String.format("%02X", responseBytes[0]),
  200 + HexUtil.encodeHexStr(responseBytes));
  201 + return CODEC.decode(responseBytes);
  202 + }
  203 + return null;
  204 + }
  205 +
  206 + /**
  207 + * 发送 TCP 请求并接收响应
  208 + *
  209 + * @param socket TCP Socket
  210 + * @param payload 请求数据
  211 + * @return 响应数据
  212 + */
  213 + private byte[] sendAndReceive(Socket socket, byte[] payload) throws Exception {
  214 + // 1. 发送请求
  215 + OutputStream out = socket.getOutputStream();
  216 + InputStream in = socket.getInputStream();
  217 + out.write(payload);
  218 + out.flush();
  219 +
  220 + // 2.1 等待一小段时间让服务器处理
  221 + Thread.sleep(100);
  222 + // 2.2 接收响应
  223 + byte[] buffer = new byte[4096];
  224 + try {
  225 + int length = in.read(buffer);
  226 + if (length > 0) {
  227 + byte[] response = new byte[length];
  228 + System.arraycopy(buffer, 0, response, 0, length);
  229 + return response;
  230 + }
  231 + return null;
  232 + } catch (java.net.SocketTimeoutException e) {
  233 + log.warn("[sendAndReceive][接收响应超时]");
  234 + return null;
  235 + }
  236 + }
  237 +
  238 +}
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/IotDirectDeviceTcpBinaryProtocolIntegrationTest.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.binary;  
2 -  
3 -import cn.hutool.core.map.MapUtil;  
4 -import cn.hutool.core.util.IdUtil;  
5 -import cn.hutool.core.util.StrUtil;  
6 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
7 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;  
8 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;  
9 -import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;  
10 -import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;  
11 -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;  
12 -import io.vertx.core.buffer.Buffer;  
13 -import lombok.extern.slf4j.Slf4j;  
14 -import org.junit.jupiter.api.Test;  
15 -  
16 -import java.io.InputStream;  
17 -import java.io.OutputStream;  
18 -import java.net.Socket;  
19 -import java.nio.charset.StandardCharsets;  
20 -  
21 -/**  
22 - * IoT 直连设备 TCP 二进制协议集成测试(手动测试)  
23 - *  
24 - * <p>测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 TCP 二进制协议直接连接平台  
25 - *  
26 - * <p>使用步骤:  
27 - * <ol>  
28 - * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li>  
29 - * <li>运行 {@link #testAuth()} 获取设备认证,认证成功后连接保持</li>  
30 - * <li>运行以下测试方法:  
31 - * <ul>  
32 - * <li>{@link #testPropertyPost()} - 设备属性上报</li>  
33 - * <li>{@link #testEventPost()} - 设备事件上报</li>  
34 - * </ul>  
35 - * </li>  
36 - * </ol>  
37 - *  
38 - * <p>注意:TCP 协议是有状态的长连接,认证成功后同一连接上的后续请求无需再携带认证信息  
39 - *  
40 - * <p>二进制协议格式说明请参考:{@code tcp-binary-packet-examples.md}  
41 - *  
42 - * @author 芋道源码  
43 - */  
44 -@Slf4j  
45 -public class IotDirectDeviceTcpBinaryProtocolIntegrationTest {  
46 -  
47 - private static final String SERVER_HOST = "127.0.0.1";  
48 - private static final int SERVER_PORT = 8091;  
49 - private static final int TIMEOUT_MS = 5000;  
50 -  
51 - // ===================== 二进制协议常量 =====================  
52 - /**  
53 - * 协议魔术字,用于协议识别  
54 - */  
55 - private static final byte MAGIC_NUMBER = (byte) 0x7E;  
56 -  
57 - /**  
58 - * 协议版本号  
59 - */  
60 - private static final byte PROTOCOL_VERSION = (byte) 0x01;  
61 -  
62 - /**  
63 - * 请求消息类型  
64 - */  
65 - private static final byte REQUEST = (byte) 0x01;  
66 -  
67 - // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) =====================  
68 - private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT";  
69 - private static final String DEVICE_NAME = "small";  
70 - private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3";  
71 -  
72 - // ===================== 认证测试 =====================  
73 -  
74 - /**  
75 - * 认证测试:设备认证(二进制格式)  
76 - */  
77 - @Test  
78 - public void testAuth() throws Exception {  
79 - // 1.1 构建认证参数  
80 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);  
81 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
82 - .setClientId(authInfo.getClientId())  
83 - .setUsername(authInfo.getUsername())  
84 - .setPassword(authInfo.getPassword());  
85 - // 1.2 构建二进制请求  
86 - String messageId = IdUtil.fastSimpleUUID();  
87 - String method = "auth";  
88 - byte[] payload = buildBinaryRequest(messageId, method, authReqDTO);  
89 - // 1.3 输出请求  
90 - log.info("[testAuth][消息ID: {}, 方法: {}, 参数: {}]", messageId, method, JsonUtils.toJsonString(authReqDTO));  
91 - log.info("[testAuth][二进制数据包长度: {} 字节]", payload.length);  
92 - log.info("[testAuth][二进制数据包(HEX): {}]", bytesToHex(payload));  
93 -  
94 - // 2.1 发送请求  
95 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
96 - socket.setSoTimeout(TIMEOUT_MS);  
97 - byte[] response = sendAndReceiveBinary(socket, payload);  
98 - // 2.2 输出结果  
99 - if (response != null) {  
100 - log.info("[testAuth][响应数据包长度: {} 字节]", response.length);  
101 - log.info("[testAuth][响应数据包(HEX): {}]", bytesToHex(response));  
102 - parseBinaryResponse(response);  
103 - } else {  
104 - log.warn("[testAuth][未收到响应]");  
105 - }  
106 - }  
107 - }  
108 -  
109 - // ===================== 直连设备属性上报测试 =====================  
110 -  
111 - /**  
112 - * 属性上报测试(二进制格式)  
113 - *  
114 - * 注意:TCP 协议需要先认证,这里为了简化测试,在同一连接上先认证再上报  
115 - */  
116 - @Test  
117 - public void testPropertyPost() throws Exception {  
118 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
119 - socket.setSoTimeout(TIMEOUT_MS);  
120 -  
121 - // 1. 先进行认证  
122 - byte[] authResponse = authenticateBinary(socket);  
123 - log.info("[testPropertyPost][认证响应长度: {} 字节]", authResponse != null ? authResponse.length : 0);  
124 - if (authResponse != null) {  
125 - parseBinaryResponse(authResponse);  
126 - }  
127 -  
128 - // 2.1 构建属性上报请求  
129 - String messageId = IdUtil.fastSimpleUUID();  
130 - String method = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod();  
131 - Object params = IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()  
132 - .put("width", 1)  
133 - .put("height", "2")  
134 - .build());  
135 - byte[] payload = buildBinaryRequest(messageId, method, params);  
136 - // 2.2 输出请求  
137 - log.info("[testPropertyPost][消息ID: {}, 方法: {}, 参数: {}]", messageId, method, JsonUtils.toJsonString(params));  
138 - log.info("[testPropertyPost][二进制数据包长度: {} 字节]", payload.length);  
139 -  
140 - // 3.1 发送请求  
141 - byte[] response = sendAndReceiveBinary(socket, payload);  
142 - // 3.2 输出结果  
143 - if (response != null) {  
144 - log.info("[testPropertyPost][响应数据包长度: {} 字节]", response.length);  
145 - parseBinaryResponse(response);  
146 - } else {  
147 - log.warn("[testPropertyPost][未收到响应]");  
148 - }  
149 - }  
150 - }  
151 -  
152 - // ===================== 直连设备事件上报测试 =====================  
153 -  
154 - /**  
155 - * 事件上报测试(二进制格式)  
156 - *  
157 - * 注意:TCP 协议需要先认证,这里为了简化测试,在同一连接上先认证再上报  
158 - */  
159 - @Test  
160 - public void testEventPost() throws Exception {  
161 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
162 - socket.setSoTimeout(TIMEOUT_MS);  
163 -  
164 - // 1. 先进行认证  
165 - byte[] authResponse = authenticateBinary(socket);  
166 - log.info("[testEventPost][认证响应长度: {} 字节]", authResponse != null ? authResponse.length : 0);  
167 - if (authResponse != null) {  
168 - parseBinaryResponse(authResponse);  
169 - }  
170 -  
171 - // 2.1 构建事件上报请求  
172 - String messageId = IdUtil.fastSimpleUUID();  
173 - String method = IotDeviceMessageMethodEnum.EVENT_POST.getMethod();  
174 - Object params = IotDeviceEventPostReqDTO.of(  
175 - "eat",  
176 - MapUtil.<String, Object>builder().put("rice", 3).build(),  
177 - System.currentTimeMillis());  
178 - byte[] payload = buildBinaryRequest(messageId, method, params);  
179 - // 2.2 输出请求  
180 - log.info("[testEventPost][消息ID: {}, 方法: {}, 参数: {}]", messageId, method, JsonUtils.toJsonString(params));  
181 - log.info("[testEventPost][二进制数据包长度: {} 字节]", payload.length);  
182 -  
183 - // 3.1 发送请求  
184 - byte[] response = sendAndReceiveBinary(socket, payload);  
185 - // 3.2 输出结果  
186 - if (response != null) {  
187 - log.info("[testEventPost][响应数据包长度: {} 字节]", response.length);  
188 - parseBinaryResponse(response);  
189 - } else {  
190 - log.warn("[testEventPost][未收到响应]");  
191 - }  
192 - }  
193 - }  
194 -  
195 - // ===================== 辅助方法 =====================  
196 -  
197 - /**  
198 - * 执行设备认证(二进制格式)  
199 - *  
200 - * @param socket TCP 连接  
201 - * @return 认证响应  
202 - */  
203 - private byte[] authenticateBinary(Socket socket) throws Exception {  
204 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);  
205 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
206 - .setClientId(authInfo.getClientId())  
207 - .setUsername(authInfo.getUsername())  
208 - .setPassword(authInfo.getPassword());  
209 - String messageId = IdUtil.fastSimpleUUID();  
210 - byte[] payload = buildBinaryRequest(messageId, "auth", authReqDTO);  
211 - return sendAndReceiveBinary(socket, payload);  
212 - }  
213 -  
214 - /**  
215 - * 构建二进制请求数据包  
216 - *  
217 - * <p>协议格式:  
218 - * <pre>  
219 - * +--------+--------+--------+---------------------------+  
220 - * | 魔术字 | 版本号 | 消息类型| 消息长度(4字节) |  
221 - * +--------+--------+--------+---------------------------+  
222 - * | 消息ID长度(2字节) | 消息ID(变长字符串) |  
223 - * +--------+--------+--------+--------+--------+--------+  
224 - * | 方法名长度(2字节) | 方法名(变长字符串) |  
225 - * +--------+--------+--------+--------+--------+--------+  
226 - * | 消息体数据(变长) |  
227 - * +--------+--------+--------+--------+--------+--------+  
228 - * </pre>  
229 - *  
230 - * @param messageId 消息 ID  
231 - * @param method 方法名  
232 - * @param params 请求参数  
233 - * @return 二进制数据包  
234 - */  
235 - private byte[] buildBinaryRequest(String messageId, String method, Object params) {  
236 - Buffer buffer = Buffer.buffer();  
237 - // 1. 写入协议头部  
238 - buffer.appendByte(MAGIC_NUMBER);  
239 - buffer.appendByte(PROTOCOL_VERSION);  
240 - buffer.appendByte(REQUEST);  
241 - // 2. 预留消息长度位置  
242 - int lengthPosition = buffer.length();  
243 - buffer.appendInt(0);  
244 - // 3. 写入消息 ID  
245 - byte[] messageIdBytes = StrUtil.utf8Bytes(messageId);  
246 - buffer.appendShort((short) messageIdBytes.length);  
247 - buffer.appendBytes(messageIdBytes);  
248 - // 4. 写入方法名  
249 - byte[] methodBytes = StrUtil.utf8Bytes(method);  
250 - buffer.appendShort((short) methodBytes.length);  
251 - buffer.appendBytes(methodBytes);  
252 - // 5. 写入消息体(params 序列化为 JSON)  
253 - if (params != null) {  
254 - buffer.appendBytes(JsonUtils.toJsonByte(params));  
255 - }  
256 - // 6. 更新消息长度  
257 - buffer.setInt(lengthPosition, buffer.length());  
258 - return buffer.getBytes();  
259 - }  
260 -  
261 - /**  
262 - * 解析二进制响应数据包  
263 - *  
264 - * @param data 响应数据  
265 - */  
266 - private void parseBinaryResponse(byte[] data) {  
267 - if (data == null || data.length < 11) {  
268 - log.warn("[parseBinaryResponse][数据包过短]");  
269 - return;  
270 - }  
271 -  
272 - Buffer buffer = Buffer.buffer(data);  
273 - int index = 0;  
274 -  
275 - // 1. 解析魔术字  
276 - byte magic = buffer.getByte(index++);  
277 - log.info("[parseBinaryResponse][魔术字: 0x{} (预期: 0x7E)]", String.format("%02X", magic));  
278 -  
279 - // 2. 解析版本号  
280 - byte version = buffer.getByte(index++);  
281 - log.info("[parseBinaryResponse][版本号: 0x{}]", String.format("%02X", version));  
282 -  
283 - // 3. 解析消息类型  
284 - byte messageType = buffer.getByte(index++);  
285 - log.info("[parseBinaryResponse][消息类型: 0x{} (0x01=请求, 0x02=响应)]", String.format("%02X", messageType));  
286 -  
287 - // 4. 解析消息长度  
288 - int messageLength = buffer.getInt(index);  
289 - index += 4;  
290 - log.info("[parseBinaryResponse][消息长度: {}]", messageLength);  
291 -  
292 - // 5. 解析消息 ID  
293 - short messageIdLength = buffer.getShort(index);  
294 - index += 2;  
295 - String messageId = buffer.getString(index, index + messageIdLength, StandardCharsets.UTF_8.name());  
296 - index += messageIdLength;  
297 - log.info("[parseBinaryResponse][消息ID: {}]", messageId);  
298 -  
299 - // 6. 解析方法名  
300 - short methodLength = buffer.getShort(index);  
301 - index += 2;  
302 - String method = buffer.getString(index, index + methodLength, StandardCharsets.UTF_8.name());  
303 - index += methodLength;  
304 - log.info("[parseBinaryResponse][方法名: {}]", method);  
305 -  
306 - // 7. 解析消息体  
307 - if (messageType == 0x02) { // RESPONSE  
308 - // 响应消息:code(4) + msgLen(2) + msg + data(JSON)  
309 - if (index + 4 <= buffer.length()) {  
310 - int code = buffer.getInt(index);  
311 - index += 4;  
312 - log.info("[parseBinaryResponse][响应码: {}]", code);  
313 -  
314 - if (index + 2 <= buffer.length()) {  
315 - short msgLength = buffer.getShort(index);  
316 - index += 2;  
317 - if (msgLength > 0 && index + msgLength <= buffer.length()) {  
318 - String msg = buffer.getString(index, index + msgLength, StandardCharsets.UTF_8.name());  
319 - index += msgLength;  
320 - log.info("[parseBinaryResponse][响应消息: {}]", msg);  
321 - }  
322 -  
323 - if (index < buffer.length()) {  
324 - String dataJson = buffer.getString(index, buffer.length(), StandardCharsets.UTF_8.name());  
325 - log.info("[parseBinaryResponse][响应数据: {}]", dataJson);  
326 - }  
327 - }  
328 - }  
329 - } else {  
330 - // 请求消息:params(JSON)  
331 - if (index < buffer.length()) {  
332 - String paramsJson = buffer.getString(index, buffer.length(), StandardCharsets.UTF_8.name());  
333 - log.info("[parseBinaryResponse][请求参数: {}]", paramsJson);  
334 - }  
335 - }  
336 - }  
337 -  
338 - /**  
339 - * 发送二进制请求并接收响应  
340 - *  
341 - * @param socket TCP Socket  
342 - * @param payload 二进制数据包  
343 - * @return 响应数据  
344 - */  
345 - private byte[] sendAndReceiveBinary(Socket socket, byte[] payload) throws Exception {  
346 - // 1. 发送请求  
347 - OutputStream out = socket.getOutputStream();  
348 - InputStream in = socket.getInputStream();  
349 - out.write(payload);  
350 - out.flush();  
351 -  
352 - // 2.1 等待一小段时间让服务器处理  
353 - Thread.sleep(100);  
354 - // 2.2 接收响应  
355 - byte[] buffer = new byte[4096];  
356 - try {  
357 - int length = in.read(buffer);  
358 - if (length > 0) {  
359 - byte[] response = new byte[length];  
360 - System.arraycopy(buffer, 0, response, 0, length);  
361 - return response;  
362 - }  
363 - return null;  
364 - } catch (java.net.SocketTimeoutException e) {  
365 - log.warn("[sendAndReceiveBinary][接收响应超时]");  
366 - return null;  
367 - }  
368 - }  
369 -  
370 - // TODO @AI:hutool 简化下;  
371 - /**  
372 - * 字节数组转十六进制字符串  
373 - *  
374 - * @param bytes 字节数组  
375 - * @return 十六进制字符串  
376 - */  
377 - private String bytesToHex(byte[] bytes) {  
378 - StringBuilder sb = new StringBuilder();  
379 - for (byte b : bytes) {  
380 - sb.append(String.format("%02X ", b));  
381 - }  
382 - return sb.toString().trim();  
383 - }  
384 -  
385 -}  
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/IotDirectDeviceTcpJsonProtocolIntegrationTest.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.json;  
2 -  
3 -import cn.hutool.core.map.MapUtil;  
4 -import cn.hutool.core.util.IdUtil;  
5 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
6 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;  
7 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;  
8 -import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;  
9 -import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;  
10 -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;  
11 -import lombok.extern.slf4j.Slf4j;  
12 -import org.junit.jupiter.api.Test;  
13 -  
14 -import java.io.InputStream;  
15 -import java.io.OutputStream;  
16 -import java.net.Socket;  
17 -import java.nio.charset.StandardCharsets;  
18 -  
19 -/**  
20 - * IoT 直连设备 TCP JSON 协议集成测试(手动测试)  
21 - *  
22 - * <p>测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 TCP JSON 协议直接连接平台  
23 - *  
24 - * <p>使用步骤:  
25 - * <ol>  
26 - * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li>  
27 - * <li>运行 {@link #testAuth()} 获取设备认证,认证成功后连接保持</li>  
28 - * <li>运行以下测试方法:  
29 - * <ul>  
30 - * <li>{@link #testPropertyPost()} - 设备属性上报</li>  
31 - * <li>{@link #testEventPost()} - 设备事件上报</li>  
32 - * </ul>  
33 - * </li>  
34 - * </ol>  
35 - *  
36 - * <p>注意:TCP 协议是有状态的长连接,认证成功后同一连接上的后续请求无需再携带认证信息  
37 - *  
38 - * @author 芋道源码  
39 - */  
40 -@Slf4j  
41 -public class IotDirectDeviceTcpJsonProtocolIntegrationTest {  
42 -  
43 - private static final String SERVER_HOST = "127.0.0.1";  
44 - private static final int SERVER_PORT = 8091;  
45 - private static final int TIMEOUT_MS = 5000;  
46 -  
47 - // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) =====================  
48 - private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT";  
49 - private static final String DEVICE_NAME = "small";  
50 - private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3";  
51 -  
52 - // ===================== 认证测试 =====================  
53 -  
54 - /**  
55 - * 认证测试:设备认证  
56 - */  
57 - @Test  
58 - public void testAuth() throws Exception {  
59 - // 1.1 构建请求  
60 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);  
61 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
62 - .setClientId(authInfo.getClientId())  
63 - .setUsername(authInfo.getUsername())  
64 - .setPassword(authInfo.getPassword());  
65 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
66 - .put("id", IdUtil.fastSimpleUUID())  
67 - .put("method", "auth")  
68 - .put("params", authReqDTO)  
69 - .put("timestamp", System.currentTimeMillis())  
70 - .build());  
71 - // 1.2 输出请求  
72 - log.info("[testAuth][请求体: {}]", payload);  
73 -  
74 - // 2.1 发送请求  
75 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
76 - socket.setSoTimeout(TIMEOUT_MS);  
77 - String response = sendAndReceive(socket, payload);  
78 - // 2.2 输出结果  
79 - log.info("[testAuth][响应体: {}]", response);  
80 - }  
81 - }  
82 -  
83 - // ===================== 直连设备属性上报测试 =====================  
84 -  
85 - /**  
86 - * 属性上报测试  
87 - */  
88 - @Test  
89 - public void testPropertyPost() throws Exception {  
90 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
91 - socket.setSoTimeout(TIMEOUT_MS);  
92 -  
93 - // 1. 先进行认证  
94 - String authResponse = authenticate(socket);  
95 - log.info("[testPropertyPost][认证响应: {}]", authResponse);  
96 -  
97 - // 2.1 构建属性上报请求  
98 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
99 - .put("id", IdUtil.fastSimpleUUID())  
100 - .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())  
101 - .put("params", IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()  
102 - .put("width", 1)  
103 - .put("height", "2")  
104 - .build()))  
105 - .put("timestamp", System.currentTimeMillis())  
106 - .build());  
107 - // 2.2 输出请求  
108 - log.info("[testPropertyPost][请求体: {}]", payload);  
109 -  
110 - // 3.1 发送请求  
111 - String response = sendAndReceive(socket, payload);  
112 - // 3.2 输出结果  
113 - log.info("[testPropertyPost][响应体: {}]", response);  
114 - }  
115 - }  
116 -  
117 - // ===================== 直连设备事件上报测试 =====================  
118 -  
119 - /**  
120 - * 事件上报测试  
121 - */  
122 - @Test  
123 - public void testEventPost() throws Exception {  
124 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {  
125 - socket.setSoTimeout(TIMEOUT_MS);  
126 -  
127 - // 1. 先进行认证  
128 - String authResponse = authenticate(socket);  
129 - log.info("[testEventPost][认证响应: {}]", authResponse);  
130 -  
131 - // 2.1 构建事件上报请求  
132 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
133 - .put("id", IdUtil.fastSimpleUUID())  
134 - .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod())  
135 - .put("params", IotDeviceEventPostReqDTO.of(  
136 - "eat",  
137 - MapUtil.<String, Object>builder().put("rice", 3).build(),  
138 - System.currentTimeMillis()))  
139 - .put("timestamp", System.currentTimeMillis())  
140 - .build());  
141 - // 2.2 输出请求  
142 - log.info("[testEventPost][请求体: {}]", payload);  
143 -  
144 - // 3.1 发送请求  
145 - String response = sendAndReceive(socket, payload);  
146 - // 3.2 输出结果  
147 - log.info("[testEventPost][响应体: {}]", response);  
148 - }  
149 - }  
150 -  
151 - // ===================== 辅助方法 =====================  
152 -  
153 - /**  
154 - * 执行设备认证  
155 - *  
156 - * @param socket TCP 连接  
157 - * @return 认证响应  
158 - */  
159 - private String authenticate(Socket socket) throws Exception {  
160 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);  
161 - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()  
162 - .setClientId(authInfo.getClientId())  
163 - .setUsername(authInfo.getUsername())  
164 - .setPassword(authInfo.getPassword());  
165 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
166 - .put("id", IdUtil.fastSimpleUUID())  
167 - .put("method", "auth")  
168 - .put("params", authReqDTO)  
169 - .put("timestamp", System.currentTimeMillis())  
170 - .build());  
171 - return sendAndReceive(socket, payload);  
172 - }  
173 -  
174 - /**  
175 - * 发送 TCP 请求并接收响应  
176 - *  
177 - * @param socket TCP Socket  
178 - * @param payload 请求体  
179 - * @return 响应内容  
180 - */  
181 - private String sendAndReceive(Socket socket, String payload) throws Exception {  
182 - // 1. 发送请求  
183 - OutputStream out = socket.getOutputStream();  
184 - InputStream in = socket.getInputStream();  
185 - byte[] sendData = payload.getBytes(StandardCharsets.UTF_8);  
186 - out.write(sendData);  
187 - out.flush();  
188 -  
189 - // 2.1 等待一小段时间让服务器处理  
190 - Thread.sleep(100);  
191 - // 2.2 接收响应  
192 - byte[] buffer = new byte[4096];  
193 - try {  
194 - int length = in.read(buffer);  
195 - if (length > 0) {  
196 - return new String(buffer, 0, length, StandardCharsets.UTF_8);  
197 - }  
198 - return null;  
199 - } catch (java.net.SocketTimeoutException e) {  
200 - log.warn("[sendAndReceive][接收响应超时]");  
201 - return null;  
202 - }  
203 - }  
204 -  
205 -}  
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/tcp-binary-packet-examples.md renamed to yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-binary-packet-examples.md
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/tcp-json-packet-examples.md renamed to yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-json-packet-examples.md