# 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<Response>();
    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<string, Room> = 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<ResultType> {
  // 方法实现
}
```

### 接口注释

```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/)
