Commit de1a53a5f14af453f3c22459c409db6d26e5f0e5

Authored by YunaiV
1 parent 4003f4b0

feat:【iot】UDP 协议:兼容下行的时候,基于连接的 codec 处理

yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
... ... @@ -223,11 +223,9 @@ public class IotGatewayConfiguration {
223 223 @Bean
224 224 public IotUdpDownstreamSubscriber iotUdpDownstreamSubscriber(IotUdpUpstreamProtocol protocolHandler,
225 225 IotDeviceMessageService messageService,
226   - IotDeviceService deviceService,
227 226 IotUdpSessionManager sessionManager,
228 227 IotMessageBus messageBus) {
229   - return new IotUdpDownstreamSubscriber(protocolHandler, messageService, deviceService, sessionManager,
230   - messageBus);
  228 + return new IotUdpDownstreamSubscriber(protocolHandler, messageService, sessionManager, messageBus);
231 229 }
232 230  
233 231 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpDownstreamSubscriber.java
... ... @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 6 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7 7 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager;
8 8 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.router.IotUdpDownstreamHandler;
9   -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
10 9 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
11 10 import jakarta.annotation.PostConstruct;
12 11 import lombok.RequiredArgsConstructor;
... ... @@ -25,8 +24,6 @@ public class IotUdpDownstreamSubscriber implements IotMessageSubscriber<IotDevic
25 24  
26 25 private final IotDeviceMessageService messageService;
27 26  
28   - private final IotDeviceService deviceService;
29   -
30 27 private final IotUdpSessionManager sessionManager;
31 28  
32 29 private final IotMessageBus messageBus;
... ... @@ -36,7 +33,7 @@ public class IotUdpDownstreamSubscriber implements IotMessageSubscriber<IotDevic
36 33 @PostConstruct
37 34 public void init() {
38 35 // 初始化下游处理器
39   - this.downstreamHandler = new IotUdpDownstreamHandler(messageService, deviceService, sessionManager, protocol);
  36 + this.downstreamHandler = new IotUdpDownstreamHandler(messageService, sessionManager, protocol);
40 37  
41 38 messageBus.register(this);
42 39 log.info("[init][UDP 下游订阅者初始化完成,服务器 ID: {},Topic: {}]",
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/manager/IotUdpSessionManager.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager;
2 2  
3 3 import io.vertx.core.buffer.Buffer;
4 4 import io.vertx.core.datagram.DatagramSocket;
  5 +import lombok.Data;
5 6 import lombok.extern.slf4j.Slf4j;
6 7 import org.springframework.stereotype.Component;
7 8  
... ... @@ -29,9 +30,9 @@ import java.util.concurrent.ConcurrentHashMap;
29 30 public class IotUdpSessionManager {
30 31  
31 32 /**
32   - * 设备 ID -> 设备地址(用于下行消息发送)
  33 + * 设备 ID -> 会话信息(包含地址和 codecType)
33 34 */
34   - private final Map<Long, InetSocketAddress> deviceAddressMap = new ConcurrentHashMap<>();
  35 + private final Map<Long, SessionInfo> deviceSessionMap = new ConcurrentHashMap<>();
35 36  
36 37 /**
37 38 * 设备地址 Key -> 最后活跃时间(用于清理)
... ... @@ -44,18 +45,41 @@ public class IotUdpSessionManager {
44 45 private final Map<String, Long> addressDeviceMap = new ConcurrentHashMap<>();
45 46  
46 47 /**
47   - * 更新设备地址(每次收到上行消息时调用)
  48 + * 更新设备会话(每次收到上行消息时调用)
48 49 *
49   - * @param deviceId 设备 ID
50   - * @param address 设备地址
  50 + * @param deviceId 设备 ID
  51 + * @param address 设备地址
  52 + * @param codecType 消息编解码类型
51 53 */
52   - public void updateDeviceAddress(Long deviceId, InetSocketAddress address) {
  54 + public void updateDeviceSession(Long deviceId, InetSocketAddress address, String codecType) {
53 55 String addressKey = buildAddressKey(address);
54   - // 更新设备地址映射
55   - deviceAddressMap.put(deviceId, address);
  56 + // 更新设备会话映射
  57 + deviceSessionMap.put(deviceId, new SessionInfo().setAddress(address).setCodecType(codecType));
56 58 lastActiveTimeMap.put(addressKey, LocalDateTime.now());
57 59 addressDeviceMap.put(addressKey, deviceId);
58   - log.debug("[updateDeviceAddress][更新设备地址,设备 ID: {},地址: {}]", deviceId, addressKey);
  60 + log.debug("[updateDeviceSession][更新设备会话,设备 ID: {},地址: {},codecType: {}]", deviceId, addressKey, codecType);
  61 + }
  62 +
  63 + /**
  64 + * 更新设备地址(兼容旧接口,默认不更新 codecType)
  65 + *
  66 + * @param deviceId 设备 ID
  67 + * @param address 设备地址
  68 + */
  69 + public void updateDeviceAddress(Long deviceId, InetSocketAddress address) {
  70 + SessionInfo sessionInfo = deviceSessionMap.get(deviceId);
  71 + String codecType = sessionInfo != null ? sessionInfo.getCodecType() : null;
  72 + updateDeviceSession(deviceId, address, codecType);
  73 + }
  74 +
  75 + /**
  76 + * 获取设备会话信息
  77 + *
  78 + * @param deviceId 设备 ID
  79 + * @return 会话信息
  80 + */
  81 + public SessionInfo getSessionInfo(Long deviceId) {
  82 + return deviceSessionMap.get(deviceId);
59 83 }
60 84  
61 85 /**
... ... @@ -65,7 +89,7 @@ public class IotUdpSessionManager {
65 89 * @return 是否在线
66 90 */
67 91 public boolean isDeviceOnline(Long deviceId) {
68   - return deviceAddressMap.containsKey(deviceId);
  92 + return deviceSessionMap.containsKey(deviceId);
69 93 }
70 94  
71 95 /**
... ... @@ -87,12 +111,13 @@ public class IotUdpSessionManager {
87 111 * @return 是否发送成功
88 112 */
89 113 public boolean sendToDevice(Long deviceId, byte[] data, DatagramSocket socket) {
90   - InetSocketAddress address = deviceAddressMap.get(deviceId);
91   - if (address == null) {
92   - log.warn("[sendToDevice][设备地址不存在,设备 ID: {}]", deviceId);
  114 + SessionInfo sessionInfo = deviceSessionMap.get(deviceId);
  115 + if (sessionInfo == null || sessionInfo.getAddress() == null) {
  116 + log.warn("[sendToDevice][设备会话不存在,设备 ID: {}]", deviceId);
93 117 return false;
94 118 }
95 119  
  120 + InetSocketAddress address = sessionInfo.getAddress();
96 121 try {
97 122 socket.send(Buffer.buffer(data), address.getPort(), address.getHostString(), result -> {
98 123 if (result.succeeded()) {
... ... @@ -134,8 +159,8 @@ public class IotUdpSessionManager {
134 159 iterator.remove();
135 160 continue;
136 161 }
137   - InetSocketAddress address = deviceAddressMap.remove(deviceId);
138   - if (address == null) {
  162 + SessionInfo sessionInfo = deviceSessionMap.remove(deviceId);
  163 + if (sessionInfo == null) {
139 164 iterator.remove();
140 165 continue;
141 166 }
... ... @@ -157,4 +182,22 @@ public class IotUdpSessionManager {
157 182 return address.getHostString() + ":" + address.getPort();
158 183 }
159 184  
  185 + /**
  186 + * 会话信息
  187 + */
  188 + @Data
  189 + public static class SessionInfo {
  190 +
  191 + /**
  192 + * 设备地址
  193 + */
  194 + private InetSocketAddress address;
  195 +
  196 + /**
  197 + * 消息编解码类型
  198 + */
  199 + private String codecType;
  200 +
  201 + }
  202 +
160 203 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/router/IotUdpDownstreamHandler.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp.router;
2 2  
3   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
4 3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 4 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpUpstreamProtocol;
6 5 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager;
7   -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
8 6 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
9 7 import io.vertx.core.datagram.DatagramSocket;
10 8 import lombok.extern.slf4j.Slf4j;
... ... @@ -19,18 +17,14 @@ public class IotUdpDownstreamHandler {
19 17  
20 18 private final IotDeviceMessageService deviceMessageService;
21 19  
22   - private final IotDeviceService deviceService;
23   -
24 20 private final IotUdpSessionManager sessionManager;
25 21  
26 22 private final IotUdpUpstreamProtocol protocol;
27 23  
28 24 public IotUdpDownstreamHandler(IotDeviceMessageService deviceMessageService,
29   - IotDeviceService deviceService,
30 25 IotUdpSessionManager sessionManager,
31 26 IotUdpUpstreamProtocol protocol) {
32 27 this.deviceMessageService = deviceMessageService;
33   - this.deviceService = deviceService;
34 28 this.sessionManager = sessionManager;
35 29 this.protocol = protocol;
36 30 }
... ... @@ -45,21 +39,15 @@ public class IotUdpDownstreamHandler {
45 39 log.info("[handle][处理下行消息,设备 ID: {},方法: {},消息 ID: {}]",
46 40 message.getDeviceId(), message.getMethod(), message.getId());
47 41  
48   - // 1.1 获取设备信息
49   - IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId());
50   - if (deviceInfo == null) {
51   - log.error("[handle][设备不存在,设备 ID: {}]", message.getDeviceId());
52   - return;
53   - }
54   - // 1.2 检查设备是否在线(即是否有地址映射)
55   - if (sessionManager.isDeviceOffline(message.getDeviceId())) {
  42 + // 1. 获取会话信息(包含 codecType)
  43 + IotUdpSessionManager.SessionInfo sessionInfo = sessionManager.getSessionInfo(message.getDeviceId());
  44 + if (sessionInfo == null) {
56 45 log.warn("[handle][设备不在线,设备 ID: {}]", message.getDeviceId());
57 46 return;
58 47 }
59 48  
60   - // 2. 根据产品 Key 和设备名称编码消息,并发送到设备
61   - byte[] bytes = deviceMessageService.encodeDeviceMessage(message, deviceInfo.getProductKey(),
62   - deviceInfo.getDeviceName());
  49 + // 2. 使用会话中的 codecType 编码消息,并发送到设备
  50 + byte[] bytes = deviceMessageService.encodeDeviceMessage(message, sessionInfo.getCodecType());
63 51 DatagramSocket socket = protocol.getUdpSocket();
64 52 if (socket == null) {
65 53 log.error("[handle][UDP Socket 不可用,设备 ID: {}]", message.getDeviceId());
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/router/IotUdpUpstreamHandler.java
... ... @@ -195,8 +195,8 @@ public class IotUdpUpstreamHandler {
195 195 // 3.1 生成 JWT Token(无状态)
196 196 String token = deviceTokenService.createToken(device.getProductKey(), device.getDeviceName());
197 197  
198   - // 3.2 更新设备地址映射(用于下行消息)
199   - sessionManager.updateDeviceAddress(device.getId(), senderAddress);
  198 + // 3.2 更新设备会话信息(用于下行消息,保存 codecType)
  199 + sessionManager.updateDeviceSession(device.getId(), senderAddress, codecType);
200 200  
201 201 // 3.3 发送上线消息
202 202 sendOnlineMessage(device);
... ... @@ -298,8 +298,8 @@ public class IotUdpUpstreamHandler {
298 298 return;
299 299 }
300 300  
301   - // 3. 更新设备地址映射(保持最新)
302   - sessionManager.updateDeviceAddress(device.getId(), senderAddress);
  301 + // 3. 更新设备会话信息(保持最新,保存 codecType)
  302 + sessionManager.updateDeviceSession(device.getId(), senderAddress, codecType);
303 303  
304 304 // 4. 将 body 设置为实际的 params,发送消息到消息总线
305 305 message.setParams(body);
... ...
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotDirectDeviceUdpProtocolIntegrationTest.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.HexUtil;
4 5 import cn.hutool.core.util.IdUtil;
5   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
7 7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
8   -import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
  8 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
9 9 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
10 10 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
11 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;
12 15 import lombok.extern.slf4j.Slf4j;
13 16 import org.junit.jupiter.api.Test;
14 17  
15 18 import java.net.DatagramPacket;
16 19 import java.net.DatagramSocket;
17 20 import java.net.InetAddress;
18   -import java.nio.charset.StandardCharsets;
19 21 import java.util.HashMap;
20 22 import java.util.Map;
21 23  
... ... @@ -24,10 +26,16 @@ import java.util.Map;
24 26 *
25 27 * <p>测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 UDP 协议直接连接平台
26 28 *
  29 + * <p>支持两种编解码格式:
  30 + * <ul>
  31 + * <li>{@link IotTcpJsonDeviceMessageCodec} - JSON 格式</li>
  32 + * <li>{@link IotTcpBinaryDeviceMessageCodec} - 二进制格式</li>
  33 + * </ul>
  34 + *
27 35 * <p>使用步骤:
28 36 * <ol>
29   - * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8092)</li>
30   - * <li>运行 {@link #testDeviceRegister()} 测试直连设备动态注册(一型一密)</li>
  37 + * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li>
  38 + * <li>修改 {@link #CODEC} 选择测试的编解码格式</li>
31 39 * <li>运行 {@link #testAuth()} 获取设备 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li>
32 40 * <li>运行以下测试方法:
33 41 * <ul>
... ... @@ -48,6 +56,10 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
48 56 private static final int SERVER_PORT = 8093;
49 57 private static final int TIMEOUT_MS = 5000;
50 58  
  59 + // ===================== 编解码器选择(修改此处切换 JSON / Binary) =====================
  60 + private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec();
  61 +// private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec();
  62 +
51 63 // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) =====================
52 64 private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT";
53 65 private static final String DEVICE_NAME = "small";
... ... @@ -65,27 +77,32 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
65 77 */
66 78 @Test
67 79 public void testAuth() throws Exception {
68   - // 1.1 构建请求
  80 + // 1.1 构建认证消息
69 81 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
70 82 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
71 83 .setClientId(authInfo.getClientId())
72 84 .setUsername(authInfo.getUsername())
73 85 .setPassword(authInfo.getPassword());
74   - String payload = JsonUtils.toJsonString(MapUtil.builder()
75   - .put("id", IdUtil.fastSimpleUUID())
76   - .put("method", "auth")
77   - .put("params", authReqDTO)
78   - .build());
79   - // 1.2 输出请求
80   - log.info("[testAuth][请求体: {}]", payload);
  86 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  87 + // 1.2 编码
  88 + byte[] payload = CODEC.encode(request);
  89 + log.info("[testAuth][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length);
  90 + if (CODEC instanceof IotTcpBinaryDeviceMessageCodec) {
  91 + log.info("[testAuth][二进制数据包(HEX): {}]", HexUtil.encodeHexStr(payload));
  92 + }
81 93  
82 94 // 2.1 发送请求
83 95 try (DatagramSocket socket = new DatagramSocket()) {
84 96 socket.setSoTimeout(TIMEOUT_MS);
85   - String response = sendAndReceive(socket, payload);
86   - // 2.2 输出结果
87   - log.info("[testAuth][响应体: {}]", response);
88   - log.info("[testAuth][请将返回的 token 复制到 TOKEN 常量中]");
  97 + byte[] responseBytes = sendAndReceive(socket, payload);
  98 + // 2.2 解码响应
  99 + if (responseBytes != null) {
  100 + IotDeviceMessage response = CODEC.decode(responseBytes);
  101 + log.info("[testAuth][响应消息: {}]", response);
  102 + log.info("[testAuth][请将返回的 token 复制到 TOKEN 常量中]");
  103 + } else {
  104 + log.warn("[testAuth][未收到响应]");
  105 + }
89 106 }
90 107 }
91 108  
... ... @@ -96,25 +113,30 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
96 113 */
97 114 @Test
98 115 public void testPropertyPost() throws Exception {
99   - // 1.1 构建请求(UDP 协议:token 放在 params 中)
100   - String payload = JsonUtils.toJsonString(MapUtil.builder()
101   - .put("id", IdUtil.fastSimpleUUID())
102   - .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())
103   - .put("version", "1.0")
104   - .put("params", withToken(IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
  116 + // 1.1 构建属性上报消息(UDP 协议:token 放在 params 中)
  117 + IotDeviceMessage request = IotDeviceMessage.of(
  118 + IdUtil.fastSimpleUUID(),
  119 + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
  120 + withToken(IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
105 121 .put("width", 1)
106 122 .put("height", "2")
107   - .build())))
108   - .build());
109   - // 1.2 输出请求
110   - log.info("[testPropertyPost][请求体: {}]", payload);
  123 + .build())),
  124 + null, null, null);
  125 + // 1.2 编码
  126 + byte[] payload = CODEC.encode(request);
  127 + log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
111 128  
112   - // 2. 发送请求
  129 + // 2.1 发送请求
113 130 try (DatagramSocket socket = new DatagramSocket()) {
114 131 socket.setSoTimeout(TIMEOUT_MS);
115   - String response = sendAndReceive(socket, payload);
116   - // 2.2 输出结果
117   - log.info("[testPropertyPost][响应体: {}]", response);
  132 + byte[] responseBytes = sendAndReceive(socket, payload);
  133 + // 2.2 解码响应
  134 + if (responseBytes != null) {
  135 + IotDeviceMessage response = CODEC.decode(responseBytes);
  136 + log.info("[testPropertyPost][响应消息: {}]", response);
  137 + } else {
  138 + log.warn("[testPropertyPost][未收到响应]");
  139 + }
118 140 }
119 141 }
120 142  
... ... @@ -125,60 +147,30 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
125 147 */
126 148 @Test
127 149 public void testEventPost() throws Exception {
128   - // 1.1 构建请求(UDP 协议:token 放在 params 中)
129   - String payload = JsonUtils.toJsonString(MapUtil.builder()
130   - .put("id", IdUtil.fastSimpleUUID())
131   - .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod())
132   - .put("version", "1.0")
133   - .put("params", withToken(IotDeviceEventPostReqDTO.of(
  150 + // 1.1 构建事件上报消息(UDP 协议:token 放在 params 中)
  151 + IotDeviceMessage request = IotDeviceMessage.of(
  152 + IdUtil.fastSimpleUUID(),
  153 + IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
  154 + withToken(IotDeviceEventPostReqDTO.of(
134 155 "eat",
135 156 MapUtil.<String, Object>builder().put("rice", 3).build(),
136   - System.currentTimeMillis())))
137   - .build());
138   - // 1.2 输出请求
139   - log.info("[testEventPost][请求体: {}]", payload);
140   -
141   - // 2.1 发送请求
142   - try (DatagramSocket socket = new DatagramSocket()) {
143   - socket.setSoTimeout(TIMEOUT_MS);
144   - String response = sendAndReceive(socket, payload);
145   - // 2.2 输出结果
146   - log.info("[testEventPost][响应体: {}]", response);
147   - }
148   - }
149   -
150   - // ===================== 动态注册测试 =====================
151   -
152   - /**
153   - * 直连设备动态注册测试(一型一密)
154   - * <p>
155   - * 使用产品密钥(productSecret)验证身份,成功后返回设备密钥(deviceSecret)
156   - * <p>
157   - * 注意:此接口不需要 Token 认证
158   - */
159   - @Test
160   - public void testDeviceRegister() throws Exception {
161   - // 1.1 构建请求参数
162   - IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO();
163   - reqDTO.setProductKey(PRODUCT_KEY);
164   - reqDTO.setDeviceName("test-" + System.currentTimeMillis());
165   - reqDTO.setProductSecret("test-product-secret");
166   - // 1.2 构建请求
167   - String payload = JsonUtils.toJsonString(MapUtil.builder()
168   - .put("id", IdUtil.fastSimpleUUID())
169   - .put("method", IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod())
170   - .put("params", reqDTO)
171   - .build());
172   - // 1.3 输出请求
173   - log.info("[testDeviceRegister][请求体: {}]", payload);
  157 + System.currentTimeMillis())),
  158 + null, null, null);
  159 + // 1.2 编码
  160 + byte[] payload = CODEC.encode(request);
  161 + log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
174 162  
175 163 // 2.1 发送请求
176 164 try (DatagramSocket socket = new DatagramSocket()) {
177 165 socket.setSoTimeout(TIMEOUT_MS);
178   - String response = sendAndReceive(socket, payload);
179   - // 2.2 输出结果
180   - log.info("[testDeviceRegister][响应体: {}]", response);
181   - log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]");
  166 + byte[] responseBytes = sendAndReceive(socket, payload);
  167 + // 2.2 解码响应
  168 + if (responseBytes != null) {
  169 + IotDeviceMessage response = CODEC.decode(responseBytes);
  170 + log.info("[testEventPost][响应消息: {}]", response);
  171 + } else {
  172 + log.warn("[testEventPost][未收到响应]");
  173 + }
182 174 }
183 175 }
184 176  
... ... @@ -201,20 +193,18 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
201 193 return result;
202 194 }
203 195  
204   -
205 196 /**
206 197 * 发送 UDP 请求并接收响应
207 198 *
208 199 * @param socket UDP Socket
209   - * @param payload 请求体
210   - * @return 响应内容
  200 + * @param payload 请求数据
  201 + * @return 响应数据
211 202 */
212   - public static String sendAndReceive(DatagramSocket socket, String payload) throws Exception {
213   - byte[] sendData = payload.getBytes(StandardCharsets.UTF_8);
  203 + public static byte[] sendAndReceive(DatagramSocket socket, byte[] payload) throws Exception {
214 204 InetAddress address = InetAddress.getByName(SERVER_HOST);
215 205  
216 206 // 发送请求
217   - DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, SERVER_PORT);
  207 + DatagramPacket sendPacket = new DatagramPacket(payload, payload.length, address, SERVER_PORT);
218 208 socket.send(sendPacket);
219 209  
220 210 // 接收响应
... ... @@ -222,7 +212,9 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
222 212 DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
223 213 try {
224 214 socket.receive(receivePacket);
225   - return new String(receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);
  215 + byte[] response = new byte[receivePacket.getLength()];
  216 + System.arraycopy(receivePacket.getData(), 0, response, 0, receivePacket.getLength());
  217 + return response;
226 218 } catch (java.net.SocketTimeoutException e) {
227 219 log.warn("[sendAndReceive][接收响应超时]");
228 220 return null;
... ...
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotGatewayDeviceUdpProtocolIntegrationTest.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.HexUtil;
4 5 import cn.hutool.core.util.IdUtil;
5   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
7 7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  8 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
8 9 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
9 10 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO;
10 11 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPackPostReqDTO;
... ... @@ -12,6 +13,9 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO;
12 13 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO;
13 14 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetReqDTO;
14 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;
15 19 import lombok.extern.slf4j.Slf4j;
16 20 import org.junit.jupiter.api.Test;
17 21  
... ... @@ -28,9 +32,16 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd
28 32 *
29 33 * <p>测试场景:网关设备(IotProductDeviceTypeEnum 的 GATEWAY 类型)通过 UDP 协议管理子设备拓扑关系
30 34 *
  35 + * <p>支持两种编解码格式:
  36 + * <ul>
  37 + * <li>{@link IotTcpJsonDeviceMessageCodec} - JSON 格式</li>
  38 + * <li>{@link IotTcpBinaryDeviceMessageCodec} - 二进制格式</li>
  39 + * </ul>
  40 + *
31 41 * <p>使用步骤:
32 42 * <ol>
33 43 * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li>
  44 + * <li>修改 {@link #CODEC} 选择测试的编解码格式</li>
34 45 * <li>运行 {@link #testAuth()} 获取网关设备 token,将返回的 token 粘贴到 {@link #GATEWAY_TOKEN} 常量</li>
35 46 * <li>运行以下测试方法:
36 47 * <ul>
... ... @@ -50,10 +61,12 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd
50 61 @Slf4j
51 62 public class IotGatewayDeviceUdpProtocolIntegrationTest {
52 63  
53   - private static final String SERVER_HOST = "127.0.0.1";
54   - private static final int SERVER_PORT = 8093;
55 64 private static final int TIMEOUT_MS = 5000;
56 65  
  66 + // ===================== 编解码器选择(修改此处切换 JSON / Binary) =====================
  67 + private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec();
  68 +// private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec();
  69 +
57 70 // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) =====================
58 71 private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v";
59 72 private static final String GATEWAY_DEVICE_NAME = "sub-ddd";
... ... @@ -76,28 +89,33 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
76 89 */
77 90 @Test
78 91 public void testAuth() throws Exception {
79   - // 1.1 构建请求
  92 + // 1.1 构建认证消息
80 93 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
81 94 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);
82 95 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
83 96 .setClientId(authInfo.getClientId())
84 97 .setUsername(authInfo.getUsername())
85 98 .setPassword(authInfo.getPassword());
86   - String payload = JsonUtils.toJsonString(MapUtil.builder()
87   - .put("id", IdUtil.fastSimpleUUID())
88   - .put("method", "auth")
89   - .put("params", authReqDTO)
90   - .build());
91   - // 1.2 输出请求
92   - log.info("[testAuth][请求体: {}]", payload);
  99 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  100 + // 1.2 编码
  101 + byte[] payload = CODEC.encode(request);
  102 + log.info("[testAuth][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length);
  103 + if (CODEC instanceof IotTcpBinaryDeviceMessageCodec) {
  104 + log.info("[testAuth][二进制数据包(HEX): {}]", HexUtil.encodeHexStr(payload));
  105 + }
93 106  
94 107 // 2.1 发送请求
95 108 try (DatagramSocket socket = new DatagramSocket()) {
96 109 socket.setSoTimeout(TIMEOUT_MS);
97   - String response = sendAndReceive(socket, payload);
98   - // 2.2 输出结果
99   - log.info("[testAuth][响应体: {}]", response);
100   - log.info("[testAuth][请将返回的 token 复制到 GATEWAY_TOKEN 常量中]");
  110 + byte[] responseBytes = sendAndReceive(socket, payload);
  111 + // 2.2 解码响应
  112 + if (responseBytes != null) {
  113 + IotDeviceMessage response = CODEC.decode(responseBytes);
  114 + log.info("[testAuth][响应消息: {}]", response);
  115 + log.info("[testAuth][请将返回的 token 复制到 GATEWAY_TOKEN 常量中]");
  116 + } else {
  117 + log.warn("[testAuth][未收到响应]");
  118 + }
101 119 }
102 120 }
103 121  
... ... @@ -120,21 +138,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
120 138 // 1.2 构建请求参数
121 139 IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO();
122 140 params.setSubDevices(Collections.singletonList(subDeviceAuth));
123   - String payload = JsonUtils.toJsonString(MapUtil.builder()
124   - .put("id", IdUtil.fastSimpleUUID())
125   - .put("method", IotDeviceMessageMethodEnum.TOPO_ADD.getMethod())
126   - .put("version", "1.0")
127   - .put("params", withToken(params))
128   - .build());
129   - // 1.3 输出请求
130   - log.info("[testTopoAdd][请求体: {}]", payload);
  141 + IotDeviceMessage request = IotDeviceMessage.of(
  142 + IdUtil.fastSimpleUUID(),
  143 + IotDeviceMessageMethodEnum.TOPO_ADD.getMethod(),
  144 + withToken(params),
  145 + null, null, null);
  146 + // 1.3 编码
  147 + byte[] payload = CODEC.encode(request);
  148 + log.info("[testTopoAdd][Codec: {}, 请求消息: {}]", CODEC.type(), request);
131 149  
132 150 // 2.1 发送请求
133 151 try (DatagramSocket socket = new DatagramSocket()) {
134 152 socket.setSoTimeout(TIMEOUT_MS);
135   - String response = sendAndReceive(socket, payload);
136   - // 2.2 输出结果
137   - log.info("[testTopoAdd][响应体: {}]", response);
  153 + byte[] responseBytes = sendAndReceive(socket, payload);
  154 + // 2.2 解码响应
  155 + if (responseBytes != null) {
  156 + IotDeviceMessage response = CODEC.decode(responseBytes);
  157 + log.info("[testTopoAdd][响应消息: {}]", response);
  158 + } else {
  159 + log.warn("[testTopoAdd][未收到响应]");
  160 + }
138 161 }
139 162 }
140 163  
... ... @@ -149,21 +172,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
149 172 IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO();
150 173 params.setSubDevices(Collections.singletonList(
151 174 new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME)));
152   - String payload = JsonUtils.toJsonString(MapUtil.builder()
153   - .put("id", IdUtil.fastSimpleUUID())
154   - .put("method", IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod())
155   - .put("version", "1.0")
156   - .put("params", withToken(params))
157   - .build());
158   - // 1.2 输出请求
159   - log.info("[testTopoDelete][请求体: {}]", payload);
  175 + IotDeviceMessage request = IotDeviceMessage.of(
  176 + IdUtil.fastSimpleUUID(),
  177 + IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod(),
  178 + withToken(params),
  179 + null, null, null);
  180 + // 1.2 编码
  181 + byte[] payload = CODEC.encode(request);
  182 + log.info("[testTopoDelete][Codec: {}, 请求消息: {}]", CODEC.type(), request);
160 183  
161 184 // 2.1 发送请求
162 185 try (DatagramSocket socket = new DatagramSocket()) {
163 186 socket.setSoTimeout(TIMEOUT_MS);
164   - String response = sendAndReceive(socket, payload);
165   - // 2.2 输出结果
166   - log.info("[testTopoDelete][响应体: {}]", response);
  187 + byte[] responseBytes = sendAndReceive(socket, payload);
  188 + // 2.2 解码响应
  189 + if (responseBytes != null) {
  190 + IotDeviceMessage response = CODEC.decode(responseBytes);
  191 + log.info("[testTopoDelete][响应消息: {}]", response);
  192 + } else {
  193 + log.warn("[testTopoDelete][未收到响应]");
  194 + }
167 195 }
168 196 }
169 197  
... ... @@ -176,21 +204,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
176 204 public void testTopoGet() throws Exception {
177 205 // 1.1 构建请求参数(目前为空,预留扩展)
178 206 IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO();
179   - String payload = JsonUtils.toJsonString(MapUtil.builder()
180   - .put("id", IdUtil.fastSimpleUUID())
181   - .put("method", IotDeviceMessageMethodEnum.TOPO_GET.getMethod())
182   - .put("version", "1.0")
183   - .put("params", withToken(params))
184   - .build());
185   - // 1.2 输出请求
186   - log.info("[testTopoGet][请求体: {}]", payload);
  207 + IotDeviceMessage request = IotDeviceMessage.of(
  208 + IdUtil.fastSimpleUUID(),
  209 + IotDeviceMessageMethodEnum.TOPO_GET.getMethod(),
  210 + withToken(params),
  211 + null, null, null);
  212 + // 1.2 编码
  213 + byte[] payload = CODEC.encode(request);
  214 + log.info("[testTopoGet][Codec: {}, 请求消息: {}]", CODEC.type(), request);
187 215  
188 216 // 2.1 发送请求
189 217 try (DatagramSocket socket = new DatagramSocket()) {
190 218 socket.setSoTimeout(TIMEOUT_MS);
191   - String response = sendAndReceive(socket, payload);
192   - // 2.2 输出结果
193   - log.info("[testTopoGet][响应体: {}]", response);
  219 + byte[] responseBytes = sendAndReceive(socket, payload);
  220 + // 2.2 解码响应
  221 + if (responseBytes != null) {
  222 + IotDeviceMessage response = CODEC.decode(responseBytes);
  223 + log.info("[testTopoGet][响应消息: {}]", response);
  224 + } else {
  225 + log.warn("[testTopoGet][未收到响应]");
  226 + }
194 227 }
195 228 }
196 229  
... ... @@ -209,21 +242,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
209 242 IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO();
210 243 subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY);
211 244 subDevice.setDeviceName("mougezishebei");
212   - String payload = JsonUtils.toJsonString(MapUtil.builder()
213   - .put("id", IdUtil.fastSimpleUUID())
214   - .put("method", IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod())
215   - .put("version", "1.0")
216   - .put("params", withToken(Collections.singletonList(subDevice)))
217   - .build());
218   - // 1.2 输出请求
219   - log.info("[testSubDeviceRegister][请求体: {}]", payload);
  245 + IotDeviceMessage request = IotDeviceMessage.of(
  246 + IdUtil.fastSimpleUUID(),
  247 + IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod(),
  248 + withToken(Collections.singletonList(subDevice)),
  249 + null, null, null);
  250 + // 1.2 编码
  251 + byte[] payload = CODEC.encode(request);
  252 + log.info("[testSubDeviceRegister][Codec: {}, 请求消息: {}]", CODEC.type(), request);
220 253  
221 254 // 2.1 发送请求
222 255 try (DatagramSocket socket = new DatagramSocket()) {
223 256 socket.setSoTimeout(TIMEOUT_MS);
224   - String response = sendAndReceive(socket, payload);
225   - // 2.2 输出结果
226   - log.info("[testSubDeviceRegister][响应体: {}]", response);
  257 + byte[] responseBytes = sendAndReceive(socket, payload);
  258 + // 2.2 解码响应
  259 + if (responseBytes != null) {
  260 + IotDeviceMessage response = CODEC.decode(responseBytes);
  261 + log.info("[testSubDeviceRegister][响应消息: {}]", response);
  262 + } else {
  263 + log.warn("[testSubDeviceRegister][未收到响应]");
  264 + }
227 265 }
228 266 }
229 267  
... ... @@ -268,21 +306,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
268 306 params.setProperties(gatewayProperties);
269 307 params.setEvents(gatewayEvents);
270 308 params.setSubDevices(List.of(subDeviceData));
271   - String payload = JsonUtils.toJsonString(MapUtil.builder()
272   - .put("id", IdUtil.fastSimpleUUID())
273   - .put("method", IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod())
274   - .put("version", "1.0")
275   - .put("params", withToken(params))
276   - .build());
277   - // 1.7 输出请求
278   - log.info("[testPropertyPackPost][请求体: {}]", payload);
  309 + IotDeviceMessage request = IotDeviceMessage.of(
  310 + IdUtil.fastSimpleUUID(),
  311 + IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod(),
  312 + withToken(params),
  313 + null, null, null);
  314 + // 1.7 编码
  315 + byte[] payload = CODEC.encode(request);
  316 + log.info("[testPropertyPackPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
279 317  
280 318 // 2.1 发送请求
281 319 try (DatagramSocket socket = new DatagramSocket()) {
282 320 socket.setSoTimeout(TIMEOUT_MS);
283   - String response = sendAndReceive(socket, payload);
284   - // 2.2 输出结果
285   - log.info("[testPropertyPackPost][响应体: {}]", response);
  321 + byte[] responseBytes = sendAndReceive(socket, payload);
  322 + // 2.2 解码响应
  323 + if (responseBytes != null) {
  324 + IotDeviceMessage response = CODEC.decode(responseBytes);
  325 + log.info("[testPropertyPackPost][响应消息: {}]", response);
  326 + } else {
  327 + log.warn("[testPropertyPackPost][未收到响应]");
  328 + }
286 329 }
287 330 }
288 331  
... ...
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotGatewaySubDeviceUdpProtocolIntegrationTest.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.HexUtil;
4 5 import cn.hutool.core.util.IdUtil;
5   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
7 7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  8 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
8 9 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
9 10 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
10 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;
11 15 import lombok.extern.slf4j.Slf4j;
12 16 import org.junit.jupiter.api.Test;
13 17  
... ... @@ -25,10 +29,17 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd
25 29 * <p><b>重要说明:子设备无法直接连接平台,所有请求均由网关设备(Gateway)代为转发。</b>
26 30 * <p>网关设备转发子设备请求时,Token 使用子设备自己的信息。
27 31 *
  32 + * <p>支持两种编解码格式:
  33 + * <ul>
  34 + * <li>{@link IotTcpJsonDeviceMessageCodec} - JSON 格式</li>
  35 + * <li>{@link IotTcpBinaryDeviceMessageCodec} - 二进制格式</li>
  36 + * </ul>
  37 + *
28 38 * <p>使用步骤:
29 39 * <ol>
30 40 * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li>
31 41 * <li>确保子设备已通过 {@link IotGatewayDeviceUdpProtocolIntegrationTest#testTopoAdd()} 绑定到网关</li>
  42 + * <li>修改 {@link #CODEC} 选择测试的编解码格式</li>
32 43 * <li>运行 {@link #testAuth()} 获取子设备 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li>
33 44 * <li>运行以下测试方法:
34 45 * <ul>
... ... @@ -45,10 +56,12 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd
45 56 @Slf4j
46 57 public class IotGatewaySubDeviceUdpProtocolIntegrationTest {
47 58  
48   - private static final String SERVER_HOST = "127.0.0.1";
49   - private static final int SERVER_PORT = 8093;
50 59 private static final int TIMEOUT_MS = 5000;
51 60  
  61 + // ===================== 编解码器选择(修改此处切换 JSON / Binary) =====================
  62 + private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec();
  63 +// private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec();
  64 +
52 65 // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) =====================
53 66 private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn";
54 67 private static final String DEVICE_NAME = "chazuo-it";
... ... @@ -66,27 +79,32 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest {
66 79 */
67 80 @Test
68 81 public void testAuth() throws Exception {
69   - // 1.1 构建请求
  82 + // 1.1 构建认证消息
70 83 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
71 84 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
72 85 .setClientId(authInfo.getClientId())
73 86 .setUsername(authInfo.getUsername())
74 87 .setPassword(authInfo.getPassword());
75   - String payload = JsonUtils.toJsonString(MapUtil.builder()
76   - .put("id", IdUtil.fastSimpleUUID())
77   - .put("method", "auth")
78   - .put("params", authReqDTO)
79   - .build());
80   - // 1.2 输出请求
81   - log.info("[testAuth][请求体: {}]", payload);
  88 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  89 + // 1.2 编码
  90 + byte[] payload = CODEC.encode(request);
  91 + log.info("[testAuth][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length);
  92 + if (CODEC instanceof IotTcpBinaryDeviceMessageCodec) {
  93 + log.info("[testAuth][二进制数据包(HEX): {}]", HexUtil.encodeHexStr(payload));
  94 + }
82 95  
83 96 // 2.1 发送请求
84 97 try (DatagramSocket socket = new DatagramSocket()) {
85 98 socket.setSoTimeout(TIMEOUT_MS);
86   - String response = sendAndReceive(socket, payload);
87   - // 2.2 输出结果
88   - log.info("[testAuth][响应体: {}]", response);
89   - log.info("[testAuth][请将返回的 token 复制到 TOKEN 常量中]");
  99 + byte[] responseBytes = sendAndReceive(socket, payload);
  100 + // 2.2 解码响应
  101 + if (responseBytes != null) {
  102 + IotDeviceMessage response = CODEC.decode(responseBytes);
  103 + log.info("[testAuth][响应消息: {}]", response);
  104 + log.info("[testAuth][请将返回的 token 复制到 TOKEN 常量中]");
  105 + } else {
  106 + log.warn("[testAuth][未收到响应]");
  107 + }
90 108 }
91 109 }
92 110  
... ... @@ -97,27 +115,32 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest {
97 115 */
98 116 @Test
99 117 public void testPropertyPost() throws Exception {
100   - // 1.1 构建请求(UDP 协议:token 放在 params 中)
101   - String payload = JsonUtils.toJsonString(MapUtil.builder()
102   - .put("id", IdUtil.fastSimpleUUID())
103   - .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())
104   - .put("version", "1.0")
105   - .put("params", withToken(IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
  118 + // 1.1 构建属性上报消息(UDP 协议:token 放在 params 中)
  119 + IotDeviceMessage request = IotDeviceMessage.of(
  120 + IdUtil.fastSimpleUUID(),
  121 + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
  122 + withToken(IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
106 123 .put("power", 100)
107 124 .put("status", "online")
108 125 .put("temperature", 36.5)
109   - .build())))
110   - .build());
111   - // 1.2 输出请求
  126 + .build())),
  127 + null, null, null);
  128 + // 1.2 编码
  129 + byte[] payload = CODEC.encode(request);
112 130 log.info("[testPropertyPost][子设备属性上报 - 请求实际由 Gateway 代为转发]");
113   - log.info("[testPropertyPost][请求体: {}]", payload);
  131 + log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
114 132  
115 133 // 2.1 发送请求
116 134 try (DatagramSocket socket = new DatagramSocket()) {
117 135 socket.setSoTimeout(TIMEOUT_MS);
118   - String response = sendAndReceive(socket, payload);
119   - // 2.2 输出结果
120   - log.info("[testPropertyPost][响应体: {}]", response);
  136 + byte[] responseBytes = sendAndReceive(socket, payload);
  137 + // 2.2 解码响应
  138 + if (responseBytes != null) {
  139 + IotDeviceMessage response = CODEC.decode(responseBytes);
  140 + log.info("[testPropertyPost][响应消息: {}]", response);
  141 + } else {
  142 + log.warn("[testPropertyPost][未收到响应]");
  143 + }
121 144 }
122 145 }
123 146  
... ... @@ -128,12 +151,11 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest {
128 151 */
129 152 @Test
130 153 public void testEventPost() throws Exception {
131   - // 1.1 构建请求(UDP 协议:token 放在 params 中)
132   - String payload = JsonUtils.toJsonString(MapUtil.builder()
133   - .put("id", IdUtil.fastSimpleUUID())
134   - .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod())
135   - .put("version", "1.0")
136   - .put("params", withToken(IotDeviceEventPostReqDTO.of(
  154 + // 1.1 构建事件上报消息(UDP 协议:token 放在 params 中)
  155 + IotDeviceMessage request = IotDeviceMessage.of(
  156 + IdUtil.fastSimpleUUID(),
  157 + IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
  158 + withToken(IotDeviceEventPostReqDTO.of(
137 159 "alarm",
138 160 MapUtil.<String, Object>builder()
139 161 .put("level", "warning")
... ... @@ -141,18 +163,24 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest {
141 163 .put("threshold", 40)
142 164 .put("current", 42)
143 165 .build(),
144   - System.currentTimeMillis())))
145   - .build());
146   - // 1.2 输出请求
  166 + System.currentTimeMillis())),
  167 + null, null, null);
  168 + // 1.2 编码
  169 + byte[] payload = CODEC.encode(request);
147 170 log.info("[testEventPost][子设备事件上报 - 请求实际由 Gateway 代为转发]");
148   - log.info("[testEventPost][请求体: {}]", payload);
  171 + log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
149 172  
150 173 // 2.1 发送请求
151 174 try (DatagramSocket socket = new DatagramSocket()) {
152 175 socket.setSoTimeout(TIMEOUT_MS);
153   - String response = sendAndReceive(socket, payload);
154   - // 2.2 输出结果
155   - log.info("[testEventPost][响应体: {}]", response);
  176 + byte[] responseBytes = sendAndReceive(socket, payload);
  177 + // 2.2 解码响应
  178 + if (responseBytes != null) {
  179 + IotDeviceMessage response = CODEC.decode(responseBytes);
  180 + log.info("[testEventPost][响应消息: {}]", response);
  181 + } else {
  182 + log.warn("[testEventPost][未收到响应]");
  183 + }
156 184 }
157 185 }
158 186  
... ...