uni-app实现本地MQTT连接

最近接到安卓端的需求,要求使用MQTT连接实现设备信息的收发。

可能有兄弟不太清楚 MQTT协议 是什么,简单地说它是一种轻量级的、基于发布/订阅模式的消息传输协议,广泛用于物联网(IoT)领域。

常见的操作就是连接后有N个设备订阅了主题A,这时候任意一台设备对主题A发布了一条信息,则当前N个设备都能收到这条消息。

至于其他的也不用知道的太清楚,反正知道一般是这么玩的就行。

假如你想知道的更为详细,可以询问 Trae

需求

  1. 实现MQTT连接
  2. 实现主题(Topic)订阅
  3. 能够发送消息
  4. 能够接收消息

实现方案

首先是安装 mqtt.js 插件和 uuid.js 插件。

bash 复制代码
npm i mqtt@3.0.0 uuid

注意 : 这里一定要安装 mqtt v3.0 版本,我之前装了一个 v5.x 版本,有比较大的 API 改动。项目上时间紧张,遂没有再尝试。(有兴趣的朋友可以考虑用最新版实现)

uuid 只是起到生成 连接ID 和 消息ID 的作用,这里也可以直接使用时间戳的方式,但是为了保证不重复,这里便不再更换了。

js 复制代码
// mqttClient.js 对 MQTT 的简单封装
let client = null;
/**
 * 初始化 MQTT 连接
 * @param {Object} options
 * @param {string} options.host - 服务器地址(不带协议)
 * @param {number} options.port - 端口(通常 WebSocket 是 8083、8084 或 443)
 * @param {string} options.clientId - 客户端 ID(建议唯一)
 * @param {string} [options.username] - 用户名
 * @param {string} [options.password] - 密码
 * @param {boolean} [options.useWss=false] - 是否使用 wss(HTTPS)
 */
export function initMqtt(options) {
	const {
		host,
		port,
		clientId,
		username = '',
		password = '',
		useWss = false
	} = options;

	// 根据平台选择协议(App 和 H5 用 ws/wss,小程序用 wxs/wss)
	let protocol = 'ws';
	if (useWss) protocol = 'wss';

	// #ifdef MP-WEIXIN
	protocol = useWss ? 'wxs' : 'wxs';
	// #endif

	const url = `${protocol}://${host}:${port}/mqtt`; // 注意:路径可能是 / 或 /mqtt,根据你的 broker 配置

	const connectOptions = {
		clientId: clientId,
		username: username,
		password: password,
		keepalive: 60, // 心跳间隔(秒)
		reconnectPeriod: 5000, // 自动重连间隔(毫秒)
		connectTimeout: 10000, // 连接超时
		clean: true // 是否清除会话
	};

	// 引入 mqtt(必须用 min 版本,否则小程序报错)
	const mqtt = require('mqtt/dist/mqtt.min.js');

	client = mqtt.connect(url, connectOptions);

	// 监听连接事件
	client.on('connect', () => {
		console.log('MQTT 连接成功');
	});

	client.on('reconnect', () => {
		console.log('MQTT 正在重连...');
	});

	client.on('error', (err) => {
		console.error('MQTT 连接错误:', err);
	});

	client.on('close', () => {
		console.log('MQTT 连接已关闭');
	});

	return client;
}

/**
 * 订阅主题
 * @param {string} topic
 * @param {Function} callback - (topic, message) => {}
 */
export function subscribe(topic, callback) {
	if (!client) {
		console.warn('MQTT 客户端未初始化');
		return;
	}

	client.subscribe(topic, (err) => {
		if (!err) {
			console.log(`订阅主题成功: ${topic}`);
		} else {
			console.error('订阅失败:', err);
		}
	});

	// 监听消息(全局监听,需配合 topic 过滤)
	client.on('message', (receivedTopic, message) => {
		// if (receivedTopic === topic) {
			try {
				// 尝试解析 JSON
				const payload = JSON.parse(message.toString());
				callback(receivedTopic, payload);
			} catch (e) {
				// 非 JSON 消息
				callback(receivedTopic, message.toString());
			}
		// }
	});
}

/**
 * 发布消息
 * @param {string} topic
 * @param {string|object} message
 */
export function publish(topic, message) {
	if (!client) {
		console.warn('MQTT 客户端未初始化');
		return;
	}

	const payload = typeof message === 'object' ? JSON.stringify(message) : message;
	client.publish(topic, payload, {
		qos: 1
	}, (err) => {
		if (err) {
			console.error('发布失败:', err);
		} else {
			console.log(`发布成功: ${topic}`, payload);
		}
	});
}

/**
 * 断开连接
 */
export function disconnect() {
	if (client) {
		client.end(true); // true 表示强制断开
		client = null;
		console.log('MQTT 已断开');
	}
}

我让 Trae 基于网上的封装简单进行了修改,生成了上述封装Js文件。

注意 :在监听消息的部分,如果你的 Topic 是固定的,则请将 if (receivedTopic === topic)注释打开 ,如果不是,则注释掉,后续有详解。

现在进入到 page/index/index.vue 文件中

js 复制代码
import {initMqtt, subscribe, publish, disconnect} from './mqttClient.js';
export default {
    data() {
        return {
            host: 'broker.emqx.io',
            messages: []
        }
    },
    mounted() {
        this.connect()
    },
    beforeDestroy() {
        this.disconnect()
    },
    methods: {
        // 连接
        connect() {
            const uuid = uuidv4();
            const client = initMqtt({
                host: this.host,
                port: 8083,
                clientId: uuid,
                username: '',
                password: '',
                useWss: false // 如果是 HTTPS/WSS,设为 true
            });
            if (client) {
                this.subscribeTopic();
            }
        },
        // 订阅 test/topic 主题
        subscribeTopic() {
            subscribe('test/topic', (topic, message) => {
                // 接收到的消息
                const msg = JSON.stringify(message);
                this.messages.unshift(msg);
            });
        },
        // 订阅 带通配符 的主题
        subscribeTopics() {
            subscribe('+/topic', (topic, message) => {
                // 接收到的消息
                const msg = JSON.stringify(message);
                this.messages.unshift(msg);
            });
        },
        // 发送消息到 test/topic 主题
        publishMessage() {
            publish('test/topic', {
                name: 'uni-app',
                time: new Date().toISOString(),
                msg: '测试信息'
            });
        },
        disconnect() {
            disconnect();
        }
    }
}

注意:在卸载页面的时候建议将 MQTT连接 断开,或者在 uni-app 中使用也可以使用 unload 方法断开连接。

另外订阅主题可以采用常规的,确定性的 Topic 主题名称,也可以采用 通配符(+) 的主题名称。

Trae还提醒我一定要注意,假如你的 Topic 为:

123/456/report 则你的通配符主题名称为 +/+/report

假如你的 Topic 为:

/123/456/report 则你的通配符主题名称为 +/+/+/report,因为他的第一位是 空字符串

结论

在开发 IoT 设备时这个协议用的比较广泛,如果不接触这个方面一般不会用上这个。

另外 MQTT 本身自带心跳自动重连机制,大致上相当于 WebSocket,这个部分一般不需要做特殊开发。

相关推荐
EndingCoder13 小时前
Any、Unknown 和 Void:特殊类型的用法
前端·javascript·typescript
oden13 小时前
代码高亮、数学公式、流程图... Astro 博客进阶全指南
前端
GIS之路13 小时前
GDAL 实现空间分析
前端
JosieBook14 小时前
【Vue】09 Vue技术——JavaScript 数据代理的实现与应用
前端·javascript·vue.js
pusheng202514 小时前
算力时代的隐形防线:数据中心氢气安全挑战与技术突破
前端·安全
起名时在学Aiifox14 小时前
前端文件下载功能深度解析:从基础实现到企业级方案
前端·vue.js·typescript
2501_9418779815 小时前
从配置热更新到运行时自适应的互联网工程语法演进与多语言实践随笔分享
开发语言·前端·python
云上凯歌15 小时前
01 ruoyi-vue-pro框架架构剖析
前端·vue.js·架构
华仔啊16 小时前
JavaScript 如何准确判断数据类型?5 种方法深度对比
前端·javascript