SimpleAdmin手摸手教学之:即时通讯

一、前言

作为前后端分离项目,前后端交互是一个非常重要的功能。目前主流框架都是通过Socket实现,本系统自然也是实现了基于Signalr的前后端交互,并在此基础上实现了基于MQTT的前后端交互功能,MQTT相比socket业务场景更多更灵活,在物联网方向有着非常多的应用。在工业物联网方向,mqtt也是应用非常广泛,最为.neter来说学习mqtt很有必要。

二、基于Signalr

系统默认是用的Signalr做前后端通信,关于Signalr使用文档可以去看Furion的文档:https://furion.baiqian.ltd/docs/signalr

2.1 后端部分

首先需要在启动时注册signalr服务和集线器。

新建一个集线器,在类名上加上MapHub特性,这样我们前端就能通过signalr连接到后端。

2.2 前端部分

前端的signalr基于"@microsoft/signalr": "^7.0.0" 可以在package.json中查看,关于signalr的连接也是非常简单,我封装在了utils文件夹下的signalr.js

import { Modal } from 'ant-design-vue'
import sysConfig from '@/config/index'
import tool from '@/utils/tool'
import * as signalR from '@microsoft/signalr'
import * as signalrMessage from './mqtt/message'
//使用signalr
export default function useSignalr() {
	const userInfo = tool.data.get('USER_INFO') //用户信息
	let socketUrl = '/hubs/simple' //socket地址
	if (sysConfig.VITE_PROXY === 'false') {
		socketUrl = sysConfig.API_URL + socketUrl //判断是否要走代理模式,走了的话发布之后直接nginx代理
	}
	//开始
	const startSignalr = () => {
		//初始化连接
		const connection = init()
		// 启动连接
		connection.start().then(() => {
			console.log('启动连接')
		})
	}

	//初始化
	const init = () => {
		console.log('初始化SignalR对象')
		// SignalR对象
		const connection = new signalR.HubConnectionBuilder()
			.withUrl(socketUrl, {
				accessTokenFactory: () => tool.data.get(sysConfig.ACCESS_TOEKN_KEY)
			})
			.withAutomaticReconnect({
				nextRetryDelayInMilliseconds: () => {
					return 5000 // 每5秒重连一次
				}
			}) //自动重新连接
			.configureLogging(signalR.LogLevel.Information)
			.build()
		connection.keepAliveIntervalInMilliseconds = 15 * 1000 // 心跳检测15s
		// connection.serverTimeoutInMilliseconds = 30 * 60 * 1000 // 超时时间30m
		// 断开连接
		connection.onclose(async () => {
			console.log('断开连接')
		})

		//断线重新
		connection.onreconnected(() => {
			console.log('断线重新连接成功')
		})
		//消息处理
		receiveMsg(connection)
		return connection
	}

	//接收消息处理
	const receiveMsg = (connection) => {
		// 接收登出
		connection.on('LoginOut', (data) => {
			signalrMessage.loginOut(data)
		})
	}

	//页面销毁
	onUnmounted(() => {})
	return { startSignalr }
}

使用也是很简单,只需要在需要连接的页面引用usesignalr,在本系统中,我们需要全局监听,所以我在layout文件夹下的index.vue中启用signalr并封装一个方法用来连接signalr

import useSignalr from '@/utils/signalr'
//连接signalr
connectSignalr() {
	const { startSignalr } = useSignalr()
	startSignalr()
}

然后在页面created的最后连接signalr就行

F12查看控制台输出,登录系统之后,应该会提示连接singlar成功。

三、基于Mqtt

如果使用mqtt则需要一个mqtt broker来进行数据中转,前端和后端都是通过客户端的方式去连接服务端,然后再通过发布/订阅的方式进行数据交互。这里mqtt broker推荐使用emqx来搭建。

下载地址:https://www.emqx.com/zh/try?product=broker

后端mqtt客户端基于我自己封装的SimpleMQTT组件,gitee地址:https://gitee.com/zxzyjs/SimpleMQTT.git

前端基于"mqtt": "^4.3.7",可在package.json中查看。

3.1 MQTT配置

既然登录系统需要用户名/密码登录,那么连接mqtt服务器也应该需要账号密码才行,然而如果将账号密码信息写在前端配置文件中是不安全的,别有用心的人可能会盗取我们的用户名和密码。而且账号密码写死在前端也显得不那么灵活,如果账号密码修改了需要重新打包上传发布。基于以上两种情况,本系统将mqtt配置改为可配置化,用户可以在系统运维->系统配置->MQTT配置中配置域名和账号密码。

3.2 后端部分

 首先需要在SimpleAdmin.Web.Core项目的配置文件中的WebSettings打开mqtt配置。

配置账号密码

系统会自动注册mqtt服务,连接mqttbroker

因为我们mqtt的连接信息都是存储在后端,前端想要连接就得通过接口获取连接信息,所以我们需要写一个接口来返回连接信息和订阅的主题。

3.3 前端部分

首先需要在配置文件中设置 VITE_MQTT = true

关于mqtt的封装可以在utils下的mqtt文件夹中找到。

mqtt.js
 import * as mqtt from 'mqtt/dist/mqtt.min.js'
//mqtt客户端对象
export const mqttClient = ref()

//初始化操作
export const init = (url, options) => {
	mqttClient.value = mqtt.connect(url, options) //连接mqtt
	//报错
	mqttClient.value.on('error', (error) => {
		console.log('mqtt连接报错')
		console.log(error)
	})
	//重连
	mqttClient.value.on('reconnect', (error) => {
		console.log('mqtt重连')
		console.log(error)
	})
}

//接收消息
export const link = (callback) => {
	mqttClient.value.on('connect', callback)
}

//订阅
export const subscribes = (topics) => {
	if (Array.isArray(topics)) {
		//订阅频道
		topics.forEach((topic) => {
			subscribe(topic)
		})
	}
}

//订阅
export const subscribe = (topic) => {
	mqttClient.value.subscribe(topic, (error) => {
		if (!error) {
			console.log(topic, '订阅成功')
		} else {
			console.log(topic, '订阅失败')
		}
	})
}

//取消订阅
export const unSubscribes = (topic) => {
	mqttClient.value.unsubscribe(topic, (error) => {
		if (!error) {
			console.log(topic, '取消订阅成功')
		} else {
			console.log(topic, '取消订阅失败')
		}
	})
}

//接收消息
export const getMessage = (callback) => {
	mqttClient.value.on('message', callback)
}

//关闭连接
export const close = () => {
	console.log('关闭连接')
	mqttClient.value.end()
	mqttClient.value = null
}
usemqtt.js
 import { mqttClient, init, link, getMessage, close, subscribes } from '@/utils/mqtt/mqtt'
import mqttapi from '@/api/auth/mqttApi'
import * as mqttMessage from './message'
//使用mqtt
export default function useMqtt() {
	let options = {
		clientId: '',
		username: '',
		password: '',
		clean: true,
		keepalive: 60,
		connectTimeout: 3000
	}
	//连接mqtt并订阅频道
	const startMqtt = () => {
		mqttapi.getParameter().then((res) => {
			options.clientId = res.clientId
			options.username = res.userName
			options.password = res.password
			console.log(options)
			//mqtt初始化
			init(res.url, options)
			//连接成功
			link(() => {
				console.log('mqtt连接成功')
				console.log(res.topics)
				subscribes(res.topics)
				//接收消息
				receivceMessage()
			})
		})
	}
	//接收消息
	const receivceMessage = () => {
		getMessage((topic, message) => {
			console.log(`收到主题${topic}的消息`)
			const msg = JSON.parse(message.toString())
			console.log(`消息为:${message}`)
			// mqttMessage.loginOut(message)
			const msgType = msg.MsgType
			if (msgType === 'LoginOut') {
				var clientIds = msg.Data.ClientIds
				clientIds.forEach((clientId) => {
					if (clientId == options.clientId) {
						mqttMessage.loginOut(msg.Data.Message)
					}
				})
			}
		})
	}

	//页面销毁
	onUnmounted(() => {
		if (mqttClient.value) {
			close()
		}
	})
	return { startMqtt }
}

在需要启用mqtt的页面引入usemqtt并封装成方法

import useMqtt from '@/utils/mqtt/usemqtt'
//连接mqtt
connectMqtt() {
	const { startMqtt } = useMqtt()
	startMqtt()
},

这样在系统启动时就会启用mqtt而不是singalr

F12查看控制台输出,登录系统之后已经成功连上mqtt服务器并订阅了Topic

四、在线用户

通过即时通讯我们可以判断当前用户是否在线,原理非常简单,用户登录系统后,无论哪种方式后台都会收到当前token连接了,然后把当前连接的客户端ID存储到该token信息中的客户端id列表中,当用户关闭了浏览器或者网络断开了连接,则会将断开的客户端idtoken中的客户端id列表中删除。在前端会话管理中只需要判断当前token的客户端id列表是否有数据就行了,如果有就是在线,如果没有就是离线。

4.1 Signalr方式

集线器里重写OnDisconnectedAsyncOnConnectedAsync方法

收到连接或断开的请求后更新redis

4.2 mqtt方式

对应signalrOnConnectedAsyncOnDisconnectedAsyncmqtt叫做上线下线,设备上线代表连接到服务器,设备下线代表断开服务器,通过emqx我们可以订阅上下线主题获取设备的上下线信息。所以我们需要启动一个客户端后台去订阅上下线事件,并且不能像iis那样会被回收,所以我们可以通过建立workerservice项目来后台运行。对应的SimpleAdmin.Background后台服务层。

MqttWorker中监听设备上下线主题,然后根据客户端id去更新redis就行了,原理和signalr一样。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章