likes
comments
collection
share

记录一次nestjs使用ws出现的问题

作者站长头像
站长
· 阅读数 11

需求分析

作者最近一段时间在写一个功能,一个权限更改后要做的事情,在管理项目中常见的权限,角色分配的问题。

我这里的需求是,比如我是管理员,我修改了某个人的角色,或者修改了角色对应的菜单权限,但是我这个人上线了,他如果不手动刷新,他是不知道修改了,并且他这里看到的还是之前的页面,这个就会出现一些问题了,想要得到修改后的状态必须要手动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中文文档有写,我这里就给大家写出来,

首先要安装的包有

ws
@nestjs/platform-ws
@nestjs/websockets

之后要在main.ts中将适配器切换为ws的

记录一次nestjs使用ws出现的问题

接下来我们就来写一下逻辑,比如,我们在有一个用户连接上时,我会放一个map来存,而标识就是用户id,所以当我们连接的时候,我会在路径上拼接到id,我的项目是拼接token,因为是演示项目,不好给大家展示出来,我写个大概的逻辑就可


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"}))
         } )
      }
  }
}

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官网可以看到这样一句话

记录一次nestjs使用ws出现的问题

这句话的意思是gateway可以被当作一个provide,它可以在内部注入其他服务,也可以被注入到其他地方,那我们想一想,我们直接注入gateway,然后我们把connectsMap放在gateway当中,因为我们的gateway可是实时的,并且它的数据是根据我们handleConnect和handledisConnect这俩生命周期来控制,所以就没有这个顾虑了,于是我将gateway作为provide传入要用的模块当中,那我们可以改造一下代码


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)
}
 
}
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等等这些不便之处,希望这篇文章能够对你有帮助!