Files
whale-town-end/docs/nestjs_guide.md

461 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 {}
```
## 数据验证
使用 DTOData 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 测试
## 更多资源
- [NestJS 官方文档](https://docs.nestjs.com/)
- [NestJS 中文文档](https://docs.nestjs.cn/)
- [Socket.IO 文档](https://socket.io/docs/)