Commit de1a53a5f14af453f3c22459c409db6d26e5f0e5
1 parent
4003f4b0
feat:【iot】UDP 协议:兼容下行的时候,基于连接的 codec 处理
Showing
8 changed files
with
329 additions
and
240 deletions
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,11 +223,9 @@ public class IotGatewayConfiguration { | ||
| 223 | @Bean | 223 | @Bean |
| 224 | public IotUdpDownstreamSubscriber iotUdpDownstreamSubscriber(IotUdpUpstreamProtocol protocolHandler, | 224 | public IotUdpDownstreamSubscriber iotUdpDownstreamSubscriber(IotUdpUpstreamProtocol protocolHandler, |
| 225 | IotDeviceMessageService messageService, | 225 | IotDeviceMessageService messageService, |
| 226 | - IotDeviceService deviceService, | ||
| 227 | IotUdpSessionManager sessionManager, | 226 | IotUdpSessionManager sessionManager, |
| 228 | IotMessageBus messageBus) { | 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,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.udp.manager.IotUdpSessionManager; | 7 | import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager; |
| 8 | import cn.iocoder.yudao.module.iot.gateway.protocol.udp.router.IotUdpDownstreamHandler; | 8 | import cn.iocoder.yudao.module.iot.gateway.protocol.udp.router.IotUdpDownstreamHandler; |
| 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 IotUdpDownstreamSubscriber implements IotMessageSubscriber<IotDevic | @@ -25,8 +24,6 @@ public class IotUdpDownstreamSubscriber 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 IotUdpSessionManager sessionManager; | 27 | private final IotUdpSessionManager sessionManager; |
| 31 | 28 | ||
| 32 | private final IotMessageBus messageBus; | 29 | private final IotMessageBus messageBus; |
| @@ -36,7 +33,7 @@ public class IotUdpDownstreamSubscriber implements IotMessageSubscriber<IotDevic | @@ -36,7 +33,7 @@ public class IotUdpDownstreamSubscriber implements IotMessageSubscriber<IotDevic | ||
| 36 | @PostConstruct | 33 | @PostConstruct |
| 37 | public void init() { | 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 | messageBus.register(this); | 38 | messageBus.register(this); |
| 42 | log.info("[init][UDP 下游订阅者初始化完成,服务器 ID: {},Topic: {}]", | 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,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager; | ||
| 2 | 2 | ||
| 3 | import io.vertx.core.buffer.Buffer; | 3 | import io.vertx.core.buffer.Buffer; |
| 4 | import io.vertx.core.datagram.DatagramSocket; | 4 | import io.vertx.core.datagram.DatagramSocket; |
| 5 | +import lombok.Data; | ||
| 5 | import lombok.extern.slf4j.Slf4j; | 6 | import lombok.extern.slf4j.Slf4j; |
| 6 | import org.springframework.stereotype.Component; | 7 | import org.springframework.stereotype.Component; |
| 7 | 8 | ||
| @@ -29,9 +30,9 @@ import java.util.concurrent.ConcurrentHashMap; | @@ -29,9 +30,9 @@ import java.util.concurrent.ConcurrentHashMap; | ||
| 29 | public class IotUdpSessionManager { | 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 | * 设备地址 Key -> 最后活跃时间(用于清理) | 38 | * 设备地址 Key -> 最后活跃时间(用于清理) |
| @@ -44,18 +45,41 @@ public class IotUdpSessionManager { | @@ -44,18 +45,41 @@ public class IotUdpSessionManager { | ||
| 44 | private final Map<String, Long> addressDeviceMap = new ConcurrentHashMap<>(); | 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 | String addressKey = buildAddressKey(address); | 55 | String addressKey = buildAddressKey(address); |
| 54 | - // 更新设备地址映射 | ||
| 55 | - deviceAddressMap.put(deviceId, address); | 56 | + // 更新设备会话映射 |
| 57 | + deviceSessionMap.put(deviceId, new SessionInfo().setAddress(address).setCodecType(codecType)); | ||
| 56 | lastActiveTimeMap.put(addressKey, LocalDateTime.now()); | 58 | lastActiveTimeMap.put(addressKey, LocalDateTime.now()); |
| 57 | addressDeviceMap.put(addressKey, deviceId); | 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,7 +89,7 @@ public class IotUdpSessionManager { | ||
| 65 | * @return 是否在线 | 89 | * @return 是否在线 |
| 66 | */ | 90 | */ |
| 67 | public boolean isDeviceOnline(Long deviceId) { | 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,12 +111,13 @@ public class IotUdpSessionManager { | ||
| 87 | * @return 是否发送成功 | 111 | * @return 是否发送成功 |
| 88 | */ | 112 | */ |
| 89 | public boolean sendToDevice(Long deviceId, byte[] data, DatagramSocket socket) { | 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 | return false; | 117 | return false; |
| 94 | } | 118 | } |
| 95 | 119 | ||
| 120 | + InetSocketAddress address = sessionInfo.getAddress(); | ||
| 96 | try { | 121 | try { |
| 97 | socket.send(Buffer.buffer(data), address.getPort(), address.getHostString(), result -> { | 122 | socket.send(Buffer.buffer(data), address.getPort(), address.getHostString(), result -> { |
| 98 | if (result.succeeded()) { | 123 | if (result.succeeded()) { |
| @@ -134,8 +159,8 @@ public class IotUdpSessionManager { | @@ -134,8 +159,8 @@ public class IotUdpSessionManager { | ||
| 134 | iterator.remove(); | 159 | iterator.remove(); |
| 135 | continue; | 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 | iterator.remove(); | 164 | iterator.remove(); |
| 140 | continue; | 165 | continue; |
| 141 | } | 166 | } |
| @@ -157,4 +182,22 @@ public class IotUdpSessionManager { | @@ -157,4 +182,22 @@ public class IotUdpSessionManager { | ||
| 157 | return address.getHostString() + ":" + address.getPort(); | 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 | package cn.iocoder.yudao.module.iot.gateway.protocol.udp.router; | 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 | 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.udp.IotUdpUpstreamProtocol; | 4 | import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpUpstreamProtocol; |
| 6 | import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager; | 5 | import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager; |
| 7 | -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; | ||
| 8 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; | 6 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; |
| 9 | import io.vertx.core.datagram.DatagramSocket; | 7 | import io.vertx.core.datagram.DatagramSocket; |
| 10 | import lombok.extern.slf4j.Slf4j; | 8 | import lombok.extern.slf4j.Slf4j; |
| @@ -19,18 +17,14 @@ public class IotUdpDownstreamHandler { | @@ -19,18 +17,14 @@ public class IotUdpDownstreamHandler { | ||
| 19 | 17 | ||
| 20 | private final IotDeviceMessageService deviceMessageService; | 18 | private final IotDeviceMessageService deviceMessageService; |
| 21 | 19 | ||
| 22 | - private final IotDeviceService deviceService; | ||
| 23 | - | ||
| 24 | private final IotUdpSessionManager sessionManager; | 20 | private final IotUdpSessionManager sessionManager; |
| 25 | 21 | ||
| 26 | private final IotUdpUpstreamProtocol protocol; | 22 | private final IotUdpUpstreamProtocol protocol; |
| 27 | 23 | ||
| 28 | public IotUdpDownstreamHandler(IotDeviceMessageService deviceMessageService, | 24 | public IotUdpDownstreamHandler(IotDeviceMessageService deviceMessageService, |
| 29 | - IotDeviceService deviceService, | ||
| 30 | IotUdpSessionManager sessionManager, | 25 | IotUdpSessionManager sessionManager, |
| 31 | IotUdpUpstreamProtocol protocol) { | 26 | IotUdpUpstreamProtocol protocol) { |
| 32 | this.deviceMessageService = deviceMessageService; | 27 | this.deviceMessageService = deviceMessageService; |
| 33 | - this.deviceService = deviceService; | ||
| 34 | this.sessionManager = sessionManager; | 28 | this.sessionManager = sessionManager; |
| 35 | this.protocol = protocol; | 29 | this.protocol = protocol; |
| 36 | } | 30 | } |
| @@ -45,21 +39,15 @@ public class IotUdpDownstreamHandler { | @@ -45,21 +39,15 @@ public class IotUdpDownstreamHandler { | ||
| 45 | log.info("[handle][处理下行消息,设备 ID: {},方法: {},消息 ID: {}]", | 39 | log.info("[handle][处理下行消息,设备 ID: {},方法: {},消息 ID: {}]", |
| 46 | message.getDeviceId(), message.getMethod(), message.getId()); | 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 | log.warn("[handle][设备不在线,设备 ID: {}]", message.getDeviceId()); | 45 | log.warn("[handle][设备不在线,设备 ID: {}]", message.getDeviceId()); |
| 57 | return; | 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 | DatagramSocket socket = protocol.getUdpSocket(); | 51 | DatagramSocket socket = protocol.getUdpSocket(); |
| 64 | if (socket == null) { | 52 | if (socket == null) { |
| 65 | log.error("[handle][UDP Socket 不可用,设备 ID: {}]", message.getDeviceId()); | 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,8 +195,8 @@ public class IotUdpUpstreamHandler { | ||
| 195 | // 3.1 生成 JWT Token(无状态) | 195 | // 3.1 生成 JWT Token(无状态) |
| 196 | String token = deviceTokenService.createToken(device.getProductKey(), device.getDeviceName()); | 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 | // 3.3 发送上线消息 | 201 | // 3.3 发送上线消息 |
| 202 | sendOnlineMessage(device); | 202 | sendOnlineMessage(device); |
| @@ -298,8 +298,8 @@ public class IotUdpUpstreamHandler { | @@ -298,8 +298,8 @@ public class IotUdpUpstreamHandler { | ||
| 298 | return; | 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 | // 4. 将 body 设置为实际的 params,发送消息到消息总线 | 304 | // 4. 将 body 设置为实际的 params,发送消息到消息总线 |
| 305 | message.setParams(body); | 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 | package cn.iocoder.yudao.module.iot.gateway.protocol.udp; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.udp; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.map.MapUtil; | 3 | import cn.hutool.core.map.MapUtil; |
| 4 | +import cn.hutool.core.util.HexUtil; | ||
| 4 | import cn.hutool.core.util.IdUtil; | 5 | 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; | 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 7 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 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 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; | 9 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; |
| 10 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; | 10 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; |
| 11 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 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 | import lombok.extern.slf4j.Slf4j; | 15 | import lombok.extern.slf4j.Slf4j; |
| 13 | import org.junit.jupiter.api.Test; | 16 | import org.junit.jupiter.api.Test; |
| 14 | 17 | ||
| 15 | import java.net.DatagramPacket; | 18 | import java.net.DatagramPacket; |
| 16 | import java.net.DatagramSocket; | 19 | import java.net.DatagramSocket; |
| 17 | import java.net.InetAddress; | 20 | import java.net.InetAddress; |
| 18 | -import java.nio.charset.StandardCharsets; | ||
| 19 | import java.util.HashMap; | 21 | import java.util.HashMap; |
| 20 | import java.util.Map; | 22 | import java.util.Map; |
| 21 | 23 | ||
| @@ -24,10 +26,16 @@ import java.util.Map; | @@ -24,10 +26,16 @@ import java.util.Map; | ||
| 24 | * | 26 | * |
| 25 | * <p>测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 UDP 协议直接连接平台 | 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 | * <p>使用步骤: | 35 | * <p>使用步骤: |
| 28 | * <ol> | 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 | * <li>运行 {@link #testAuth()} 获取设备 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li> | 39 | * <li>运行 {@link #testAuth()} 获取设备 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li> |
| 32 | * <li>运行以下测试方法: | 40 | * <li>运行以下测试方法: |
| 33 | * <ul> | 41 | * <ul> |
| @@ -48,6 +56,10 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { | @@ -48,6 +56,10 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { | ||
| 48 | private static final int SERVER_PORT = 8093; | 56 | private static final int SERVER_PORT = 8093; |
| 49 | private static final int TIMEOUT_MS = 5000; | 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 | // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== | 63 | // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 52 | private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; | 64 | private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; |
| 53 | private static final String DEVICE_NAME = "small"; | 65 | private static final String DEVICE_NAME = "small"; |
| @@ -65,27 +77,32 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { | @@ -65,27 +77,32 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { | ||
| 65 | */ | 77 | */ |
| 66 | @Test | 78 | @Test |
| 67 | public void testAuth() throws Exception { | 79 | public void testAuth() throws Exception { |
| 68 | - // 1.1 构建请求 | 80 | + // 1.1 构建认证消息 |
| 69 | IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); | 81 | IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); |
| 70 | IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() | 82 | IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() |
| 71 | .setClientId(authInfo.getClientId()) | 83 | .setClientId(authInfo.getClientId()) |
| 72 | .setUsername(authInfo.getUsername()) | 84 | .setUsername(authInfo.getUsername()) |
| 73 | .setPassword(authInfo.getPassword()); | 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 | // 2.1 发送请求 | 94 | // 2.1 发送请求 |
| 83 | try (DatagramSocket socket = new DatagramSocket()) { | 95 | try (DatagramSocket socket = new DatagramSocket()) { |
| 84 | socket.setSoTimeout(TIMEOUT_MS); | 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,25 +113,30 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { | ||
| 96 | */ | 113 | */ |
| 97 | @Test | 114 | @Test |
| 98 | public void testPropertyPost() throws Exception { | 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 | .put("width", 1) | 121 | .put("width", 1) |
| 106 | .put("height", "2") | 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 | try (DatagramSocket socket = new DatagramSocket()) { | 130 | try (DatagramSocket socket = new DatagramSocket()) { |
| 114 | socket.setSoTimeout(TIMEOUT_MS); | 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,60 +147,30 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { | ||
| 125 | */ | 147 | */ |
| 126 | @Test | 148 | @Test |
| 127 | public void testEventPost() throws Exception { | 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 | "eat", | 155 | "eat", |
| 135 | MapUtil.<String, Object>builder().put("rice", 3).build(), | 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 | // 2.1 发送请求 | 163 | // 2.1 发送请求 |
| 176 | try (DatagramSocket socket = new DatagramSocket()) { | 164 | try (DatagramSocket socket = new DatagramSocket()) { |
| 177 | socket.setSoTimeout(TIMEOUT_MS); | 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,20 +193,18 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { | ||
| 201 | return result; | 193 | return result; |
| 202 | } | 194 | } |
| 203 | 195 | ||
| 204 | - | ||
| 205 | /** | 196 | /** |
| 206 | * 发送 UDP 请求并接收响应 | 197 | * 发送 UDP 请求并接收响应 |
| 207 | * | 198 | * |
| 208 | * @param socket UDP Socket | 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 | InetAddress address = InetAddress.getByName(SERVER_HOST); | 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 | socket.send(sendPacket); | 208 | socket.send(sendPacket); |
| 219 | 209 | ||
| 220 | // 接收响应 | 210 | // 接收响应 |
| @@ -222,7 +212,9 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { | @@ -222,7 +212,9 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { | ||
| 222 | DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); | 212 | DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); |
| 223 | try { | 213 | try { |
| 224 | socket.receive(receivePacket); | 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 | } catch (java.net.SocketTimeoutException e) { | 218 | } catch (java.net.SocketTimeoutException e) { |
| 227 | log.warn("[sendAndReceive][接收响应超时]"); | 219 | log.warn("[sendAndReceive][接收响应超时]"); |
| 228 | return null; | 220 | return null; |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotGatewayDeviceUdpProtocolIntegrationTest.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.udp; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.udp; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.map.MapUtil; | 3 | import cn.hutool.core.map.MapUtil; |
| 4 | +import cn.hutool.core.util.HexUtil; | ||
| 4 | import cn.hutool.core.util.IdUtil; | 5 | 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; | 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 7 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 7 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 8 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 8 | import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | 9 | import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; |
| 9 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO; | 10 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO; |
| 10 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPackPostReqDTO; | 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,6 +13,9 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO; | ||
| 12 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO; | 13 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO; |
| 13 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetReqDTO; | 14 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetReqDTO; |
| 14 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 15 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 16 | +import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; | ||
| 17 | +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpBinaryDeviceMessageCodec; | ||
| 18 | +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec; | ||
| 15 | import lombok.extern.slf4j.Slf4j; | 19 | import lombok.extern.slf4j.Slf4j; |
| 16 | import org.junit.jupiter.api.Test; | 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,9 +32,16 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd | ||
| 28 | * | 32 | * |
| 29 | * <p>测试场景:网关设备(IotProductDeviceTypeEnum 的 GATEWAY 类型)通过 UDP 协议管理子设备拓扑关系 | 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 | * <p>使用步骤: | 41 | * <p>使用步骤: |
| 32 | * <ol> | 42 | * <ol> |
| 33 | * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li> | 43 | * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li> |
| 44 | + * <li>修改 {@link #CODEC} 选择测试的编解码格式</li> | ||
| 34 | * <li>运行 {@link #testAuth()} 获取网关设备 token,将返回的 token 粘贴到 {@link #GATEWAY_TOKEN} 常量</li> | 45 | * <li>运行 {@link #testAuth()} 获取网关设备 token,将返回的 token 粘贴到 {@link #GATEWAY_TOKEN} 常量</li> |
| 35 | * <li>运行以下测试方法: | 46 | * <li>运行以下测试方法: |
| 36 | * <ul> | 47 | * <ul> |
| @@ -50,10 +61,12 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd | @@ -50,10 +61,12 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd | ||
| 50 | @Slf4j | 61 | @Slf4j |
| 51 | public class IotGatewayDeviceUdpProtocolIntegrationTest { | 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 | private static final int TIMEOUT_MS = 5000; | 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 | // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) ===================== | 70 | // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) ===================== |
| 58 | private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v"; | 71 | private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v"; |
| 59 | private static final String GATEWAY_DEVICE_NAME = "sub-ddd"; | 72 | private static final String GATEWAY_DEVICE_NAME = "sub-ddd"; |
| @@ -76,28 +89,33 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { | @@ -76,28 +89,33 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { | ||
| 76 | */ | 89 | */ |
| 77 | @Test | 90 | @Test |
| 78 | public void testAuth() throws Exception { | 91 | public void testAuth() throws Exception { |
| 79 | - // 1.1 构建请求 | 92 | + // 1.1 构建认证消息 |
| 80 | IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo( | 93 | IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo( |
| 81 | GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET); | 94 | GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET); |
| 82 | IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() | 95 | IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() |
| 83 | .setClientId(authInfo.getClientId()) | 96 | .setClientId(authInfo.getClientId()) |
| 84 | .setUsername(authInfo.getUsername()) | 97 | .setUsername(authInfo.getUsername()) |
| 85 | .setPassword(authInfo.getPassword()); | 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 | // 2.1 发送请求 | 107 | // 2.1 发送请求 |
| 95 | try (DatagramSocket socket = new DatagramSocket()) { | 108 | try (DatagramSocket socket = new DatagramSocket()) { |
| 96 | socket.setSoTimeout(TIMEOUT_MS); | 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,21 +138,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { | ||
| 120 | // 1.2 构建请求参数 | 138 | // 1.2 构建请求参数 |
| 121 | IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO(); | 139 | IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO(); |
| 122 | params.setSubDevices(Collections.singletonList(subDeviceAuth)); | 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 | // 2.1 发送请求 | 150 | // 2.1 发送请求 |
| 133 | try (DatagramSocket socket = new DatagramSocket()) { | 151 | try (DatagramSocket socket = new DatagramSocket()) { |
| 134 | socket.setSoTimeout(TIMEOUT_MS); | 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,21 +172,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { | ||
| 149 | IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO(); | 172 | IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO(); |
| 150 | params.setSubDevices(Collections.singletonList( | 173 | params.setSubDevices(Collections.singletonList( |
| 151 | new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME))); | 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 | // 2.1 发送请求 | 184 | // 2.1 发送请求 |
| 162 | try (DatagramSocket socket = new DatagramSocket()) { | 185 | try (DatagramSocket socket = new DatagramSocket()) { |
| 163 | socket.setSoTimeout(TIMEOUT_MS); | 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,21 +204,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { | ||
| 176 | public void testTopoGet() throws Exception { | 204 | public void testTopoGet() throws Exception { |
| 177 | // 1.1 构建请求参数(目前为空,预留扩展) | 205 | // 1.1 构建请求参数(目前为空,预留扩展) |
| 178 | IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO(); | 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 | // 2.1 发送请求 | 216 | // 2.1 发送请求 |
| 189 | try (DatagramSocket socket = new DatagramSocket()) { | 217 | try (DatagramSocket socket = new DatagramSocket()) { |
| 190 | socket.setSoTimeout(TIMEOUT_MS); | 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,21 +242,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { | ||
| 209 | IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO(); | 242 | IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO(); |
| 210 | subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY); | 243 | subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY); |
| 211 | subDevice.setDeviceName("mougezishebei"); | 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 | // 2.1 发送请求 | 254 | // 2.1 发送请求 |
| 222 | try (DatagramSocket socket = new DatagramSocket()) { | 255 | try (DatagramSocket socket = new DatagramSocket()) { |
| 223 | socket.setSoTimeout(TIMEOUT_MS); | 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,21 +306,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { | ||
| 268 | params.setProperties(gatewayProperties); | 306 | params.setProperties(gatewayProperties); |
| 269 | params.setEvents(gatewayEvents); | 307 | params.setEvents(gatewayEvents); |
| 270 | params.setSubDevices(List.of(subDeviceData)); | 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 | // 2.1 发送请求 | 318 | // 2.1 发送请求 |
| 281 | try (DatagramSocket socket = new DatagramSocket()) { | 319 | try (DatagramSocket socket = new DatagramSocket()) { |
| 282 | socket.setSoTimeout(TIMEOUT_MS); | 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 | package cn.iocoder.yudao.module.iot.gateway.protocol.udp; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.udp; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.map.MapUtil; | 3 | import cn.hutool.core.map.MapUtil; |
| 4 | +import cn.hutool.core.util.HexUtil; | ||
| 4 | import cn.hutool.core.util.IdUtil; | 5 | 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; | 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 7 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 7 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 8 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 8 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; | 9 | 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.topic.property.IotDevicePropertyPostReqDTO; |
| 10 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 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 | import lombok.extern.slf4j.Slf4j; | 15 | import lombok.extern.slf4j.Slf4j; |
| 12 | import org.junit.jupiter.api.Test; | 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,10 +29,17 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd | ||
| 25 | * <p><b>重要说明:子设备无法直接连接平台,所有请求均由网关设备(Gateway)代为转发。</b> | 29 | * <p><b>重要说明:子设备无法直接连接平台,所有请求均由网关设备(Gateway)代为转发。</b> |
| 26 | * <p>网关设备转发子设备请求时,Token 使用子设备自己的信息。 | 30 | * <p>网关设备转发子设备请求时,Token 使用子设备自己的信息。 |
| 27 | * | 31 | * |
| 32 | + * <p>支持两种编解码格式: | ||
| 33 | + * <ul> | ||
| 34 | + * <li>{@link IotTcpJsonDeviceMessageCodec} - JSON 格式</li> | ||
| 35 | + * <li>{@link IotTcpBinaryDeviceMessageCodec} - 二进制格式</li> | ||
| 36 | + * </ul> | ||
| 37 | + * | ||
| 28 | * <p>使用步骤: | 38 | * <p>使用步骤: |
| 29 | * <ol> | 39 | * <ol> |
| 30 | * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li> | 40 | * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li> |
| 31 | * <li>确保子设备已通过 {@link IotGatewayDeviceUdpProtocolIntegrationTest#testTopoAdd()} 绑定到网关</li> | 41 | * <li>确保子设备已通过 {@link IotGatewayDeviceUdpProtocolIntegrationTest#testTopoAdd()} 绑定到网关</li> |
| 42 | + * <li>修改 {@link #CODEC} 选择测试的编解码格式</li> | ||
| 32 | * <li>运行 {@link #testAuth()} 获取子设备 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li> | 43 | * <li>运行 {@link #testAuth()} 获取子设备 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li> |
| 33 | * <li>运行以下测试方法: | 44 | * <li>运行以下测试方法: |
| 34 | * <ul> | 45 | * <ul> |
| @@ -45,10 +56,12 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd | @@ -45,10 +56,12 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd | ||
| 45 | @Slf4j | 56 | @Slf4j |
| 46 | public class IotGatewaySubDeviceUdpProtocolIntegrationTest { | 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 | private static final int TIMEOUT_MS = 5000; | 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 | // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== | 65 | // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 53 | private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn"; | 66 | private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 54 | private static final String DEVICE_NAME = "chazuo-it"; | 67 | private static final String DEVICE_NAME = "chazuo-it"; |
| @@ -66,27 +79,32 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest { | @@ -66,27 +79,32 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest { | ||
| 66 | */ | 79 | */ |
| 67 | @Test | 80 | @Test |
| 68 | public void testAuth() throws Exception { | 81 | public void testAuth() throws Exception { |
| 69 | - // 1.1 构建请求 | 82 | + // 1.1 构建认证消息 |
| 70 | IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); | 83 | IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); |
| 71 | IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() | 84 | IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() |
| 72 | .setClientId(authInfo.getClientId()) | 85 | .setClientId(authInfo.getClientId()) |
| 73 | .setUsername(authInfo.getUsername()) | 86 | .setUsername(authInfo.getUsername()) |
| 74 | .setPassword(authInfo.getPassword()); | 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 | // 2.1 发送请求 | 96 | // 2.1 发送请求 |
| 84 | try (DatagramSocket socket = new DatagramSocket()) { | 97 | try (DatagramSocket socket = new DatagramSocket()) { |
| 85 | socket.setSoTimeout(TIMEOUT_MS); | 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,27 +115,32 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest { | ||
| 97 | */ | 115 | */ |
| 98 | @Test | 116 | @Test |
| 99 | public void testPropertyPost() throws Exception { | 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 | .put("power", 100) | 123 | .put("power", 100) |
| 107 | .put("status", "online") | 124 | .put("status", "online") |
| 108 | .put("temperature", 36.5) | 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 | log.info("[testPropertyPost][子设备属性上报 - 请求实际由 Gateway 代为转发]"); | 130 | log.info("[testPropertyPost][子设备属性上报 - 请求实际由 Gateway 代为转发]"); |
| 113 | - log.info("[testPropertyPost][请求体: {}]", payload); | 131 | + log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request); |
| 114 | 132 | ||
| 115 | // 2.1 发送请求 | 133 | // 2.1 发送请求 |
| 116 | try (DatagramSocket socket = new DatagramSocket()) { | 134 | try (DatagramSocket socket = new DatagramSocket()) { |
| 117 | socket.setSoTimeout(TIMEOUT_MS); | 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,12 +151,11 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest { | ||
| 128 | */ | 151 | */ |
| 129 | @Test | 152 | @Test |
| 130 | public void testEventPost() throws Exception { | 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 | "alarm", | 159 | "alarm", |
| 138 | MapUtil.<String, Object>builder() | 160 | MapUtil.<String, Object>builder() |
| 139 | .put("level", "warning") | 161 | .put("level", "warning") |
| @@ -141,18 +163,24 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest { | @@ -141,18 +163,24 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest { | ||
| 141 | .put("threshold", 40) | 163 | .put("threshold", 40) |
| 142 | .put("current", 42) | 164 | .put("current", 42) |
| 143 | .build(), | 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 | log.info("[testEventPost][子设备事件上报 - 请求实际由 Gateway 代为转发]"); | 170 | log.info("[testEventPost][子设备事件上报 - 请求实际由 Gateway 代为转发]"); |
| 148 | - log.info("[testEventPost][请求体: {}]", payload); | 171 | + log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request); |
| 149 | 172 | ||
| 150 | // 2.1 发送请求 | 173 | // 2.1 发送请求 |
| 151 | try (DatagramSocket socket = new DatagramSocket()) { | 174 | try (DatagramSocket socket = new DatagramSocket()) { |
| 152 | socket.setSoTimeout(TIMEOUT_MS); | 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 |