WebSocket(网关)
WebSocket 通过 @WebSocketGateway 实现,常配合 Socket.IO。
在 NestJS 中,WebSocket 功能通过 网关(Gateway) 来实现。网关是一个使用 @WebSocketGateway() 装饰器标记的类,提供了与 HTTP 控制器(Controller)类似的、基于装饰器的 WebSocket 服务器开发体验,是目前构建实时服务的主流选择。得益于其良好的平台抽象,NestJS 的网关可以兼容 socket.io 和原生 ws 等多个 WebSocket 库。
核心构件
1. @WebSocketGateway() 装饰器
用于将一个类标记为 WebSocket 网关。它可以接收一个配置对象来指定端口、命名空间和跨域资源共享(CORS)等选项。从功能上看,它类似 HTTP 中的 @Controller(),但与之并列,是与 HTTP 层平行的 WebSocket 独立网关,通常不建议通过注入 HttpService 等方式将其与 HTTP 处理器组在一起工作。
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
OnGatewayInit,
OnGatewayConnection,
OnGatewayDisconnect,
} from "@nestjs/websockets";
import { Server, Socket } from "socket.io";
@WebSocketGateway({
cors: { origin: "*" }, // 允许跨域(开发环境),生产环境需细化
namespace: "chat", // 可选命名空间
// port: 3001, // 不指定则使用 HTTP 服务器端口
})
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
// ... 实现生命周期方法
}
常见的配置选项:
- port:指定 WebSocket 服务器的端口。默认为与 HTTP 服务器相同的端口(例如
3000)。如果显式指定使用port: 3001,但同端口3001已被其他服务占用,就会导致冲突并报错。故额外指定端口时,需确保其空闲。 - namespace:命名空间用于将连接分组。默认根命名空间是
/(空字符串)。通过namespace: 'chat'可以让客户端连接到ws://localhost/chat或https://domain/chat(Socket.IO)。 - cors:跨域资源共享配置,对于 WebSocket 环境尤为重要。
- Socket.IO 下直接在
@WebSocketGateway()中配置cors对象。 - 使用原生
ws库时,CORS 通常由上层 HTTP 服务器通过app.enableCors()统一处理(需格外留意中间件的顺序)。
- Socket.IO 下直接在
2. @WebSocketServer() 装饰器
用于获取底层的 WebSocket 服务器实例,以便向所有连接到该网关的客户端广播消息。
@WebSocketServer()
server: Server; // 对于 socket.io,类型为 Server;对于 ws 库,可能是 WebSocketServer
获取到 server 实例后,即可调用 emit / send 等方法主动推送消息。
3. @SubscribeMessage() 装饰器
用于监听客户端发送的特定事件,是一个被动的消息处理器,需要等待客户端触发事件。
@SubscribeMessage('message')
handleMessage(client: Socket, payload: any): void {
console.log(`Received message: ${payload.text}`);
// 直接返回数据会向当前客户端发送一个以方法名为事件的响应
// 注意:如果返回 `WsResponse`,其 `event` 字段默认与方法名相同;如不特殊关注响应事件名称,可保持简单返回对象。
return { event: 'message', data: 'Message received!' };
}
@SubscribeMessage 支持复杂的返回场景:
- 直接返回一个对象:将对象作为
data,并自动使用'message'作为响应事件名。 - 完全手动控制:返回
Observable。 - 发送到不同客户端:既不依赖
@WebSocketServer也不返回数据,可以注入@WebSocketServer() server: Server并通过server.to(room).emit(...)任意控制消息的分发。 - 你还可以在网关中注入自定义服务,并与数据库、队列、业务逻辑等进行更深度的集成。
4. 生命周期钩子
通过实现相应接口,可以监控 WebSocket 连接的生命周期事件,适用于鉴权、清理资源等场景:
| 接口 | 方法 | 触发时机 |
|---|---|---|
OnGatewayInit | afterInit(server: Server) | WebSocket 服务器初始化后 |
OnGatewayConnection | handleConnection(client: Socket, ...args: any[]) | 新客户端连接时 |
OnGatewayDisconnect | handleDisconnect(client: Socket) | 客户端断开连接时 |
📦 项目集成
第一步:安装依赖
如果要使用 socket.io:
npm install @nestjs/websockets @nestjs/platform-socket.io socket.io
如果要使用原生库 ws,可安装:
npm install @nestjs/websockets @nestjs/platform-ws ws
第二步:定义网关
将核心逻辑写在网关类里。一般情况下,网关自身也是 @Injectable() 的,可依赖注入其他服务。
注意:通常情况下不需要在模块的 providers 中重复注册网关;默认情况下,NestJS 的 WebSocketsModule 会自动识别并实例化使用 @WebSocketGateway() 装饰器的类(该行为适用于全局模式)。但如果要对网关进行显式的模块管理,依然推荐在某些模块的 providers: [ChatGateway] 里注册,这能确保在被其他模块引用时,网关实例是受控的。
第三步:注册模块
@nestjs/websockets 并不导出 WebSocketsModule;网关本质上只是一个普通的 @Injectable() 类,只需像其他 Provider 一样注册到某个模块的 providers 中即可,NestJS 会自动识别其 @WebSocketGateway() 元数据并接管连接:
// app.module.ts(或对应的功能模块)
import { Module } from "@nestjs/common";
import { ChatGateway } from "./chat.gateway";
@Module({
providers: [ChatGateway], // 直接注册即可,无需额外导入某个 "WebSocket 模块"
})
export class AppModule {}
如果希望网关只属于某个特定功能模块(不在全局范围共享实例),将其 providers 声明在该功能模块中即可,无需其他配置。
🧩 适配器(Adapter)选择
NestJS 的 WebSocketAdapter 接口抽象了不同 WebSocket 库之间的差异,让网关代码可以平滑切换底层实现。
- Socket.IO 适配器(
@nestjs/platform-socket.io): 提供更丰富的功能,如自动降级(轮询+xhr)、房间(Rooms)支持、命名空间(Namespaces)和中间件。官方示例文档详尽,适合大部分实时通信场景。 - 原生 WebSocket(ws)适配器(
@nestjs/platform-ws): 原生库ws更为轻量,性能更高,适合对传输体积和原始吞吐量极其敏感的场景。不提供自动降级和房间等内置高级特性,需要手动实现分组逻辑。两种适配器都支持使用app.useWebSocketAdapter(new WsAdapter(app))来指定。
在实际项目里,
@nestjs/websockets是所有 WebSocket 功能的基础包,而@nestjs/platform-socket.io是 socket.io 的专属适配器实现,@nestjs/platform-ws则是ws的专属适配器。
️ 安全认证(Authentication)
WebSocket 认证主要通过在连接建立阶段验证 Token 来实现,例如提取 Socket.IO 握手时的查询参数或请求头。
Socket.IO 示例:
import { WebSocketGateway, OnGatewayConnection } from "@nestjs/websockets";
import { Socket } from "socket.io";
@WebSocketGateway()
export class AuthGateway implements OnGatewayConnection {
handleConnection(client: Socket) {
const token = client.handshake.auth.token;
// 验证 token,若无效则断开连接
if (!token || !this.validateToken(token)) {
client.disconnect();
return;
}
// 认证通过后可执行额外逻辑
}
}
原生 ws 示例:
原生 ws 适配器下,可通过 client.upgradeReq(或 Node.js 的 IncomingMessage)获取 headers 中的 authorization 字段。所以在实际集成时,建议优先考虑使用官方推荐的 @nestjs/platform-ws,而不要直接依赖 ws 的原始回调。
对于生产环境,建议将认证逻辑独立为一个**守卫(Guard)**或中间件,以提高代码的可维护性。
📈 高级技巧与最佳实践
房间(Rooms)管理
对于聊天室这类多房间的场景,可以利用 socket.join() / socket.leave() 以及 server.to(room).emit()。
@SubscribeMessage('joinRoom')
handleJoinRoom(client: Socket, roomName: string) {
client.join(roomName);
client.emit('joinedRoom', roomName);
}
@SubscribeMessage('messageToRoom')
handleMessageToRoom(client: Socket, payload: { room: string; message: string }) {
this.server.to(payload.room).emit('roomMessage', payload.message);
}
网关设计与可测试性
推荐将网关仅作为轻量级消息路由层,具体的业务逻辑(如消息持久化、推送通知等)应委托给独立的服务(Service)处理。这样可以保持网关代码的简洁,并使其更易于单元测试。
// 单一职责的网关示例:只负责收发,业务逻辑交给 service
constructor(private messageService: MessageService) {}
@SubscribeMessage('message')
async handleMessage(client: Socket, payload: any) {
const response = await this.messageService.processMessage(payload);
// 返回给当前客户端或广播
return response;
}
部署与性能
- 扩展性:Socket.IO 和 WebSocket 都支持使用 Redis Adapter 进行水平扩展,允许多个 Node.js 进程之间共享连接信息和广播消息。基本思路是先安装
socket.io-redis包,再通过IoAdapter启用 Redis 适配器。官方文档提供了现成的集成步骤。 - 负载均衡:如使用 Nginx 或 HAProxy,务必启用
proxy_pass并配置负载均衡策略(例如基于ip_hash或sticky session),确保同一客户端始终路由到同一后端服务,避免长连接中断或认证失败。在实际生产环境里,还需要额外验证 WebSocket 连接升级(Upgrade)头是否被防火墙或代理正确转发。
总结
| 概念 | 核心作用 |
|---|---|
@WebSocketGateway() | 声明一个类为 WebSocket 网关,并定义端口、命名空间、CORS 等 |
@WebSocketServer() | 获取底层服务器实例,用于主动广播消息 |
@SubscribeMessage() | 定义消息监听器,处理客户端发送的特定事件 |
| 生命周期钩子 | 监听连接、断开、初始化等事件,处理认证/资源清理 |
| 适配器(Adapter) | 抽象底层 WebSocket 库(Socket.IO / ws),灵活切换 |
| 认证与授权 | 在 handleConnection 或自定义守卫中验证客户端身份 |
| 房间与命名空间 | 逻辑隔离,实现群组通信等功能 |
对于绝大多数实时通信场景,Socket.IO 提供了最丰富的开箱即用功能,开发效率很高。若对性能和包体积有极致要求,可考虑 原生 ws 库进行定制开发。官方文档的示例都比较简略,建议结合官方教程和社区实战示例来进一步完善鉴权,也可多阅读 NestJS 的 WebSocket 官方文档 来获得更全面的配置信息。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| NestJS 文档 | 官方 |
| Gateways | 本章主题 |
| Request lifecycle | 执行顺序 |
相关文章
认证与授权
认证(Authentication):确认「你是谁」(如 JWT、Session)。 - 授权(Authorization):确认「你能做什么」(如 RBAC、策略检查)。
缓存
@nestjs/cache-manager 统一缓存 API,存储实现可插拔。
提供者与服务
Service 是最常见的 Provider,封装业务逻辑。见 module。
守卫 (Guards) 与授权
守卫决定是否放行请求,常用于认证与授权。见 auth。
配置管理(Config 模块)
@nestjs/config 加载 .env 并提供 ConfigService。见 工程化 env。
数据库集成(以 TypeORM 为例)
Nest 通过 @nestjs/typeorm 等包集成 ORM;生产环境用 migration,慎用 synchronize。亦可选用 Prisma、MikroORM 等(见 官方 Database)。
Series
new
19 / 19