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

需求分析

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

我这里的需求是,比如我是管理员,我修改了某个人的角色,或者修改了角色对应的菜单权限,但是我这个人上线了,他如果不手动刷新,他是不知道修改了,并且他这里看到的还是之前的页面,这个就会出现一些问题了,想要得到修改后的状态必须要手动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等等这些不便之处,希望这篇文章能够对你有帮助!

相关推荐
徐子颐7 小时前
从 Vibe Coding 到 Agent Coding:Cursor 2.0 开启下一代 AI 开发范式
前端
小月鸭7 小时前
如何理解HTML语义化
前端·html
jump6807 小时前
url输入到网页展示会发生什么?
前端
诸葛韩信7 小时前
我们需要了解的Web Workers
前端
brzhang7 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
yivifu8 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花8 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋8 小时前
场景模拟:基础路由配置
前端
六月的可乐8 小时前
实战干货-Vue实现AI聊天助手全流程解析
前端·vue.js·ai编程
Q_Q5110082858 小时前
python+django/flask的莱元元电商数据分析系统_电商销量预测
spring boot·python·django·flask·node.js·php