Sfoglia il codice sorgente

提交mqtt优化代码

wangming 3 settimane fa
parent
commit
5f4a6717e8

+ 4 - 0
src/App.vue

@@ -77,5 +77,9 @@ export default {
     onUnload() {
         MqttService.disconnectAll();
     },
+    onShow() {},
+    onHide() {
+        // MqttService.disconnectAll();
+    },
 };
 </script>

+ 0 - 120
src/components/component/alarModel.vue

@@ -415,126 +415,6 @@ export default {
                 console.error(`call error`, error);
             }
         },
-        // mqtt相关方法
-        // connectMQTTwo() {
-        //     if (this.mqttClientFlag) {
-        //         console.log("主题已订阅");
-        //         return;
-        //     }
-        //     const THRESHOLD = 2;
-        //     const params = {
-        //         keepalive: 6000,
-        //         clean: true,
-        //         connectTimeout: 30 * 1000,
-        //         clientId:
-        //             "xcx_mqtt_cmd1" +
-        //             uni.getStorageSync("userId") +
-        //             Math.random().toString(16).substring(2, 8),
-        //         username: "lnradar",
-        //         password: "lnradar",
-        //         // 微信小程序特定配置
-        //         wsOptions: {
-        //             WebSocket: function (url) {
-        //                 return wx.connectSocket({
-        //                     url: url,
-        //                     header: {
-        //                         "content-type": "application/json",
-        //                     },
-        //                     protocols: ["mqtt"],
-        //                 });
-        //             },
-        //         },
-        //         reconnectPeriod: 0,
-        //         rejectUnauthorized: false, // 仅开发环境使用,生产环境应设为true或移除
-        //     };
-        //     let client = "";
-        //     let selectedService = uni.getStorageSync("sercviceChoice");
-        //     if (!selectedService || selectedService == "aloneServe") {
-        //         if (__wxConfig.envVersion == "develop") {
-        //             client = mqtt.connect(
-        //                 "wxs://cmd.radar-power.cn/mqtt/",
-        //                 params
-        //             );
-        //         }
-        //         if (__wxConfig.envVersion == "trial") {
-        //             client = mqtt.connect(
-        //                 "wxs://cmd.radar-power.cn/mqtt/",
-        //                 params
-        //             );
-        //         }
-        //     }
-        //     // 存储client引用以便后续操作
-        //     this.mqttClientTwo = client;
-        //     client.on("connect", () => {
-        //         console.log("MQTT连接成功");
-        //         this.mqttClientFlag = true;
-        //         let userId = uni.getStorageSync("userId");
-        //         client.subscribe(`/mps/wx_${userId}/notice`, (err) => {
-        //             if (err) {
-        //                 console.error("订阅失败", err);
-        //             } else {
-        //                 console.log(
-        //                     `成功订阅设备主题: /mps/wx_${userId}/notice`
-        //                 );
-        //             }
-        //         });
-        //     });
-        //     client.on("disconnect", () => {
-        //         console.log("MQTT不在连接");
-        //     });
-        //     client.on("error", (err) => {
-        //         this.mqttClientFlag = false;
-        //         setTimeout(() => {
-        //             this.connectMQTTwo();
-        //         }, 1000);
-        //     });
-
-        //     client.on("reconnect", () => {});
-
-        //     client.on("close", () => {});
-
-        //     client.on("message", (topic, message) => {
-        //         console.log(JSON.parse(message.toString()), "8870");
-        //         // 处理点位消息
-        //         let userId = uni.getStorageSync("userId");
-
-        //         console.log("topic777777", userId);
-
-        //         const noticeMatch = /^\/mps\/wx_(.+)\/notice$/;
-        //         const match = topic.match(noticeMatch);
-        //         if (!match) return;
-        //         this.alarmModel = true;
-        //         const data = JSON.parse(message.toString());
-        //         this.clientId = data.clientId;
-        //         this.devName = data.devName;
-        //         this.sn = data.clientId;
-        //         this.eventListId = data.eventListId;
-        //         this.getVoipAuthor(this.clientId, userId);
-        //         console.log(data, topic, "接收到消息222:");
-        //         console.log(
-        //             "接收到消息:",
-        //             this.clientId,
-        //             this.devName,
-        //             this.sn,
-        //             this.eventListId,
-        //             data
-        //         );
-        //         const now = new Date();
-        //         const year = now.getFullYear();
-        //         const month = (now.getMonth() + 1).toString().padStart(2, "0");
-        //         const day = now.getDate().toString().padStart(2, "0");
-        //         const hours = now.getHours().toString().padStart(2, "0");
-        //         const minutes = now.getMinutes().toString().padStart(2, "0");
-        //         const formattedTime = `${year}-${month}-${day} ${hours}:${minutes}`;
-        //         this.nowTime = formattedTime;
-        //         console.log("isWmpf:", isWmpf);
-        //         if (!isWmpf) {
-        //             console.log("isWmpf:", isWmpf);
-        //             this.getVoipDevices();
-        //         }
-        //         console.log("接收到消息:", topic, data);
-        //     });
-        // },
 
         hanledList(type) {
             this.$http

+ 8 - 1
src/main.js

@@ -23,9 +23,16 @@ Vue.prototype.$initMqtt = function () {
         return;
     }
     // 连接 DATA
-    MqttService.connectData(userId)
+    // 如果已经连接过 DATA,直接返回
+    if (MqttService.dataConnected) {
+        uni.$emit('mqttData-ready', MqttService.dataClient);
+        uni.$emit('mqttDataReady', MqttService.dataClient);
+        return Promise.resolve(MqttService.dataClient);
+    }
+    return MqttService.connectData(userId)
         .then(client => {
             uni.$emit('mqttDataReadyOnce', this.dataClient);
+            uni.$emit('mqttDataReady', this.dataClient);
             console.log("DATA MQTT 初始化完成", client);
         })
         .catch(err => {

+ 13 - 10
src/pages.json

@@ -4,7 +4,7 @@
             "path": "pages/home/home",
             "style": {
                 "navigationStyle": "custom",
-                "enablePullDownRefresh": true
+                "enablePullDownRefresh": false
             }
         }
     ],
@@ -15,6 +15,7 @@
                 {
                     "path": "deviceDetail/deviceDetail",
                     "style": {
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "设备详情",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -24,6 +25,7 @@
                 {
                     "path": "adDevice/adDevice",
                     "style": {
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "添加设备",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -42,6 +44,7 @@
                 {
                     "path": "roomSetting/roomSetting",
                     "style": {
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "房间布局",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -59,7 +62,7 @@
                 {
                     "path": "deviceSetting/deviceSetting",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "设置",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -69,7 +72,7 @@
                 {
                     "path": "my/my",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "我的",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -89,7 +92,7 @@
                 {
                     "path": "linkShare/linkShare",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "链接分享",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -99,7 +102,7 @@
                 {
                     "path": "serChange/serChange",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "网络配置",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -108,7 +111,7 @@
                 {
                     "path": "sharePages/sharePages",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "分享确认",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -135,7 +138,7 @@
                 {
                     "path": "discrepancy/discrepancy",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "出入详情",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -180,7 +183,7 @@
                 {
                     "path": "healthAlarm/healthAlarm",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "守护计划",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -189,7 +192,7 @@
                 {
                     "path": "playSetting/playSetting",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "计划配置",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -198,7 +201,7 @@
                 {
                     "path": "adNewDevice/adNewDevice",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "绑定设备",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"

+ 1 - 1
src/pages/home/home.vue

@@ -98,7 +98,7 @@
             </movable-view>
         </movable-area> -->
 
-        <view class="bot_version"> v3.0.19</view>
+        <view class="bot_version"> v3.0.20</view>
         <view
             class="shareInfo"
             @click="goDeviceShare('sure')"

+ 115 - 70
src/pagesA/deviceDetail/deviceDetail.vue

@@ -959,10 +959,115 @@ export default {
         formatTime(time) {
             return time ? time.slice(11, 19) : "暂无";
         },
+        initSubscriptions() {
+            const topicList = [
+                {
+                    topic: `/dev/${this.clientId}/tracker_targets`,
+                    key: "unsubscribeFn",
+                    callback: (message, msgTopic) => {
+                        const dataMatch = msgTopic.match(
+                            /^\/dev\/(.+)\/tracker_targets$/
+                        );
+                        const cmdMatch = msgTopic.match(
+                            /^\/mps\/wx_(.+)\/notice$/
+                        );
+                        if (dataMatch && dataMatch[1] === this.clientId) {
+                            this.handleMessage(
+                                msgTopic,
+                                message,
+                                this.clientId
+                            );
+                        } else if (cmdMatch) {
+                            this.$refs.alarmModel.hanOtherMessage(
+                                msgTopic,
+                                message
+                            );
+                        }
+                    },
+                },
+                {
+                    topic: `/dev/${this.clientId}/falling_event_change`,
+                    key: "fallingEventChange",
+                    callback: (message, msgTopic) => {
+                        const dataMatch = msgTopic.match(
+                            /^\/dev\/(.+)\/falling_event_change$/
+                        );
+                        if (dataMatch && dataMatch[1] === this.clientId) {
+                            const dataMessage = JSON.parse(message.toString());
+                            if (dataMessage.falling == 1) {
+                                this.falling = dataMessage.falling;
+                                this.lnbAction = "actionWarn";
+                            } else if (
+                                dataMessage.falling == 2 ||
+                                dataMessage.falling == 3
+                            ) {
+                                this.falling = dataMessage.falling;
+                                this.lnbAction = "actionSerious";
+                            } else {
+                                this.lnbAction = "action8";
+                            }
+                        }
+                    },
+                },
+            ];
+
+            topicList.forEach((item) => {
+                // 避免重复订阅
+                if (this[item.key]) return;
+
+                const subscribeFunc = () => {
+                    const unsubscribe = MqttService.subscribe(
+                        "DATA",
+                        item.topic,
+                        item.callback
+                    );
+                    if (unsubscribe) {
+                        this[item.key] = unsubscribe;
+                        console.log(`✅ 已成功订阅主题: ${item.topic}`);
+                    }
+                };
+
+                if (MqttService.dataConnected) {
+                    subscribeFunc();
+                } else {
+                    // MQTT 未连接,等待重连成功再订阅
+                    const handler = () => {
+                        subscribeFunc();
+                        uni.$off("mqttData-ready", handler);
+                    };
+                    uni.$on("mqttData-ready", handler);
+                }
+            });
+        },
     },
     onShow() {
         this.showModle = true;
+        this.autoPlayinterval = setTimeout(() => {
+            this.autoSwipe();
+        }, 3000);
+
         this.todayDate = this.$time(new Date(), 2);
+        this.$initMqtt();
+
+        // 初始化订阅
+        this.initSubscriptions();
+        if (MqttService.dataConnected) {
+            this.initSubscriptions();
+        } else {
+            const userId = uni.getStorageSync("userId");
+            MqttService.connectData(userId)
+                .then((client) => {
+                    // 触发页面订阅事件
+                    const handler = () => {
+                        this.initSubscriptions();
+                        uni.$off("mqttData-ready", handler);
+                    };
+                    uni.$on("mqttData-ready", handler);
+                })
+                .catch((err) => {
+                    console.error("DATA MQTT 初始化失败", err);
+                });
+        }
     },
     onLoad(options) {
         const devItem = JSON.parse(options.devItem);
@@ -970,86 +1075,26 @@ export default {
         this.getFrequency(devId);
         this.getdevInfo(devId);
         this.getdevRoomInfo(devId);
-        this.autoPlayinterval = setTimeout(() => {
-            this.autoSwipe();
-        }, 3000);
         this.getCurrentDate();
         this.clientId = clientId;
-        console.log(this.clientId, "clientIDetail");
-        this.clientId = clientId;
-        let userId = uni.getStorageSync("userId");
-
-        const topic = `/dev/${this.clientId}/tracker_targets`;
-        // 使用 MqttService.subscribe 管理订阅
-        this.unsubscribeFn = MqttService.subscribe(
-            "DATA",
-            topic,
-            (message, msgTopic) => {
-                const dataMatch = msgTopic.match(
-                    /^\/dev\/(.+)\/tracker_targets$/
-                );
-                const cmdMatch = msgTopic.match(/^\/mps\/wx_(.+)\/notice$/);
-                if (dataMatch && dataMatch[1] === this.clientId) {
-                    this.handleMessage(msgTopic, message, this.clientId);
-                } else if (cmdMatch) {
-                    this.$refs.alarmModel.hanOtherMessage(msgTopic, message);
-                }
-            }
-        );
-        if (this.unsubscribeFn) {
-            console.log(`✅ 已成功订阅主题: ${topic}`);
-        }
-
-        const topicTwo = `/dev/${this.clientId}/falling_event_change`;
-        this.fallingEventChange = MqttService.subscribe(
-            "DATA",
-            topicTwo,
-            (message, msgTopic) => {
-                const dataMatch = msgTopic.match(
-                    /^\/dev\/(.+)\/falling_event_change$/
-                );
-                if (dataMatch && dataMatch[1] === this.clientId) {
-                    const dataMessage = JSON.parse(message.toString());
-                    // console.log(dataMessage.falling, "dataMessage");
-                    if (dataMessage.falling == 1) {
-                        this.falling = dataMessage.falling;
-                        this.lnbAction = "actionWarn";
-                    } else if (
-                        dataMessage.falling == 2 ||
-                        dataMessage.falling == 3
-                    ) {
-                        this.falling = dataMessage.falling;
-                        this.lnbAction = "actionSerious";
-                    } else {
-                        this.lnbAction = "action8";
-                    }
-                }
-            }
-        );
-        if (this.fallingEventChange) {
-            console.log(`✅ 已成功订阅主题: ${topicTwo}`);
-        }
     },
+
     onUnload() {
-        this.inactivityTimer = null;
-        this.autoPlayinterval = null;
-        if (this.unsubscribeFn) {
-            this.unsubscribeFn();
-            this.unsubscribeFn = null;
-        }
-        if (this.fallingEventChange) {
-            this.fallingEventChange();
-            this.fallingEventChange = null;
-        }
         // 关闭 DATA 连接
         // MqttService.disconnectData();
     },
     onHide() {
+        this.inactivityTimer = "";
+        clearTimeout(this.autoPlayinterval);
+        this.autoPlayinterval = "";
+        ["unsubscribeFn", "fallingEventChange"].forEach((key) => {
+            if (this[key]) {
+                this[key]();
+                this[key] = null;
+            }
+        });
         this.showModle = false;
     },
-    beforeDestroy() {
-        console.log("组件销毁时调用");
-    },
 };
 </script>
 <style lang="less" scoped>

+ 2 - 2
src/pagesA/discrepancy/discrepancy.vue

@@ -12,7 +12,7 @@
                         <text class="time-text">{{
                             formatTime(item.enterTime)
                         }}</text>
-                        <text class="event-text">目标离开</text>
+                        <text class="event-text">目标进入</text>
                     </view>
 
                     <!-- 中间:时间轴 -->
@@ -29,7 +29,7 @@
                         <text class="time-text">{{
                             formatTime(item.leaveTime)
                         }}</text>
-                        <text class="event-text">目标进入</text>
+                        <text class="event-text">目标离开</text>
                     </view>
                 </view>
             </view>

+ 48 - 52
src/utils/globalMqtt.js

@@ -13,7 +13,7 @@ class MqttService {
         return MqttService.instance;
     }
 
-    // 连接 CMD
+    // ---------------- 连接 CMD ----------------
     connectCmd(userId) {
         if (this.cmdConnected && this.cmdClient) {
             console.log("CMD MQTT 已连接,复用现有实例");
@@ -31,45 +31,24 @@ class MqttService {
 
                 // 默认订阅 CMD 主题
                 const topic = `/mps/wx_${userId}/notice`;
-                this.subscribe('CMD', topic, (msg) => {
-                    console.log('CMD 消息:', msg);
-                });
-
-                // 打印订阅成功
-                console.log(`✅ CMD MQTT 主题已订阅成功: ${topic}`);
+                this.subscribe('CMD', topic, (msg) => console.log('CMD 消息:', msg));
 
                 uni.$emit('mqtt-ready', this.cmdClient);
                 return client;
             });
     }
 
-    // 连接 DATA
+    // ---------------- 连接 DATA ----------------
     connectData(userId) {
-        uni.showToast({
-            title: "平台连接中...",
-            icon: "loading",
-            duration: 1000, //持续的时间
-        });
+
         if (this.dataConnected && this.dataClient) {
             console.log("DATA MQTT 已连接,复用现有实例");
             return Promise.resolve(this.dataClient);
         }
+        uni.showToast({ title: "平台连接中...", icon: "loading", duration: 1000 });
 
         const brokerName = 'DATA';
-
-        let url = ""
-        // if (__wxConfig.envVersion == 'develop') {
-        //     url = 'wxs://data.radar-power.asia:8084/mqtt/'
-        // }
-
-        // if (__wxConfig.envVersion == 'trial') {
-        //     url = 'wxs://data.radar-power.cn/mqtt/';
-        // }
-        // if (__wxConfig.envVersion == 'release') {
-        //     url = 'wxs://data.radar-power.cn/mqtt/';
-        // }
-        url = 'wxs://data.radar-power.cn/mqtt/';
-
+        const url = 'wxs://data.radar-power.cn/mqtt/';
         const clientId = `xcx_mqtt_data1_${userId}_${Date.now()}`;
 
         return this.connectToBroker(brokerName, { url, clientId, username: 'lnradar', password: 'lnradar' })
@@ -81,7 +60,7 @@ class MqttService {
             });
     }
 
-    // 内部通用连接方法(多连接管理)
+    // ---------------- 内部通用连接方法 ----------------
     connectToBroker(brokerName, config) {
         return new Promise((resolve, reject) => {
             if (this.connections.has(brokerName)) {
@@ -96,7 +75,7 @@ class MqttService {
                 clientId: config.clientId,
                 username: config.username,
                 password: config.password,
-                reconnectPeriod: config.reconnectPeriod || 5000,
+                reconnectPeriod: config.reconnectPeriod || 500,
                 wsOptions: {
                     WebSocket: url => wx.connectSocket({
                         url,
@@ -109,16 +88,27 @@ class MqttService {
             const connection = {
                 client,
                 connected: false,
-                subscriptions: new Map(),
+                subscriptions: new Map(), // topic => [callbacks]
             };
             this.connections.set(brokerName, connection);
 
+            // ---------------- 连接成功 ----------------
             client.on('connect', () => {
                 console.log(`${brokerName} MQTT 连接成功`);
                 connection.connected = true;
+
+                // 重连时恢复所有订阅
+                connection.subscriptions.forEach((callbacks, topic) => {
+                    connection.client.subscribe(topic, (err) => {
+                        if (err) console.error(`重连订阅失败: ${topic}`, err);
+                        else console.log(`✅ 重连订阅成功: ${topic}`);
+                    });
+                });
+
                 resolve(client);
             });
 
+            // ---------------- 连接错误 ----------------
             client.on('error', (err) => {
                 uni.showModal({
                     title: '提示',
@@ -127,30 +117,25 @@ class MqttService {
                     success: (res) => {
                         if (res.confirm) {
                             uni.clearStorageSync();
-                            uni.reLaunch({
-                                url: "/pagesA/loginNew/loginNew"
-                            })
-                        }
-                        if (res.cancel) {
-
+                            uni.reLaunch({ url: "/pagesA/loginNew/loginNew" });
                         }
                     }
-                })
+                });
                 console.error(`${brokerName} MQTT 连接错误:`, err);
                 connection.connected = false;
                 reject(err);
             });
 
+            // ---------------- 连接断开 ----------------
             client.on('close', () => {
-                uni.showToast({
-                    title: `平台已经断开`,
-                    icon: "none",
-                    duration: 1500,
-                });
+                uni.showToast({ title: `平台已断开`, icon: "none", duration: 1500 });
                 console.log(`${brokerName} MQTT 断开`);
                 connection.connected = false;
+                if (brokerName === 'DATA') this.dataConnected = false;
+                if (brokerName === 'CMD') this.cmdConnected = false;
             });
 
+            // ---------------- 接收消息 ----------------
             client.on('message', (topic, message) => {
                 const callbacks = connection.subscriptions.get(topic) || [];
                 callbacks.forEach(cb => {
@@ -159,21 +144,21 @@ class MqttService {
                 });
             });
 
-            // 连接超时
+            // ---------------- 连接超时 ----------------
             setTimeout(() => {
                 if (!connection.connected) reject(new Error(`${brokerName} 连接超时`));
             }, 10000);
         });
     }
 
-    // 发布消息
+    // ---------------- 发布消息 ----------------
     publish(clientType, topic, message) {
         const client = clientType === 'CMD' ? this.cmdClient : this.dataClient;
         if (client) client.publish(topic, message);
         else console.warn(`${clientType} MQTT 未连接,无法 publish`);
     }
 
-    // 订阅
+    // ---------------- 订阅 ----------------
     subscribe(clientType, topic, callback, onSubscribeDone) {
         const connection = clientType === 'CMD' ? this.connections.get('CMD') : this.connections.get('DATA');
         if (!connection || !connection.connected) {
@@ -189,10 +174,7 @@ class MqttService {
 
         // 执行订阅
         connection.client.subscribe(topic, (err) => {
-            if (err) {
-                console.error(`Subscribe error on ${clientType}:`, err);
-            }
-            // 订阅完成回调
+            if (err) console.error(`Subscribe error on ${clientType}:`, err);
             if (onSubscribeDone) onSubscribeDone(err);
         });
 
@@ -200,7 +182,7 @@ class MqttService {
         return () => this.unsubscribe(clientType, topic, callback);
     }
 
-    // 取消订阅
+    // ---------------- 取消订阅 ----------------
     unsubscribe(clientType, topic, callback) {
         const connection = clientType === 'CMD' ? this.connections.get('CMD') : this.connections.get('DATA');
         if (!connection) return;
@@ -216,14 +198,28 @@ class MqttService {
         }
     }
 
-    // 断开所有连接
+    // ---------------- 手动恢复订阅 ----------------
+    resubscribeAll(brokerName) {
+        const connection = this.connections.get(brokerName); ``
+        if (!connection || !connection.connected) return;
+
+        connection.subscriptions.forEach((callbacks, topic) => {
+            connection.client.subscribe(topic, (err) => {
+                if (err) console.error(`手动恢复订阅失败: ${topic}`, err);
+                else console.log(`✅ 手动恢复订阅成功: ${topic}`);
+            });
+        });
+    }
+
+    // ---------------- 断开连接 ----------------
     disconnectAll() {
         if (this.cmdClient) { this.cmdClient.end(true); this.cmdClient = null; this.cmdConnected = false; }
         if (this.dataClient) { this.dataClient.end(true); this.dataClient = null; this.dataConnected = false; }
-        this.connections.forEach(conn => conn.client.end());
+        this.connections.forEach(conn => conn.client.end(true));
         this.connections.clear();
         console.log('所有 MQTT 连接已关闭');
     }
+
     disconnectData() {
         if (this.dataClient) {
             this.dataClient.end(true);