一、前言
作为前后端分离项目,前后端交互是一个非常重要的功能。目前主流框架都是通过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'//使用signalrexportdefaultfunctionuseSignalr() {
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: () => {
return5000// 每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
usemqtt.js
在需要启用mqtt的页面引入usemqtt并封装成方法
import useMqtt from'@/utils/mqtt/usemqtt'//连接mqtt
connectMqtt() {
const { startMqtt } = useMqtt()
startMqtt()
},
这样在系统启动时就会启用mqtt而不是singalr

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

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

4.1 Signalr方式
集线器里重写OnDisconnectedAsync和OnConnectedAsync方法

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

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

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