需求分析
作者最近一段时间在写一个功能,一个权限更改后要做的事情,在管理项目中常见的权限,角色分配的问题。
我这里的需求是,比如我是管理员,我修改了某个人的角色,或者修改了角色对应的菜单权限,但是我这个人上线了,他如果不手动刷新,他是不知道修改了,并且他这里看到的还是之前的页面,这个就会出现一些问题了,想要得到修改后的状态必须要手动reload,但是他不知道他该啥时候reload,这个时候我就想到了用websocket来实现双端实时通信,当我们权限修改后,会推送给这个用户一个消息,等用户监听到这个消息时,可以给个提示,说你的权限被修改了,来个modal框,然后点击按钮后会进行reload,这样我们的用户体验就不错了。
我之前有写过如何在nestjs中体验websocket的,那次用的socket.io 这个平台,这次我想并没有太多用到太多的功能,比如namespace , subscribeMessage ,因为是简单的收发消息,而且ws比socket.io要快,既然如此,我就选择了这个来写,不过这个确实没有太多开箱即用的功能,没有socket.io方便,不过也足够了。
项目起步
要使用这个ws和socket.io还是不一样的,因为nestjs官方默认适配器是socket.io,所以当我们按照官方文档的步骤就能完成,但是用ws就不行,ws的步骤在netsj中文文档有写,我这里就给大家写出来,
首先要安装的包有
bash
ws
@nestjs/platform-ws
@nestjs/websockets
之后要在main.ts中将适配器切换为ws的
接下来我们就来写一下逻辑,比如,我们在有一个用户连接上时,我会放一个map来存,而标识就是用户id,所以当我们连接的时候,我会在路径上拼接到id,我的项目是拼接token,因为是演示项目,不好给大家展示出来,我写个大概的逻辑就可
socket.service.ts
import { Injectable } from '@nestjs/common';
import {WebSocket} from 'ws'
@Injectable()
export class SocketService {
connectsMap = new Map<string, WebSocket[]>([])
addConnect (id:string, socket:WebSocket){
const curConnect = this.connectsMap.get(id)
if(curConnect){
curConnect.push(socket)
} else {
this.connectsMap.set(id, [socket])
}
}
sendMessage<T>(id:string, data:T){
const sockets = this.connectsMap.get(id)
if(sockets?.length){
sockets.forEach((socket) => {
socket.send(JSON.stringify({type:"111"}))
} )
}
}
}
socket.gateway.ts
import { WebSocketGateway, OnGatewayConnection, OnGatewayDisconnect, WebSocketServer } from '@nestjs/websockets';
import { SocketService } from './socket.service';
import { WebSocket,Server } from 'ws';
import { IncomingMessage } from 'http';
@WebSocketGateway(
{
cors: {
origin: '*'
}
}
)
export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect{
@WebSocketServer()
server: Server;
constructor(private readonly socketService: SocketService) {}
handleConnection(socket: WebSocket, request: IncomingMessage) {
const id = request.url
console.log(id);
if(!id) {
socket.close()
return
}
this.socketService.addConnect(id, socket)
}
handleDisconnect(client: any) {
return 1
}
}
逻辑差不多就是这样,其实就是连接的时候存一个用户,断开的逻辑这里就先不写了,也就是清除一个而已, 当我们发生修改的时候,我会把service注入到要用的模块当中,但是我们知道,其实你每一次注入,相当于一次实例化,那你觉得你注入后的connectsMap还有值了吗,这个地方我当时一直为空,就突然想到是这个原因,那我应该怎么注入呢,我们在nestjs官网可以看到这样一句话
这句话的意思是gateway可以被当作一个provide,它可以在内部注入其他服务,也可以被注入到其他地方,那我们想一想,我们直接注入gateway,然后我们把connectsMap放在gateway当中,因为我们的gateway可是实时的,并且它的数据是根据我们handleConnect和handledisConnect这俩生命周期来控制,所以就没有这个顾虑了,于是我将gateway作为provide传入要用的模块当中,那我们可以改造一下代码
socket.gateway.ts
import { WebSocketGateway, OnGatewayConnection, OnGatewayDisconnect, WebSocketServer } from '@nestjs/websockets';
import { SocketService } from './socket.service';
import { WebSocket,Server } from 'ws';
import { IncomingMessage } from 'http';
@WebSocketGateway(
{
cors: {
origin: '*'
}
}
)
export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect{
@WebSocketServer()
server: Server;
connectsMap = new Map<string, WebSocket[]>([])
constructor(private readonly socketService: SocketService) {}
handleConnection(socket: WebSocket, request: IncomingMessage) {
const id = request.url
console.log(id);
if(!id) {
socket.close()
return
}
this.addConnect(id, socket)
}
handleDisconnect(client: any) {
return 1
}
addConnect (id:string, socket:WebSocket){
const curConnect = this.connectsMap.get(id)
if(curConnect){
curConnect.push(socket)
} else {
this.connectsMap.set(id, [socket])
}
}
sendMessage<T>(id:string, data:T){
const sockets = this.connectsMap.get(id)
this.socketService.sendMessage(sockets, data)
}
}
socket.service.ts
import { Injectable } from '@nestjs/common';
import {WebSocket} from 'ws'
@Injectable()
export class SocketService {
sendMessage<T>(sockets:WebSocket[], data:T){
if(sockets?.length){
sockets.forEach((socket) => {
socket.send(JSON.stringify({type:"111", content: data}))
} )
}
}
}
我将控制连接数的逻辑放在gateway中,将发送消息等等其他服务放在service当中,之后我们注入的时候,在provide当中提供这俩即可,这样我们就保证了只有一个map,就可以正常实现逻辑了。
最后
如果不是太简单的ws交互,还是推荐大家用socket.io作为驱动平台,ws确实不方便,没有namespace等等这些不便之处,希望这篇文章能够对你有帮助!