解决微信小程序 MQTT 无法使用?这款终极兼容版救急!

50 阅读11分钟

微信图片_20251231084837_5_66.jpg 在做微信小程序或 H5 对接 MQTT 服务时,你是不是踩过这些坑:

  • onOpen is not a function 报错闪退
  • task not found 连接异常
  • TextEncoder/TextDecoder 未定义 兼容性问题
  • 中文消息收发乱码
  • 小程序和 H5 无法一套代码兼容运行

别再挨个排查填坑了!我整理了一款MQTT 终极兼容版(wx + H5) ,完美解决以上所有问题,且已固定对接 wss://mq.dtszbj.com/mqtt 服务,开箱即用!

一、完整兼容版 JS 代码(mqtt-wx.js)

直接复制下方代码,保存为 mqtt-wx.js 文件即可使用,无需额外依赖!

/**
 * mqtt-wx.js 终极兼容版(写死wss://mq.dtszbj.com/mqtt)
 * 适配:微信小程序(wx) + H5(window),解决 onOpen 不是函数、task not found、TextEncoder 未定义、中文乱码等问题
 */
; (function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
      (global = global || self, global.mqtt = factory());
}(this, (function () {
  'use strict';

  // 环境检测:区分小程序/H5
  var isMiniProgram = typeof wx !== 'undefined' && typeof wx.connectSocket === 'function';
  var isH5 = !isMiniProgram && typeof window !== 'undefined' && typeof window.WebSocket === 'function';

  // 写死核心配置:固定为wss://mq.dtszbj.com/mqtt
  var defaultOptions = {
    timeout: 60 * 1000,
    keepalive: 60,
    clean: true,
    reconnectPeriod: 1000,
    clientId: 'mqttjs_' + Math.random().toString(16).substr(2, 8),
    protocolId: 'MQTT',
    protocolVersion: 4,
    encoding: 'utf8',
    protocol: 'wss', // 固定wss协议
    host: 'mq.dtszbj.com', // 固定主机地址
    port: 443, // wss默认端口443
  };

  // MQTT报文类型
  var PacketType = {
    CONNECT: 1,
    CONNACK: 2,
    PUBLISH: 3,
    PUBACK: 4,
    PUBREC: 5,
    PUBREL: 6,
    PUBCOMP: 7,
    SUBSCRIBE: 8,
    SUBACK: 9,
    UNSUBSCRIBE: 10,
    UNSUBACK: 11,
    PINGREQ: 12,
    PINGRESP: 13,
    DISCONNECT: 14
  };

  // 手动字符串转UTF-8 Uint8Array(替代TextEncoder,支持中文)
  function stringToUint8Array(str) {
    var arr = [];
    var len = str.length;

    for (var i = 0; i < len; i++) {
      var charCode = str.charCodeAt(i);
      // 单字节 (0x00-0x7F)
      if (charCode <= 0x7F) {
        arr.push(charCode);
      }
      // 双字节 (0x80-0x7FF)
      else if (charCode <= 0x7FF) {
        arr.push(0xC0 | (charCode >> 6));
        arr.push(0x80 | (charCode & 0x3F));
      }
      // 三字节 (0x800-0xFFFF)
      else if (charCode <= 0xFFFF) {
        arr.push(0xE0 | (charCode >> 12));
        arr.push(0x80 | ((charCode >> 6) & 0x3F));
        arr.push(0x80 | (charCode & 0x3F));
      }
      // 四字节 (0x10000-0x10FFFF)
      else {
        arr.push(0xF0 | (charCode >> 18));
        arr.push(0x80 | ((charCode >> 12) & 0x3F));
        arr.push(0x80 | ((charCode >> 6) & 0x3F));
        arr.push(0x80 | (charCode & 0x3F));
      }
    }

    return new Uint8Array(arr);
  }

  // 手动Uint8Array转UTF-8字符串(修复中文乱码,替代TextDecoder)
  function uint8ArrayToString(buf, encoding) {
    if (encoding !== 'utf8' && encoding !== 'utf-8') {
      console.warn('仅支持UTF-8编码,已自动切换');
    }

    // UTF-8解码核心逻辑
    var str = '';
    var i = 0;
    var len = buf.length;

    while (i < len) {
      var byte = buf[i];
      // 单字节字符 (0x00-0x7F)
      if (byte < 0x80) {
        str += String.fromCharCode(byte);
        i++;
      }
      // 双字节字符 (0xC0-0xDF)
      else if (byte >= 0xC0 && byte <= 0xDF && i + 1 < len) {
        var byte2 = buf[i + 1];
        var charCode = ((byte & 0x1F) << 6) | (byte2 & 0x3F);
        str += String.fromCharCode(charCode);
        i += 2;
      }
      // 三字节字符 (0xE0-0xEF)
      else if (byte >= 0xE0 && byte <= 0xEF && i + 2 < len) {
        var byte2 = buf[i + 1];
        var byte3 = buf[i + 2];
        var charCode = ((byte & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F);
        str += String.fromCharCode(charCode);
        i += 3;
      }
      // 四字节字符 (0xF0-0xF7)
      else if (byte >= 0xF0 && byte <= 0xF7 && i + 3 < len) {
        var byte2 = buf[i + 1];
        var byte3 = buf[i + 2];
        var byte4 = buf[i + 3];
        var charCode = ((byte & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F);
        str += String.fromCharCode(charCode);
        i += 4;
      }
      // 无效字节,跳过
      else {
        str += '�'; // 替换未知字符
        i++;
      }
    }

    return str;
  }

  // 剩余长度编码
  function encodeRemainingLength(length) {
    var buffer = [];
    do {
      var byte = length % 128;
      length = Math.floor(length / 128);
      if (length > 0) {
        byte |= 0x80;
      }
      buffer.push(byte);
    } while (length > 0);
    return new Uint8Array(buffer);
  }

  // 剩余长度解码
  function decodeRemainingLength(buffer, offset) {
    var length = 0;
    var multiplier = 1;
    var byte;
    do {
      byte = buffer[offset++];
      length += (byte & 0x7F) * multiplier;
      multiplier *= 128;
      if (multiplier > 128 * 128 * 128) {
        throw new Error('Remaining length is too large');
      }
    } while ((byte & 0x80) !== 0);
    return { length: length, offset: offset };
  }

  // 构造CONNECT报文(修复重复定义connectFlags,无TextEncoder依赖)
  function createConnectPacket(options) {
    var clientId = options.clientId;
    var clean = options.clean;
    var keepalive = options.keepalive;
    var protocolId = options.protocolId;
    var protocolVersion = options.protocolVersion;
    
    // 连接标志(仅声明一次,显式清零遗嘱相关位)
    var connectFlags = 0x00;
    if (clean) connectFlags |= 0x02;
    var connectFlagsBuf = new Uint8Array([connectFlags]);

    // 手动编码协议名
    var protoNameBuf = stringToUint8Array(protocolId);
    var protoVerBuf = new Uint8Array([protocolVersion]);

    // 心跳间隔(2字节)
    var keepaliveBuf = new Uint8Array([(keepalive >> 8) & 0xFF, keepalive & 0xFF]);

    // 客户端ID(带长度前缀)
    var clientIdBuf = stringToUint8Array(clientId);
    var clientIdLenBuf = new Uint8Array([(clientIdBuf.length >> 8) & 0xFF, clientIdBuf.length & 0xFF]);

    // 拼接可变头
    var variableHeaderParts = [
      new Uint8Array([protoNameBuf.length >> 8, protoNameBuf.length & 0xFF]),
      protoNameBuf,
      protoVerBuf,
      connectFlagsBuf,
      keepaliveBuf
    ];
    var variableHeaderLength = 0;
    variableHeaderParts.forEach(part => variableHeaderLength += part.length);
    var variableHeader = new Uint8Array(variableHeaderLength);
    var offset = 0;
    variableHeaderParts.forEach(part => {
      variableHeader.set(part, offset);
      offset += part.length;
    });

    // 拼接有效载荷
    var payload = new Uint8Array([...clientIdLenBuf, ...clientIdBuf]);

    // 计算剩余长度并编码
    var remainingLength = variableHeader.length + payload.length;
    var remainingLengthBuf = encodeRemainingLength(remainingLength);

    // 拼接最终报文
    var packetParts = [
      new Uint8Array([PacketType.CONNECT << 4]),
      remainingLengthBuf,
      variableHeader,
      payload
    ];
    var totalLength = 0;
    packetParts.forEach(part => totalLength += part.length);
    var packet = new Uint8Array(totalLength);
    offset = 0;
    packetParts.forEach(part => {
      packet.set(part, offset);
      offset += part.length;
    });

    return packet;
  }

  // 构造SUBSCRIBE报文(无TextEncoder依赖)
  function createSubscribePacket(topic, packetId) {
    var fixedHeader = new Uint8Array([(PacketType.SUBSCRIBE << 4) | 0x02]);
    var packetIdBuf = new Uint8Array([(packetId >> 8) & 0xFF, packetId & 0xFF]);

    // 手动编码主题
    var topicBuf = stringToUint8Array(topic);
    var topicLenBuf = new Uint8Array([(topicBuf.length >> 8) & 0xFF, topicBuf.length & 0xFF]);
    var qosBuf = new Uint8Array([0x00]);

    // 拼接有效载荷
    var payloadParts = [topicLenBuf, topicBuf, qosBuf];
    var payloadLength = 0;
    payloadParts.forEach(part => payloadLength += part.length);
    var payload = new Uint8Array(payloadLength);
    var offset = 0;
    payloadParts.forEach(part => {
      payload.set(part, offset);
      offset += part.length;
    });

    // 计算剩余长度并编码
    var remainingLength = packetIdBuf.length + payload.length;
    var remainingLengthBuf = encodeRemainingLength(remainingLength);

    // 拼接最终报文
    var packetParts = [
      fixedHeader,
      remainingLengthBuf,
      packetIdBuf,
      payload
    ];
    var totalLength = 0;
    packetParts.forEach(part => totalLength += part.length);
    var packet = new Uint8Array(totalLength);
    offset = 0;
    packetParts.forEach(part => {
      packet.set(part, offset);
      offset += part.length;
    });

    return packet;
  }

  // 构造PUBLISH报文由prototype方法实现

  // 强制返回固定地址(写死后无需解析动态URL)
  function parseUrl(url) {
    return {
      protocol: 'wss',
      host: 'mq.dtszbj.com',
      port: 443,
    };
  }

  // MQTT客户端类
  function MqttClient(options) {
    var connectOptions = typeof options === 'string' ? parseUrl(options) : options;
    this.options = Object.assign({}, defaultOptions, connectOptions);
    // 跨端Socket实例(小程序:socketTask,H5:WebSocket)
    this.socket = null;
    this.socketCreated = false;
    this.connected = false;
    this.disconnected = false;
    this.packetId = 1;
    this.keepaliveTimer = null;
    this.reconnectTimer = null;
    this.eventListeners = {};
    this.connectTimeoutTimer = null;
    this.reconnectAttempts = 0; // 重连计数

    // 重连逻辑
    this.reconnect = () => {
      if (this.disconnected || this.reconnectTimer) return;
      this.reconnectTimer = setTimeout(() => {
        this.connect();
        this.reconnectTimer = null;
      }, this.options.reconnectPeriod);
    };

    // 心跳逻辑
    this.startKeepalive = () => {
      if (this.keepaliveTimer) clearInterval(this.keepaliveTimer);
      this.keepaliveTimer = setInterval(() => {
        if (this.connected && this.socket && this.socketCreated) {
          var pingPacket = new Uint8Array([PacketType.PINGREQ << 4, 0]);
          this.send(pingPacket);
        }
      }, this.options.keepalive * 1000);
    };
  }

  // 绑定事件
  MqttClient.prototype.on = function (event, callback) {
    if (!this.eventListeners[event]) {
      this.eventListeners[event] = [];
    }
    this.eventListeners[event].push(callback);
  };

  // 触发事件
  MqttClient.prototype.emit = function (event, ...args) {
    var listeners = this.eventListeners[event] || [];
    listeners.forEach(callback => {
      try {
        callback(...args);
      } catch (e) {
        console.error('MQTT事件回调错误:', e);
      }
    });
  };

  // 发送数据(跨端适配)
  MqttClient.prototype.send = function (data) {
    if (!this.connected || !this.socket || !this.socketCreated) return;

    try {
      // 转换为ArrayBuffer(小程序真机更兼容)
      const arrayBuffer = data.buffer.slice(
        data.byteOffset,
        data.byteOffset + data.byteLength
      );

      if (isMiniProgram) {
        this.socket.send({ data: arrayBuffer }); // 用ArrayBuffer发送
      } else if (isH5) {
        this.socket.send(arrayBuffer);
      }
    } catch (e) {
      console.error('MQTT发送数据失败:', e);
      this.emit('error', e);
    }
  };

  // 安全关闭Socket(跨端适配)
  MqttClient.prototype.safeCloseSocket = function () {
    if (this.socket && this.socketCreated) {
      try {
        if (isMiniProgram) {
          this.socket.close({ code: 1000, reason: 'normal closure' });
        } else if (isH5) {
          this.socket.close(1000, 'normal closure');
        }
      } catch (e) {
        console.warn('关闭Socket失败(非关键):', e);
      }
    }
    // 重置状态
    this.socket = null;
    this.socketCreated = false;
    this.connected = false;
  };

  // 连接MQTT服务器(核心:跨端API适配,固定地址)
  MqttClient.prototype.connect = function () {
    // 安全关闭旧连接
    this.safeCloseSocket();

    // 清空定时器
    if (this.connectTimeoutTimer) clearTimeout(this.connectTimeoutTimer);
    if (this.reconnectTimer) clearTimeout(this.reconnectTimer);

    this.connected = false;
    // 固定连接地址:wss://mq.dtszbj.com/mqtt
    var url = 'wss://mq.dtszbj.com/mqtt';

    console.log(`MQTT连接(${isMiniProgram ? '小程序' : 'H5'}):`, url);

    try {
      // ========== 小程序Socket创建 ==========
      if (isMiniProgram) {
        this.socket = wx.connectSocket({
          url: url,
          header: { 'content-type': 'application/json' },
          protocols: ['mqtt']
        });
        this.socketCreated = true;

        // 小程序事件绑定(修复onOpen调用)
        this.socket.onOpen((res) => {
          console.log('MQTT Socket(小程序)连接打开:', res);
          try {
            var connectPacket = createConnectPacket(this.options);
            this.socket.send({ data: connectPacket });
          } catch (e) {
            console.error('发送CONNECT报文失败:', e);
            this.emit('error', e);
            this.safeCloseSocket();
            return;
          }

          // 连接超时处理
          this.connectTimeoutTimer = setTimeout(() => {
            if (!this.connected) {
              const timeoutErr = new Error(`Connection timeout (${this.options.timeout}ms)`);
              console.error(`MQTT连接超时: 服务器未在${this.options.timeout}ms内响应`, timeoutErr);
              this.emit('error', timeoutErr);

              // 更安全的关闭方式
              this.safeCloseSocket();

              // 指数退避重连策略
              const currentReconnectPeriod = this.options.reconnectPeriod * Math.min(
                Math.pow(2, this.reconnectAttempts || 0),
                30000 // 最大重连间隔30秒
              );
              this.reconnectAttempts = (this.reconnectAttempts || 0) + 1;

              console.log(`将在${currentReconnectPeriod}ms后尝试重连...`);
              setTimeout(() => this.reconnect(), currentReconnectPeriod);
            }
          }, this.options.timeout);
        });

        this.socket.onMessage((res) => {
          try {
            var buffer = new Uint8Array(res.data);
            this.handlePacket(buffer);
          } catch (e) {
            console.error('解析MQTT报文失败:', e);
            this.emit('error', e);
          }
        });

        this.socket.onError((err) => {
          console.error('MQTT Socket(小程序)错误:', err);
          this.emit('error', err);
          this.safeCloseSocket();
          this.reconnect();
        });

        this.socket.onClose((res) => {
          console.log('MQTT Socket(小程序)关闭:', res);
          this.socketCreated = false;
          this.connected = false;
          this.emit('close', res);

          if (this.keepaliveTimer) clearInterval(this.keepaliveTimer);
          if (this.connectTimeoutTimer) clearTimeout(this.connectTimeoutTimer);

          if (!this.disconnected) {
            this.reconnect();
          }
        });

        // ========== H5 WebSocket创建 ==========
      } else if (isH5) {
        this.socket = new WebSocket(url, ['mqtt']);
        this.socket.binaryType = 'arraybuffer';
        this.socketCreated = true;

        // H5事件绑定(替换小程序的onOpen为onopen)
        this.socket.onopen = (res) => {
          console.log('MQTT Socket(H5)连接打开:', res);
          try {
            var connectPacket = createConnectPacket(this.options);
            this.socket.send(connectPacket);
          } catch (e) {
            console.error('发送CONNECT报文失败:', e);
            this.emit('error', e);
            this.safeCloseSocket();
            return;
          }

          // 连接超时处理
          this.connectTimeoutTimer = setTimeout(() => {
            if (!this.connected) {
              var timeoutErr = new Error('Connection timeout');
              this.emit('error', timeoutErr);
              this.safeCloseSocket();
              this.reconnect();
            }
          }, this.options.timeout);
        };

        this.socket.onmessage = (res) => {
          try {
            var buffer = new Uint8Array(res.data);
            this.handlePacket(buffer);
          } catch (e) {
            console.error('解析MQTT报文失败:', e);
            this.emit('error', e);
          }
        };

        this.socket.onerror = (err) => {
          console.error('MQTT Socket(H5)错误:', err);
          this.emit('error', err);
          this.safeCloseSocket();
          this.reconnect();
        };

        this.socket.onclose = (res) => {
          console.log('MQTT Socket(H5)关闭:', res);
          this.socketCreated = false;
          this.connected = false;
          this.emit('close', res);

          if (this.keepaliveTimer) clearInterval(this.keepaliveTimer);
          if (this.connectTimeoutTimer) clearTimeout(this.connectTimeoutTimer);

          if (!this.disconnected) {
            this.reconnect();
          }
        };
      } else {
        throw new Error('不支持当前运行环境');
      }
    } catch (e) {
      console.error('创建Socket失败:', e);
      this.socketCreated = false;
      this.emit('error', new Error('创建Socket失败:' + e.message));
      this.reconnect();
      return;
    }
  };

  // 处理收到的报文(无TextDecoder依赖,支持中文)
  MqttClient.prototype.handlePacket = function (buffer) {
    try {
      var offset = 0;
      var type = (buffer[offset] >> 4) & 0x0F;
      offset++;

      var remaining = decodeRemainingLength(buffer, offset);
      offset = remaining.offset;

      switch (type) {
        case PacketType.CONNACK:
          if (this.connectTimeoutTimer) clearTimeout(this.connectTimeoutTimer);
          var connAckCode = buffer[offset + 1];
          if (connAckCode === 0) {
            this.connected = true;
            this.emit('connect');
            this.startKeepalive();
          } else {
            var errors = [
              'Connection Accepted',
              'Unacceptable Protocol Version',
              'Identifier Rejected',
              'Server Unavailable',
              'Bad User Name or Password',
              'Not Authorized'
            ];
            var connErr = new Error(errors[connAckCode] || 'Unknown error');
            this.emit('error', connErr);
            this.safeCloseSocket();
            this.reconnect();
          }
          break;

        case PacketType.SUBACK:
          this.emit('suback', buffer);
          break;

        case PacketType.PUBLISH:
          var topicLength = (buffer[offset] << 8) | buffer[offset + 1];
          offset += 2;
          // 手动解码主题(支持中文)
          var topic = uint8ArrayToString(buffer.slice(offset, offset + topicLength));
          offset += topicLength;

          var payload = buffer.slice(offset, offset + remaining.length - (topicLength + 2));
          // 手动解码消息内容(支持中文)
          var message = uint8ArrayToString(payload, this.options.encoding);
          this.emit('message', topic, message);
          break;

        case PacketType.PINGRESP:
          break;

        default:
          break;
      }
    } catch (e) {
      console.error('处理MQTT报文异常:', e);
      this.emit('error', e);
    }
  };

  // 订阅主题
  MqttClient.prototype.subscribe = function (topic, callback) {
    callback = callback || function () { };
    if (!this.connected || !this.socketCreated) {
      var err = new Error('Not connected');
      this.emit('error', err);
      callback(err);
      return;
    }
    try {
      var packetId = this.packetId++;
      var subscribePacket = createSubscribePacket(topic, packetId);
      this.send(subscribePacket);
      var subackHandler = () => {
        this.eventListeners.suback = this.eventListeners.suback.filter(fn => fn !== subackHandler);
        callback(null);
      };
      this.on('suback', subackHandler);
    } catch (e) {
      console.error('MQTT订阅主题失败:', e);
      this.emit('error', e);
      callback(e);
    }
  };

  // 发布消息(保留有效实现,删除重复的空实现)
  MqttClient.prototype.publish = function (topic, message) {
    if (!this.connected || !this.socketCreated) {
      var err = new Error('Not connected');
      this.emit('error', err);
      return;
    }

    try {
      // 编码主题和消息
      var topicBuf = stringToUint8Array(topic);
      var topicLenBuf = new Uint8Array([(topicBuf.length >> 8) & 0xFF, topicBuf.length & 0xFF]);
      var messageBuf = stringToUint8Array(message);

      // 固定头(QoS 0,不保留)
      var fixedHeader = new Uint8Array([PacketType.PUBLISH << 4]);

      // 剩余长度 = 主题长度(2字节) + 主题内容 + 消息内容
      var remainingLength = 2 + topicBuf.length + messageBuf.length;
      var remainingLengthBuf = encodeRemainingLength(remainingLength);

      // 拼接报文
      var packetParts = [
        fixedHeader,
        remainingLengthBuf,
        topicLenBuf,
        topicBuf,
        messageBuf
      ];
      var totalLength = 0;
      packetParts.forEach(part => totalLength += part.length);
      var packet = new Uint8Array(totalLength);
      var offset = 0;
      packetParts.forEach(part => {
        packet.set(part, offset);
        offset += part.length;
      });

      this.send(packet);
    } catch (e) {
      console.error('发送消息失败:', e);
      this.emit('error', e);
    }
  };

  // 取消订阅
  MqttClient.prototype.unsubscribe = function (topic) {
    this.emit('warn', 'unsubscribe方法未实现');
  };

  // 关闭连接
  MqttClient.prototype.end = function (force) {
    this.disconnected = true;
    if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
    if (this.keepaliveTimer) clearInterval(this.keepaliveTimer);
    if (this.connectTimeoutTimer) clearTimeout(this.connectTimeoutTimer);
    this.safeCloseSocket();
    this.emit('close');
  };

  // 导出connect方法
  var mqtt = {
    connect: function (options, urlOptions) {
      var finalOptions = typeof options === 'string' ? parseUrl(options) : options;
      var client = new MqttClient(finalOptions);
      client.connect();
      return client;
    }
  };

  return mqtt;

})));

二、核心优势

  1. 双端无缝兼容:同时支持微信小程序(wx API)和 H5(window WebSocket),一套代码两处运行

  2. 彻底解决兼容性报错

    • 修复 onOpen 不是函数task not found 经典报错
    • 摒弃 TextEncoder/TextDecoder 依赖,手动实现 UTF-8 编解码,适配所有小程序基础库版本
  3. 中文友好:完美解决 MQTT 消息中文乱码问题,收发中文无压力

  4. 稳定可靠:内置自动重连(指数退避策略)、心跳保活、连接超时处理,杜绝意外断连

  5. 开箱即用:已写死核心配置 wss://mq.dtszbj.com/mqtt,无需手动配置 URL、协议等参数

  6. 轻量无依赖:无第三方 npm 包依赖,直接引入即可使用,体积小巧

三、快速使用步骤

1. 保存文件

将上方完整 JS 代码复制,保存为 mqtt-wx.js 文件,放入你的小程序 / H5 项目目录(如 utils/mqtt-wx.js

2. 引入文件

微信小程序中引入

// 在需要使用的页面/组件js文件中
const mqtt = require('../../utils/mqtt-wx.js');

H5 中引入

<!-- 方式1:script标签引入(直接使用) -->
<script src="./utils/mqtt-wx.js"></script>

<!-- 方式2:ES6模块化引入(需打包工具支持,如Webpack/Vite) -->
import mqtt from './utils/mqtt-wx.js';

3. 核心使用示例(可直接复制运行)

// 1. 创建MQTT客户端(无需传参,已固定对接wss://mq.dtszbj.com/mqtt)
const mqttClient = mqtt.connect();

// 2. 监听连接成功事件
mqttClient.on('connect', () => {
  console.log('✅ MQTT连接成功!');
  
  // 3. 订阅指定主题(示例:订阅test_topic主题)
  mqttClient.subscribe('test_topic', (err) => {
    if (!err) {
      console.log('✅ 订阅主题test_topic成功!');
      // 4. 发布中文消息(向test_topic主题推送消息)
      mqttClient.publish('test_topic', '大家好!这是一条兼容小程序和H5的MQTT中文消息');
    } else {
      console.error('❌ 订阅主题失败:', err);
    }
  });
});

// 5. 监听收到的消息(核心:接收订阅主题的推送内容)
mqttClient.on('message', (topic, msg) => {
  console.log(`📩 收到主题【${topic}】的消息:`, msg);
  // 在这里编写消息处理逻辑(如渲染到页面、触发业务操作等)
});

// 6. 监听错误事件(排查问题必备)
mqttClient.on('error', (err) => {
  console.error('❌ MQTT异常:', err);
});

// 7. 监听连接关闭事件
mqttClient.on('close', () => {
  console.log('🔴 MQTT连接已关闭');
});

// 8. 手动关闭MQTT连接(如需退出页面/销毁组件时调用)
// mqttClient.end();

四、关键功能说明

1. 自动环境检测

代码内部自动判断运行环境,无需手动切换配置:

var isMiniProgram = typeof wx !== 'undefined' && typeof wx.connectSocket === 'function';
var isH5 = !isMiniProgram && typeof window !== 'undefined' && typeof window.WebSocket === 'function';

2. 手动 UTF-8 编解码(解决中文乱码核心)

  • stringToUint8Array(str):将字符串(含中文)转为 MQTT 协议支持的 UTF-8 Uint8Array 格式
  • uint8ArrayToString(buf):将收到的二进制数据解码为 UTF-8 字符串,完美支持中文

3. 稳定重连与心跳保活

  • 自动重连:连接断开后自动触发重连,采用指数退避策略(最大间隔 30 秒),避免频繁请求服务器
  • 心跳保活:默认 60 秒发送一次 PING 报文,维持长连接,防止被服务器主动断开

4. 核心 API 列表

API 方法功能说明
mqtt.connect()创建并初始化 MQTT 客户端,自动发起连接
client.on(event, cb)绑定事件监听(connect/message/error/close)
client.subscribe(topic, cb)订阅指定 MQTT 主题
client.publish(topic, msg)向指定主题发布消息
client.end()手动关闭 MQTT 连接,停止自动重连

五、常见问题排查

  1. 小程序提示 “未配置合法域名”

    • 解决方案:小程序后台 → 开发 → 开发设置 → 服务器域名,添加 mq.dtszbj.com 到「socket 合法域名」和「request 合法域名」
    • 开发环境临时方案:微信开发者工具 → 详情 → 本地设置 → 勾选 “不校验合法域名、HTTPS 证书”
  2. 连接超时 / 无法连接

    • 检查网络是否正常,mq.dtszbj.com 服务是否可用
    • 小程序端确认已配置合法域名,H5 端确认无跨域限制(服务端需开启 CORS)
    • 确认设备能正常访问 wss://mq.dtszbj.com/mqtt
  3. 消息收发异常

    • 确保 connect 事件触发后,再执行订阅 / 发布操作
    • 检查主题名称是否一致(MQTT 主题区分大小写)
    • 通过 error 事件监听错误信息,辅助排查问题

总结

这款 mqtt-wx.js 终极兼容版,专为小程序和 H5 场景量身打造,彻底解决了常规 MQTT 库的兼容性痛点,中文支持友好,无需复杂配置,直接引入即可快速实现 MQTT 消息收发功能,极大提升开发效率,节省排错时间!