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

相关推荐
__zRainy__4 分钟前
Node系列 · Node基础:全局变量与全局对象
开发语言·前端·javascript
码云骑士9 分钟前
104-实战论文搜索引擎-ArXiv爬取-Milvus存储-RAG问答-Gradio前端
前端·python·搜索引擎·milvus
sunly_20 分钟前
TypeScript总结:15、类型速查
前端·javascript·typescript
Brown.alexis20 分钟前
es6知识点3-自备使用
前端·javascript·es6
2601_9539880723 分钟前
Ricon组态系统vs传统组态软件:为什么选择新一代Web组态平台
前端·后端·物联网·tcp/ip·数学建模·前端框架
天空之城--28 分钟前
Claude Code 高效开发 Web 2D/3D 完全指南:心法、自定义 Skill 体系与社区技能包实战
前端·3d
IT_陈寒35 分钟前
SpringBoot自动配置坑了我三天,原来漏了这个注解
前端·人工智能·后端
逝水无殇1 小时前
HTML 文本格式化详解:掌握 <strong>、<em>、<mark>、<del> 等常用标签
前端·javascript·html
特立独行的猫a1 小时前
DeepSeek Harness插件和工具的区别介绍及开发入门指南
前端·ai·agent·插件·deepseek·harness
小新讲网安1 小时前
HTTP请求走私攻击实战:CL.TE与TE.CL绕过前端服务器全解析
服务器·前端·网络·web安全·http·架构·漏洞