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

相关推荐
FungLeo1 小时前
成为全栈·Node 后端篇·全文搜索:从 LIKE 到全文索引
node.js·全文索引·模糊搜索·全文搜索·成为全栈·like 搜索
mldong4 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排4 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60615 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角6 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!6 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰6 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi8 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen8 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马9 小时前
AI源码分析:拆解模型底层实现逻辑
前端