docs:创建必要的文档内容,丰富readme

This commit is contained in:
moyin
2025-12-08 17:08:43 +08:00
parent 98eab7ed81
commit e973da4efe
5 changed files with 1452 additions and 3 deletions

View File

@@ -4,18 +4,108 @@
## 技术栈
- **NestJS** - 渐进式 Node.js 框架
- **TypeScript** - 类型安全
- **WebSocket** - 实时通信支持
- **NestJS** `^10.0.0` - 渐进式 Node.js 框架
- **TypeScript** `^5.3.0` - 类型安全
- **Socket.IO** - WebSocket 实时通信支持
- **RxJS** `^7.8.1` - 响应式编程库
### 核心依赖
**生产环境:**
- `@nestjs/common` `^10.0.0` - NestJS 核心功能
- `@nestjs/core` `^10.0.0` - NestJS 核心模块
- `@nestjs/platform-express` `^10.0.0` - Express 平台适配器
- `@nestjs/websockets` `^10.0.0` - WebSocket 支持
- `@nestjs/platform-socket.io` `^10.0.0` - Socket.IO 适配器
- `reflect-metadata` `^0.1.13` - 装饰器元数据支持
- `rxjs` `^7.8.1` - 响应式编程
**开发环境:**
- `@nestjs/cli` `^10.0.0` - NestJS 命令行工具
- `@nestjs/schematics` `^10.0.0` - NestJS 代码生成器
- `@types/node` `^20.0.0` - Node.js 类型定义
- `ts-node` `^10.9.0` - TypeScript 运行时
- `typescript` `^5.3.0` - TypeScript 编译器
## 开发规范
### 命名规范
项目采用统一的命名规范,确保代码风格一致:
- **文件/文件夹**:下划线分隔(如 `order_controller.ts`
- **变量/函数**:小驼峰命名(如 `userName``async queryUserInfo()`
- **类/构造函数**:大驼峰命名(如 `UserModel``OrderService`
- **常量**:全大写 + 下划线(如 `PORT``DB_HOST`
- **接口路由**:全小写 + 短横线(如 `/user/get-info``/order/create-order`
详细规范请查看:[命名规范文档](./docs/naming_convention.md)
### Git 提交规范
项目采用约定式提交规范,提交信息格式:`<类型><简短描述>`
**常用提交类型:**
- `feat` - 新增功能
- `fix` - 修复 Bug
- `docs` - 文档更新
- `style` - 代码格式调整
- `refactor` - 代码重构
- `perf` - 性能优化
- `test` - 测试相关
- `chore` - 构建/工具变动
**后端特定类型:**
- `api` - API 接口
- `db` - 数据库
- `websocket` - WebSocket
- `auth` - 认证授权
- `dto` - 数据传输对象
- `service` - 服务层
**核心原则:**
- ⭐ 一次提交只做一件事
- 使用中文冒号 ``
- 简短明确(不超过 50 字符)
- 能拆分就拆分,保持提交历史清晰
**示例:**
```bash
git commit -m "feat实现玩家注册和登录功能"
git commit -m "fix修复房间加入时的并发问题"
git commit -m "api添加玩家信息查询接口"
```
详细规范请查看:[Git 提交规范文档](./docs/git_commit_guide.md)
## 文档
- [NestJS 使用指南](./docs/nestjs_guide.md) - 详细的 NestJS 开发指南,包含实战案例
- [命名规范](./docs/naming_convention.md) - 项目命名规范和最佳实践
- [Git 提交规范](./docs/git_commit_guide.md) - Git 提交信息格式和最佳实践
## 前置要求
- **Node.js** >= 18.0.0
- **Yarn** >= 1.22.0(推荐)或 npm >= 9.0.0
如果还没有安装 Yarn请先安装
```bash
npm install -g yarn
```
检查版本:
```bash
node --version
yarn --version
```
## 安装依赖
```bash

0
docs/.gitkeep Normal file
View File

372
docs/git_commit_guide.md Normal file
View File

@@ -0,0 +1,372 @@
# Git 提交规范
本文档定义了项目的 Git 提交信息格式规范,以保持提交历史的清晰和一致性。
## 提交格式
```
<类型><简短描述>
[可选的详细描述]
[可选的注释或关联 Issue]
```
## 提交类型
### 主要类型
| 类型 | 说明 | 示例 |
|------|------|------|
| `init` | 项目初始化 | `init项目初始化搭建Godot文件结构` |
| `feat` | 新增功能 | `feat添加角色移动系统` |
| `fix` | 修复 Bug | `fix修复角色跳跃时的碰撞检测问题` |
| `docs` | 文档更新 | `docs更新 README 中的安装说明` |
| `style` | 代码格式调整(不影响功能) | `style统一代码缩进格式` |
| `refactor` | 代码重构(不新增功能也不修复 Bug | `refactor重构敌人 AI 逻辑` |
| `perf` | 性能优化 | `perf优化场景加载速度` |
| `test` | 添加或修改测试 | `test添加角色控制器单元测试` |
| `chore` | 构建过程或辅助工具的变动 | `chore更新 .gitignore 文件` |
| `revert` | 回滚之前的提交 | `revert回滚 feat添加角色移动系统` |
### 后端开发特定类型
| 类型 | 说明 | 示例 |
|------|------|------|
| `api` | API 接口相关 | `api添加玩家信息查询接口` |
| `db` | 数据库相关 | `db创建房间表结构` |
| `config` | 配置文件相关 | `config添加数据库连接配置` |
| `middleware` | 中间件相关 | `middleware添加请求日志中间件` |
| `websocket` | WebSocket 相关 | `websocket实现游戏状态实时推送` |
| `auth` | 认证授权相关 | `auth实现 JWT 身份验证` |
| `dto` | 数据传输对象相关 | `dto添加创建房间的 DTO` |
| `service` | 服务层相关 | `service实现玩家匹配逻辑` |
## 提交示例
### 基础示例
```bash
# 项目初始化
git commit -m "init项目初始化搭建 NestJS 项目结构"
# 新增功能
git commit -m "feat实现玩家注册和登录功能"
# 修复问题
git commit -m "fix修复房间加入时的并发问题"
# 文档更新
git commit -m "docs添加 Git 提交规范文档"
```
### 带详细描述的示例
```bash
git commit -m "feat添加房间系统
- 实现房间创建和加入功能
- 支持多人房间管理
- 添加房间状态同步机制
关联 Issue #12"
```
### 后端开发示例
```bash
# API 接口
git commit -m "api添加玩家信息查询接口"
# 数据库
git commit -m "db创建玩家和房间表结构"
# WebSocket
git commit -m "websocket实现玩家位置实时同步"
# 中间件
git commit -m "middleware添加请求日志和错误处理中间件"
# 认证授权
git commit -m "auth实现 JWT 身份验证机制"
# DTO
git commit -m "dto添加创建房间的数据验证"
# 服务层
git commit -m "service实现玩家匹配算法"
# 配置
git commit -m "config添加 Redis 缓存配置"
```
### 重构和优化示例
```bash
# 代码重构
git commit -m "refactor重构房间管理服务逻辑"
# 性能优化
git commit -m "perf优化数据库查询性能"
# 代码格式
git commit -m "style统一 TypeScript 代码风格"
```
## 多类型改动的处理
### 问题场景
有时一次开发工作可能同时包含多种类型的改动,例如:
- 既修复了 Bug又添加了新功能
- 既重构了代码,又优化了性能
- 既更新了场景,又修改了脚本
### 推荐做法:拆分提交(强烈推荐)⭐
**原则**:一次提交只做一件事,保持提交历史清晰
```bash
# ❌ 不推荐:混合提交
git commit -m "fix + feat修复房间 Bug 并添加踢人功能"
# ✅ 推荐:拆分成两次提交
git add src/service/room_service.ts # 只添加修复 Bug 的部分
git commit -m "fix修复房间加入时的并发问题"
git add src/service/room_service.ts # 添加新功能的部分
git commit -m "feat实现房间踢人功能"
```
### 拆分提交的优势
1. **清晰的历史记录**:每次提交目的明确,便于回溯
2. **便于代码审查**:审查者可以分别查看不同类型的改动
3. **灵活的回滚**:可以单独回滚某个功能或修复,而不影响其他改动
4. **更好的协作**:团队成员能快速理解每次提交的意图
5. **自动化工具友好**CI/CD 和版本管理工具能更好地处理
### 如何拆分提交
#### 方法一:使用 git add -p交互式暂存
```bash
# 交互式选择要暂存的代码块
git add -p src/service/room_service.ts
# 选择修复 Bug 的部分,提交
git commit -m "fix修复房间加入时的并发问题"
# 暂存剩余的新功能代码
git add src/service/room_service.ts
git commit -m "feat实现房间踢人功能"
```
#### 方法二:分步开发和提交
```bash
# 第一步:先完成并提交 Bug 修复
# 修改代码...
git add src/service/room_service.ts
git commit -m "fix修复房间加入时的并发问题"
# 第二步:再开发并提交新功能
# 继续修改代码...
git add src/service/room_service.ts
git commit -m "feat实现房间踢人功能"
```
#### 方法三:使用临时分支
```bash
# 创建临时分支保存当前工作
git stash
# 只恢复修复 Bug 的部分并提交
git stash pop
# 手动撤销新功能代码,只保留 Bug 修复
git add src/service/room_service.ts
git commit -m "fix修复房间加入时的并发问题"
# 恢复新功能代码并提交
# 重新添加新功能代码...
git add src/service/room_service.ts
git commit -m "feat实现房间踢人功能"
```
### 特殊情况:确实无法拆分时
如果改动确实紧密耦合、无法拆分(这种情况应该很少见),可以使用以下方式:
#### 方式一:选择主要类型 + 详细描述(推荐)
```bash
git commit -m "feat实现房间匹配系统
- 添加自动匹配的核心逻辑
- 修复原有房间系统的并发问题
- 优化匹配算法的性能
本次提交包含了功能开发和 Bug 修复,因为匹配系统的实现
依赖于修复原有房间系统的并发问题。"
```
#### 方式二:使用多行描述分别说明
```bash
git commit -m "feat重构并优化房间管理系统
功能改进:
- 实现新的房间状态机
- 添加房间自动回收功能
Bug 修复:
- 修复房间满员时的加入问题
- 修复房间解散时的通知问题
性能优化:
- 减少数据库查询频率
- 优化房间列表缓存策略"
```
### 什么时候应该拆分?
| 情况 | 是否拆分 | 原因 |
|------|---------|------|
| 修复 Bug + 添加新功能 | ✅ 应该拆分 | 两个独立的逻辑改动 |
| 重构代码 + 性能优化 | ✅ 应该拆分 | 目的不同,影响范围不同 |
| 添加 API + 编写服务层 | ✅ 应该拆分 | 不同层次的代码改动 |
| 修复 Bug + 添加该 Bug 的测试 | ❌ 可以合并 | 测试是修复的一部分 |
| 重构 + 重构后必需的修复 | ❌ 可以合并 | 修复是重构的直接结果 |
| 添加功能 + 更新相关文档 | ⚠️ 视情况而定 | 简单文档可合并,复杂文档应拆分 |
## 注意事项
1. **使用中文冒号**:类型后使用中文冒号 `` 而非英文冒号 `:`
2. **简短明确**:描述应简洁明了,一般不超过 50 个字符
3. **动词开头**:描述部分使用动词开头,如"添加"、"修复"、"更新"等
4. **一次提交一个改动**:每次提交应该只包含一个逻辑改动(最重要的原则)⭐
5. **详细描述**:对于复杂的改动,应该添加详细描述说明改动的原因和影响
6. **避免混合类型**:不要在一次提交中混合多种类型的改动(如 fix + feat
7. **先提交修复,再提交功能**:如果必须在同一开发周期内完成,优先提交 Bug 修复
8. **使用 git add -p**:善用交互式暂存来精确控制每次提交的内容
9. **提交前自查**:问自己"这次提交是否只做了一件事?"
10. **保持提交原子性**:每次提交都应该是完整的、可独立运行的改动
## 分支命名规范
```bash
# 功能分支
feature/player-system
feature/room-matching
# 修复分支
fix/room-concurrency
fix/websocket-leak
# 开发分支
dev
develop
# 发布分支
release/v1.0.0
release/v1.1.0
```
## 常见问题 FAQ
### Q1: 我同时修复了 3 个小 Bug应该分 3 次提交吗?
**A**: 视情况而定:
- 如果是**相关的 Bug**(同一个系统/模块),可以合并为一次提交
- 如果是**不相关的 Bug**(不同系统/模块),应该分别提交
```bash
# ✅ 相关 Bug 可以合并
git commit -m "fix修复房间系统的多个问题
- 修复房间满员时的加入问题
- 修复房间解散时的通知问题
- 修复房间列表的分页错误"
# ✅ 不相关 Bug 应该拆分
git commit -m "fix修复房间加入时的并发问题"
git commit -m "fix修复玩家信息查询的权限问题"
git commit -m "fix修复 WebSocket 连接的内存泄漏"
```
### Q2: 我在开发新功能时发现了 Bug应该怎么办
**A**: 推荐流程:
1. 暂存当前新功能的代码(`git stash`
2. 修复 Bug 并提交
3. 恢复新功能代码继续开发
4. 完成后提交新功能
```bash
# 保存当前工作
git stash save "WIP: 开发房间匹配功能"
# 修复 Bug
git add src/service/room_service.ts
git commit -m "fix修复房间加入时的并发问题"
# 恢复工作并继续开发
git stash pop
# 继续开发...
git commit -m "feat实现房间自动匹配功能"
```
### Q3: 重构代码时顺便优化了性能,算一次提交吗?
**A**: 应该拆分:
- 重构(`refactor`):改变代码结构但不改变行为
- 优化(`perf`):改善性能表现
```bash
# ✅ 拆分提交
git commit -m "refactor重构房间管理服务逻辑"
git commit -m "perf优化房间列表查询性能"
```
### Q4: 我应该多久提交一次?
**A**: 遵循以下原则:
- ✅ 完成一个**完整的逻辑单元**就提交
- ✅ 代码能够**正常运行**时提交
- ✅ 下班前提交当天的工作进度
- ❌ 不要等到功能完全完成才提交(可能跨越多天)
- ❌ 不要提交无法运行的代码
### Q5: 提交信息写错了怎么办?
**A**: 使用 `git commit --amend` 修改最后一次提交:
```bash
# 修改最后一次提交信息
git commit --amend -m "fix修复房间加入时的并发问题"
# 如果已经推送到远程,需要强制推送(谨慎使用)
git push --force-with-lease
```
⚠️ **注意**:只修改未推送或未被他人使用的提交!
## 工具推荐
- **Commitizen**: 交互式提交信息生成工具
- **Git Hooks**: 使用 pre-commit 钩子自动检查提交格式
- **Conventional Commits**: 遵循约定式提交规范
- **Git GUI 工具**: GitKraken、SourceTree 等支持可视化暂存部分代码
## 总结
记住这些核心原则:
1.**一次提交只做一件事** - 最重要的原则
2. 🔀 **能拆分就拆分** - 保持提交历史清晰
3. 🎯 **提交要有意义** - 每次提交都应该是完整的改动
4. 📝 **描述要清晰** - 让别人(和未来的自己)能快速理解
5. 🚫 **避免混合类型** - 不要 fix + feat 混在一起
**好的提交习惯 = 清晰的项目历史 = 高效的团队协作**

527
docs/naming_convention.md Normal file
View File

@@ -0,0 +1,527 @@
# 命名规范
本文档定义了项目中所有代码的命名规范,确保代码风格统一,提高可读性和可维护性。
## 目录
- [文件和文件夹命名](#文件和文件夹命名)
- [变量和函数命名](#变量和函数命名)
- [类和构造函数命名](#类和构造函数命名)
- [常量命名](#常量命名)
- [接口路由命名](#接口路由命名)
- [TypeScript 特定规范](#typescript-特定规范)
- [命名示例](#命名示例)
## 文件和文件夹命名
**规则使用下划线分隔snake_case**
### 文件命名
```
✅ 正确示例:
- order_controller.ts
- user_service.ts
- game_gateway.ts
- player_entity.ts
- create_room_dto.ts
- database_config.ts
❌ 错误示例:
- OrderController.ts
- userService.ts
- game-gateway.ts
- playerEntity.ts
```
### 文件夹命名
```
✅ 正确示例:
- src/api/
- src/service/
- src/model/dto/
- src/utils/
- src/config/
- test/unit_test/
- test/integration_test/
❌ 错误示例:
- src/API/
- src/Service/
- src/model-dto/
- src/Utils/
```
### 特殊说明
- 所有文件和文件夹名使用小写字母
- 多个单词之间使用下划线 `_` 连接
- 避免使用缩写,除非是广泛认可的缩写(如 dto、api
## 变量和函数命名
**规则使用小驼峰命名camelCase**
### 变量命名
```typescript
const userName = 'Alice';
let playerScore = 100;
const roomId = '12345';
const isGameStarted = false;
const maxPlayerCount = 4;
const UserName = 'Alice';
const player_score = 100;
const RoomId = '12345';
const is_game_started = false;
```
### 函数命名
```typescript
function getUserInfo() { }
async function queryUserInfo() { }
function calculateDamage() { }
function isPlayerAlive() { }
function handlePlayerMove() { }
function GetUserInfo() { }
function query_user_info() { }
function CalculateDamage() { }
function IsPlayerAlive() { }
```
### 命名建议
- 使用动词开头描述函数功能:
- `get` - 获取数据(如 `getUserById`
- `set` - 设置数据(如 `setPlayerPosition`
- `create` - 创建实体(如 `createRoom`
- `update` - 更新数据(如 `updatePlayerScore`
- `delete` - 删除数据(如 `deleteRoom`
- `is/has` - 布尔判断(如 `isGameOver``hasPermission`
- `handle` - 事件处理(如 `handlePlayerAttack`
- `calculate` - 计算逻辑(如 `calculateDamage`
- `validate` - 验证逻辑(如 `validateInput`
- 布尔变量使用 `is``has``can` 等前缀:
```typescript
const isActive = true;
const hasPermission = false;
const canMove = true;
```
## 类和构造函数命名
**规则使用大驼峰命名PascalCase**
### 类命名
```typescript
✅ 正确示例:
class UserModel { }
class OrderService { }
class GameController { }
class PlayerEntity { }
class RoomGateway { }
class DatabaseConnection { }
❌ 错误示例:
class userModel { }
class order_service { }
class gameController { }
class player_entity { }
```
### 接口命名
```typescript
✅ 正确示例:
interface User { }
interface GameConfig { }
interface PlayerData { }
interface RoomOptions { }
// 或使用 I 前缀(可选)
interface IUser { }
interface IGameConfig { }
❌ 错误示例:
interface user { }
interface game_config { }
interface playerData { }
```
### DTO 命名
```typescript
✅ 正确示例:
class CreateUserDto { }
class UpdatePlayerDto { }
class JoinRoomDto { }
class GameStateDto { }
❌ 错误示例:
class createUserDto { }
class update_player_dto { }
class joinRoomDTO { }
```
### 装饰器命名
```typescript
✅ 正确示例:
@Controller('users')
@Injectable()
@Module()
@Get()
// 自定义装饰器
function CustomDecorator() { }
```
## 常量命名
**规则:全大写 + 下划线分隔SCREAMING_SNAKE_CASE**
```typescript
✅ 正确示例:
const PORT = 3000;
const DB_HOST = 'localhost';
const MAX_PLAYERS = 10;
const API_VERSION = 'v1';
const DEFAULT_TIMEOUT = 5000;
const GAME_STATUS_WAITING = 'waiting';
const GAME_STATUS_PLAYING = 'playing';
❌ 错误示例:
const port = 3000;
const dbHost = 'localhost';
const maxPlayers = 10;
const ApiVersion = 'v1';
const default_timeout = 5000;
```
### 枚举命名
```typescript
✅ 正确示例:
enum GameStatus {
WAITING = 'waiting',
PLAYING = 'playing',
FINISHED = 'finished',
}
enum PlayerRole {
ADMIN = 'admin',
PLAYER = 'player',
SPECTATOR = 'spectator',
}
❌ 错误示例:
enum gameStatus {
waiting = 'waiting',
playing = 'playing',
}
enum PlayerRole {
admin = 'admin',
player = 'player',
}
```
## 接口路由命名
**规则:全小写 + 短横线分隔kebab-case**
```typescript
✅ 正确示例:
@Get('user/get-info')
@Post('order/create-order')
@Put('player/update-position')
@Delete('room/delete-room')
@Get('game/get-state')
@Post('room/join-room')
❌ 错误示例:
@Get('user/getInfo')
@Post('order/createOrder')
@Put('player/update_position')
@Delete('room/DeleteRoom')
@Get('game/GetState')
```
### 路由结构建议
```typescript
// 资源型路由
@Controller('api/players')
export class PlayerController {
@Get() // GET /api/players
@Get(':id') // GET /api/players/:id
@Post() // POST /api/players
@Put(':id') // PUT /api/players/:id
@Delete(':id') // DELETE /api/players/:id
}
// 动作型路由
@Controller('api/game')
export class GameController {
@Post('start-game') // POST /api/game/start-game
@Post('end-game') // POST /api/game/end-game
@Get('get-state') // GET /api/game/get-state
}
// 嵌套资源路由
@Controller('api/rooms')
export class RoomController {
@Post(':id/join') // POST /api/rooms/:id/join
@Post(':id/leave') // POST /api/rooms/:id/leave
@Get(':id/players') // GET /api/rooms/:id/players
}
```
## TypeScript 特定规范
### 类型别名
```typescript
✅ 正确示例:
type UserId = string;
type PlayerPosition = { x: number; y: number };
type GameCallback = (state: GameState) => void;
❌ 错误示例:
type userId = string;
type player_position = { x: number; y: number };
```
### 泛型参数
```typescript
✅ 正确示例:
function findById<T>(id: string): T { }
class Repository<T, K> { }
interface Response<T> { }
// 使用有意义的名称
function mapArray<TInput, TOutput>(arr: TInput[]): TOutput[] { }
❌ 错误示例:
function findById<t>(id: string): t { }
class Repository<type, key> { }
```
### 装饰器参数
```typescript
✅ 正确示例:
@Column({ name: 'user_name' })
@IsString({ message: 'Name must be a string' })
@ApiProperty({ description: 'User ID' })
❌ 错误示例:
@Column({ name: 'UserName' })
@IsString({ message: 'name_must_be_string' })
```
## 命名示例
### 完整的模块示例
```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';
const MAX_PLAYERS = 100;
@Controller('api/players')
export class PlayerController {
constructor(private readonly playerService: PlayerService) {}
@Get()
async getAllPlayers() {
return this.playerService.findAll();
}
@Get(':id')
async getPlayerById(@Param('id') playerId: string) {
return this.playerService.findById(playerId);
}
@Post()
async createPlayer(@Body() createPlayerDto: CreatePlayerDto) {
return this.playerService.create(createPlayerDto);
}
@Post(':id/update-position')
async updatePlayerPosition(
@Param('id') playerId: string,
@Body() body: { x: number; y: number }
) {
const { x, y } = body;
return this.playerService.updatePosition(playerId, x, y);
}
}
```
```typescript
// 文件src/service/player_service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { Player } from '../model/player_entity';
import { CreatePlayerDto } from '../model/dto/create_player_dto';
const DEFAULT_HEALTH = 100;
const DEFAULT_SPEED = 5;
@Injectable()
export class PlayerService {
private players: Map<string, Player> = new Map();
findAll(): Player[] {
return Array.from(this.players.values());
}
findById(playerId: string): Player {
const player = this.players.get(playerId);
if (!player) {
throw new NotFoundException(`Player with ID ${playerId} not found`);
}
return player;
}
create(createPlayerDto: CreatePlayerDto): Player {
const newPlayer: Player = {
id: this.generatePlayerId(),
name: createPlayerDto.name,
health: DEFAULT_HEALTH,
speed: DEFAULT_SPEED,
position: { x: 0, y: 0 },
isAlive: true,
createdAt: new Date(),
};
this.players.set(newPlayer.id, newPlayer);
return newPlayer;
}
updatePosition(playerId: string, x: number, y: number): Player {
const player = this.findById(playerId);
player.position = { x, y };
return player;
}
private generatePlayerId(): string {
return `player_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private calculateDamage(attackPower: number, defense: number): number {
return Math.max(0, attackPower - defense);
}
isPlayerAlive(playerId: string): boolean {
const player = this.findById(playerId);
return player.isAlive && player.health > 0;
}
}
```
```typescript
// 文件src/model/player_entity.ts
export interface Player {
id: string;
name: string;
health: number;
speed: number;
position: Position;
isAlive: boolean;
createdAt: Date;
}
export interface Position {
x: number;
y: number;
}
export enum PlayerStatus {
IDLE = 'idle',
MOVING = 'moving',
ATTACKING = 'attacking',
DEAD = 'dead',
}
```
```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;
}
```
## 检查清单
在提交代码前,请确保:
- [ ] 所有文件和文件夹使用下划线分隔命名
- [ ] 所有变量和函数使用小驼峰命名
- [ ] 所有类、接口、DTO 使用大驼峰命名
- [ ] 所有常量和枚举值使用全大写 + 下划线命名
- [ ] 所有路由使用全小写 + 短横线命名
- [ ] 函数名清晰表达其功能
- [ ] 布尔变量使用 is/has/can 前缀
- [ ] 避免使用无意义的缩写
## 工具配置
### ESLint 配置建议
```json
{
"rules": {
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "variable",
"format": ["camelCase", "UPPER_CASE"]
},
{
"selector": "function",
"format": ["camelCase"]
},
{
"selector": "class",
"format": ["PascalCase"]
},
{
"selector": "interface",
"format": ["PascalCase"]
}
]
}
}
```
## 总结
遵循统一的命名规范能够:
- 提高代码可读性
- 减少团队沟通成本
- 降低代码维护难度
- 避免命名冲突
- 提升项目专业度
记住:**好的命名是自解释的,不需要额外的注释。**

460
docs/nestjs_guide.md Normal file
View File

@@ -0,0 +1,460 @@
# 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
yarn 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
yarn 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/)