diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/router/IotUdpUpstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/router/IotUdpUpstreamHandler.java index eebc7253d9..e982af340f 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/router/IotUdpUpstreamHandler.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/router/IotUdpUpstreamHandler.java @@ -52,6 +52,10 @@ public class IotUdpUpstreamHandler { * Token 参数 Key */ private static final String PARAM_KEY_TOKEN = "token"; + /** + * Body 参数 Key(实际请求内容) + */ + private static final String PARAM_KEY_BODY = "body"; private final IotDeviceMessageService deviceMessageService; @@ -248,6 +252,10 @@ public class IotUdpUpstreamHandler { /** * 处理业务请求 + *
+ * 请求参数格式:
+ * - token:JWT 令牌
+ * - body:实际请求内容(可以是 Map、List 或其他类型)
*
* @param message 消息信息
* @param codecType 消息编解码类型
@@ -259,11 +267,13 @@ public class IotUdpUpstreamHandler {
InetSocketAddress senderAddress, DatagramSocket socket) {
String addressKey = sessionManager.buildAddressKey(senderAddress);
try {
- // 1.1 从消息中提取 token(无状态:消息体携带 token)
+ // 1.1 从消息中提取 token 和 body(格式:{token: "xxx", body: {...}} 或 {token: "xxx", body: [...]})
String token = null;
+ Object body = null;
if (message.getParams() instanceof Map) {
Map
- * 支持 Map 或普通对象,通过 JSON 转换统一处理
+ * 返回格式:{token: "xxx", body: params}
+ * - token:JWT 令牌
+ * - body:实际请求内容(可以是 Map、List 或其他类型)
*
- * @param params 原始参数(Map 或对象)
- * @return 添加了 token 的 Map
+ * @param params 原始参数(Map、List 或对象)
+ * @return 包含 token 和 body 的 Map
*/
- @SuppressWarnings("unchecked")
private Map 测试场景:网关设备(IotProductDeviceTypeEnum 的 GATEWAY 类型)通过 UDP 协议管理子设备拓扑关系
+ *
+ * 使用步骤:
+ * 注意:UDP 协议是无状态的,每次请求需要在 params 中携带 token(与 HTTP 通过 Header 传递不同)
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class IotGatewayDeviceUdpProtocolIntegrationTest {
+
+ private static final String SERVER_HOST = "127.0.0.1";
+ private static final int SERVER_PORT = 8093;
+ private static final int TIMEOUT_MS = 5000;
+
+ // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) =====================
+ private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v";
+ private static final String GATEWAY_DEVICE_NAME = "sub-ddd";
+ private static final String GATEWAY_DEVICE_SECRET = "b3d62c70f8a4495487ed1d35d61ac2b3";
+
+ /**
+ * 网关设备 Token:从 {@link #testAuth()} 方法获取后,粘贴到这里
+ */
+ private static final String GATEWAY_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9kdWN0S2V5IjoibTZYY1MxWkozVFc4ZUMwdiIsImV4cCI6MTc2OTk1NDcxNSwiZGV2aWNlTmFtZSI6InN1Yi1kZGQifQ.Vg5iateNrpg0FVQI2eJomggxrYXGpwug8wsz9BsVr5w";
+
+ // ===================== 子设备信息(根据实际情况修改,从 iot_device 表查询子设备) =====================
+ private static final String SUB_DEVICE_PRODUCT_KEY = "jAufEMTF1W6wnPhn";
+ private static final String SUB_DEVICE_NAME = "chazuo-it";
+ private static final String SUB_DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af";
+
+ // ===================== 认证测试 =====================
+
+ /**
+ * 网关设备认证测试:获取网关设备 Token
+ */
+ @Test
+ public void testAuth() throws Exception {
+ // 1.1 构建请求
+ IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
+ GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);
+ IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
+ .setClientId(authInfo.getClientId())
+ .setUsername(authInfo.getUsername())
+ .setPassword(authInfo.getPassword());
+ String payload = JsonUtils.toJsonString(MapUtil.builder()
+ .put("id", IdUtil.fastSimpleUUID())
+ .put("method", "auth")
+ .put("params", authReqDTO)
+ .build());
+ // 1.2 输出请求
+ log.info("[testAuth][请求体: {}]", payload);
+
+ // 2.1 发送请求
+ try (DatagramSocket socket = new DatagramSocket()) {
+ socket.setSoTimeout(TIMEOUT_MS);
+ String response = sendAndReceive(socket, payload);
+ // 2.2 输出结果
+ log.info("[testAuth][响应体: {}]", response);
+ log.info("[testAuth][请将返回的 token 复制到 GATEWAY_TOKEN 常量中]");
+ }
+ }
+
+ // ===================== 拓扑管理测试 =====================
+
+ /**
+ * 添加子设备拓扑关系测试
+ *
+ * 网关设备向平台上报需要绑定的子设备信息
+ */
+ @Test
+ public void testTopoAdd() throws Exception {
+ // 1.1 构建子设备认证信息
+ IotDeviceAuthReqDTO subAuthInfo = IotDeviceAuthUtils.getAuthInfo(
+ SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME, SUB_DEVICE_SECRET);
+ IotDeviceAuthReqDTO subDeviceAuth = new IotDeviceAuthReqDTO()
+ .setClientId(subAuthInfo.getClientId())
+ .setUsername(subAuthInfo.getUsername())
+ .setPassword(subAuthInfo.getPassword());
+ // 1.2 构建请求参数
+ IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO();
+ params.setSubDevices(Collections.singletonList(subDeviceAuth));
+ String payload = JsonUtils.toJsonString(MapUtil.builder()
+ .put("id", IdUtil.fastSimpleUUID())
+ .put("method", IotDeviceMessageMethodEnum.TOPO_ADD.getMethod())
+ .put("version", "1.0")
+ .put("params", withToken(params))
+ .build());
+ // 1.3 输出请求
+ log.info("[testTopoAdd][请求体: {}]", payload);
+
+ // 2.1 发送请求
+ try (DatagramSocket socket = new DatagramSocket()) {
+ socket.setSoTimeout(TIMEOUT_MS);
+ String response = sendAndReceive(socket, payload);
+ // 2.2 输出结果
+ log.info("[testTopoAdd][响应体: {}]", response);
+ }
+ }
+
+ /**
+ * 删除子设备拓扑关系测试
+ *
+ * 网关设备向平台上报需要解绑的子设备信息
+ */
+ @Test
+ public void testTopoDelete() throws Exception {
+ // 1.1 构建请求参数
+ IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO();
+ params.setSubDevices(Collections.singletonList(
+ new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME)));
+ String payload = JsonUtils.toJsonString(MapUtil.builder()
+ .put("id", IdUtil.fastSimpleUUID())
+ .put("method", IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod())
+ .put("version", "1.0")
+ .put("params", withToken(params))
+ .build());
+ // 1.2 输出请求
+ log.info("[testTopoDelete][请求体: {}]", payload);
+
+ // 2.1 发送请求
+ try (DatagramSocket socket = new DatagramSocket()) {
+ socket.setSoTimeout(TIMEOUT_MS);
+ String response = sendAndReceive(socket, payload);
+ // 2.2 输出结果
+ log.info("[testTopoDelete][响应体: {}]", response);
+ }
+ }
+
+ /**
+ * 获取子设备拓扑关系测试
+ *
+ * 网关设备向平台查询已绑定的子设备列表
+ */
+ @Test
+ public void testTopoGet() throws Exception {
+ // 1.1 构建请求参数(目前为空,预留扩展)
+ IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO();
+ String payload = JsonUtils.toJsonString(MapUtil.builder()
+ .put("id", IdUtil.fastSimpleUUID())
+ .put("method", IotDeviceMessageMethodEnum.TOPO_GET.getMethod())
+ .put("version", "1.0")
+ .put("params", withToken(params))
+ .build());
+ // 1.2 输出请求
+ log.info("[testTopoGet][请求体: {}]", payload);
+
+ // 2.1 发送请求
+ try (DatagramSocket socket = new DatagramSocket()) {
+ socket.setSoTimeout(TIMEOUT_MS);
+ String response = sendAndReceive(socket, payload);
+ // 2.2 输出结果
+ log.info("[testTopoGet][响应体: {}]", response);
+ }
+ }
+
+ // ===================== 子设备注册测试 =====================
+
+ /**
+ * 子设备动态注册测试
+ *
+ * 网关设备代理子设备进行动态注册,平台返回子设备的 deviceSecret
+ *
+ * 注意:此接口需要网关 Token 认证
+ */
+ @Test
+ public void testSubDeviceRegister() throws Exception {
+ // 1.1 构建请求参数
+ IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO();
+ subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY);
+ subDevice.setDeviceName("mougezishebei");
+ String payload = JsonUtils.toJsonString(MapUtil.builder()
+ .put("id", IdUtil.fastSimpleUUID())
+ .put("method", IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod())
+ .put("version", "1.0")
+ .put("params", withToken(Collections.singletonList(subDevice)))
+ .build());
+ // 1.2 输出请求
+ log.info("[testSubDeviceRegister][请求体: {}]", payload);
+
+ // 2.1 发送请求
+ try (DatagramSocket socket = new DatagramSocket()) {
+ socket.setSoTimeout(TIMEOUT_MS);
+ String response = sendAndReceive(socket, payload);
+ // 2.2 输出结果
+ log.info("[testSubDeviceRegister][响应体: {}]", response);
+ }
+ }
+
+ // ===================== 批量上报测试 =====================
+
+ /**
+ * 批量上报属性测试(网关 + 子设备)
+ *
+ * 网关设备批量上报自身属性、事件,以及子设备的属性、事件
+ */
+ @Test
+ public void testPropertyPackPost() throws Exception {
+ // 1.1 构建【网关设备】自身属性
+ Map
+ * 返回格式:{token: "xxx", body: params}
+ * - token:JWT 令牌
+ * - body:实际请求内容(可以是 Map、List 或其他类型)
+ *
+ * @param params 原始参数(Map、List 或对象)
+ * @return 包含 token 和 body 的 Map
+ */
+ private Map 测试场景:子设备(IotProductDeviceTypeEnum 的 SUB 类型)通过网关设备代理上报数据
+ *
+ * 重要说明:子设备无法直接连接平台,所有请求均由网关设备(Gateway)代为转发。
+ * 网关设备转发子设备请求时,Token 使用子设备自己的信息。
+ *
+ * 使用步骤:
+ * 注意:UDP 协议是无状态的,每次请求需要在 params 中携带 token(与 HTTP 通过 Header 传递不同)
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class IotGatewaySubDeviceUdpProtocolIntegrationTest {
+
+ private static final String SERVER_HOST = "127.0.0.1";
+ private static final int SERVER_PORT = 8093;
+ private static final int TIMEOUT_MS = 5000;
+
+ // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) =====================
+ private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn";
+ private static final String DEVICE_NAME = "chazuo-it";
+ private static final String DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af";
+
+ /**
+ * 网关子设备 Token:从 {@link #testAuth()} 方法获取后,粘贴到这里
+ */
+ private static final String TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9kdWN0S2V5IjoiakF1ZkVNVEYxVzZ3blBobiIsImV4cCI6MTc2OTk1NDY3OSwiZGV2aWNlTmFtZSI6ImNoYXp1by1pdCJ9.jfbUAoU0xkJl4UvO-NUvcJ6yITPRgUjQ4MKATPuwneg";
+
+ // ===================== 认证测试 =====================
+
+ /**
+ * 子设备认证测试:获取子设备 Token
+ */
+ @Test
+ public void testAuth() throws Exception {
+ // 1.1 构建请求
+ IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
+ IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
+ .setClientId(authInfo.getClientId())
+ .setUsername(authInfo.getUsername())
+ .setPassword(authInfo.getPassword());
+ String payload = JsonUtils.toJsonString(MapUtil.builder()
+ .put("id", IdUtil.fastSimpleUUID())
+ .put("method", "auth")
+ .put("params", authReqDTO)
+ .build());
+ // 1.2 输出请求
+ log.info("[testAuth][请求体: {}]", payload);
+
+ // 2.1 发送请求
+ try (DatagramSocket socket = new DatagramSocket()) {
+ socket.setSoTimeout(TIMEOUT_MS);
+ String response = sendAndReceive(socket, payload);
+ // 2.2 输出结果
+ log.info("[testAuth][响应体: {}]", response);
+ log.info("[testAuth][请将返回的 token 复制到 TOKEN 常量中]");
+ }
+ }
+
+ // ===================== 子设备属性上报测试 =====================
+
+ /**
+ * 子设备属性上报测试
+ */
+ @Test
+ public void testPropertyPost() throws Exception {
+ // 1.1 构建请求(UDP 协议:token 放在 params 中)
+ String payload = JsonUtils.toJsonString(MapUtil.builder()
+ .put("id", IdUtil.fastSimpleUUID())
+ .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())
+ .put("version", "1.0")
+ .put("params", withToken(IotDevicePropertyPostReqDTO.of(MapUtil.
+ * 返回格式:{token: "xxx", body: params}
+ * - token:JWT 令牌
+ * - body:实际请求内容(可以是 Map、List 或其他类型)
+ *
+ * @param params 原始参数(Map、List 或对象)
+ * @return 包含 token 和 body 的 Map
+ */
+ private Map
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *