Files
whale-town-end/docs/development/nestjs_guide.md
moyin bb796a2469 refactor:项目架构重构和命名规范化
- 统一文件命名为snake_case格式(kebab-case  snake_case)
- 重构zulip模块为zulip_core,明确Core层职责
- 重构user-mgmt模块为user_mgmt,统一命名规范
- 调整模块依赖关系,优化架构分层
- 删除过时的文件和目录结构
- 更新相关文档和配置文件

本次重构涉及大量文件重命名和模块重组,
旨在建立更清晰的项目架构和统一的命名规范。
2026-01-08 00:14:14 +08:00

598 lines
13 KiB
Markdown
Raw Permalink 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 测试
## 注释规范
### 文件头注释
每个 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/)