/** * 智能农业小程序 - MQTT全局工具类(uniapp+小程序适配版) * 核心设计: * 1. 配置暂时写死全局,后续可替换为登录后从数据字典获取 * 2. clientId仅随机生成(无需关联userId),保证唯一性即可 * 3. 同步方法为主,全局统一管理连接/订阅/发布,多页面复用 */ // 引入小程序版MQTT核心库(确保mqtt.min.js在utils目录) // 微信小程序端:引入适配小程序的 mqtt 版本 import mqtt from 'mqtt/dist/mqtt' import {batchUnsubscribe} from "../api/system/mqtt"; import {getToken} from "./auth"; // ===================== MQTT配置(暂时写死,TODO:后续从数据字典获取)===================== const MQTT_CONFIG = { server: 'wxs://mq.xiaoces.com:443/mqtt', // 替换为你的MQTT服务器地址 username: 'admin', // 替换为通用账号 password: 'Admin#12345678', // 替换为通用密码 clean: true, host: 'mq.xiaoces.com', port: 443, reconnectPeriod: 5000, // 重连间隔 connectTimeout: 10000, // 连接超时 keepalive: 60, // 心跳时间 } // ===================== MQTT全局状态管理 ===================== const mqttState = { client: null, // MQTT客户端实例 isConnected: false, // 连接状态(同步标记) subscribeList: [], // 全局订阅列表(登录后赋值) options: { // MQTT连接选项 clientId: '', // 仅随机生成,无需关联userId ...MQTT_CONFIG // 合并基础配置 }, onMessageCallback: null // 全局消息回调(各页面自定义) } /** * 初始化MQTT配置(生成随机clientId,无需传入userId) * @returns {Boolean} - 是否配置成功 */ export function initMqttConfig() { if (mqttState.client) { console.info("重连前强制断开",mqttState.options.clientId) // 加try-catch,避免断开时客户端已异常导致报错 try { mqttState.client.end(true) } catch (err) { console.warn('旧连接断开失败:', err) } } try { // 仅随机生成clientId(保证唯一性,避免连接冲突) mqttState.options.clientId = `wx_mqtt_${Math.random().toString(16).substr(2, 10)}` // 重置旧状态,避免干扰 mqttState.isConnected = false mqttState.client = null console.log('MQTT配置初始化成功,clientId:', mqttState.options.clientId) return true } catch (err) { uni.showToast({ title: '设备连接异常-设备初始化失败', icon: 'none', duration: 2000 }) console.error('MQTT配置初始化失败:', err) return false } } /** * 建立MQTT连接(初始化后调用,同步返回触发状态) * @returns {Boolean} - 是否成功触发连接 */ export function connectMqtt() { // 前置校验:是否已初始化配置 if (!mqttState.options.clientId) { uni.showToast({ title: '设备连接失败', icon: 'none' }) console.error('MQTT连接失败:请先调用initMqttConfig初始化配置') return false } console.info(mqttState.client!=null,mqttState.client) // 避免重复连接 if (mqttState.client!=null) { console.log('MQTT已连接,无需重复操作') return true } try { // 创建客户端实例(同步操作) // #ifndef MP-WEIXIN MQTT_CONFIG.server = 'wxs://mq.xiaoces.com:443/mqtt' // #endif console.info("mqttState.connect",mqttState) mqttState.client = mqtt.connect(MQTT_CONFIG.server, mqttState.options) console.info("重连中。。") // 监听核心事件(异步,同步更新状态) mqttState.client.on('connect', () => { console.log('MQTT连接成功',mqttState.options.clientId) mqttState.isConnected = true // 连接成功后自动订阅全局列表 subscribeAllTopics() }) mqttState.client.on('close', () => { console.log('MQTT连接断开') mqttState.isConnected = false }) mqttState.client.on('error', (err) => { uni.showToast({ title: '设备连接异常', icon: 'none' }) console.error('MQTT连接错误:', err) mqttState.isConnected = false }) // 全局接收消息,转发给页面自定义回调 mqttState.client.on('message', (topic, payload) => { const message = payload.toString() // console.log(`收到MQTT消息:topic=${topic},message=${message}`) if (typeof mqttState.onMessageCallback === 'function') { mqttState.onMessageCallback(topic, message) } }) return true } catch (err) { uni.showToast({ title: '设备连接异常', icon: 'none' }) console.error('MQTT连接创建失败:', err) mqttState.isConnected = false return false } } /** * 设置页面专属消息回调(各页面独立处理消息) * @param {Function} callback - (topic, message) => {} */ export function setOnMessageCallback(callback) { if (typeof callback === 'function') { mqttState.onMessageCallback = callback console.log('MQTT消息回调已注册') } else { console.error('MQTT回调必须是函数类型') } } /** * 移除消息回调(页面卸载时调用,避免内存泄漏) */ export function removeOnMessageCallback() { mqttState.onMessageCallback = null console.log('MQTT消息回调已移除') } /** * 更新全局订阅列表(登录后调用,自动订阅) * @param {Array} list - 订阅主题列表 ['topic1', 'topic2'] * @returns {Boolean} - 是否更新成功 */ export function updateSubscribeList(list) { if (!Array.isArray(list)) { uni.showToast({ title: '设备订阅更新异常', icon: 'none' }) console.error('订阅列表必须是数组') return false } mqttState.subscribeList = [...list] console.log('MQTT订阅列表已更新:', list) // 已连接则立即订阅 if (mqttState.isConnected) { subscribeAllTopics() } return true } /** * 内部方法:订阅所有全局主题 */ function subscribeAllTopics() { const { isConnected, client, subscribeList } = mqttState if (!isConnected || !client || subscribeList.length === 0) { console.warn('MQTT订阅跳过:未连接或列表为空') return } client.subscribe(subscribeList, { qos: 0 }, (err) => { if (err) { uni.showToast({ title: '设备订阅异常', icon: 'none' }) console.error('MQTT订阅失败:', err) } else { console.log(`MQTT成功订阅:${subscribeList.join(', ')}`) } }) } /** * 发布MQTT消息(同步调用) * @param {String} topic - 发布主题 * @param {String/Object} message - 发布内容 * @returns {Boolean} - 是否触发发布成功 */ export function publishMqtt(topic, message) { if (process.env.NODE_ENV === "production") { const { isConnected, client } = mqttState if (!isConnected || !client) { uni.showToast({ title: '控制异常', icon: 'none' }) console.error('MQTT发布失败:未连接') return false } if (!topic) { uni.showToast({ title: '控制异常', icon: 'none' }) console.error('MQTT发布失败:主题为空') return false } try { const msg = typeof message === 'object' ? JSON.stringify(message) : String(message) client.publish(topic, msg, (err) => { if (err) { uni.showToast({ title: '控制异常', icon: 'none' }) console.error(`MQTT发布失败:topic=${topic},err=${err}`) } else { console.log(`MQTT发布成功:topic=${topic},message=${msg}`) } }) return true } catch (err) { uni.showToast({ title: '控制异常', icon: 'none' }) console.error('MQTT发布异常:', err) return false } } } /** * 断开MQTT连接(登出/小程序切后台时调用) * @returns {Boolean} - 是否断开成功 */ export function disconnectMqtt() { if (!mqttState.client) { console.log('MQTT无客户端实例,无需断开') resetMqttState() return true } if (getToken()) { batchUnsubscribe({clientId:mqttState.options.clientId}).then(response => { if (response.code === 200) { console.info("取消订阅成功!") } }) } try { mqttState.client.end(true) // 强制断开 resetMqttState() console.log('MQTT连接已断开') return true } catch (err) { uni.showToast({ title: '设备通信异常', icon: 'none' }) console.error('MQTT断开失败:', err) return false } } /** * 重置MQTT状态(内部方法) */ function resetMqttState() { mqttState.isConnected = false mqttState.client = null mqttState.subscribeList = [] mqttState.onMessageCallback = null // 保留clientId,直到下一次初始化覆盖 } /** * 获取当前MQTT状态(同步) * @returns {Object} - 状态副本 */ export function getMqttState() { return { isConnected: mqttState.isConnected, subscribeList: [...mqttState.subscribeList], clientId: mqttState.options.clientId, client: mqttState.client } } // utils/mqttOnline.js let timer = null; export function startMqttOnlinePing(intervalMs = 20000) { if (!mqttState.client || !mqttState.options.clientId) return; if (timer) return; const topic = `frontend/${mqttState.options.clientId}/online`; const ping = () => { try { if (!mqttState.client.connected) return; const payload = JSON.stringify({ ts: Date.now() }); // qos=0 足够;retain 不要 mqttState.client.publish(topic, payload, { qos: 0, retain: false }); // console.info(`主题:${topic}:${payload}`) } catch (e) {} }; ping(); // 每 20000ms(20秒)执行一次 ping timer = setInterval(ping, intervalMs); } export function stopMqttOnlinePing() { if (timer) { clearInterval(timer); timer = null; } } // 导出所有方法(全局调用) export default { initMqttConfig, connectMqtt, setOnMessageCallback, removeOnMessageCallback, updateSubscribeList, publishMqtt, disconnectMqtt, getMqttState }