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一樣。

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