# 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 测试 ## 注释规范 ### 文件头注释 每个 TypeScript 文件都应该包含完整的文件头注释: ```typescript /** * 文件功能描述 * * 功能描述: * - 主要功能点1 * - 主要功能点2 * - 主要功能点3 * * 职责分离: * - 职责描述1 * - 职责描述2 * * 最近修改: * - YYYY-MM-DD: 修改类型 - 具体修改内容描述 * - YYYY-MM-DD: 修改类型 - 具体修改内容描述 * * @author 作者名 * @version x.x.x * @since 创建日期 * @lastModified 最后修改日期 */ ``` ### 类注释 ```typescript /** * 类功能描述 * * 职责: * - 主要职责1 * - 主要职责2 * * 主要方法: * - method1() - 方法1功能 * - method2() - 方法2功能 * * 使用场景: * - 场景描述 */ @Injectable() export class ExampleService { // 类实现 } ``` ### 方法注释 ```typescript /** * 方法功能描述 * * 业务逻辑: * 1. 步骤1描述 * 2. 步骤2描述 * 3. 步骤3描述 * * @param param1 参数1描述 * @param param2 参数2描述 * @returns 返回值描述 * @throws ExceptionType 异常情况描述 * * @example * ```typescript * const result = await service.methodName(param1, param2); * ``` */ async methodName(param1: string, param2: number): Promise { // 方法实现 } ``` ### 接口注释 ```typescript /** * 接口功能描述 */ export interface ExampleInterface { /** 字段1描述 */ field1: string; /** 字段2描述 */ field2: number; /** 可选字段描述 */ optionalField?: boolean; } ``` ### 修改记录规范 当修改现有文件时,必须在文件头注释中添加修改记录: #### 修改类型定义 - **代码规范优化** - 命名规范、注释规范、代码清理等 - **功能新增** - 添加新的功能或方法 - **功能修改** - 修改现有功能的实现 - **Bug修复** - 修复代码缺陷 - **性能优化** - 提升代码性能 - **重构** - 代码结构调整但功能不变 #### 修改记录格式 ```typescript /** * 最近修改: * - 2025-01-07: 代码规范优化 - 清理未使用的导入(EmailSendResult, crypto) * - 2025-01-07: 代码规范优化 - 修复常量命名(saltRounds -> SALT_ROUNDS) * - 2025-01-07: 功能新增 - 添加用户验证码登录功能 * - 2025-01-06: Bug修复 - 修复邮箱验证逻辑错误 * * @version 1.0.1 (修改后需要递增版本号) * @lastModified 2025-01-07 */ ``` **📏 修改记录长度限制:只保留最近5次修改,超出时删除最旧记录,保持注释简洁。** ### 注释最佳实践 1. **保持更新**:修改代码时同步更新注释 2. **描述意图**:注释应该说明"为什么"而不只是"做什么" 3. **业务逻辑**:复杂的业务逻辑必须有详细的步骤说明 4. **异常处理**:明确说明可能抛出的异常和处理方式 5. **示例代码**:复杂方法提供使用示例 6. **版本管理**:修改文件时必须更新修改记录和版本号 ## 更多资源 - [NestJS 官方文档](https://docs.nestjs.com/) - [NestJS 中文文档](https://docs.nestjs.cn/) - [Socket.IO 文档](https://socket.io/docs/)