Commit 432e1ed230d853bcd1c92108f5e4525244c8f953

Authored by YunaiV
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   -