mqtt
parent
01bece15cc
commit
e5c243b74e
64
App.vue
64
App.vue
|
|
@ -1,10 +1,51 @@
|
||||||
<script>
|
<script>
|
||||||
import config from './config'
|
import config from './config'
|
||||||
import { getToken } from '@/utils/auth'
|
import { getToken } from '@/utils/auth'
|
||||||
|
import {
|
||||||
|
initMQTT,
|
||||||
|
disconnectMQTT,
|
||||||
|
getMQTTStatus,
|
||||||
|
manualReconnect
|
||||||
|
} from '@/utils/mqtt';
|
||||||
export default {
|
export default {
|
||||||
onLaunch: function() {
|
globalData: {
|
||||||
|
mqttClient: null, // 全局MQTT客户端实例
|
||||||
|
mqttConfig: { // MQTT全局配置(可根据环境切换)
|
||||||
|
host: '1.94.254.176',
|
||||||
|
port: 9001, // 小程序用wss端口(如EMQ X的8084)
|
||||||
|
isSSL: false, // true=wss,false=ws
|
||||||
|
username: 'admin',
|
||||||
|
password: 'Admin#12345678',
|
||||||
|
keepalive: 60,
|
||||||
|
clean: true
|
||||||
|
},
|
||||||
|
globalSubscribeTopic: [ // 全局基础订阅列表(所有页面都需要的主题)
|
||||||
|
{topic: 'dtu/#', qos: 0},
|
||||||
|
// {topic: 'system/status', qos: 1}
|
||||||
|
],
|
||||||
|
mqttStatus: 'disconnected' // 全局连接状态:connected/disconnected/reconnecting
|
||||||
|
},
|
||||||
|
onLaunch() {
|
||||||
this.initApp()
|
this.initApp()
|
||||||
|
console.log('应用启动,初始化MQTT连接');
|
||||||
|
this.initGlobalMQTT();
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
console.log('应用切前台,检查MQTT连接');
|
||||||
|
const { isConnected } = getMQTTStatus();
|
||||||
|
if (!isConnected && this.globalData.mqttStatus !== 'disconnected') {
|
||||||
|
// 非主动断开的情况下,切前台重连
|
||||||
|
manualReconnect(this.globalData.mqttConfig);
|
||||||
|
this.globalData.mqttStatus = 'reconnecting';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onHide() {
|
||||||
|
console.log('应用切后台,断开MQTT连接');
|
||||||
|
// 切后台主动断开(避免小程序后台占用资源)
|
||||||
|
disconnectMQTT();
|
||||||
|
this.globalData.mqttStatus = 'disconnected';
|
||||||
|
this.globalData.mqttClient = null;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 初始化应用
|
// 初始化应用
|
||||||
|
|
@ -23,11 +64,28 @@
|
||||||
if (!getToken()) {
|
if (!getToken()) {
|
||||||
this.$tab.reLaunch('/pages/login')
|
this.$tab.reLaunch('/pages/login')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// 初始化全局MQTT
|
||||||
|
async initGlobalMQTT() {
|
||||||
|
try {
|
||||||
|
// 调用工具类初始化连接(传入全局配置+全局订阅列表)
|
||||||
|
const client = await initMQTT(
|
||||||
|
this.globalData.mqttConfig,
|
||||||
|
this.globalData.globalSubscribeTopic
|
||||||
|
);
|
||||||
|
// 挂载客户端到全局
|
||||||
|
this.globalData.mqttClient = client;
|
||||||
|
this.globalData.mqttStatus = 'connected';
|
||||||
|
console.log('全局MQTT初始化成功');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('全局MQTT初始化失败:', err);
|
||||||
|
this.globalData.mqttStatus = 'disconnected';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '@/static/scss/index.scss'
|
@import '@/static/scss/index.scss';
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// 应用全局配置
|
// 应用全局配置
|
||||||
module.exports = {
|
module.exports = {
|
||||||
baseUrl: "production" ? "/api" : "http://localhost:8088",
|
baseUrl: process.env.NODE_ENV === "production" ? "/api" : "http://localhost:8088",
|
||||||
// 应用信息
|
// 应用信息
|
||||||
appInfo: {
|
appInfo: {
|
||||||
// 应用名称
|
// 应用名称
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,9 @@
|
||||||
</uni-section>
|
</uni-section>
|
||||||
|
|
||||||
<uni-section title="设备控制" titleFontSize="16px" type="line" v-if="value!== 1">
|
<uni-section title="设备控制" titleFontSize="16px" type="line" v-if="value!== 1">
|
||||||
|
<template v-slot:right >
|
||||||
|
{{ control }}
|
||||||
|
</template>
|
||||||
<!-- 卷膜/卷被卡片容器(2列栅格布局) -->
|
<!-- 卷膜/卷被卡片容器(2列栅格布局) -->
|
||||||
<view class="card-grid">
|
<view class="card-grid">
|
||||||
<!-- 卷被开卡片 -->
|
<!-- 卷被开卡片 -->
|
||||||
|
|
@ -169,12 +171,12 @@ export default {
|
||||||
mqttConfig: {
|
mqttConfig: {
|
||||||
host: '1.94.254.176',
|
host: '1.94.254.176',
|
||||||
port: 9001,
|
port: 9001,
|
||||||
clientId: 'uniapp_mqtt_' + Math.random().toString(16).substr(2, 8),
|
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
password: 'Admin#12345678',
|
password: 'Admin#12345678',
|
||||||
subscribeTopic:'/up',
|
subscribeTopic:'/up',
|
||||||
},
|
},
|
||||||
value: 1,
|
value: 1,
|
||||||
|
control: '正在加载中...',
|
||||||
range: [{
|
range: [{
|
||||||
"value": '864865085016294',
|
"value": '864865085016294',
|
||||||
"text": "十方北棚"
|
"text": "十方北棚"
|
||||||
|
|
@ -273,7 +275,7 @@ export default {
|
||||||
}
|
}
|
||||||
findDtuDataByInfo(queryParams).then(response => {
|
findDtuDataByInfo(queryParams).then(response => {
|
||||||
this.liveData = {
|
this.liveData = {
|
||||||
temp1: response.data.temp1 || '已离线...',
|
temp1: response.data.temp1 || '已离线..',
|
||||||
temp2: response.data.temp2 || '已离线..',
|
temp2: response.data.temp2 || '已离线..',
|
||||||
temp3: response.data.temp3 || '已离线..',
|
temp3: response.data.temp3 || '已离线..',
|
||||||
temp4: response.data.temp4 || '已离线..',
|
temp4: response.data.temp4 || '已离线..',
|
||||||
|
|
@ -317,6 +319,7 @@ export default {
|
||||||
jm3k: 0,
|
jm3k: 0,
|
||||||
jm3g: 0
|
jm3g: 0
|
||||||
};
|
};
|
||||||
|
this.control = '正在加载中...';
|
||||||
this.message = {};
|
this.message = {};
|
||||||
this.temp = '';
|
this.temp = '';
|
||||||
this.liveData = {
|
this.liveData = {
|
||||||
|
|
@ -393,7 +396,7 @@ export default {
|
||||||
},
|
},
|
||||||
connectMqtt() {
|
connectMqtt() {
|
||||||
const options = {
|
const options = {
|
||||||
clientId: this.mqttConfig.clientId,
|
clientId: 'uniapp_mqtt_' + Math.random().toString(16).substr(2, 8),
|
||||||
username: this.mqttConfig.username,
|
username: this.mqttConfig.username,
|
||||||
password: this.mqttConfig.password,
|
password: this.mqttConfig.password,
|
||||||
clean: true,
|
clean: true,
|
||||||
|
|
@ -409,6 +412,7 @@ export default {
|
||||||
this.connected = true
|
this.connected = true
|
||||||
this.client.subscribe('dtu/+/up', {qos: 0})
|
this.client.subscribe('dtu/+/up', {qos: 0})
|
||||||
this.addMessage('已连接到MQTT服务器')
|
this.addMessage('已连接到MQTT服务器')
|
||||||
|
console.info(this.client)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.client.on("message", this.ackMessage);
|
this.client.on("message", this.ackMessage);
|
||||||
|
|
@ -425,6 +429,7 @@ export default {
|
||||||
this.client.on('close', () => {
|
this.client.on('close', () => {
|
||||||
this.addMessage('连接已关闭')
|
this.addMessage('连接已关闭')
|
||||||
this.connected = false
|
this.connected = false
|
||||||
|
console.info(this.client)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -527,6 +532,7 @@ export default {
|
||||||
// 判断值:0→暂停,1→运行,其他值可补充默认值(可选)
|
// 判断值:0→暂停,1→运行,其他值可补充默认值(可选)
|
||||||
this.show[key] = value === 0 ? '暂停' : '运行';
|
this.show[key] = value === 0 ? '暂停' : '运行';
|
||||||
});
|
});
|
||||||
|
this.control = '最后更新时间:' + this.getCurrentTime();
|
||||||
// console.info("imei: "+this.publishTopic+"copy: ",this.status)
|
// console.info("imei: "+this.publishTopic+"copy: ",this.status)
|
||||||
}
|
}
|
||||||
const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key));
|
const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key));
|
||||||
|
|
@ -546,7 +552,7 @@ export default {
|
||||||
humi4: div10(msgData["104"]) || "已离线...",
|
humi4: div10(msgData["104"]) || "已离线...",
|
||||||
}
|
}
|
||||||
// 调用函数获取并输出格式化后的当前时间
|
// 调用函数获取并输出格式化后的当前时间
|
||||||
this.temp = "最新更新时间:" + this.getCurrentTime();
|
this.temp = "最后更新时间:" + this.getCurrentTime();
|
||||||
this.fontStyle = 'font-size:16px;'
|
this.fontStyle = 'font-size:16px;'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
424
utils/mqtt.js
424
utils/mqtt.js
|
|
@ -1,214 +1,260 @@
|
||||||
|
/**
|
||||||
|
* MQTT工具类 - 全局唯一连接、自动重连、支持自定义配置和订阅列表
|
||||||
|
* 适配uniapp小程序/APP/H5(小程序仅支持ws/wss)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 引入mqtt库(需先安装:npm install mqtt --save,或下载mqtt.min.js到本地引入)
|
||||||
import mqtt from 'mqtt'
|
import mqtt from 'mqtt'
|
||||||
// 全局MQTT实例(单例,避免多页面重复创建)
|
|
||||||
let mqttInstance = null;
|
// 全局变量
|
||||||
|
let client = null; // MQTT客户端实例
|
||||||
|
let subscribeList = []; // 订阅主题列表(重连后自动恢复订阅)
|
||||||
|
let reconnectTimer = null; // 重连定时器
|
||||||
|
let maxReconnectTimes = 10; // 最大重连次数
|
||||||
|
let currentReconnectTimes = 0; // 当前重连次数
|
||||||
|
let isManualDisconnect = false; // 是否主动断开(用于区分主动/被动断开,避免主动断开后重连)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化MQTT客户端(单例模式)
|
* 初始化MQTT连接
|
||||||
* @param {Object} mqttConfig 配置项
|
* @param {Object} config - MQTT连接配置
|
||||||
* @param {Array|String} subTopic 初始订阅主题
|
* @param {Array} subs - 初始订阅列表,格式:[{ topic: 'topic1', qos: 0 }, { topic: 'topic2', qos: 1 }]
|
||||||
* @returns {Object} MQTT实例
|
* @returns {Promise} 连接成功/失败的Promise
|
||||||
*/
|
*/
|
||||||
function createMqttClient(mqttConfig, subTopic) {
|
export function initMQTT(config, subs = []) {
|
||||||
// 已存在实例则直接返回
|
|
||||||
if (mqttInstance && mqttInstance.connected) {
|
|
||||||
// 若传入新主题,补充订阅
|
|
||||||
if (subTopic) {
|
|
||||||
mqttInstance.subscribe(subTopic);
|
|
||||||
}
|
|
||||||
return mqttInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标准化配置(默认值 + 防错)
|
|
||||||
const config = {
|
|
||||||
host: mqttConfig.host || '1.94.254.176',
|
|
||||||
port: mqttConfig.port || 9001,
|
|
||||||
clientId: mqttConfig.clientId || `uniapp_mqtt_${Date.now()}${Math.random().toString(16).substr(2, 4)}`, // 时间戳+随机数,减少重复
|
|
||||||
username: mqttConfig.username || 'admin',
|
|
||||||
password: mqttConfig.password || 'Admin#12345678',
|
|
||||||
clean: false, // 关键:改为false,保持会话缓存消息
|
|
||||||
reconnectPeriod: mqttConfig.reconnectPeriod || 3000,
|
|
||||||
connectTimeout: mqttConfig.connectTimeout || 5000,
|
|
||||||
qos: mqttConfig.qos || 1 // 默认QoS1,确保消息不丢
|
|
||||||
};
|
|
||||||
|
|
||||||
// 连接选项
|
|
||||||
const options = {
|
|
||||||
clientId: config.clientId,
|
|
||||||
username: config.username,
|
|
||||||
password: config.password,
|
|
||||||
clean: config.clean,
|
|
||||||
connectTimeout: config.connectTimeout,
|
|
||||||
reconnectPeriod: config.reconnectPeriod,
|
|
||||||
keepalive: 60 // 新增心跳,避免连接被断开
|
|
||||||
};
|
|
||||||
|
|
||||||
// 拼接WS地址(兼容配置错误)
|
|
||||||
const url = `ws://${config.host}:${config.port}/mqtt`;
|
|
||||||
|
|
||||||
// 创建客户端
|
|
||||||
const client = mqtt.connect(url, options);
|
|
||||||
|
|
||||||
// 实例状态管理
|
|
||||||
const instance = {
|
|
||||||
client: client,
|
|
||||||
connected: false,
|
|
||||||
subscribedTopics: new Set(), // 记录已订阅主题
|
|
||||||
config: config,
|
|
||||||
messageCallback: null, // 消息接收回调
|
|
||||||
statusCallback: null, // 状态变更回调
|
|
||||||
|
|
||||||
// 订阅主题(支持单个/多个,带重试)
|
|
||||||
subscribe: function (topics, qos = config.qos) {
|
|
||||||
if (!this.connected) {
|
|
||||||
console.warn('MQTT未连接,延迟订阅:', topics);
|
|
||||||
// 连接成功后自动订阅
|
|
||||||
client.once('connect', () => this.subscribe(topics, qos));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const topicList = Array.isArray(topics) ? topics : [topics];
|
|
||||||
client.subscribe(topicList, { qos }, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('订阅失败:', err, '主题:', topicList);
|
|
||||||
// 订阅失败重试(仅一次)
|
|
||||||
setTimeout(() => this.subscribe(topics, qos), 1000);
|
|
||||||
} else {
|
|
||||||
topicList.forEach(t => this.subscribedTopics.add(t));
|
|
||||||
console.log(`订阅成功${topicList.length > 1 ? '(批量)' : '(单个)'}:`, topicList);
|
|
||||||
this.statusCallback && this.statusCallback('subscribe_success', topicList);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 取消订阅
|
|
||||||
unsubscribe: function (topics) {
|
|
||||||
if (!this.connected) return;
|
|
||||||
const topicList = Array.isArray(topics) ? topics : [topics];
|
|
||||||
client.unsubscribe(topicList, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('取消订阅失败:', err);
|
|
||||||
} else {
|
|
||||||
topicList.forEach(t => this.subscribedTopics.delete(t));
|
|
||||||
console.log('取消订阅成功:', topicList);
|
|
||||||
this.statusCallback && this.statusCallback('unsubscribe_success', topicList);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 发布消息(带参数,失败重试)
|
|
||||||
publish: function (topic, message, qos = config.qos, retain = false) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!this.connected) {
|
// 1. 校验配置(必填项)
|
||||||
reject(new Error('MQTT未连接,无法发布消息'));
|
if (!config.host || !config.port) {
|
||||||
// 自动重连后发布
|
reject(new Error('MQTT配置错误:host和port为必填项'));
|
||||||
this.reconnectAndPublish(topic, message, qos, retain);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标准化消息格式(对象转JSON)
|
// 2. 保存订阅列表(用于重连恢复)
|
||||||
const payload = typeof message === 'object' ? JSON.stringify(message) : String(message);
|
subscribeList = subs;
|
||||||
|
|
||||||
client.publish(topic, payload, { qos, retain }, (err) => {
|
// 3. 避免重复连接
|
||||||
if (err) {
|
if (client && client.connected) {
|
||||||
console.error('发布失败:', err, '主题:', topic);
|
resolve(client);
|
||||||
reject(err);
|
return;
|
||||||
// 发布失败重试
|
|
||||||
setTimeout(() => this.publish(topic, message, qos, retain), 1000);
|
|
||||||
} else {
|
|
||||||
console.log('发布成功:', topic, '内容:', payload);
|
|
||||||
resolve({ topic, payload });
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 重连后补发消息
|
// 4. 标记为非主动断开(允许重连)
|
||||||
reconnectAndPublish: function (topic, message, qos, retain) {
|
isManualDisconnect = false;
|
||||||
client.once('connect', () => {
|
|
||||||
this.publish(topic, message, qos, retain);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 断开连接
|
// 5. 构建连接地址(小程序仅支持ws/wss,优先用wss更安全)
|
||||||
disconnect: function () {
|
const protocol = config.protocol || (config.isSSL ? 'wss' : 'ws');
|
||||||
if (this.connected && this.client) {
|
const connectUrl = `${protocol}://${config.host}:${config.port}/mqtt`;
|
||||||
this.client.end(false, () => { // false:等待剩余消息发送完成
|
|
||||||
this.connected = false;
|
// 6. 构建MQTT连接选项
|
||||||
this.subscribedTopics.clear();
|
const mqttOptions = {
|
||||||
console.log('MQTT连接已断开(保留会话)');
|
clientId: config.clientId || `uni_mqtt_${Math.random().toString(16).substr(2, 8)}`,
|
||||||
this.statusCallback && this.statusCallback('disconnect');
|
username: config.username || '',
|
||||||
});
|
password: config.password || '',
|
||||||
}
|
keepalive: config.keepalive || 60, // 心跳间隔(秒)
|
||||||
}
|
clean: config.clean !== undefined ? config.clean : true, // 是否清除会话
|
||||||
|
reconnectPeriod: 0, // 关闭内置重连(自定义重连逻辑)
|
||||||
|
connectTimeout: config.connectTimeout || 10000, // 连接超时(毫秒)
|
||||||
|
...config.extraOptions // 额外扩展配置
|
||||||
};
|
};
|
||||||
|
|
||||||
// 绑定客户端事件
|
try {
|
||||||
|
// 7. 创建客户端并连接
|
||||||
|
client = mqtt.connect(connectUrl, mqttOptions);
|
||||||
|
|
||||||
|
// 8. 监听连接成功
|
||||||
client.on('connect', () => {
|
client.on('connect', () => {
|
||||||
instance.connected = true;
|
console.log('MQTT连接成功:', connectUrl);
|
||||||
console.log('MQTT连接成功,ClientId:', config.clientId);
|
currentReconnectTimes = 0; // 重置重连次数
|
||||||
instance.statusCallback && instance.statusCallback('connect_success');
|
// 9. 订阅初始主题
|
||||||
// 初始订阅主题
|
subscribeTopics(subscribeList);
|
||||||
if (subTopic) {
|
resolve(client);
|
||||||
instance.subscribe(subTopic);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('reconnect', (error) => {
|
|
||||||
instance.connected = false;
|
|
||||||
console.log('MQTT正在重连...', error);
|
|
||||||
instance.statusCallback && instance.statusCallback('reconnect', error);
|
|
||||||
})
|
|
||||||
.on('error', (error) => {
|
|
||||||
instance.connected = false;
|
|
||||||
console.error('MQTT连接错误:', error);
|
|
||||||
instance.statusCallback && instance.statusCallback('error', error);
|
|
||||||
})
|
|
||||||
.on('close', () => {
|
|
||||||
instance.connected = false;
|
|
||||||
console.log('MQTT连接关闭');
|
|
||||||
instance.statusCallback && instance.statusCallback('close');
|
|
||||||
})
|
|
||||||
.on('offline', () => {
|
|
||||||
instance.connected = false;
|
|
||||||
console.log('MQTT客户端下线');
|
|
||||||
instance.statusCallback && instance.statusCallback('offline');
|
|
||||||
})
|
|
||||||
.on('message', (topic, payload) => {
|
|
||||||
const msg = payload.toString();
|
|
||||||
console.log('收到MQTT消息:', topic, msg);
|
|
||||||
// 消息回调,交给业务层处理
|
|
||||||
instance.messageCallback && instance.messageCallback(topic, msg);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 赋值单例
|
// 10. 监听连接错误
|
||||||
mqttInstance = instance;
|
client.on('error', (err) => {
|
||||||
return instance;
|
console.error('MQTT连接错误:', err);
|
||||||
|
client.end();
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 11. 监听连接断开(被动断开则触发重连)
|
||||||
|
client.on('close', () => {
|
||||||
|
console.log('MQTT连接已断开');
|
||||||
|
client = null;
|
||||||
|
// 非主动断开 + 未达最大重连次数 → 触发重连
|
||||||
|
if (!isManualDisconnect && currentReconnectTimes < maxReconnectTimes) {
|
||||||
|
reconnectMQTT(config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 12. 监听消息(全局消息转发,页面层通过uni.$on监听)
|
||||||
|
client.on('message', (topic, message) => {
|
||||||
|
const msg = {
|
||||||
|
topic,
|
||||||
|
payload: message.toString(), // 转字符串(原始是Buffer)
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
// 全局广播消息,页面层按需监听
|
||||||
|
uni.$emit('mqtt_message', msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('MQTT初始化失败:', err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对外暴露的核心方法
|
* 订阅主题(支持单个/多个)
|
||||||
|
* @param {Array} topics - 订阅列表,格式:[{ topic: 'topic1', qos: 0 }, { topic: 'topic2', qos: 1 }]
|
||||||
*/
|
*/
|
||||||
export const mqttTool = {
|
export function subscribeTopics(topics = []) {
|
||||||
// 初始化连接
|
if (!client || !client.connected) {
|
||||||
connect: function (mqttConfig, subTopic) {
|
console.warn('MQTT未连接,无法订阅主题');
|
||||||
return createMqttClient(mqttConfig, subTopic);
|
return;
|
||||||
},
|
|
||||||
|
|
||||||
// 获取全局实例
|
|
||||||
getInstance: function () {
|
|
||||||
return mqttInstance;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 页面切换时的订阅管理(核心解决多页面订阅问题)
|
|
||||||
switchPageTopic: function (newTopics, oldTopics) {
|
|
||||||
const instance = mqttInstance;
|
|
||||||
if (!instance) return;
|
|
||||||
|
|
||||||
// 先订阅新主题,再取消旧主题(避免漏消息)
|
|
||||||
if (newTopics) {
|
|
||||||
instance.subscribe(newTopics);
|
|
||||||
}
|
}
|
||||||
if (oldTopics) {
|
|
||||||
instance.unsubscribe(oldTopics);
|
// 过滤空主题
|
||||||
|
const validTopics = topics.filter(item => item && item.topic);
|
||||||
|
if (validTopics.length === 0) return;
|
||||||
|
|
||||||
|
// 转换为mqtt库要求的格式:{ topic1: { qos: 0 }, topic2: { qos: 1 } }
|
||||||
|
const subscribeMap = {};
|
||||||
|
validTopics.forEach(item => {
|
||||||
|
subscribeMap[item.topic] = { qos: item.qos || 0 };
|
||||||
|
});
|
||||||
|
|
||||||
|
client.subscribe(subscribeMap, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('MQTT订阅失败:', err);
|
||||||
|
} else {
|
||||||
|
console.log('MQTT订阅成功:', validTopics.map(item => item.topic));
|
||||||
|
// 更新订阅列表(用于重连恢复)
|
||||||
|
subscribeList = [...new Set([...subscribeList, ...validTopics])]; // 去重
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消订阅主题
|
||||||
|
* @param {Array} topics - 取消订阅的主题列表,格式:['topic1', 'topic2']
|
||||||
|
*/
|
||||||
|
export function unsubscribeTopics(topics = []) {
|
||||||
|
if (!client || !client.connected) {
|
||||||
|
console.warn('MQTT未连接,无法取消订阅');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.unsubscribe(topics, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('MQTT取消订阅失败:', err);
|
||||||
|
} else {
|
||||||
|
console.log('MQTT取消订阅成功:', topics);
|
||||||
|
// 更新订阅列表
|
||||||
|
subscribeList = subscribeList.filter(item => !topics.includes(item.topic));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布消息
|
||||||
|
* @param {String} topic - 发布的主题
|
||||||
|
* @param {String/Buffer} payload - 发布的消息内容
|
||||||
|
* @param {Object} options - 发布选项,如{ qos: 0, retain: false }
|
||||||
|
* @returns {Promise} 发布成功/失败的Promise
|
||||||
|
*/
|
||||||
|
export function publishMQTT(topic, payload, options = { qos: 0, retain: false }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!client || !client.connected) {
|
||||||
|
reject(new Error('MQTT未连接,无法发布消息'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!topic) {
|
||||||
|
reject(new Error('发布失败:主题不能为空'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.publish(topic, payload, options, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(`MQTT发布${topic}失败:`, err);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
console.log(`MQTT发布${topic}成功:`, payload);
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断开MQTT连接(主动断开,不会触发重连)
|
||||||
|
*/
|
||||||
|
export function disconnectMQTT() {
|
||||||
|
// 标记为主动断开
|
||||||
|
isManualDisconnect = true;
|
||||||
|
// 清除重连定时器
|
||||||
|
if (reconnectTimer) {
|
||||||
|
clearTimeout(reconnectTimer);
|
||||||
|
reconnectTimer = null;
|
||||||
|
}
|
||||||
|
// 断开连接
|
||||||
|
if (client && client.connected) {
|
||||||
|
client.end(false, () => { // false:不发送遗嘱消息
|
||||||
|
console.log('MQTT主动断开连接');
|
||||||
|
client = null;
|
||||||
|
subscribeList = [];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重连MQTT(内部调用,也可外部手动触发)
|
||||||
|
* @param {Object} config - 连接配置(同initMQTT的config)
|
||||||
|
*/
|
||||||
|
export function reconnectMQTT(config) {
|
||||||
|
// 清除已有定时器,避免重复触发
|
||||||
|
if (reconnectTimer) {
|
||||||
|
clearTimeout(reconnectTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重连间隔:1秒/次(可自定义)
|
||||||
|
reconnectTimer = setTimeout(async () => {
|
||||||
|
currentReconnectTimes++;
|
||||||
|
console.log(`MQTT重连中,第${currentReconnectTimes}/${maxReconnectTimes}次`);
|
||||||
|
try {
|
||||||
|
await initMQTT(config, subscribeList);
|
||||||
|
} catch (err) {
|
||||||
|
// 重连失败,继续尝试(直到达到最大次数)
|
||||||
|
if (currentReconnectTimes < maxReconnectTimes) {
|
||||||
|
reconnectMQTT(config);
|
||||||
|
} else {
|
||||||
|
console.error('MQTT重连次数已达上限,停止重连');
|
||||||
|
uni.$emit('mqtt_reconnect_fail'); // 全局通知重连失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取MQTT客户端状态
|
||||||
|
* @returns {Object} 状态信息
|
||||||
|
*/
|
||||||
|
export function getMQTTStatus() {
|
||||||
|
return {
|
||||||
|
isConnected: !!client && client.connected, // 是否连接
|
||||||
|
client: client, // 客户端实例
|
||||||
|
subscribeList: [...subscribeList], // 已订阅列表(浅拷贝,避免外部修改)
|
||||||
|
currentReconnectTimes, // 当前重连次数
|
||||||
|
maxReconnectTimes // 最大重连次数
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动触发重连(外部调用,比如页面主动刷新连接)
|
||||||
|
* @param {Object} config - 连接配置
|
||||||
|
*/
|
||||||
|
export function manualReconnect(config) {
|
||||||
|
currentReconnectTimes = 0; // 重置重连次数
|
||||||
|
reconnectMQTT(config);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue