forked from datawhale/whale-town-end
docs:创建必要的文档内容,丰富readme
This commit is contained in:
96
README.md
96
README.md
@@ -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
0
docs/.gitkeep
Normal file
372
docs/git_commit_guide.md
Normal file
372
docs/git_commit_guide.md
Normal 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
527
docs/naming_convention.md
Normal 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
460
docs/nestjs_guide.md
Normal 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 {}
|
||||
```
|
||||
|
||||
## 数据验证
|
||||
|
||||
使用 DTO(Data 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/)
|
||||
Reference in New Issue
Block a user