# NestJS 使用指南 本文档提供 NestJS 在游戏后端开发中的常用模式和最佳实践。 ## 目录 - [核心概念](#核心概念) - [模块化开发](#模块化开发) - [控制器与路由](#控制器与路由) - [服务与依赖注入](#服务与依赖注入) - [WebSocket 实时通信](#websocket-实时通信) - [数据验证](#数据验证) - [异常处理](#异常处理) ## 核心概念 NestJS 采用模块化架构,主要由以下几个部分组成: - **Module(模块)**:组织代码的基本单元 - **Controller(控制器)**:处理 HTTP 请求 - **Provider(提供者)**:包括 Service、Repository 等,处理业务逻辑 - **Gateway(网关)**:处理 WebSocket 连接 ## 模块化开发 ### 创建游戏模块 使用 NestJS CLI 快速生成模块: ```bash nest g module game nest g controller game nest g service game ``` ### 模块示例 ```typescript // src/game/game_module.ts import { Module } from '@nestjs/common'; import { GameController } from './game_controller'; import { GameService } from './game_service'; @Module({ controllers: [GameController], providers: [GameService], exports: [GameService], // 导出供其他模块使用 }) export class GameModule {} ``` 在根模块中导入: ```typescript // src/app_module.ts import { Module } from '@nestjs/common'; import { GameModule } from './game/game_module'; @Module({ imports: [GameModule], }) export class AppModule {} ``` ## 控制器与路由 控制器负责处理 HTTP 请求,定义 RESTful API。 ### 基础控制器示例 ```typescript // src/api/player_controller.ts import { Controller, Get, Post, Body, Param } from '@nestjs/common'; import { PlayerService } from '../service/player_service'; import { CreatePlayerDto } from '../model/dto/create_player_dto'; @Controller('api/players') export class PlayerController { constructor(private readonly playerService: PlayerService) {} // GET /api/players @Get() findAll() { return this.playerService.findAll(); } // GET /api/players/:id @Get(':id') findOne(@Param('id') id: string) { return this.playerService.findOne(id); } // POST /api/players @Post() create(@Body() createPlayerDto: CreatePlayerDto) { return this.playerService.create(createPlayerDto); } } ``` ### 常用装饰器 - `@Get()`, `@Post()`, `@Put()`, `@Delete()` - HTTP 方法 - `@Param()` - 路由参数 - `@Body()` - 请求体 - `@Query()` - 查询参数 - `@Headers()` - 请求头 ## 服务与依赖注入 服务层包含业务逻辑,通过依赖注入使用。 ### 服务示例 ```typescript // src/service/player_service.ts import { Injectable } from '@nestjs/common'; import { CreatePlayerDto } from '../model/dto/create_player_dto'; import { Player } from '../model/player_entity'; @Injectable() export class PlayerService { private players: Player[] = []; findAll(): Player[] { return this.players; } findOne(id: string): Player { return this.players.find(player => player.id === id); } create(createPlayerDto: CreatePlayerDto): Player { const player: Player = { id: Date.now().toString(), ...createPlayerDto, createdAt: new Date(), }; this.players.push(player); return player; } updatePosition(id: string, x: number, y: number): Player { const player = this.findOne(id); if (player) { player.x = x; player.y = y; } return player; } } ``` ## WebSocket 实时通信 游戏需要实时通信,使用 WebSocket Gateway。 ### 安装依赖 ```bash pnpm add @nestjs/websockets @nestjs/platform-socket.io socket.io ``` ### Gateway 示例 ```typescript // src/api/game_gateway.ts import { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayConnection, OnGatewayDisconnect, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; @WebSocketGateway({ cors: { origin: '*', }, }) export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; // 玩家连接 handleConnection(client: Socket) { console.log(`Player connected: ${client.id}`); } // 玩家断开 handleDisconnect(client: Socket) { console.log(`Player disconnected: ${client.id}`); } // 监听玩家移动事件 @SubscribeMessage('player-move') handlePlayerMove(client: Socket, payload: { x: number; y: number }) { // 广播给所有其他玩家 client.broadcast.emit('player-moved', { playerId: client.id, x: payload.x, y: payload.y, }); return { success: true }; } // 监听玩家攻击事件 @SubscribeMessage('player-attack') handlePlayerAttack(client: Socket, payload: { targetId: string }) { // 发送给特定玩家 this.server.to(payload.targetId).emit('attacked', { attackerId: client.id, }); return { success: true }; } // 服务器主动推送游戏状态 broadcastGameState(gameState: any) { this.server.emit('game-state', gameState); } } ``` ### 在模块中注册 ```typescript // src/game/game_module.ts import { Module } from '@nestjs/common'; import { GameGateway } from '../api/game_gateway'; import { GameService } from '../service/game_service'; @Module({ providers: [GameGateway, GameService], }) export class GameModule {} ``` ## 数据验证 使用 DTO(Data Transfer Object)和 class-validator 进行数据验证。 ### 安装依赖 ```bash pnpm add class-validator class-transformer ``` ### DTO 示例 ```typescript // src/model/dto/create_player_dto.ts import { IsString, IsNotEmpty, MinLength, MaxLength } from 'class-validator'; export class CreatePlayerDto { @IsString() @IsNotEmpty() @MinLength(3) @MaxLength(20) name: string; @IsString() avatar?: string; } ``` ### 启用全局验证管道 ```typescript // src/main.ts import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 启用全局验证 app.useGlobalPipes(new ValidationPipe({ whitelist: true, // 自动移除非白名单属性 forbidNonWhitelisted: true, // 存在非白名单属性时抛出错误 transform: true, // 自动转换类型 })); await app.listen(3000); } bootstrap(); ``` ## 异常处理 ### 使用内置异常 ```typescript import { Injectable, NotFoundException } from '@nestjs/common'; @Injectable() export class PlayerService { findOne(id: string): Player { const player = this.players.find(p => p.id === id); if (!player) { throw new NotFoundException(`Player with ID ${id} not found`); } return player; } } ``` ### 常用异常类型 - `BadRequestException` - 400 - `UnauthorizedException` - 401 - `NotFoundException` - 404 - `ForbiddenException` - 403 - `InternalServerErrorException` - 500 ### 自定义异常过滤器 ```typescript // src/utils/http_exception_filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpException, } from '@nestjs/common'; import { Response } from 'express'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const status = exception.getStatus(); response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), message: exception.message, }); } } ``` ## 实战案例:房间系统 ### 数据模型 ```typescript // src/model/room_entity.ts export interface Room { id: string; name: string; maxPlayers: number; players: string[]; status: 'waiting' | 'playing' | 'finished'; createdAt: Date; } ``` ### 服务层 ```typescript // src/service/room_service.ts import { Injectable, BadRequestException } from '@nestjs/common'; import { Room } from '../model/room_entity'; @Injectable() export class RoomService { private rooms: Map = new Map(); createRoom(name: string, maxPlayers: number): Room { const room: Room = { id: Date.now().toString(), name, maxPlayers, players: [], status: 'waiting', createdAt: new Date(), }; this.rooms.set(room.id, room); return room; } joinRoom(roomId: string, playerId: string): Room { const room = this.rooms.get(roomId); if (!room) { throw new BadRequestException('Room not found'); } if (room.players.length >= room.maxPlayers) { throw new BadRequestException('Room is full'); } if (room.status !== 'waiting') { throw new BadRequestException('Game already started'); } room.players.push(playerId); return room; } leaveRoom(roomId: string, playerId: string): void { const room = this.rooms.get(roomId); if (room) { room.players = room.players.filter(id => id !== playerId); if (room.players.length === 0) { this.rooms.delete(roomId); } } } listRooms(): Room[] { return Array.from(this.rooms.values()); } } ``` ### 控制器 ```typescript // src/api/room_controller.ts import { Controller, Get, Post, Body, Param } from '@nestjs/common'; import { RoomService } from '../service/room_service'; @Controller('api/rooms') export class RoomController { constructor(private readonly roomService: RoomService) {} @Get() listRooms() { return this.roomService.listRooms(); } @Post() createRoom(@Body() body: { name: string; maxPlayers: number }) { return this.roomService.createRoom(body.name, body.maxPlayers); } @Post(':id/join') joinRoom(@Param('id') id: string, @Body() body: { playerId: string }) { return this.roomService.joinRoom(id, body.playerId); } } ``` ## 最佳实践 1. **模块化设计**:按功能划分模块(player、room、game 等) 2. **分层架构**:Controller → Service → Data,职责清晰 3. **使用 DTO**:定义清晰的数据传输对象,启用验证 4. **依赖注入**:充分利用 NestJS 的 DI 系统 5. **异常处理**:使用内置异常类,提供友好的错误信息 6. **配置管理**:使用 @nestjs/config 管理环境变量 7. **日志记录**:使用内置 Logger 或集成第三方日志库 8. **测试**:编写单元测试和 E2E 测试 ## 更多资源 - [NestJS 官方文档](https://docs.nestjs.com/) - [NestJS 中文文档](https://docs.nestjs.cn/) - [Socket.IO 文档](https://socket.io/docs/)