uniapp + MQTT协议对接物联网平台(EMQX/阿里云IoT)
文章目录前言一、为什么选MQTT二、uniapp端MQTT接入方案选型三、mqtt.js接入3.1 安装3.2 核心连接封装3.3 页面中使用四、EMQX平台对接4.1 连接地址4.2 认证配置五、阿里云IoT平台对接5.1 一机一密认证参数计算5.2 主题格式5.3 消息格式六、踩坑记录6.1 小程序WebSocket连接数限制6.2 小程序后台运行断连6.3 mqtt.js包体积过大6.4 阿里云IoT的timestamp签名过期总结前言做物联网前端MQTT协议是绕不开的。几乎所有主流IoT平台都基于MQTT做设备通信而uniapp作为跨端框架对接MQTT有一些平台差异和坑需要注意。本文基于实际项目经验总结uniapp对接EMQX和阿里云IoT平台的完整方案。一、为什么选MQTT相比HTTP轮询MQTT有三个核心优势长连接低功耗一次握手持续通信设备端不需要反复建连发布/订阅模型天然支持一对多推送设备状态变更实时触达前端QoS分级消息可靠性可配置关键指令不怕丢在物联网场景下设备状态上报、远程控制、告警推送都是MQTT的典型应用。二、uniapp端MQTT接入方案选型uniapp跨端不同运行环境对MQTT的支持方式不同运行环境方案说明H5mqtt.jsWebSocket最简单直接npm安装微信小程序mqtt.jswx WebSocket需适配小程序APIAppmqtt.js / 原生插件复杂场景建议原生插件推荐统一用mqtt.js一套代码多端跑只需要处理WebSocket连接的适配。三、mqtt.js接入3.1 安装npminstallmqtt--save3.2 核心连接封装// utils/mqttClient.jsimportmqttfrommqttclassMqttManager{constructor(){this.clientnullthis.reconnectTimes0this.maxReconnect5this.subscriptionsnewMap()// 订阅管理}/** * 建立连接 * param {Object} config - 连接配置 */connect(config){const{url,options}config// 小程序端需要适配WebSocket// #ifdef MP-WEIXINoptions.transformWsUrl(url,options,client){// 小程序WebSocket连接超时处理client.options.reconnectPeriod5000returnurl}// #endifthis.clientmqtt.connect(url,{...options,reconnectPeriod:5000,// 重连间隔clean:true,// 清除会话connectTimeout:10000,// 连接超时})this._bindEvents()returnthis}/** 绑定事件监听 */_bindEvents(){this.client.on(connect,(){console.log([MQTT] 连接成功)this.reconnectTimes0// 重连后恢复订阅this._resubscribe()})this.client.on(error,(err){console.error([MQTT] 连接错误:,err)})this.client.on(reconnect,(){this.reconnectTimesconsole.warn([MQTT] 第${this.reconnectTimes}次重连)if(this.reconnectTimesthis.maxReconnect){console.error([MQTT] 超过最大重连次数停止重连)this.client.end()uni.showToast({title:设备连接已断开,icon:none})}})this.client.on(message,(topic,message){constpayloadmessage.toString()try{constdataJSON.parse(payload)this._dispatchMessage(topic,data)}catch(e){console.warn([MQTT] 非JSON消息:,payload)}})this.client.on(close,(){console.log([MQTT] 连接关闭)})}/** 订阅主题 */subscribe(topic,callback,options{qos:1}){if(!this.client||!this.client.connected){console.warn([MQTT] 未连接订阅缓存)}this.subscriptions.set(topic,callback)this.client.subscribe(topic,options,(err){if(err)console.error([MQTT] 订阅失败:${topic},err)elseconsole.log([MQTT] 订阅成功:${topic})})}/** 发布消息 */publish(topic,data,options{qos:1}){if(!this.client||!this.client.connected){console.error([MQTT] 未连接发布失败)return}constpayloadtypeofdatastring?data:JSON.stringify(data)this.client.publish(topic,payload,options,(err){if(err)console.error([MQTT] 发布失败:${topic},err)})}/** 重连后恢复订阅 */_resubscribe(){this.subscriptions.forEach((callback,topic){this.client.subscribe(topic,{qos:1})})}/** 消息分发 */_dispatchMessage(topic,data){constcallbackthis.subscriptions.get(topic)if(callback){callback(data)}else{// 通配符订阅匹配this.subscriptions.forEach((cb,subTopic){if(this._matchTopic(subTopic,topic))cb(data)})}}/** 简易通配符匹配 */_matchTopic(subscription,topic){constsubPartssubscription.split(/)consttopicPartstopic.split(/)for(leti0;isubParts.length;i){if(subParts[i]#)returntrueif(subParts[i]!subParts[i]!topicParts[i])returnfalse}returnsubParts.lengthtopicParts.length}/** 断开连接 */disconnect(){if(this.client){this.client.end()this.subscriptions.clear()this.clientnull}}}// 单例导出exportdefaultnewMqttManager()3.3 页面中使用importmqttClientfrom/utils/mqttClientexportdefault{data(){return{deviceStatus:{}}},onLoad(){this._initMqtt()},onUnload(){// 页面销毁时断开避免后台持续连接mqttClient.disconnect()},methods:{_initMqtt(){mqttClient.connect({url:wx://your-emqx-server:8083/mqtt,// 小程序用wx协议options:{clientId:app_Math.random().toString(16).substr(2,8),username:your_username,password:your_password,}})// 订阅设备状态mqttClient.subscribe(device/status/,(data){this.deviceStatusdata})},// 发送控制指令sendCommand(deviceId,action){mqttClient.publish(device/command/${deviceId},{action,timestamp:Date.now()})}}}四、EMQX平台对接EMQX部署相对简单自建服务器或用EMQX Cloud都行。4.1 连接地址客户端协议地址示例H5wsws://server:8083/mqtt小程序wxwx://server:8083/mqttAppws/tcpws://server:8083/mqtt 或 tcp://server:1883⚠️ 小程序只支持wx://协议且要求域名必须备案SSL开发阶段可在小程序管理后台开启不校验合法域名4.2 认证配置EMQX内置多种认证方式推荐用用户名密码认证# EMQX Dashboard - 认证 - 密码认证 # 添加用户名/密码前端连接时传入生产环境建议开启TLS加密防止凭证明文传输。五、阿里云IoT平台对接阿里云IoT和自建EMQX最大区别在于认证机制不是简单的用户名密码而是基于DeviceName DeviceSecret动态生成签名。5.1 一机一密认证参数计算// utils/aliyunIot.jsimportCryptoJSfromcrypto-js/** * 生成阿里云IoT MQTT连接参数 * param {Object} config - { productKey, deviceName, deviceSecret } * param {number} expiresIn - 过期时间(秒)默认24小时 */exportfunctiongenerateMqttConfig(config,expiresIn86400){const{productKey,deviceName,deviceSecret}configconstclientId${deviceName}|securemode2,signmethodhmacsha256|consttimestampDate.now()constexpireTimeMath.floor(timestamp/1000)expiresIn// 构造签名内容constcontentclientId${deviceName}deviceName${deviceName}productKey${productKey}timestamp${timestamp}// HMAC-SHA256签名constsignCryptoJS.HmacSHA256(content,deviceSecret).toString(CryptoJS.enc.Base64)// MQTT用户名constusername${deviceName}${productKey}// MQTT密码 签名constpasswordsignreturn{url:wx://${productKey}.iot-as-mqtt.cn-shanghai.aliyuncs.com:443,options:{clientId,username,password,keepalive:60,}}}5.2 主题格式阿里云IoT的主题是固定的不能自定义// 属性上报设备→平台constPOST_PROP_TOPIC/sys/${productKey}/${deviceName}/thing/event/property/post// 属性设置平台→设备constSET_PROP_TOPIC/sys/${productKey}/${deviceName}/thing/service/property/set// 服务调用平台→设备constSERVICE_TOPIC/sys/${productKey}/${deviceName}/thing/service/// 事件上报设备→平台constEVENT_TOPIC/sys/${productKey}/${deviceName}/thing/event/⚠️ 阿里云IoT平台对App端直连有限制正式项目建议通过自己的后端服务桥接前端只对接后端APIWebSocket推送。5.3 消息格式阿里云IoT的消息体有固定结构不是随便发个JSON就行的// 属性上报mqttClient.publish(POST_PROP_TOPIC,{id:Date.now().toString(),version:1.0,params:{temperature:26.5,humidity:65},method:thing.event.property.post})// 属性设置接收端解析mqttClient.subscribe(SET_PROP_TOPIC,(data){const{params}data// params: { power: 1, mode: auto }this.updateDeviceState(params)})六、踩坑记录6.1 小程序WebSocket连接数限制微信小程序同时只能维护1个WebSocket连接如果你的项目同时用了IM聊天和MQTT会冲突。解决方案用一个WebSocket做中转后端把MQTT消息通过同一个WS通道推给前端或者用uni-socket统一管理。6.2 小程序后台运行断连小程序切到后台5秒后WebSocket会被系统回收。解决方案// App.vueonHide(){// 切后台不断开但标记状态this.$mqtt.isBackgroundtrue},onShow(){// 回前台检测重连if(this.$mqtt.isBackground!this.$mqtt.client?.connected){this.$mqtt.client.reconnect()}}6.3 mqtt.js包体积过大mqtt.js完整包约300KB小程序对包体积敏感。解决方案// 只引入mqtt的WebSocket实现importmqttfrommqtt/dist/mqtt.min// 或者使用打包工具tree-shaking6.4 阿里云IoT的timestamp签名过期签名的timestamp不是连接时刻而是签名计算时的时间戳如果客户端时间偏差大会鉴权失败。解决方案签名前先和服务端同步一次时间或设置较长的过期时间。总结维度EMQX阿里云IoT部署自建/Cloud灵活托管开箱即用认证用户名密码/TLS证书一机一密/一型一密主题自定义固定格式消息格式自由标准物模型适用场景内部项目、自定义协议标准设备接入、快速上线成本自建服务器成本按消息量计费