Compare commits
No commits in common. "e5c243b74e6ce8d74381182278c547f6d66421d9" and "8cc77d2852e2ccaa1bec169aaced4ab563345537" have entirely different histories.
e5c243b74e
...
8cc77d2852
66
App.vue
66
App.vue
|
|
@ -1,51 +1,10 @@
|
||||||
<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 {
|
|
||||||
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()
|
|
||||||
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() {
|
export default {
|
||||||
console.log('应用切后台,断开MQTT连接');
|
onLaunch: function() {
|
||||||
// 切后台主动断开(避免小程序后台占用资源)
|
this.initApp()
|
||||||
disconnectMQTT();
|
|
||||||
this.globalData.mqttStatus = 'disconnected';
|
|
||||||
this.globalData.mqttClient = null;
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 初始化应用
|
// 初始化应用
|
||||||
|
|
@ -64,28 +23,11 @@
|
||||||
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,8 @@
|
||||||
// 应用全局配置
|
// 应用全局配置
|
||||||
module.exports = {
|
module.exports = {
|
||||||
baseUrl: process.env.NODE_ENV === "production" ? "/api" : "http://localhost:8088",
|
// baseUrl: 'https://vue.ruoyi.vip/prod-api',
|
||||||
|
baseUrl: 'http://172.14.24.109:8088',
|
||||||
|
// baseUrl: 'http://1.94.254.176:8088',
|
||||||
// 应用信息
|
// 应用信息
|
||||||
appInfo: {
|
appInfo: {
|
||||||
// 应用名称
|
// 应用名称
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,7 @@
|
||||||
</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">
|
||||||
<!-- 卷被开卡片 -->
|
<!-- 卷被开卡片 -->
|
||||||
|
|
@ -171,12 +169,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": "十方北棚"
|
||||||
|
|
@ -275,7 +273,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 || '已离线..',
|
||||||
|
|
@ -319,7 +317,6 @@ export default {
|
||||||
jm3k: 0,
|
jm3k: 0,
|
||||||
jm3g: 0
|
jm3g: 0
|
||||||
};
|
};
|
||||||
this.control = '正在加载中...';
|
|
||||||
this.message = {};
|
this.message = {};
|
||||||
this.temp = '';
|
this.temp = '';
|
||||||
this.liveData = {
|
this.liveData = {
|
||||||
|
|
@ -396,7 +393,7 @@ export default {
|
||||||
},
|
},
|
||||||
connectMqtt() {
|
connectMqtt() {
|
||||||
const options = {
|
const options = {
|
||||||
clientId: 'uniapp_mqtt_' + Math.random().toString(16).substr(2, 8),
|
clientId: this.mqttConfig.clientId,
|
||||||
username: this.mqttConfig.username,
|
username: this.mqttConfig.username,
|
||||||
password: this.mqttConfig.password,
|
password: this.mqttConfig.password,
|
||||||
clean: true,
|
clean: true,
|
||||||
|
|
@ -412,7 +409,6 @@ 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);
|
||||||
|
|
@ -429,7 +425,6 @@ export default {
|
||||||
this.client.on('close', () => {
|
this.client.on('close', () => {
|
||||||
this.addMessage('连接已关闭')
|
this.addMessage('连接已关闭')
|
||||||
this.connected = false
|
this.connected = false
|
||||||
console.info(this.client)
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -526,13 +521,6 @@ export default {
|
||||||
if (allKeysNumeric) {
|
if (allKeysNumeric) {
|
||||||
// console.info(msgData)
|
// console.info(msgData)
|
||||||
this.status = {...msgData}
|
this.status = {...msgData}
|
||||||
// 3. 遍历msgData的所有键,根据值设置this.show的对应文本
|
|
||||||
Object.keys(msgData).forEach(key => {
|
|
||||||
const value = msgData[key];
|
|
||||||
// 判断值:0→暂停,1→运行,其他值可补充默认值(可选)
|
|
||||||
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));
|
||||||
|
|
@ -552,7 +540,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;'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -238,10 +238,10 @@ export default {
|
||||||
return {
|
return {
|
||||||
// MQTT配置
|
// MQTT配置
|
||||||
mqttConfig: {
|
mqttConfig: {
|
||||||
broker: 'ws://1.94.254.176:9001/mqtt', // 公共测试服务器
|
broker: '', // 公共测试服务器
|
||||||
clientId: 'uniapp_mqtt_' + Math.random().toString(16).substr(2, 8),
|
clientId: 'uniapp_mqtt_' + Math.random().toString(16).substr(2, 8),
|
||||||
username: 'admin',
|
username: '',
|
||||||
password: 'Admin#12345678',
|
password: '',
|
||||||
timeout: 30
|
timeout: 30
|
||||||
},
|
},
|
||||||
// 连接状态
|
// 连接状态
|
||||||
|
|
|
||||||
380
utils/mqtt.js
380
utils/mqtt.js
|
|
@ -1,260 +1,214 @@
|
||||||
/**
|
|
||||||
* 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} config - MQTT连接配置
|
* @param {Object} mqttConfig 配置项
|
||||||
* @param {Array} subs - 初始订阅列表,格式:[{ topic: 'topic1', qos: 0 }, { topic: 'topic2', qos: 1 }]
|
* @param {Array|String} subTopic 初始订阅主题
|
||||||
* @returns {Promise} 连接成功/失败的Promise
|
* @returns {Object} MQTT实例
|
||||||
*/
|
*/
|
||||||
export function initMQTT(config, subs = []) {
|
function createMqttClient(mqttConfig, subTopic) {
|
||||||
return new Promise((resolve, reject) => {
|
// 已存在实例则直接返回
|
||||||
// 1. 校验配置(必填项)
|
if (mqttInstance && mqttInstance.connected) {
|
||||||
if (!config.host || !config.port) {
|
// 若传入新主题,补充订阅
|
||||||
reject(new Error('MQTT配置错误:host和port为必填项'));
|
if (subTopic) {
|
||||||
return;
|
mqttInstance.subscribe(subTopic);
|
||||||
|
}
|
||||||
|
return mqttInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 保存订阅列表(用于重连恢复)
|
// 标准化配置(默认值 + 防错)
|
||||||
subscribeList = subs;
|
const config = {
|
||||||
|
host: mqttConfig.host || '1.94.254.176',
|
||||||
// 3. 避免重复连接
|
port: mqttConfig.port || 9001,
|
||||||
if (client && client.connected) {
|
clientId: mqttConfig.clientId || `uniapp_mqtt_${Date.now()}${Math.random().toString(16).substr(2, 4)}`, // 时间戳+随机数,减少重复
|
||||||
resolve(client);
|
username: mqttConfig.username || 'admin',
|
||||||
return;
|
password: mqttConfig.password || 'Admin#12345678',
|
||||||
}
|
clean: false, // 关键:改为false,保持会话缓存消息
|
||||||
|
reconnectPeriod: mqttConfig.reconnectPeriod || 3000,
|
||||||
// 4. 标记为非主动断开(允许重连)
|
connectTimeout: mqttConfig.connectTimeout || 5000,
|
||||||
isManualDisconnect = false;
|
qos: mqttConfig.qos || 1 // 默认QoS1,确保消息不丢
|
||||||
|
|
||||||
// 5. 构建连接地址(小程序仅支持ws/wss,优先用wss更安全)
|
|
||||||
const protocol = config.protocol || (config.isSSL ? 'wss' : 'ws');
|
|
||||||
const connectUrl = `${protocol}://${config.host}:${config.port}/mqtt`;
|
|
||||||
|
|
||||||
// 6. 构建MQTT连接选项
|
|
||||||
const mqttOptions = {
|
|
||||||
clientId: config.clientId || `uni_mqtt_${Math.random().toString(16).substr(2, 8)}`,
|
|
||||||
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. 创建客户端并连接
|
const options = {
|
||||||
client = mqtt.connect(connectUrl, mqttOptions);
|
clientId: config.clientId,
|
||||||
|
username: config.username,
|
||||||
// 8. 监听连接成功
|
password: config.password,
|
||||||
client.on('connect', () => {
|
clean: config.clean,
|
||||||
console.log('MQTT连接成功:', connectUrl);
|
connectTimeout: config.connectTimeout,
|
||||||
currentReconnectTimes = 0; // 重置重连次数
|
reconnectPeriod: config.reconnectPeriod,
|
||||||
// 9. 订阅初始主题
|
keepalive: 60 // 新增心跳,避免连接被断开
|
||||||
subscribeTopics(subscribeList);
|
|
||||||
resolve(client);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 10. 监听连接错误
|
|
||||||
client.on('error', (err) => {
|
|
||||||
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) {
|
// 拼接WS地址(兼容配置错误)
|
||||||
console.error('MQTT初始化失败:', err);
|
const url = `ws://${config.host}:${config.port}/mqtt`;
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// 创建客户端
|
||||||
* 订阅主题(支持单个/多个)
|
const client = mqtt.connect(url, options);
|
||||||
* @param {Array} topics - 订阅列表,格式:[{ topic: 'topic1', qos: 0 }, { topic: 'topic2', qos: 1 }]
|
|
||||||
*/
|
// 实例状态管理
|
||||||
export function subscribeTopics(topics = []) {
|
const instance = {
|
||||||
if (!client || !client.connected) {
|
client: client,
|
||||||
console.warn('MQTT未连接,无法订阅主题');
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤空主题
|
const topicList = Array.isArray(topics) ? topics : [topics];
|
||||||
const validTopics = topics.filter(item => item && item.topic);
|
client.subscribe(topicList, { qos }, (err) => {
|
||||||
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) {
|
if (err) {
|
||||||
console.error('MQTT订阅失败:', err);
|
console.error('订阅失败:', err, '主题:', topicList);
|
||||||
|
// 订阅失败重试(仅一次)
|
||||||
|
setTimeout(() => this.subscribe(topics, qos), 1000);
|
||||||
} else {
|
} else {
|
||||||
console.log('MQTT订阅成功:', validTopics.map(item => item.topic));
|
topicList.forEach(t => this.subscribedTopics.add(t));
|
||||||
// 更新订阅列表(用于重连恢复)
|
console.log(`订阅成功${topicList.length > 1 ? '(批量)' : '(单个)'}:`, topicList);
|
||||||
subscribeList = [...new Set([...subscribeList, ...validTopics])]; // 去重
|
this.statusCallback && this.statusCallback('subscribe_success', topicList);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
/**
|
// 取消订阅
|
||||||
* 取消订阅主题
|
unsubscribe: function (topics) {
|
||||||
* @param {Array} topics - 取消订阅的主题列表,格式:['topic1', 'topic2']
|
if (!this.connected) return;
|
||||||
*/
|
const topicList = Array.isArray(topics) ? topics : [topics];
|
||||||
export function unsubscribeTopics(topics = []) {
|
client.unsubscribe(topicList, (err) => {
|
||||||
if (!client || !client.connected) {
|
|
||||||
console.warn('MQTT未连接,无法取消订阅');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client.unsubscribe(topics, (err) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('MQTT取消订阅失败:', err);
|
console.error('取消订阅失败:', err);
|
||||||
} else {
|
} else {
|
||||||
console.log('MQTT取消订阅成功:', topics);
|
topicList.forEach(t => this.subscribedTopics.delete(t));
|
||||||
// 更新订阅列表
|
console.log('取消订阅成功:', topicList);
|
||||||
subscribeList = subscribeList.filter(item => !topics.includes(item.topic));
|
this.statusCallback && this.statusCallback('unsubscribe_success', topicList);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
/**
|
// 发布消息(带参数,失败重试)
|
||||||
* 发布消息
|
publish: function (topic, message, qos = config.qos, retain = false) {
|
||||||
* @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) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!client || !client.connected) {
|
if (!this.connected) {
|
||||||
reject(new Error('MQTT未连接,无法发布消息'));
|
reject(new Error('MQTT未连接,无法发布消息'));
|
||||||
|
// 自动重连后发布
|
||||||
|
this.reconnectAndPublish(topic, message, qos, retain);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!topic) {
|
// 标准化消息格式(对象转JSON)
|
||||||
reject(new Error('发布失败:主题不能为空'));
|
const payload = typeof message === 'object' ? JSON.stringify(message) : String(message);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client.publish(topic, payload, options, (err) => {
|
client.publish(topic, payload, { qos, retain }, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(`MQTT发布${topic}失败:`, err);
|
console.error('发布失败:', err, '主题:', topic);
|
||||||
reject(err);
|
reject(err);
|
||||||
|
// 发布失败重试
|
||||||
|
setTimeout(() => this.publish(topic, message, qos, retain), 1000);
|
||||||
} else {
|
} else {
|
||||||
console.log(`MQTT发布${topic}成功:`, payload);
|
console.log('发布成功:', topic, '内容:', payload);
|
||||||
resolve(true);
|
resolve({ topic, payload });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
|
// 重连后补发消息
|
||||||
|
reconnectAndPublish: function (topic, message, qos, retain) {
|
||||||
|
client.once('connect', () => {
|
||||||
|
this.publish(topic, message, qos, retain);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 断开MQTT连接(主动断开,不会触发重连)
|
|
||||||
*/
|
|
||||||
export function disconnectMQTT() {
|
|
||||||
// 标记为主动断开
|
|
||||||
isManualDisconnect = true;
|
|
||||||
// 清除重连定时器
|
|
||||||
if (reconnectTimer) {
|
|
||||||
clearTimeout(reconnectTimer);
|
|
||||||
reconnectTimer = null;
|
|
||||||
}
|
|
||||||
// 断开连接
|
// 断开连接
|
||||||
if (client && client.connected) {
|
disconnect: function () {
|
||||||
client.end(false, () => { // false:不发送遗嘱消息
|
if (this.connected && this.client) {
|
||||||
console.log('MQTT主动断开连接');
|
this.client.end(false, () => { // false:等待剩余消息发送完成
|
||||||
client = null;
|
this.connected = false;
|
||||||
subscribeList = [];
|
this.subscribedTopics.clear();
|
||||||
|
console.log('MQTT连接已断开(保留会话)');
|
||||||
|
this.statusCallback && this.statusCallback('disconnect');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 重连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 // 最大重连次数
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 绑定客户端事件
|
||||||
|
client.on('connect', () => {
|
||||||
|
instance.connected = true;
|
||||||
|
console.log('MQTT连接成功,ClientId:', config.clientId);
|
||||||
|
instance.statusCallback && instance.statusCallback('connect_success');
|
||||||
|
// 初始订阅主题
|
||||||
|
if (subTopic) {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 赋值单例
|
||||||
|
mqttInstance = instance;
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 手动触发重连(外部调用,比如页面主动刷新连接)
|
* 对外暴露的核心方法
|
||||||
* @param {Object} config - 连接配置
|
|
||||||
*/
|
*/
|
||||||
export function manualReconnect(config) {
|
export const mqttTool = {
|
||||||
currentReconnectTimes = 0; // 重置重连次数
|
// 初始化连接
|
||||||
reconnectMQTT(config);
|
connect: function (mqttConfig, subTopic) {
|
||||||
|
return createMqttClient(mqttConfig, subTopic);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取全局实例
|
||||||
|
getInstance: function () {
|
||||||
|
return mqttInstance;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 页面切换时的订阅管理(核心解决多页面订阅问题)
|
||||||
|
switchPageTopic: function (newTopics, oldTopics) {
|
||||||
|
const instance = mqttInstance;
|
||||||
|
if (!instance) return;
|
||||||
|
|
||||||
|
// 先订阅新主题,再取消旧主题(避免漏消息)
|
||||||
|
if (newTopics) {
|
||||||
|
instance.subscribe(newTopics);
|
||||||
}
|
}
|
||||||
|
if (oldTopics) {
|
||||||
|
instance.unsubscribe(oldTopics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue