Commit 432e1ed230d853bcd1c92108f5e4525244c8f953
1 parent
d2c000d6
feat:【iot】单元测试:优化单测质量(格式)
Showing
19 changed files
with
50 additions
and
909 deletions
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotDirectDeviceCoapProtocolIntegrationTest.java
| ... | ... | @@ -49,6 +49,7 @@ public class IotDirectDeviceCoapProtocolIntegrationTest { |
| 49 | 49 | private static final int SERVER_PORT = 5683; |
| 50 | 50 | |
| 51 | 51 | // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 52 | + | |
| 52 | 53 | private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; |
| 53 | 54 | private static final String DEVICE_NAME = "small"; |
| 54 | 55 | private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotGatewayDeviceCoapProtocolIntegrationTest.java
| ... | ... | @@ -58,6 +58,7 @@ public class IotGatewayDeviceCoapProtocolIntegrationTest { |
| 58 | 58 | private static final int SERVER_PORT = 5683; |
| 59 | 59 | |
| 60 | 60 | // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) ===================== |
| 61 | + | |
| 61 | 62 | private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v"; |
| 62 | 63 | private static final String GATEWAY_DEVICE_NAME = "sub-ddd"; |
| 63 | 64 | private static final String GATEWAY_DEVICE_SECRET = "b3d62c70f8a4495487ed1d35d61ac2b3"; |
| ... | ... | @@ -68,6 +69,7 @@ public class IotGatewayDeviceCoapProtocolIntegrationTest { |
| 68 | 69 | private static final String GATEWAY_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9kdWN0S2V5IjoibTZYY1MxWkozVFc4ZUMwdiIsImV4cCI6MTc2OTg2NjY3OCwiZGV2aWNlTmFtZSI6InN1Yi1kZGQifQ.nCLSAfHEjXLtTDRXARjOoFqpuo5WfArjFWweUAzrjKU"; |
| 69 | 70 | |
| 70 | 71 | // ===================== 子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 72 | + | |
| 71 | 73 | private static final String SUB_DEVICE_PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 72 | 74 | private static final String SUB_DEVICE_NAME = "chazuo-it"; |
| 73 | 75 | private static final String SUB_DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotGatewaySubDeviceCoapProtocolIntegrationTest.java
| ... | ... | @@ -51,6 +51,7 @@ public class IotGatewaySubDeviceCoapProtocolIntegrationTest { |
| 51 | 51 | private static final int SERVER_PORT = 5683; |
| 52 | 52 | |
| 53 | 53 | // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 54 | + | |
| 54 | 55 | private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 55 | 56 | private static final String DEVICE_NAME = "chazuo-it"; |
| 56 | 57 | private static final String DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotDirectDeviceHttpProtocolIntegrationTest.java
| ... | ... | @@ -43,6 +43,7 @@ public class IotDirectDeviceHttpProtocolIntegrationTest { |
| 43 | 43 | private static final int SERVER_PORT = 8092; |
| 44 | 44 | |
| 45 | 45 | // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 46 | + | |
| 46 | 47 | private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; |
| 47 | 48 | private static final String DEVICE_NAME = "small"; |
| 48 | 49 | private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotGatewayDeviceHttpProtocolIntegrationTest.java
| ... | ... | @@ -52,6 +52,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { |
| 52 | 52 | private static final int SERVER_PORT = 8092; |
| 53 | 53 | |
| 54 | 54 | // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) ===================== |
| 55 | + | |
| 55 | 56 | private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v"; |
| 56 | 57 | private static final String GATEWAY_DEVICE_NAME = "sub-ddd"; |
| 57 | 58 | private static final String GATEWAY_DEVICE_SECRET = "b3d62c70f8a4495487ed1d35d61ac2b3"; |
| ... | ... | @@ -62,6 +63,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { |
| 62 | 63 | private static final String GATEWAY_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9kdWN0S2V5IjoibTZYY1MxWkozVFc4ZUMwdiIsImV4cCI6MTc2OTg2NjY3OCwiZGV2aWNlTmFtZSI6InN1Yi1kZGQifQ.nCLSAfHEjXLtTDRXARjOoFqpuo5WfArjFWweUAzrjKU"; |
| 63 | 64 | |
| 64 | 65 | // ===================== 子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 66 | + | |
| 65 | 67 | private static final String SUB_DEVICE_PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 66 | 68 | private static final String SUB_DEVICE_NAME = "chazuo-it"; |
| 67 | 69 | private static final String SUB_DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotGatewaySubDeviceHttpProtocolIntegrationTest.java
| ... | ... | @@ -45,6 +45,7 @@ public class IotGatewaySubDeviceHttpProtocolIntegrationTest { |
| 45 | 45 | private static final int SERVER_PORT = 8092; |
| 46 | 46 | |
| 47 | 47 | // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 48 | + | |
| 48 | 49 | private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 49 | 50 | private static final String DEVICE_NAME = "chazuo-it"; |
| 50 | 51 | private static final String DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotDirectDeviceMqttProtocolIntegrationTest.java
| ... | ... | @@ -55,17 +55,18 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { |
| 55 | 55 | private static final int SERVER_PORT = 1883; |
| 56 | 56 | private static final int TIMEOUT_SECONDS = 10; |
| 57 | 57 | |
| 58 | + private static Vertx vertx; | |
| 59 | + | |
| 58 | 60 | // ===================== 编解码器(MQTT 使用 Alink 协议) ===================== |
| 61 | + | |
| 59 | 62 | private static final IotDeviceMessageCodec CODEC = new IotAlinkDeviceMessageCodec(); |
| 60 | 63 | |
| 61 | 64 | // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) ===================== |
| 65 | + | |
| 62 | 66 | private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; |
| 63 | 67 | private static final String DEVICE_NAME = "small"; |
| 64 | 68 | private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; |
| 65 | 69 | |
| 66 | - // ===================== 全局共享 Vertx 实例 ===================== | |
| 67 | - private static Vertx vertx; | |
| 68 | - | |
| 69 | 70 | @BeforeAll |
| 70 | 71 | public static void setUp() { |
| 71 | 72 | vertx = Vertx.vertx(); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotGatewayDeviceMqttProtocolIntegrationTest.java
| ... | ... | @@ -63,22 +63,24 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { |
| 63 | 63 | private static final int SERVER_PORT = 1883; |
| 64 | 64 | private static final int TIMEOUT_SECONDS = 10; |
| 65 | 65 | |
| 66 | + private static Vertx vertx; | |
| 67 | + | |
| 66 | 68 | // ===================== 编解码器(MQTT 使用 Alink 协议) ===================== |
| 69 | + | |
| 67 | 70 | private static final IotDeviceMessageCodec CODEC = new IotAlinkDeviceMessageCodec(); |
| 68 | 71 | |
| 69 | 72 | // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) ===================== |
| 73 | + | |
| 70 | 74 | private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v"; |
| 71 | 75 | private static final String GATEWAY_DEVICE_NAME = "sub-ddd"; |
| 72 | 76 | private static final String GATEWAY_DEVICE_SECRET = "b3d62c70f8a4495487ed1d35d61ac2b3"; |
| 73 | 77 | |
| 74 | 78 | // ===================== 子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 79 | + | |
| 75 | 80 | private static final String SUB_DEVICE_PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 76 | 81 | private static final String SUB_DEVICE_NAME = "chazuo-it"; |
| 77 | 82 | private static final String SUB_DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; |
| 78 | 83 | |
| 79 | - // ===================== 全局共享 Vertx 实例 ===================== | |
| 80 | - private static Vertx vertx; | |
| 81 | - | |
| 82 | 84 | @BeforeAll |
| 83 | 85 | public static void setUp() { |
| 84 | 86 | vertx = Vertx.vertx(); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotGatewaySubDeviceMqttProtocolIntegrationTest.java
| ... | ... | @@ -57,17 +57,18 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest { |
| 57 | 57 | private static final int SERVER_PORT = 1883; |
| 58 | 58 | private static final int TIMEOUT_SECONDS = 10; |
| 59 | 59 | |
| 60 | + private static Vertx vertx; | |
| 61 | + | |
| 60 | 62 | // ===================== 编解码器(MQTT 使用 Alink 协议) ===================== |
| 63 | + | |
| 61 | 64 | private static final IotDeviceMessageCodec CODEC = new IotAlinkDeviceMessageCodec(); |
| 62 | 65 | |
| 63 | 66 | // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 67 | + | |
| 64 | 68 | private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 65 | 69 | private static final String DEVICE_NAME = "chazuo-it"; |
| 66 | 70 | private static final String DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; |
| 67 | 71 | |
| 68 | - // ===================== 全局共享 Vertx 实例 ===================== | |
| 69 | - private static Vertx vertx; | |
| 70 | - | |
| 71 | 72 | @BeforeAll |
| 72 | 73 | public static void setUp() { |
| 73 | 74 | vertx = Vertx.vertx(); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java
| ... | ... | @@ -57,10 +57,12 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { |
| 57 | 57 | private static final int TIMEOUT_MS = 5000; |
| 58 | 58 | |
| 59 | 59 | // ===================== 编解码器选择(修改此处切换 JSON / Binary) ===================== |
| 60 | + | |
| 60 | 61 | // private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec(); |
| 61 | 62 | private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec(); |
| 62 | 63 | |
| 63 | 64 | // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) ===================== |
| 65 | + | |
| 64 | 66 | private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; |
| 65 | 67 | private static final String DEVICE_NAME = "small"; |
| 66 | 68 | private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotGatewayDeviceTcpProtocolIntegrationTest.java
| ... | ... | @@ -65,15 +65,18 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest { |
| 65 | 65 | private static final int TIMEOUT_MS = 5000; |
| 66 | 66 | |
| 67 | 67 | // ===================== 编解码器选择(修改此处切换 JSON / Binary) ===================== |
| 68 | + | |
| 68 | 69 | private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec(); |
| 69 | 70 | // private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec(); |
| 70 | 71 | |
| 71 | 72 | // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) ===================== |
| 73 | + | |
| 72 | 74 | private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v"; |
| 73 | 75 | private static final String GATEWAY_DEVICE_NAME = "sub-ddd"; |
| 74 | 76 | private static final String GATEWAY_DEVICE_SECRET = "b3d62c70f8a4495487ed1d35d61ac2b3"; |
| 75 | 77 | |
| 76 | 78 | // ===================== 子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 79 | + | |
| 77 | 80 | private static final String SUB_DEVICE_PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 78 | 81 | private static final String SUB_DEVICE_NAME = "chazuo-it"; |
| 79 | 82 | private static final String SUB_DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotGatewaySubDeviceTcpProtocolIntegrationTest.java
| ... | ... | @@ -58,10 +58,12 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest { |
| 58 | 58 | private static final int TIMEOUT_MS = 5000; |
| 59 | 59 | |
| 60 | 60 | // ===================== 编解码器选择(修改此处切换 JSON / Binary) ===================== |
| 61 | + | |
| 61 | 62 | private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec(); |
| 62 | 63 | // private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec(); |
| 63 | 64 | |
| 64 | 65 | // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 66 | + | |
| 65 | 67 | private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 66 | 68 | private static final String DEVICE_NAME = "chazuo-it"; |
| 67 | 69 | private static final String DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotDirectDeviceUdpProtocolIntegrationTest.java
| ... | ... | @@ -58,10 +58,12 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { |
| 58 | 58 | private static final int TIMEOUT_MS = 5000; |
| 59 | 59 | |
| 60 | 60 | // ===================== 编解码器选择(修改此处切换 JSON / Binary) ===================== |
| 61 | + | |
| 61 | 62 | private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec(); |
| 62 | 63 | // private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec(); |
| 63 | 64 | |
| 64 | 65 | // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 66 | + | |
| 65 | 67 | private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; |
| 66 | 68 | private static final String DEVICE_NAME = "small"; |
| 67 | 69 | private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotGatewayDeviceUdpProtocolIntegrationTest.java
| ... | ... | @@ -64,10 +64,12 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { |
| 64 | 64 | private static final int TIMEOUT_MS = 5000; |
| 65 | 65 | |
| 66 | 66 | // ===================== 编解码器选择(修改此处切换 JSON / Binary) ===================== |
| 67 | + | |
| 67 | 68 | private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec(); |
| 68 | 69 | // private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec(); |
| 69 | 70 | |
| 70 | 71 | // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) ===================== |
| 72 | + | |
| 71 | 73 | private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v"; |
| 72 | 74 | private static final String GATEWAY_DEVICE_NAME = "sub-ddd"; |
| 73 | 75 | private static final String GATEWAY_DEVICE_SECRET = "b3d62c70f8a4495487ed1d35d61ac2b3"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotGatewaySubDeviceUdpProtocolIntegrationTest.java
| ... | ... | @@ -59,10 +59,12 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest { |
| 59 | 59 | private static final int TIMEOUT_MS = 5000; |
| 60 | 60 | |
| 61 | 61 | // ===================== 编解码器选择(修改此处切换 JSON / Binary) ===================== |
| 62 | + | |
| 62 | 63 | private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec(); |
| 63 | 64 | // private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec(); |
| 64 | 65 | |
| 65 | 66 | // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 67 | + | |
| 66 | 68 | private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 67 | 69 | private static final String DEVICE_NAME = "chazuo-it"; |
| 68 | 70 | private static final String DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotDirectDeviceWebSocketProtocolIntegrationTest.java
| ... | ... | @@ -55,17 +55,18 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest { |
| 55 | 55 | private static final String WS_PATH = "/ws"; |
| 56 | 56 | private static final int TIMEOUT_SECONDS = 5; |
| 57 | 57 | |
| 58 | - // 编解码器 | |
| 58 | + private static Vertx vertx; | |
| 59 | + | |
| 60 | + // ===================== 编解码器选择 ===================== | |
| 61 | + | |
| 59 | 62 | private static final IotDeviceMessageCodec CODEC = new IotWebSocketJsonDeviceMessageCodec(); |
| 60 | 63 | |
| 61 | 64 | // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) ===================== |
| 65 | + | |
| 62 | 66 | private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; |
| 63 | 67 | private static final String DEVICE_NAME = "small"; |
| 64 | 68 | private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; |
| 65 | 69 | |
| 66 | - // Vert.x 实例 | |
| 67 | - private static Vertx vertx; | |
| 68 | - | |
| 69 | 70 | @BeforeAll |
| 70 | 71 | public static void setUp() { |
| 71 | 72 | vertx = Vertx.vertx(); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotGatewayDeviceWebSocketProtocolIntegrationTest.java
| ... | ... | @@ -63,22 +63,24 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest { |
| 63 | 63 | private static final String WS_PATH = "/ws"; |
| 64 | 64 | private static final int TIMEOUT_SECONDS = 5; |
| 65 | 65 | |
| 66 | - // 编解码器 | |
| 66 | + private static Vertx vertx; | |
| 67 | + | |
| 68 | + // ===================== 编解码器选择 ===================== | |
| 69 | + | |
| 67 | 70 | private static final IotDeviceMessageCodec CODEC = new IotWebSocketJsonDeviceMessageCodec(); |
| 68 | 71 | |
| 69 | 72 | // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) ===================== |
| 73 | + | |
| 70 | 74 | private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v"; |
| 71 | 75 | private static final String GATEWAY_DEVICE_NAME = "sub-ddd"; |
| 72 | 76 | private static final String GATEWAY_DEVICE_SECRET = "b3d62c70f8a4495487ed1d35d61ac2b3"; |
| 73 | 77 | |
| 74 | 78 | // ===================== 子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 79 | + | |
| 75 | 80 | private static final String SUB_DEVICE_PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 76 | 81 | private static final String SUB_DEVICE_NAME = "chazuo-it"; |
| 77 | 82 | private static final String SUB_DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; |
| 78 | 83 | |
| 79 | - // Vert.x 实例 | |
| 80 | - private static Vertx vertx; | |
| 81 | - | |
| 82 | 84 | @BeforeAll |
| 83 | 85 | public static void setUp() { |
| 84 | 86 | vertx = Vertx.vertx(); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotGatewaySubDeviceWebSocketProtocolIntegrationTest.java
| ... | ... | @@ -56,17 +56,18 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest { |
| 56 | 56 | private static final String WS_PATH = "/ws"; |
| 57 | 57 | private static final int TIMEOUT_SECONDS = 5; |
| 58 | 58 | |
| 59 | - // 编解码器 | |
| 59 | + private static Vertx vertx; | |
| 60 | + | |
| 61 | + // ===================== 编解码器选择 ===================== | |
| 62 | + | |
| 60 | 63 | private static final IotDeviceMessageCodec CODEC = new IotWebSocketJsonDeviceMessageCodec(); |
| 61 | 64 | |
| 62 | 65 | // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 66 | + | |
| 63 | 67 | private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn"; |
| 64 | 68 | private static final String DEVICE_NAME = "chazuo-it"; |
| 65 | 69 | private static final String DEVICE_SECRET = "d46ef9b28ab14238b9c00a3a668032af"; |
| 66 | 70 | |
| 67 | - // Vert.x 实例 | |
| 68 | - private static Vertx vertx; | |
| 69 | - | |
| 70 | 71 | @BeforeAll |
| 71 | 72 | public static void setUp() { |
| 72 | 73 | vertx = Vertx.vertx(); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/resources/mqtt-websocket-test-client.html deleted
| 1 | -<!DOCTYPE html> | |
| 2 | -<html lang="zh-CN"> | |
| 3 | -<head> | |
| 4 | - <meta charset="UTF-8"> | |
| 5 | - <meta content="width=device-width, initial-scale=1.0" name="viewport"> | |
| 6 | - <title>MQTT WebSocket 测试客户端</title> | |
| 7 | - <style> | |
| 8 | - * { | |
| 9 | - margin: 0; | |
| 10 | - padding: 0; | |
| 11 | - box-sizing: border-box; | |
| 12 | - } | |
| 13 | - | |
| 14 | - body { | |
| 15 | - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| 16 | - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| 17 | - padding: 20px; | |
| 18 | - min-height: 100vh; | |
| 19 | - } | |
| 20 | - | |
| 21 | - .container { | |
| 22 | - max-width: 1200px; | |
| 23 | - margin: 0 auto; | |
| 24 | - background: white; | |
| 25 | - border-radius: 10px; | |
| 26 | - box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); | |
| 27 | - overflow: hidden; | |
| 28 | - } | |
| 29 | - | |
| 30 | - .header { | |
| 31 | - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| 32 | - color: white; | |
| 33 | - padding: 30px; | |
| 34 | - text-align: center; | |
| 35 | - } | |
| 36 | - | |
| 37 | - .header h1 { | |
| 38 | - font-size: 28px; | |
| 39 | - margin-bottom: 10px; | |
| 40 | - } | |
| 41 | - | |
| 42 | - .header p { | |
| 43 | - opacity: 0.9; | |
| 44 | - } | |
| 45 | - | |
| 46 | - .info-box { | |
| 47 | - background: #f8f9fa; | |
| 48 | - border-left: 4px solid #667eea; | |
| 49 | - padding: 15px; | |
| 50 | - margin: 20px; | |
| 51 | - border-radius: 5px; | |
| 52 | - } | |
| 53 | - | |
| 54 | - .info-box h3 { | |
| 55 | - color: #667eea; | |
| 56 | - margin-bottom: 10px; | |
| 57 | - font-size: 16px; | |
| 58 | - } | |
| 59 | - | |
| 60 | - .info-box ul { | |
| 61 | - margin-left: 20px; | |
| 62 | - color: #666; | |
| 63 | - font-size: 14px; | |
| 64 | - line-height: 1.6; | |
| 65 | - } | |
| 66 | - | |
| 67 | - .info-box code { | |
| 68 | - background: #e9ecef; | |
| 69 | - padding: 2px 6px; | |
| 70 | - border-radius: 3px; | |
| 71 | - font-family: 'Courier New', monospace; | |
| 72 | - color: #d63384; | |
| 73 | - } | |
| 74 | - | |
| 75 | - .content { | |
| 76 | - display: grid; | |
| 77 | - grid-template-columns: 1fr 1fr; | |
| 78 | - gap: 20px; | |
| 79 | - padding: 30px; | |
| 80 | - } | |
| 81 | - | |
| 82 | - .panel { | |
| 83 | - background: #f8f9fa; | |
| 84 | - border-radius: 8px; | |
| 85 | - padding: 20px; | |
| 86 | - } | |
| 87 | - | |
| 88 | - .panel h2 { | |
| 89 | - color: #667eea; | |
| 90 | - margin-bottom: 20px; | |
| 91 | - font-size: 20px; | |
| 92 | - border-bottom: 2px solid #667eea; | |
| 93 | - padding-bottom: 10px; | |
| 94 | - } | |
| 95 | - | |
| 96 | - .form-group { | |
| 97 | - margin-bottom: 15px; | |
| 98 | - } | |
| 99 | - | |
| 100 | - .form-group label { | |
| 101 | - display: block; | |
| 102 | - margin-bottom: 5px; | |
| 103 | - color: #333; | |
| 104 | - font-weight: 500; | |
| 105 | - } | |
| 106 | - | |
| 107 | - .form-group input, | |
| 108 | - .form-group select, | |
| 109 | - .form-group textarea { | |
| 110 | - width: 100%; | |
| 111 | - padding: 10px; | |
| 112 | - border: 1px solid #ddd; | |
| 113 | - border-radius: 4px; | |
| 114 | - font-size: 14px; | |
| 115 | - } | |
| 116 | - | |
| 117 | - .form-group input:focus, | |
| 118 | - .form-group select:focus, | |
| 119 | - .form-group textarea:focus { | |
| 120 | - outline: none; | |
| 121 | - border-color: #667eea; | |
| 122 | - } | |
| 123 | - | |
| 124 | - .form-group textarea { | |
| 125 | - resize: vertical; | |
| 126 | - min-height: 80px; | |
| 127 | - font-family: monospace; | |
| 128 | - } | |
| 129 | - | |
| 130 | - .btn { | |
| 131 | - padding: 10px 20px; | |
| 132 | - border: none; | |
| 133 | - border-radius: 4px; | |
| 134 | - cursor: pointer; | |
| 135 | - font-size: 14px; | |
| 136 | - font-weight: 500; | |
| 137 | - transition: all 0.3s; | |
| 138 | - } | |
| 139 | - | |
| 140 | - .btn-primary { | |
| 141 | - background: #667eea; | |
| 142 | - color: white; | |
| 143 | - } | |
| 144 | - | |
| 145 | - .btn-primary:hover { | |
| 146 | - background: #5568d3; | |
| 147 | - } | |
| 148 | - | |
| 149 | - .btn-success { | |
| 150 | - background: #28a745; | |
| 151 | - color: white; | |
| 152 | - } | |
| 153 | - | |
| 154 | - .btn-success:hover { | |
| 155 | - background: #218838; | |
| 156 | - } | |
| 157 | - | |
| 158 | - .btn-danger { | |
| 159 | - background: #dc3545; | |
| 160 | - color: white; | |
| 161 | - } | |
| 162 | - | |
| 163 | - .btn-danger:hover { | |
| 164 | - background: #c82333; | |
| 165 | - } | |
| 166 | - | |
| 167 | - .btn-warning { | |
| 168 | - background: #ffc107; | |
| 169 | - color: #333; | |
| 170 | - } | |
| 171 | - | |
| 172 | - .btn-warning:hover { | |
| 173 | - background: #e0a800; | |
| 174 | - } | |
| 175 | - | |
| 176 | - .btn-group { | |
| 177 | - display: flex; | |
| 178 | - gap: 10px; | |
| 179 | - margin-top: 15px; | |
| 180 | - } | |
| 181 | - | |
| 182 | - .status { | |
| 183 | - padding: 10px; | |
| 184 | - border-radius: 4px; | |
| 185 | - margin-bottom: 20px; | |
| 186 | - font-weight: 500; | |
| 187 | - } | |
| 188 | - | |
| 189 | - .status.disconnected { | |
| 190 | - background: #f8d7da; | |
| 191 | - color: #721c24; | |
| 192 | - } | |
| 193 | - | |
| 194 | - .status.connected { | |
| 195 | - background: #d4edda; | |
| 196 | - color: #155724; | |
| 197 | - } | |
| 198 | - | |
| 199 | - .status.connecting { | |
| 200 | - background: #fff3cd; | |
| 201 | - color: #856404; | |
| 202 | - } | |
| 203 | - | |
| 204 | - .log-area { | |
| 205 | - background: #1e1e1e; | |
| 206 | - color: #d4d4d4; | |
| 207 | - padding: 15px; | |
| 208 | - border-radius: 4px; | |
| 209 | - height: 400px; | |
| 210 | - overflow-y: auto; | |
| 211 | - font-family: 'Courier New', monospace; | |
| 212 | - font-size: 12px; | |
| 213 | - line-height: 1.6; | |
| 214 | - } | |
| 215 | - | |
| 216 | - .log-entry { | |
| 217 | - margin-bottom: 5px; | |
| 218 | - } | |
| 219 | - | |
| 220 | - .log-entry.info { | |
| 221 | - color: #4ec9b0; | |
| 222 | - } | |
| 223 | - | |
| 224 | - .log-entry.success { | |
| 225 | - color: #6a9955; | |
| 226 | - } | |
| 227 | - | |
| 228 | - .log-entry.error { | |
| 229 | - color: #f48771; | |
| 230 | - } | |
| 231 | - | |
| 232 | - .log-entry.warning { | |
| 233 | - color: #dcdcaa; | |
| 234 | - } | |
| 235 | - | |
| 236 | - .stats { | |
| 237 | - display: grid; | |
| 238 | - grid-template-columns: repeat(3, 1fr); | |
| 239 | - gap: 10px; | |
| 240 | - margin-top: 15px; | |
| 241 | - } | |
| 242 | - | |
| 243 | - .stat-item { | |
| 244 | - background: white; | |
| 245 | - padding: 15px; | |
| 246 | - border-radius: 4px; | |
| 247 | - text-align: center; | |
| 248 | - } | |
| 249 | - | |
| 250 | - .stat-item .value { | |
| 251 | - font-size: 24px; | |
| 252 | - font-weight: bold; | |
| 253 | - color: #667eea; | |
| 254 | - } | |
| 255 | - | |
| 256 | - .stat-item .label { | |
| 257 | - font-size: 12px; | |
| 258 | - color: #666; | |
| 259 | - margin-top: 5px; | |
| 260 | - } | |
| 261 | - | |
| 262 | - @media (max-width: 768px) { | |
| 263 | - .content { | |
| 264 | - grid-template-columns: 1fr; | |
| 265 | - } | |
| 266 | - } | |
| 267 | - </style> | |
| 268 | -</head> | |
| 269 | -<body> | |
| 270 | -<div class="container"> | |
| 271 | - <div class="header"> | |
| 272 | - <h1>🚀 MQTT WebSocket 测试客户端</h1> | |
| 273 | - <p>RuoYi-Vue-Pro IoT 模块 - MQTT over WebSocket 在线测试工具</p> | |
| 274 | - </div> | |
| 275 | - | |
| 276 | - <!-- 协议格式说明 --> | |
| 277 | - <div class="info-box"> | |
| 278 | - <h3>📌 标准协议格式说明</h3> | |
| 279 | - <ul> | |
| 280 | - <li><strong>Topic 格式:</strong><code>/sys/{productKey}/{deviceName}/thing/property/post</code></li> | |
| 281 | - <li><strong>Client ID 格式:</strong><code>{productKey}.{deviceName}</code> 例如:<code>zOXKLvHjUqTo7ipD.ceshi001</code> | |
| 282 | - </li> | |
| 283 | - <li><strong>Username 格式:</strong><code>{deviceName}&{productKey}</code> 例如:<code>ceshi001&zOXKLvHjUqTo7ipD</code> | |
| 284 | - </li> | |
| 285 | - <li><strong>消息格式(Alink 协议):</strong> | |
| 286 | - <pre style="background: #e9ecef; padding: 10px; border-radius: 5px; margin-top: 5px; overflow-x: auto;"> | |
| 287 | -{ | |
| 288 | - "id": "消息 ID(唯一标识)", | |
| 289 | - "version": "1.0", | |
| 290 | - "method": "thing.property.post", | |
| 291 | - "params": { | |
| 292 | - "temperature": 25.5, | |
| 293 | - "humidity": 60 | |
| 294 | - } | |
| 295 | -}</pre> | |
| 296 | - </li> | |
| 297 | - <li><strong>常用 Topic(下行 - 服务端推送):</strong> | |
| 298 | - <ul style="margin-top: 5px;"> | |
| 299 | - <li>属性设置:<code>/sys/{pk}/{dn}/thing/property/set</code></li> | |
| 300 | - <li>服务调用:<code>/sys/{pk}/{dn}/thing/service/invoke</code></li> | |
| 301 | - <li>配置推送:<code>/sys/{pk}/{dn}/thing/config/push</code></li> | |
| 302 | - <li>OTA 升级:<code>/sys/{pk}/{dn}/thing/ota/upgrade</code></li> | |
| 303 | - </ul> | |
| 304 | - </li> | |
| 305 | - <li><strong>常用 Topic(上行 - 设备上报):</strong> | |
| 306 | - <ul style="margin-top: 5px;"> | |
| 307 | - <li>状态更新:<code>/sys/{pk}/{dn}/thing/state/update</code></li> | |
| 308 | - <li>属性上报:<code>/sys/{pk}/{dn}/thing/property/post</code></li> | |
| 309 | - <li>事件上报:<code>/sys/{pk}/{dn}/thing/event/post</code></li> | |
| 310 | - <li>OTA 进度:<code>/sys/{pk}/{dn}/thing/ota/progress</code></li> | |
| 311 | - </ul> | |
| 312 | - </li> | |
| 313 | - </ul> | |
| 314 | - </div> | |
| 315 | - | |
| 316 | - <div class="content"> | |
| 317 | - <!-- 连接配置面板 --> | |
| 318 | - <div class="panel"> | |
| 319 | - <h2>📡 连接配置</h2> | |
| 320 | - | |
| 321 | - <div class="status disconnected" id="statusBar"> | |
| 322 | - ⚫ 未连接 | |
| 323 | - </div> | |
| 324 | - | |
| 325 | - <div class="form-group"> | |
| 326 | - <label>服务器地址</label> | |
| 327 | - <input id="serverUrl" placeholder="ws://host:port/path" type="text" value="ws://localhost:8083/mqtt"> | |
| 328 | - <small style="color: #666; font-size: 12px;">WebSocket 地址,支持 ws:// 和 wss://</small> | |
| 329 | - </div> | |
| 330 | - | |
| 331 | - <div class="form-group"> | |
| 332 | - <label>Client ID</label> | |
| 333 | - <input id="clientId" placeholder="设备客户端 ID" type="text" value="fqTn4Afs982Nak4N.jiali001"> | |
| 334 | - <small style="color: #666; font-size: 12px;">格式:{productKey}.{deviceName}</small> | |
| 335 | - </div> | |
| 336 | - | |
| 337 | - <div class="form-group"> | |
| 338 | - <label>Username</label> | |
| 339 | - <input id="username" placeholder="用户名" type="text" value="jiali001&fqTn4Afs982Nak4N"> | |
| 340 | - <small style="color: #666; font-size: 12px;">格式:{deviceName}&{productKey}</small> | |
| 341 | - </div> | |
| 342 | - | |
| 343 | - <div class="form-group"> | |
| 344 | - <label>Password</label> | |
| 345 | - <input id="password" placeholder="设备密钥" | |
| 346 | - type="password" value="ae10188f93febbb6b37bd57f463b2a795ae2800fab8933aef75d3c6422873f28"> | |
| 347 | - <small style="color: #666; font-size: 12px;">设备的认证密钥(Device Secret)</small> | |
| 348 | - </div> | |
| 349 | - | |
| 350 | - <div class="btn-group"> | |
| 351 | - <button class="btn btn-success" id="connectBtn" onclick="connect()">🔌 连接</button> | |
| 352 | - <button class="btn btn-danger" disabled id="disconnectBtn" onclick="disconnect()">🔌 断开</button> | |
| 353 | - <button class="btn btn-warning" onclick="clearLogs()">🗑️ 清空日志</button> | |
| 354 | - </div> | |
| 355 | - | |
| 356 | - <!-- 统计信息 --> | |
| 357 | - <div class="stats"> | |
| 358 | - <div class="stat-item"> | |
| 359 | - <div class="value" id="sentCount">0</div> | |
| 360 | - <div class="label">发送消息数</div> | |
| 361 | - </div> | |
| 362 | - <div class="stat-item"> | |
| 363 | - <div class="value" id="receivedCount">0</div> | |
| 364 | - <div class="label">接收消息数</div> | |
| 365 | - </div> | |
| 366 | - <div class="stat-item"> | |
| 367 | - <div class="value" id="errorCount">0</div> | |
| 368 | - <div class="label">错误次数</div> | |
| 369 | - </div> | |
| 370 | - </div> | |
| 371 | - </div> | |
| 372 | - | |
| 373 | - <!-- 消息发布面板 --> | |
| 374 | - <div class="panel"> | |
| 375 | - <h2>📤 消息发布</h2> | |
| 376 | - | |
| 377 | - <div class="form-group"> | |
| 378 | - <label>快捷主题选择(上行消息 - 设备 → 服务端)</label> | |
| 379 | - <select id="quickPublishTopicSelect" onchange="selectQuickPublishTopic()" | |
| 380 | - style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 5px;"> | |
| 381 | - <option value="">-- 选择上行消息类型 --</option> | |
| 382 | - <option value="thing.state.update">设备状态更新 (thing.state.update)</option> | |
| 383 | - <option value="thing.property.post">属性上报 (thing.property.post)</option> | |
| 384 | - <option value="thing.event.post">事件上报 (thing.event.post)</option> | |
| 385 | - <option value="thing.ota.progress">OTA 升级进度 (thing.ota.progress)</option> | |
| 386 | - </select> | |
| 387 | - </div> | |
| 388 | - | |
| 389 | - <div class="form-group"> | |
| 390 | - <label>主题 (Topic)</label> | |
| 391 | - <input id="pubTopic" placeholder="消息主题,格式:/sys/{productKey}/{deviceName}/thing/property/post" type="text" | |
| 392 | - value="/sys/fqTn4Afs982Nak4N/jiali001/thing/property/post"> | |
| 393 | - <small style="color: #666; font-size: 12px;">标准格式:/sys/{productKey}/{deviceName}/thing/property/post</small> | |
| 394 | - </div> | |
| 395 | - | |
| 396 | - <div class="form-group"> | |
| 397 | - <label>QoS 级别</label> | |
| 398 | - <select id="pubQos"> | |
| 399 | - <option value="0">0 - 最多一次</option> | |
| 400 | - <option selected value="1">1 - 至少一次</option> | |
| 401 | - <option value="2">2 - 刚好一次</option> | |
| 402 | - </select> | |
| 403 | - </div> | |
| 404 | - | |
| 405 | - <div class="form-group"> | |
| 406 | - <label>消息内容 (JSON - Alink 协议格式)</label> | |
| 407 | - <textarea id="pubMessage" placeholder='Alink 协议格式消息'>{ | |
| 408 | - "id": "123456789", | |
| 409 | - "version": "1.0", | |
| 410 | - "method": "thing.property.post", | |
| 411 | - "params": { | |
| 412 | - "temperature": 25.5, | |
| 413 | - "humidity": 60 | |
| 414 | - } | |
| 415 | -}</textarea> | |
| 416 | - <small style="color: #666; font-size: 12px;"> | |
| 417 | - Alink 协议格式:id(消息 ID)、version(协议版本)、method(方法)、params(参数) | |
| 418 | - </small> | |
| 419 | - </div> | |
| 420 | - | |
| 421 | - <div class="btn-group"> | |
| 422 | - <button class="btn btn-primary" onclick="publish()">📤 发布消息</button> | |
| 423 | - <button class="btn btn-success" onclick="publishSampleData()">📊 发送样例数据</button> | |
| 424 | - </div> | |
| 425 | - | |
| 426 | - <h2 style="margin-top: 30px;">📥 主题订阅</h2> | |
| 427 | - | |
| 428 | - <div class="form-group"> | |
| 429 | - <label>快捷主题选择(下行消息 - 服务端 → 设备)</label> | |
| 430 | - <select id="quickTopicSelect" onchange="selectQuickTopic()" | |
| 431 | - style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 5px;"> | |
| 432 | - <option value="">-- 选择下行消息类型 --</option> | |
| 433 | - <optgroup label="📥 下行消息"> | |
| 434 | - <option value="thing.property.set">属性设置 (thing.property.set)</option> | |
| 435 | - <option value="thing.service.invoke">服务调用 (thing.service.invoke)</option> | |
| 436 | - <option value="thing.config.push">配置推送 (thing.config.push)</option> | |
| 437 | - <option value="thing.ota.upgrade">OTA 固件推送 (thing.ota.upgrade)</option> | |
| 438 | - </optgroup> | |
| 439 | - <optgroup label="🔄 回复主题(上行消息的回复)"> | |
| 440 | - <option value="thing.property.post_reply">属性上报回复 (thing.property.post_reply)</option> | |
| 441 | - <option value="thing.event.post_reply">事件上报回复 (thing.event.post_reply)</option> | |
| 442 | - </optgroup> | |
| 443 | - <optgroup label="🔧 通配符订阅"> | |
| 444 | - <option value="wildcard_all">订阅所有主题 (/sys/+/+/#)</option> | |
| 445 | - <option value="wildcard_thing">订阅所有 thing 主题 (/sys/+/+/thing/#)</option> | |
| 446 | - <option value="wildcard_reply">订阅所有回复主题 (/sys/+/+/#_reply)</option> | |
| 447 | - </optgroup> | |
| 448 | - </select> | |
| 449 | - </div> | |
| 450 | - | |
| 451 | - <div class="form-group"> | |
| 452 | - <label>订阅主题</label> | |
| 453 | - <input id="subTopic" placeholder="订阅主题,格式:/sys/{productKey}/{deviceName}/thing/property/set" type="text" | |
| 454 | - value="/sys/fqTn4Afs982Nak4N/jiali001/thing/property/set"> | |
| 455 | - <small style="color: #666; font-size: 12px;">标准格式:/sys/{productKey}/{deviceName}/thing/method 或使用通配符 | |
| 456 | - /sys/+/+/#</small> | |
| 457 | - </div> | |
| 458 | - | |
| 459 | - <div class="form-group"> | |
| 460 | - <label>QoS 级别</label> | |
| 461 | - <select id="subQos"> | |
| 462 | - <option value="0">0 - 最多一次</option> | |
| 463 | - <option selected value="1">1 - 至少一次</option> | |
| 464 | - <option value="2">2 - 刚好一次</option> | |
| 465 | - </select> | |
| 466 | - </div> | |
| 467 | - | |
| 468 | - <div class="btn-group"> | |
| 469 | - <button class="btn btn-primary" onclick="subscribe()">📥 订阅</button> | |
| 470 | - <button class="btn btn-danger" onclick="unsubscribe()">❌ 取消订阅</button> | |
| 471 | - </div> | |
| 472 | - </div> | |
| 473 | - | |
| 474 | - <!-- 日志面板 --> | |
| 475 | - <div class="panel" style="grid-column: 1 / -1;"> | |
| 476 | - <h2>📝 日志输出</h2> | |
| 477 | - <div class="log-area" id="logArea"></div> | |
| 478 | - </div> | |
| 479 | - </div> | |
| 480 | -</div> | |
| 481 | - | |
| 482 | -<!-- 使用 MQTT.js 库 --> | |
| 483 | -<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script> | |
| 484 | - | |
| 485 | -<script> | |
| 486 | - let client = null; | |
| 487 | - let sentCount = 0; | |
| 488 | - let receivedCount = 0; | |
| 489 | - let errorCount = 0; | |
| 490 | - | |
| 491 | - // 添加日志 | |
| 492 | - function addLog(message, type = 'info') { | |
| 493 | - const logArea = document.getElementById('logArea'); | |
| 494 | - const timestamp = new Date().toLocaleTimeString(); | |
| 495 | - const logEntry = document.createElement('div'); | |
| 496 | - logEntry.className = `log-entry ${type}`; | |
| 497 | - logEntry.textContent = `[${timestamp}] ${message}`; | |
| 498 | - logArea.appendChild(logEntry); | |
| 499 | - logArea.scrollTop = logArea.scrollHeight; | |
| 500 | - } | |
| 501 | - | |
| 502 | - // 更新状态栏 | |
| 503 | - function updateStatus(status, text) { | |
| 504 | - const statusBar = document.getElementById('statusBar'); | |
| 505 | - statusBar.className = `status ${status}`; | |
| 506 | - | |
| 507 | - const icons = { | |
| 508 | - 'disconnected': '⚫', | |
| 509 | - 'connecting': '🟡', | |
| 510 | - 'connected': '🟢' | |
| 511 | - }; | |
| 512 | - | |
| 513 | - statusBar.textContent = `${icons[status]} ${text}`; | |
| 514 | - } | |
| 515 | - | |
| 516 | - // 更新统计信息 | |
| 517 | - function updateStats() { | |
| 518 | - document.getElementById('sentCount').textContent = sentCount; | |
| 519 | - document.getElementById('receivedCount').textContent = receivedCount; | |
| 520 | - document.getElementById('errorCount').textContent = errorCount; | |
| 521 | - } | |
| 522 | - | |
| 523 | - // 连接到服务器 | |
| 524 | - function connect() { | |
| 525 | - const serverUrl = document.getElementById('serverUrl').value; | |
| 526 | - const clientId = document.getElementById('clientId').value; | |
| 527 | - const username = document.getElementById('username').value; | |
| 528 | - const password = document.getElementById('password').value; | |
| 529 | - | |
| 530 | - if (!serverUrl || !clientId) { | |
| 531 | - addLog('❌ 请填写服务器地址和 Client ID', 'error'); | |
| 532 | - errorCount++; | |
| 533 | - updateStats(); | |
| 534 | - return; | |
| 535 | - } | |
| 536 | - | |
| 537 | - updateStatus('connecting', '正在连接...'); | |
| 538 | - addLog(`🔄 正在连接到 ${serverUrl}...`, 'info'); | |
| 539 | - | |
| 540 | - const options = { | |
| 541 | - clientId: clientId, | |
| 542 | - username: username, | |
| 543 | - password: password, | |
| 544 | - clean: true, | |
| 545 | - reconnectPeriod: 5000, | |
| 546 | - connectTimeout: 30000, | |
| 547 | - }; | |
| 548 | - | |
| 549 | - client = mqtt.connect(serverUrl, options); | |
| 550 | - | |
| 551 | - // 连接成功 | |
| 552 | - client.on('connect', () => { | |
| 553 | - updateStatus('connected', '已连接'); | |
| 554 | - addLog('✅ 连接成功!', 'success'); | |
| 555 | - document.getElementById('connectBtn').disabled = true; | |
| 556 | - document.getElementById('disconnectBtn').disabled = false; | |
| 557 | - }); | |
| 558 | - | |
| 559 | - // 接收消息 | |
| 560 | - client.on('message', (topic, message) => { | |
| 561 | - receivedCount++; | |
| 562 | - updateStats(); | |
| 563 | - addLog(`📥 收到消息 [${topic}]: ${message.toString()}`, 'success'); | |
| 564 | - }); | |
| 565 | - | |
| 566 | - // 连接错误 | |
| 567 | - client.on('error', (error) => { | |
| 568 | - errorCount++; | |
| 569 | - updateStats(); | |
| 570 | - addLog(`❌ 连接错误: ${error.message}`, 'error'); | |
| 571 | - }); | |
| 572 | - | |
| 573 | - // 断开连接 | |
| 574 | - client.on('close', () => { | |
| 575 | - updateStatus('disconnected', '未连接'); | |
| 576 | - addLog('🔌 连接已断开', 'warning'); | |
| 577 | - document.getElementById('connectBtn').disabled = false; | |
| 578 | - document.getElementById('disconnectBtn').disabled = true; | |
| 579 | - }); | |
| 580 | - | |
| 581 | - // 离线 | |
| 582 | - client.on('offline', () => { | |
| 583 | - updateStatus('disconnected', '离线'); | |
| 584 | - addLog('⚠️ 客户端离线', 'warning'); | |
| 585 | - }); | |
| 586 | - | |
| 587 | - // 重连 | |
| 588 | - client.on('reconnect', () => { | |
| 589 | - updateStatus('connecting', '正在重连...'); | |
| 590 | - addLog('🔄 正在重连...', 'info'); | |
| 591 | - }); | |
| 592 | - } | |
| 593 | - | |
| 594 | - // 断开连接 | |
| 595 | - function disconnect() { | |
| 596 | - if (client) { | |
| 597 | - client.end(); | |
| 598 | - addLog('👋 主动断开连接', 'info'); | |
| 599 | - } | |
| 600 | - } | |
| 601 | - | |
| 602 | - // 发布消息 | |
| 603 | - function publish() { | |
| 604 | - if (!client || !client.connected) { | |
| 605 | - addLog('❌ 请先连接到服务器', 'error'); | |
| 606 | - errorCount++; | |
| 607 | - updateStats(); | |
| 608 | - return; | |
| 609 | - } | |
| 610 | - | |
| 611 | - const topic = document.getElementById('pubTopic').value; | |
| 612 | - const qos = parseInt(document.getElementById('pubQos').value); | |
| 613 | - const message = document.getElementById('pubMessage').value; | |
| 614 | - | |
| 615 | - if (!topic || !message) { | |
| 616 | - addLog('❌ 请填写主题和消息内容', 'error'); | |
| 617 | - errorCount++; | |
| 618 | - updateStats(); | |
| 619 | - return; | |
| 620 | - } | |
| 621 | - | |
| 622 | - // 验证 JSON 格式 | |
| 623 | - try { | |
| 624 | - JSON.parse(message); | |
| 625 | - } catch (e) { | |
| 626 | - addLog('⚠️ 消息不是有效的 JSON 格式,将作为纯文本发送', 'warning'); | |
| 627 | - } | |
| 628 | - | |
| 629 | - client.publish(topic, message, {qos: qos}, (error) => { | |
| 630 | - if (error) { | |
| 631 | - errorCount++; | |
| 632 | - updateStats(); | |
| 633 | - addLog(`❌ 发布失败: ${error.message}`, 'error'); | |
| 634 | - } else { | |
| 635 | - sentCount++; | |
| 636 | - updateStats(); | |
| 637 | - addLog(`📤 消息已发布 [${topic}] (QoS ${qos})`, 'success'); | |
| 638 | - } | |
| 639 | - }); | |
| 640 | - } | |
| 641 | - | |
| 642 | - // 发送样例数据 | |
| 643 | - function publishSampleData() { | |
| 644 | - // 使用 Alink 协议格式的样例数据 | |
| 645 | - const sampleData = { | |
| 646 | - id: Date.now().toString(), | |
| 647 | - version: "1.0", | |
| 648 | - method: "thing.property.post", | |
| 649 | - params: { | |
| 650 | - temperature: parseFloat((20 + Math.random() * 10).toFixed(2)), | |
| 651 | - humidity: parseFloat((50 + Math.random() * 20).toFixed(2)), | |
| 652 | - pressure: parseFloat((1000 + Math.random() * 50).toFixed(2)) | |
| 653 | - } | |
| 654 | - }; | |
| 655 | - | |
| 656 | - document.getElementById('pubMessage').value = JSON.stringify(sampleData, null, 2); | |
| 657 | - addLog('样例数据已生成(Alink 协议格式)', 'info'); | |
| 658 | - publish(); | |
| 659 | - } | |
| 660 | - | |
| 661 | - // 获取 productKey 和 deviceName | |
| 662 | - function getDeviceInfo() { | |
| 663 | - const clientId = document.getElementById('clientId').value; | |
| 664 | - const parts = clientId.split('.'); | |
| 665 | - | |
| 666 | - if (parts.length !== 2) { | |
| 667 | - addLog('❌ Client ID 格式不正确(应为 {productKey}.{deviceName}),无法生成主题', 'error'); | |
| 668 | - return null; | |
| 669 | - } | |
| 670 | - | |
| 671 | - return { | |
| 672 | - productKey: parts[0], | |
| 673 | - deviceName: parts[1] | |
| 674 | - }; | |
| 675 | - } | |
| 676 | - | |
| 677 | - // 快捷主题选择(消息发布 - 上行消息) | |
| 678 | - function selectQuickPublishTopic() { | |
| 679 | - const select = document.getElementById('quickPublishTopicSelect'); | |
| 680 | - const selectedValue = select.value; | |
| 681 | - | |
| 682 | - console.log('[selectQuickPublishTopic] 选择的值:', selectedValue); | |
| 683 | - | |
| 684 | - if (!selectedValue) { | |
| 685 | - return; | |
| 686 | - } | |
| 687 | - | |
| 688 | - const deviceInfo = getDeviceInfo(); | |
| 689 | - if (!deviceInfo) { | |
| 690 | - return; | |
| 691 | - } | |
| 692 | - | |
| 693 | - console.log('[selectQuickPublishTopic] 设备信息:', deviceInfo); | |
| 694 | - | |
| 695 | - // 构建标准主题,将枚举中的点号替换为斜杠 | |
| 696 | - // 例如:thing.property.post -> /sys/{pk}/{dn}/thing/property/post | |
| 697 | - const topic = `/sys/${deviceInfo.productKey}/${deviceInfo.deviceName}/${selectedValue.replace(/\./g, '/')}`; | |
| 698 | - | |
| 699 | - console.log('[selectQuickPublishTopic] 生成的主题:', topic); | |
| 700 | - | |
| 701 | - const pubTopicInput = document.getElementById('pubTopic'); | |
| 702 | - pubTopicInput.value = topic; | |
| 703 | - | |
| 704 | - console.log('[selectQuickPublishTopic] 输入框的值已设置为:', pubTopicInput.value); | |
| 705 | - | |
| 706 | - addLog(`📋 已选择发布主题: ${topic}`, 'info'); | |
| 707 | - | |
| 708 | - // 需要 reply 的消息类型(不在 REPLY_DISABLED 列表中) | |
| 709 | - const needsReply = [ | |
| 710 | - 'thing.property.post', | |
| 711 | - 'thing.event.post' | |
| 712 | - ]; | |
| 713 | - | |
| 714 | - // 如果需要 reply,自动订阅 reply 主题 | |
| 715 | - if (needsReply.includes(selectedValue)) { | |
| 716 | - const replyTopic = `${topic}_reply`; | |
| 717 | - | |
| 718 | - if (client && client.connected) { | |
| 719 | - // 自动订阅 reply 主题 | |
| 720 | - client.subscribe(replyTopic, {qos: 1}, (err) => { | |
| 721 | - if (!err) { | |
| 722 | - addLog(`✅ 已自动订阅回复主题: ${replyTopic}`, 'success'); | |
| 723 | - } else { | |
| 724 | - addLog(`❌ 自动订阅回复主题失败: ${err.message}`, 'error'); | |
| 725 | - } | |
| 726 | - }); | |
| 727 | - } else { | |
| 728 | - addLog(`💡 提示: 该消息需要订阅回复主题 ${replyTopic}`, 'warning'); | |
| 729 | - } | |
| 730 | - } | |
| 731 | - | |
| 732 | - // 重置下拉框到默认选项 | |
| 733 | - select.selectedIndex = 0; | |
| 734 | - console.log('[selectQuickPublishTopic] 下拉框已重置'); | |
| 735 | - } | |
| 736 | - | |
| 737 | - // 快捷主题选择(主题订阅 - 下行消息) | |
| 738 | - function selectQuickTopic() { | |
| 739 | - const select = document.getElementById('quickTopicSelect'); | |
| 740 | - const selectedValue = select.value; | |
| 741 | - | |
| 742 | - console.log('[selectQuickTopic] 选择的值:', selectedValue); | |
| 743 | - | |
| 744 | - if (!selectedValue) { | |
| 745 | - return; | |
| 746 | - } | |
| 747 | - | |
| 748 | - const subTopicInput = document.getElementById('subTopic'); | |
| 749 | - | |
| 750 | - // 处理通配符订阅 | |
| 751 | - if (selectedValue === 'wildcard_all') { | |
| 752 | - subTopicInput.value = '/sys/+/+/#'; | |
| 753 | - addLog('📋 已选择订阅主题: /sys/+/+/#(订阅所有主题)', 'info'); | |
| 754 | - console.log('[selectQuickTopic] 输入框的值已设置为:', subTopicInput.value); | |
| 755 | - select.selectedIndex = 0; | |
| 756 | - console.log('[selectQuickTopic] 下拉框已重置'); | |
| 757 | - return; | |
| 758 | - } else if (selectedValue === 'wildcard_thing') { | |
| 759 | - subTopicInput.value = '/sys/+/+/thing/#'; | |
| 760 | - addLog('📋 已选择订阅主题: /sys/+/+/thing/#(订阅所有 thing 主题)', 'info'); | |
| 761 | - console.log('[selectQuickTopic] 输入框的值已设置为:', subTopicInput.value); | |
| 762 | - select.selectedIndex = 0; | |
| 763 | - console.log('[selectQuickTopic] 下拉框已重置'); | |
| 764 | - return; | |
| 765 | - } else if (selectedValue === 'wildcard_reply') { | |
| 766 | - const deviceInfo = getDeviceInfo(); | |
| 767 | - if (!deviceInfo) { | |
| 768 | - select.selectedIndex = 0; | |
| 769 | - return; | |
| 770 | - } | |
| 771 | - subTopicInput.value = `/sys/${deviceInfo.productKey}/${deviceInfo.deviceName}/#_reply`; | |
| 772 | - addLog(`📋 已选择订阅主题: /sys/${deviceInfo.productKey}/${deviceInfo.deviceName}/#_reply(订阅所有回复主题)`, 'info'); | |
| 773 | - console.log('[selectQuickTopic] 输入框的值已设置为:', subTopicInput.value); | |
| 774 | - select.selectedIndex = 0; | |
| 775 | - console.log('[selectQuickTopic] 下拉框已重置'); | |
| 776 | - return; | |
| 777 | - } | |
| 778 | - | |
| 779 | - const deviceInfo = getDeviceInfo(); | |
| 780 | - if (!deviceInfo) { | |
| 781 | - select.selectedIndex = 0; | |
| 782 | - return; | |
| 783 | - } | |
| 784 | - | |
| 785 | - console.log('[selectQuickTopic] 设备信息:', deviceInfo); | |
| 786 | - | |
| 787 | - // 构建标准主题,将枚举中的点号替换为斜杠 | |
| 788 | - // 例如:thing.property.set -> /sys/{pk}/{dn}/thing/property/set | |
| 789 | - const topic = `/sys/${deviceInfo.productKey}/${deviceInfo.deviceName}/${selectedValue.replace(/\./g, '/')}`; | |
| 790 | - | |
| 791 | - console.log('[selectQuickTopic] 生成的主题:', topic); | |
| 792 | - | |
| 793 | - subTopicInput.value = topic; | |
| 794 | - addLog(`📋 已选择订阅主题: ${topic}`, 'info'); | |
| 795 | - | |
| 796 | - console.log('[selectQuickTopic] 输入框的值已设置为:', subTopicInput.value); | |
| 797 | - | |
| 798 | - // 重置下拉框到默认选项 | |
| 799 | - select.selectedIndex = 0; | |
| 800 | - console.log('[selectQuickTopic] 下拉框已重置'); | |
| 801 | - } | |
| 802 | - | |
| 803 | - // 订阅主题 | |
| 804 | - function subscribe() { | |
| 805 | - if (!client || !client.connected) { | |
| 806 | - addLog('❌ 请先连接到服务器', 'error'); | |
| 807 | - errorCount++; | |
| 808 | - updateStats(); | |
| 809 | - return; | |
| 810 | - } | |
| 811 | - | |
| 812 | - const topic = document.getElementById('subTopic').value; | |
| 813 | - const qos = parseInt(document.getElementById('subQos').value); | |
| 814 | - | |
| 815 | - if (!topic) { | |
| 816 | - addLog('❌ 请填写订阅主题', 'error'); | |
| 817 | - errorCount++; | |
| 818 | - updateStats(); | |
| 819 | - return; | |
| 820 | - } | |
| 821 | - | |
| 822 | - client.subscribe(topic, {qos: qos}, (error) => { | |
| 823 | - if (error) { | |
| 824 | - errorCount++; | |
| 825 | - updateStats(); | |
| 826 | - addLog(`❌ 订阅失败: ${error.message}`, 'error'); | |
| 827 | - } else { | |
| 828 | - addLog(`📥 已订阅主题 [${topic}] (QoS ${qos})`, 'success'); | |
| 829 | - } | |
| 830 | - }); | |
| 831 | - } | |
| 832 | - | |
| 833 | - // 取消订阅 | |
| 834 | - function unsubscribe() { | |
| 835 | - if (!client || !client.connected) { | |
| 836 | - addLog('❌ 请先连接到服务器', 'error'); | |
| 837 | - errorCount++; | |
| 838 | - updateStats(); | |
| 839 | - return; | |
| 840 | - } | |
| 841 | - | |
| 842 | - const topic = document.getElementById('subTopic').value; | |
| 843 | - | |
| 844 | - if (!topic) { | |
| 845 | - addLog('❌ 请填写要取消的订阅主题', 'error'); | |
| 846 | - errorCount++; | |
| 847 | - updateStats(); | |
| 848 | - return; | |
| 849 | - } | |
| 850 | - | |
| 851 | - client.unsubscribe(topic, (error) => { | |
| 852 | - if (error) { | |
| 853 | - errorCount++; | |
| 854 | - updateStats(); | |
| 855 | - addLog(`❌ 取消订阅失败: ${error.message}`, 'error'); | |
| 856 | - } else { | |
| 857 | - addLog(`❌ 已取消订阅 [${topic}]`, 'info'); | |
| 858 | - } | |
| 859 | - }); | |
| 860 | - } | |
| 861 | - | |
| 862 | - // 清空日志 | |
| 863 | - function clearLogs() { | |
| 864 | - document.getElementById('logArea').innerHTML = ''; | |
| 865 | - sentCount = 0; | |
| 866 | - receivedCount = 0; | |
| 867 | - errorCount = 0; | |
| 868 | - updateStats(); | |
| 869 | - addLog('🗑️ 日志已清空', 'info'); | |
| 870 | - } | |
| 871 | - | |
| 872 | - // 页面加载完成 | |
| 873 | - window.onload = function () { | |
| 874 | - addLog('👋 欢迎使用 MQTT WebSocket 测试客户端!', 'success'); | |
| 875 | - addLog('📚 请配置连接参数后点击"连接"按钮', 'info'); | |
| 876 | - }; | |
| 877 | - | |
| 878 | - // 页面关闭前断开连接 | |
| 879 | - window.onbeforeunload = function () { | |
| 880 | - if (client && client.connected) { | |
| 881 | - client.end(); | |
| 882 | - } | |
| 883 | - }; | |
| 884 | -</script> | |
| 885 | -</body> | |
| 886 | -</html> | |
| 887 | - | |
| 888 | - |