diff --git a/README.md b/README.md index 3292131..eaa2366 100644 --- a/README.md +++ b/README.md @@ -173,10 +173,18 @@ export class PlayerService { - [命名规范](./docs/naming_convention.md) - 项目命名规范和最佳实践 - [NestJS 使用指南](./docs/nestjs_guide.md) - 详细的 NestJS 开发指南,包含实战案例 +### 📖 API 文档 +- **[API 文档总览](./docs/api/README.md)** - API 文档使用指南和快速开始 +- **[Swagger UI](http://localhost:3000/api-docs)** - 交互式 API 文档(需启动服务器) +- [详细接口文档](./docs/api/api-documentation.md) - 完整的 API 接口说明 +- [OpenAPI 规范](./docs/api/openapi.yaml) - 标准化的 API 描述文件 +- [Postman 集合](./docs/api/postman-collection.json) - 可导入的 API 测试集合 + ### 💡 使用建议 1. **开发前**:先读 AI 辅助指南,了解如何用 AI 帮助遵循规范 2. **开发中**:参考具体规范文档,使用 AI 实时检查代码质量 -3. **提交前**:用 AI 检查代码和提交信息是否符合规范 +3. **API 开发**:使用 Swagger UI 进行接口测试,参考 API 文档进行开发 +4. **提交前**:用 AI 检查代码和提交信息是否符合规范 ## 前置要求 @@ -262,10 +270,19 @@ test/ ├── api/ # API 测试 └── service/ # 服务测试 docs/ # 项目文档 +├── api/ # API 接口文档 +│ ├── README.md # API 文档使用指南 +│ ├── api-documentation.md # 详细接口文档 +│ ├── openapi.yaml # OpenAPI 规范文件 +│ └── postman-collection.json # Postman 测试集合 +├── systems/ # 系统设计文档 +│ ├── logger/ # 日志系统文档 +│ └── user-auth/ # 用户认证系统文档 ├── backend_development_guide.md # 后端开发规范 ├── git_commit_guide.md # Git 提交规范 ├── naming_convention.md # 命名规范 -└── nestjs_guide.md # NestJS 使用指南 +├── nestjs_guide.md # NestJS 使用指南 +└── AI辅助开发规范指南.md # AI 辅助开发指南 ``` ## 核心功能 @@ -282,6 +299,26 @@ docs/ # 项目文档 **详细文档**: [用户认证系统文档](./docs/systems/user-auth/README.md) +### 📖 API 文档系统 + +集成了完整的 API 文档解决方案,提供多种格式的接口文档: + +- **Swagger UI** - 交互式 API 文档界面 +- **OpenAPI 规范** - 标准化的 API 描述文件 +- **Postman 集合** - 可导入的 API 测试集合 +- **详细文档** - 包含示例和最佳实践的完整说明 + +**快速访问**: +```bash +# 启动服务器 +pnpm run dev + +# 访问 Swagger UI 文档 +# 浏览器打开: http://localhost:3000/api-docs +``` + +**详细文档**: [API 文档说明](./docs/api/README.md) + ### 📊 日志系统 基于 Pino 的高性能日志系统,提供结构化日志记录: diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..88f1f00 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,139 @@ +# 项目文档 + +本目录包含了像素游戏服务器的完整文档。 + +## 文档结构 + +### 📁 api/ +API接口相关文档,包含: +- **api-documentation.md** - 详细的API接口文档 +- **openapi.yaml** - OpenAPI 3.0规范文件 +- **postman-collection.json** - Postman测试集合 +- **README.md** - API文档使用说明 + +### 📁 systems/ +系统设计文档,包含: +- **logger/** - 日志系统文档 +- **user-auth/** - 用户认证系统文档 + +### 📄 其他文档 +- **AI辅助开发规范指南.md** - AI开发规范 +- **backend_development_guide.md** - 后端开发指南 +- **git_commit_guide.md** - Git提交规范 +- **naming_convention.md** - 命名规范 +- **nestjs_guide.md** - NestJS开发指南 +- **日志系统详细说明.md** - 日志系统说明 + +## 如何使用 + +### 1. 启动服务器并查看Swagger文档 + +```bash +# 启动开发服务器 +pnpm run dev + +# 访问Swagger UI +# 浏览器打开: http://localhost:3000/api-docs +``` + +### 2. 使用Postman测试API + +1. 打开Postman +2. 点击 Import 按钮 +3. 选择 `docs/postman-collection.json` 文件 +4. 导入后即可看到所有API接口 +5. 修改环境变量 `baseUrl` 为你的服务器地址(默认:http://localhost:3000) + +### 3. 使用OpenAPI规范 + +#### 在Swagger Editor中查看 +1. 访问 [Swagger Editor](https://editor.swagger.io/) +2. 将 `docs/openapi.yaml` 的内容复制粘贴到编辑器中 +3. 即可查看可视化的API文档 + +#### 生成客户端SDK +```bash +# 使用swagger-codegen生成JavaScript客户端 +swagger-codegen generate -i docs/openapi.yaml -l javascript -o ./client-sdk + +# 使用openapi-generator生成TypeScript客户端 +openapi-generator generate -i docs/openapi.yaml -g typescript-axios -o ./client-sdk +``` + +## API接口概览 + +| 接口 | 方法 | 路径 | 描述 | +|------|------|------|------| +| 用户登录 | POST | /auth/login | 支持用户名、邮箱或手机号登录 | +| 用户注册 | POST | /auth/register | 创建新用户账户 | +| GitHub OAuth | POST | /auth/github | 使用GitHub账户登录或注册 | +| 发送验证码 | POST | /auth/forgot-password | 发送密码重置验证码 | +| 重置密码 | POST | /auth/reset-password | 使用验证码重置密码 | +| 修改密码 | PUT | /auth/change-password | 修改用户密码 | + +## 快速测试 + +### 使用cURL测试登录接口 + +```bash +# 测试用户登录 +curl -X POST http://localhost:3000/auth/login \ + -H "Content-Type: application/json" \ + -d '{ + "identifier": "testuser", + "password": "password123" + }' + +# 测试用户注册 +curl -X POST http://localhost:3000/auth/register \ + -H "Content-Type: application/json" \ + -d '{ + "username": "newuser", + "password": "password123", + "nickname": "新用户", + "email": "newuser@example.com" + }' +``` + +### 使用JavaScript测试 + +```javascript +// 用户登录 +const response = await fetch('http://localhost:3000/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + identifier: 'testuser', + password: 'password123' + }) +}); + +const data = await response.json(); +console.log(data); +``` + +## 注意事项 + +1. **开发环境**: 当前配置适用于开发环境,生产环境需要使用HTTPS +2. **认证**: 实际应用中应实现JWT认证机制 +3. **限流**: 建议对认证接口实施限流策略 +4. **验证码**: 示例中返回验证码仅用于演示,生产环境不应返回 +5. **错误处理**: 建议实现统一的错误处理机制 + +## 更新文档 + +当API接口发生变化时,请同步更新以下文件: +1. 更新DTO类的Swagger装饰器 +2. 更新 `api-documentation.md` +3. 更新 `openapi.yaml` +4. 更新 `postman-collection.json` +5. 重新生成Swagger文档 + +## 相关链接 + +- [NestJS Swagger文档](https://docs.nestjs.com/openapi/introduction) +- [OpenAPI规范](https://swagger.io/specification/) +- [Postman文档](https://learning.postman.com/docs/getting-started/introduction/) +- [Swagger Editor](https://editor.swagger.io/) \ No newline at end of file diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 0000000..13caac1 --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,141 @@ +# API接口文档 + +本目录包含了像素游戏服务器用户认证API的完整文档。 + +## 📋 文档文件说明 + +### 1. api-documentation.md +详细的API接口文档,包含: +- 接口概述和通用响应格式 +- 每个接口的详细说明、参数、响应示例 +- 错误代码说明 +- 数据验证规则 +- 使用示例(JavaScript/TypeScript 和 cURL) + +### 2. openapi.yaml +OpenAPI 3.0规范文件,可以用于: +- 导入到Swagger Editor查看和编辑 +- 生成客户端SDK +- 集成到API网关 +- 自动化测试 + +### 3. postman-collection.json +Postman集合文件,包含: +- 所有API接口的请求示例 +- 预设的请求参数 +- 响应示例 +- 可直接导入Postman进行测试 + +## 🚀 快速开始 + +### 1. 启动服务器并查看Swagger文档 + +```bash +# 启动开发服务器 +pnpm run dev + +# 访问Swagger UI +# 浏览器打开: http://localhost:3000/api-docs +``` + +### 2. 使用Postman测试API + +1. 打开Postman +2. 点击 Import 按钮 +3. 选择 `docs/api/postman-collection.json` 文件 +4. 导入后即可看到所有API接口 +5. 修改环境变量 `baseUrl` 为你的服务器地址(默认:http://localhost:3000) + +### 3. 使用OpenAPI规范 + +#### 在Swagger Editor中查看 +1. 访问 [Swagger Editor](https://editor.swagger.io/) +2. 将 `docs/api/openapi.yaml` 的内容复制粘贴到编辑器中 +3. 即可查看可视化的API文档 + +#### 生成客户端SDK +```bash +# 使用swagger-codegen生成JavaScript客户端 +swagger-codegen generate -i docs/api/openapi.yaml -l javascript -o ./client-sdk + +# 使用openapi-generator生成TypeScript客户端 +openapi-generator generate -i docs/api/openapi.yaml -g typescript-axios -o ./client-sdk +``` + +## 📊 API接口概览 + +| 接口 | 方法 | 路径 | 描述 | +|------|------|------|------| +| 用户登录 | POST | /auth/login | 支持用户名、邮箱或手机号登录 | +| 用户注册 | POST | /auth/register | 创建新用户账户 | +| GitHub OAuth | POST | /auth/github | 使用GitHub账户登录或注册 | +| 发送验证码 | POST | /auth/forgot-password | 发送密码重置验证码 | +| 重置密码 | POST | /auth/reset-password | 使用验证码重置密码 | +| 修改密码 | PUT | /auth/change-password | 修改用户密码 | + +## 🧪 快速测试 + +### 使用cURL测试登录接口 + +```bash +# 测试用户登录 +curl -X POST http://localhost:3000/auth/login \ + -H "Content-Type: application/json" \ + -d '{ + "identifier": "testuser", + "password": "password123" + }' + +# 测试用户注册 +curl -X POST http://localhost:3000/auth/register \ + -H "Content-Type: application/json" \ + -d '{ + "username": "newuser", + "password": "password123", + "nickname": "新用户", + "email": "newuser@example.com" + }' +``` + +### 使用JavaScript测试 + +```javascript +// 用户登录 +const response = await fetch('http://localhost:3000/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + identifier: 'testuser', + password: 'password123' + }) +}); + +const data = await response.json(); +console.log(data); +``` + +## ⚠️ 注意事项 + +1. **开发环境**: 当前配置适用于开发环境,生产环境需要使用HTTPS +2. **认证**: 实际应用中应实现JWT认证机制 +3. **限流**: 建议对认证接口实施限流策略 +4. **验证码**: 示例中返回验证码仅用于演示,生产环境不应返回 +5. **错误处理**: 建议实现统一的错误处理机制 + +## 🔄 更新文档 + +当API接口发生变化时,请同步更新以下文件: +1. 更新DTO类的Swagger装饰器 +2. 更新 `api-documentation.md` +3. 更新 `openapi.yaml` +4. 更新 `postman-collection.json` +5. 重新生成Swagger文档 + +## 🔗 相关链接 + +- [NestJS Swagger文档](https://docs.nestjs.com/openapi/introduction) +- [OpenAPI规范](https://swagger.io/specification/) +- [Postman文档](https://learning.postman.com/docs/getting-started/introduction/) +- [Swagger Editor](https://editor.swagger.io/) \ No newline at end of file diff --git a/docs/api/api-documentation.md b/docs/api/api-documentation.md new file mode 100644 index 0000000..3c5199d --- /dev/null +++ b/docs/api/api-documentation.md @@ -0,0 +1,412 @@ +# 用户认证API接口文档 + +## 概述 + +本文档描述了像素游戏服务器的用户认证相关API接口,包括登录、注册、密码找回等功能。 + +**基础URL**: `http://localhost:3000` +**API文档地址**: `http://localhost:3000/api-docs` + +## 通用响应格式 + +所有API接口都遵循统一的响应格式: + +```json +{ + "success": boolean, + "data": object | null, + "message": string, + "error_code": string | null +} +``` + +### 字段说明 + +- `success`: 请求是否成功 +- `data`: 响应数据(成功时返回) +- `message`: 响应消息 +- `error_code`: 错误代码(失败时返回) + +## 接口列表 + +### 1. 用户登录 + +**接口地址**: `POST /auth/login` + +**功能描述**: 用户登录,支持用户名、邮箱或手机号登录 + +#### 请求参数 + +```json +{ + "identifier": "testuser", + "password": "password123" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| identifier | string | 是 | 登录标识符,支持用户名、邮箱或手机号 | +| password | string | 是 | 用户密码 | + +#### 响应示例 + +**成功响应** (200): +```json +{ + "success": true, + "data": { + "user": { + "id": "1", + "username": "testuser", + "nickname": "测试用户", + "email": "test@example.com", + "phone": "+8613800138000", + "avatar_url": "https://example.com/avatar.jpg", + "role": 1, + "created_at": "2025-12-17T10:00:00.000Z" + }, + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "is_new_user": false, + "message": "登录成功" + }, + "message": "登录成功" +} +``` + +**失败响应** (401): +```json +{ + "success": false, + "message": "用户名或密码错误", + "error_code": "LOGIN_FAILED" +} +``` + +### 2. 用户注册 + +**接口地址**: `POST /auth/register` + +**功能描述**: 创建新用户账户 + +#### 请求参数 + +```json +{ + "username": "testuser", + "password": "password123", + "nickname": "测试用户", + "email": "test@example.com", + "phone": "+8613800138000" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| username | string | 是 | 用户名,只能包含字母、数字和下划线,长度1-50字符 | +| password | string | 是 | 密码,必须包含字母和数字,长度8-128字符 | +| nickname | string | 是 | 用户昵称,长度1-50字符 | +| email | string | 否 | 邮箱地址 | +| phone | string | 否 | 手机号码 | + +#### 响应示例 + +**成功响应** (201): +```json +{ + "success": true, + "data": { + "user": { + "id": "2", + "username": "testuser", + "nickname": "测试用户", + "email": "test@example.com", + "phone": "+8613800138000", + "avatar_url": null, + "role": 1, + "created_at": "2025-12-17T10:00:00.000Z" + }, + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "is_new_user": true, + "message": "注册成功" + }, + "message": "注册成功" +} +``` + +**失败响应** (409): +```json +{ + "success": false, + "message": "用户名已存在", + "error_code": "REGISTER_FAILED" +} +``` + +### 3. GitHub OAuth登录 + +**接口地址**: `POST /auth/github` + +**功能描述**: 使用GitHub账户登录或注册 + +#### 请求参数 + +```json +{ + "github_id": "12345678", + "username": "octocat", + "nickname": "The Octocat", + "email": "octocat@github.com", + "avatar_url": "https://github.com/images/error/octocat_happy.gif" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| github_id | string | 是 | GitHub用户ID | +| username | string | 是 | GitHub用户名 | +| nickname | string | 是 | GitHub显示名称 | +| email | string | 否 | GitHub邮箱地址 | +| avatar_url | string | 否 | GitHub头像URL | + +#### 响应示例 + +**成功响应** (200): +```json +{ + "success": true, + "data": { + "user": { + "id": "3", + "username": "octocat", + "nickname": "The Octocat", + "email": "octocat@github.com", + "phone": null, + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "role": 1, + "created_at": "2025-12-17T10:00:00.000Z" + }, + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "is_new_user": true, + "message": "GitHub账户绑定成功" + }, + "message": "GitHub账户绑定成功" +} +``` + +### 4. 发送密码重置验证码 + +**接口地址**: `POST /auth/forgot-password` + +**功能描述**: 向用户邮箱或手机发送密码重置验证码 + +#### 请求参数 + +```json +{ + "identifier": "test@example.com" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| identifier | string | 是 | 邮箱或手机号 | + +#### 响应示例 + +**成功响应** (200): +```json +{ + "success": true, + "data": { + "verification_code": "123456" + }, + "message": "验证码已发送,请查收" +} +``` + +**注意**: 实际应用中不应返回验证码,这里仅用于演示。 + +### 5. 重置密码 + +**接口地址**: `POST /auth/reset-password` + +**功能描述**: 使用验证码重置用户密码 + +#### 请求参数 + +```json +{ + "identifier": "test@example.com", + "verification_code": "123456", + "new_password": "newpassword123" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| identifier | string | 是 | 邮箱或手机号 | +| verification_code | string | 是 | 6位数字验证码 | +| new_password | string | 是 | 新密码,必须包含字母和数字,长度8-128字符 | + +#### 响应示例 + +**成功响应** (200): +```json +{ + "success": true, + "message": "密码重置成功" +} +``` + +### 6. 修改密码 + +**接口地址**: `PUT /auth/change-password` + +**功能描述**: 用户修改自己的密码(需要提供旧密码) + +#### 请求参数 + +```json +{ + "user_id": "1", + "old_password": "oldpassword123", + "new_password": "newpassword123" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| user_id | string | 是 | 用户ID(实际应用中应从JWT令牌中获取) | +| old_password | string | 是 | 当前密码 | +| new_password | string | 是 | 新密码,必须包含字母和数字,长度8-128字符 | + +#### 响应示例 + +**成功响应** (200): +```json +{ + "success": true, + "message": "密码修改成功" +} +``` + +## 错误代码说明 + +| 错误代码 | 说明 | +|----------|------| +| LOGIN_FAILED | 登录失败 | +| REGISTER_FAILED | 注册失败 | +| GITHUB_OAUTH_FAILED | GitHub OAuth失败 | +| SEND_CODE_FAILED | 发送验证码失败 | +| RESET_PASSWORD_FAILED | 重置密码失败 | +| CHANGE_PASSWORD_FAILED | 修改密码失败 | + +## 数据验证规则 + +### 用户名规则 +- 长度:1-50字符 +- 格式:只能包含字母、数字和下划线 +- 正则表达式:`^[a-zA-Z0-9_]+$` + +### 密码规则 +- 长度:8-128字符 +- 格式:必须包含字母和数字 +- 正则表达式:`^(?=.*[a-zA-Z])(?=.*\d)` + +### 验证码规则 +- 长度:6位数字 +- 正则表达式:`^\d{6}$` + +## 使用示例 + +### JavaScript/TypeScript 示例 + +```typescript +// 用户登录 +const loginResponse = await fetch('http://localhost:3000/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + identifier: 'testuser', + password: 'password123' + }) +}); + +const loginData = await loginResponse.json(); +if (loginData.success) { + const token = loginData.data.access_token; + // 保存token用于后续请求 + localStorage.setItem('token', token); +} + +// 用户注册 +const registerResponse = await fetch('http://localhost:3000/auth/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username: 'newuser', + password: 'password123', + nickname: '新用户', + email: 'newuser@example.com' + }) +}); + +const registerData = await registerResponse.json(); +``` + +### cURL 示例 + +```bash +# 用户登录 +curl -X POST http://localhost:3000/auth/login \ + -H "Content-Type: application/json" \ + -d '{ + "identifier": "testuser", + "password": "password123" + }' + +# 用户注册 +curl -X POST http://localhost:3000/auth/register \ + -H "Content-Type: application/json" \ + -d '{ + "username": "newuser", + "password": "password123", + "nickname": "新用户", + "email": "newuser@example.com" + }' + +# 发送密码重置验证码 +curl -X POST http://localhost:3000/auth/forgot-password \ + -H "Content-Type: application/json" \ + -d '{ + "identifier": "test@example.com" + }' + +# 重置密码 +curl -X POST http://localhost:3000/auth/reset-password \ + -H "Content-Type: application/json" \ + -d '{ + "identifier": "test@example.com", + "verification_code": "123456", + "new_password": "newpassword123" + }' +``` + +## 注意事项 + +1. **安全性**: 实际应用中应使用HTTPS协议 +2. **令牌**: 示例中的access_token是简单的Base64编码,实际应用中应使用JWT +3. **验证码**: 实际应用中不应在响应中返回验证码 +4. **用户ID**: 修改密码接口中的user_id应从JWT令牌中获取,而不是从请求体中传递 +5. **错误处理**: 建议在客户端实现适当的错误处理和用户提示 +6. **限流**: 建议对登录、注册等接口实施限流策略 + +## 更新日志 + +- **v1.0.0** (2025-12-17): 初始版本,包含基础的用户认证功能 \ No newline at end of file diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml new file mode 100644 index 0000000..7151c11 --- /dev/null +++ b/docs/api/openapi.yaml @@ -0,0 +1,568 @@ +openapi: 3.0.3 +info: + title: Pixel Game Server - Auth API + description: 像素游戏服务器用户认证API接口文档 + version: 1.0.0 + contact: + name: API Support + email: support@example.com + license: + name: MIT + url: https://opensource.org/licenses/MIT + +servers: + - url: http://localhost:3000 + description: 开发环境 + +tags: + - name: auth + description: 用户认证相关接口 + +paths: + /auth/login: + post: + tags: + - auth + summary: 用户登录 + description: 支持用户名、邮箱或手机号登录 + operationId: login + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginDto' + example: + identifier: testuser + password: password123 + responses: + '200': + description: 登录成功 + content: + application/json: + schema: + $ref: '#/components/schemas/LoginResponse' + example: + success: true + data: + user: + id: "1" + username: testuser + nickname: 测试用户 + email: test@example.com + phone: "+8613800138000" + avatar_url: https://example.com/avatar.jpg + role: 1 + created_at: "2025-12-17T10:00:00.000Z" + access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + refresh_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + is_new_user: false + message: 登录成功 + message: 登录成功 + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 用户名或密码错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /auth/register: + post: + tags: + - auth + summary: 用户注册 + description: 创建新用户账户 + operationId: register + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterDto' + example: + username: newuser + password: password123 + nickname: 新用户 + email: newuser@example.com + phone: "+8613800138001" + responses: + '201': + description: 注册成功 + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterResponse' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '409': + description: 用户名或邮箱已存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /auth/github: + post: + tags: + - auth + summary: GitHub OAuth登录 + description: 使用GitHub账户登录或注册 + operationId: githubOAuth + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GitHubOAuthDto' + example: + github_id: "12345678" + username: octocat + nickname: The Octocat + email: octocat@github.com + avatar_url: https://github.com/images/error/octocat_happy.gif + responses: + '200': + description: GitHub登录成功 + content: + application/json: + schema: + $ref: '#/components/schemas/GitHubOAuthResponse' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: GitHub认证失败 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /auth/forgot-password: + post: + tags: + - auth + summary: 发送密码重置验证码 + description: 向用户邮箱或手机发送密码重置验证码 + operationId: forgotPassword + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ForgotPasswordDto' + example: + identifier: test@example.com + responses: + '200': + description: 验证码发送成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ForgotPasswordResponse' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 用户不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /auth/reset-password: + post: + tags: + - auth + summary: 重置密码 + description: 使用验证码重置用户密码 + operationId: resetPassword + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ResetPasswordDto' + example: + identifier: test@example.com + verification_code: "123456" + new_password: newpassword123 + responses: + '200': + description: 密码重置成功 + content: + application/json: + schema: + $ref: '#/components/schemas/CommonResponse' + '400': + description: 请求参数错误或验证码无效 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 用户不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /auth/change-password: + put: + tags: + - auth + summary: 修改密码 + description: 用户修改自己的密码(需要提供旧密码) + operationId: changePassword + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChangePasswordDto' + example: + user_id: "1" + old_password: oldpassword123 + new_password: newpassword123 + responses: + '200': + description: 密码修改成功 + content: + application/json: + schema: + $ref: '#/components/schemas/CommonResponse' + '400': + description: 请求参数错误或旧密码不正确 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: 用户不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + +components: + schemas: + LoginDto: + type: object + required: + - identifier + - password + properties: + identifier: + type: string + description: 登录标识符,支持用户名、邮箱或手机号 + minLength: 1 + maxLength: 100 + example: testuser + password: + type: string + description: 用户密码 + minLength: 1 + maxLength: 128 + example: password123 + + RegisterDto: + type: object + required: + - username + - password + - nickname + properties: + username: + type: string + description: 用户名,只能包含字母、数字和下划线 + minLength: 1 + maxLength: 50 + pattern: '^[a-zA-Z0-9_]+$' + example: testuser + password: + type: string + description: 密码,必须包含字母和数字,长度8-128字符 + minLength: 8 + maxLength: 128 + pattern: '^(?=.*[a-zA-Z])(?=.*\d)' + example: password123 + nickname: + type: string + description: 用户昵称 + minLength: 1 + maxLength: 50 + example: 测试用户 + email: + type: string + format: email + description: 邮箱地址(可选) + example: test@example.com + phone: + type: string + description: 手机号码(可选) + example: "+8613800138000" + + GitHubOAuthDto: + type: object + required: + - github_id + - username + - nickname + properties: + github_id: + type: string + description: GitHub用户ID + minLength: 1 + maxLength: 100 + example: "12345678" + username: + type: string + description: GitHub用户名 + minLength: 1 + maxLength: 50 + example: octocat + nickname: + type: string + description: GitHub显示名称 + minLength: 1 + maxLength: 50 + example: The Octocat + email: + type: string + format: email + description: GitHub邮箱地址(可选) + example: octocat@github.com + avatar_url: + type: string + description: GitHub头像URL(可选) + example: https://github.com/images/error/octocat_happy.gif + + ForgotPasswordDto: + type: object + required: + - identifier + properties: + identifier: + type: string + description: 邮箱或手机号 + minLength: 1 + maxLength: 100 + example: test@example.com + + ResetPasswordDto: + type: object + required: + - identifier + - verification_code + - new_password + properties: + identifier: + type: string + description: 邮箱或手机号 + minLength: 1 + maxLength: 100 + example: test@example.com + verification_code: + type: string + description: 6位数字验证码 + pattern: '^\d{6}$' + example: "123456" + new_password: + type: string + description: 新密码,必须包含字母和数字,长度8-128字符 + minLength: 8 + maxLength: 128 + pattern: '^(?=.*[a-zA-Z])(?=.*\d)' + example: newpassword123 + + ChangePasswordDto: + type: object + required: + - user_id + - old_password + - new_password + properties: + user_id: + type: string + description: 用户ID(实际应用中应从JWT令牌中获取) + example: "1" + old_password: + type: string + description: 当前密码 + minLength: 1 + maxLength: 128 + example: oldpassword123 + new_password: + type: string + description: 新密码,必须包含字母和数字,长度8-128字符 + minLength: 8 + maxLength: 128 + pattern: '^(?=.*[a-zA-Z])(?=.*\d)' + example: newpassword123 + + UserInfo: + type: object + properties: + id: + type: string + description: 用户ID + example: "1" + username: + type: string + description: 用户名 + example: testuser + nickname: + type: string + description: 用户昵称 + example: 测试用户 + email: + type: string + format: email + description: 邮箱地址 + example: test@example.com + phone: + type: string + description: 手机号码 + example: "+8613800138000" + avatar_url: + type: string + description: 头像URL + example: https://example.com/avatar.jpg + role: + type: integer + description: 用户角色 + example: 1 + created_at: + type: string + format: date-time + description: 创建时间 + example: "2025-12-17T10:00:00.000Z" + + LoginResponseData: + type: object + properties: + user: + $ref: '#/components/schemas/UserInfo' + access_token: + type: string + description: 访问令牌 + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + refresh_token: + type: string + description: 刷新令牌 + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + is_new_user: + type: boolean + description: 是否为新用户 + example: false + message: + type: string + description: 响应消息 + example: 登录成功 + + LoginResponse: + type: object + properties: + success: + type: boolean + description: 请求是否成功 + example: true + data: + $ref: '#/components/schemas/LoginResponseData' + message: + type: string + description: 响应消息 + example: 登录成功 + + RegisterResponse: + type: object + properties: + success: + type: boolean + description: 请求是否成功 + example: true + data: + $ref: '#/components/schemas/LoginResponseData' + message: + type: string + description: 响应消息 + example: 注册成功 + + GitHubOAuthResponse: + type: object + properties: + success: + type: boolean + description: 请求是否成功 + example: true + data: + $ref: '#/components/schemas/LoginResponseData' + message: + type: string + description: 响应消息 + example: GitHub登录成功 + + ForgotPasswordResponseData: + type: object + properties: + verification_code: + type: string + description: 验证码(仅用于演示,实际应用中不应返回) + example: "123456" + + ForgotPasswordResponse: + type: object + properties: + success: + type: boolean + description: 请求是否成功 + example: true + data: + $ref: '#/components/schemas/ForgotPasswordResponseData' + message: + type: string + description: 响应消息 + example: 验证码已发送,请查收 + + CommonResponse: + type: object + properties: + success: + type: boolean + description: 请求是否成功 + example: true + message: + type: string + description: 响应消息 + example: 操作成功 + + ErrorResponse: + type: object + properties: + success: + type: boolean + description: 请求是否成功 + example: false + message: + type: string + description: 错误消息 + example: 操作失败 + error_code: + type: string + description: 错误代码 + example: OPERATION_FAILED \ No newline at end of file diff --git a/docs/api/postman-collection.json b/docs/api/postman-collection.json new file mode 100644 index 0000000..b7b9dfa --- /dev/null +++ b/docs/api/postman-collection.json @@ -0,0 +1,347 @@ +{ + "info": { + "name": "Pixel Game Server - Auth API", + "description": "像素游戏服务器用户认证API接口集合", + "version": "1.0.0", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:3000", + "type": "string" + } + ], + "item": [ + { + "name": "用户登录", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"identifier\": \"testuser\",\n \"password\": \"password123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/login", + "host": ["{{baseUrl}}"], + "path": ["auth", "login"] + }, + "description": "用户登录接口,支持用户名、邮箱或手机号登录" + }, + "response": [ + { + "name": "登录成功", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"identifier\": \"testuser\",\n \"password\": \"password123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/login", + "host": ["{{baseUrl}}"], + "path": ["auth", "login"] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"success\": true,\n \"data\": {\n \"user\": {\n \"id\": \"1\",\n \"username\": \"testuser\",\n \"nickname\": \"测试用户\",\n \"email\": \"test@example.com\",\n \"phone\": \"+8613800138000\",\n \"avatar_url\": \"https://example.com/avatar.jpg\",\n \"role\": 1,\n \"created_at\": \"2025-12-17T10:00:00.000Z\"\n },\n \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"refresh_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"is_new_user\": false,\n \"message\": \"登录成功\"\n },\n \"message\": \"登录成功\"\n}" + } + ] + }, + { + "name": "用户注册", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"newuser\",\n \"password\": \"password123\",\n \"nickname\": \"新用户\",\n \"email\": \"newuser@example.com\",\n \"phone\": \"+8613800138001\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/register", + "host": ["{{baseUrl}}"], + "path": ["auth", "register"] + }, + "description": "用户注册接口,创建新用户账户" + }, + "response": [ + { + "name": "注册成功", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"newuser\",\n \"password\": \"password123\",\n \"nickname\": \"新用户\",\n \"email\": \"newuser@example.com\",\n \"phone\": \"+8613800138001\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/register", + "host": ["{{baseUrl}}"], + "path": ["auth", "register"] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"success\": true,\n \"data\": {\n \"user\": {\n \"id\": \"2\",\n \"username\": \"newuser\",\n \"nickname\": \"新用户\",\n \"email\": \"newuser@example.com\",\n \"phone\": \"+8613800138001\",\n \"avatar_url\": null,\n \"role\": 1,\n \"created_at\": \"2025-12-17T10:00:00.000Z\"\n },\n \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"is_new_user\": true,\n \"message\": \"注册成功\"\n },\n \"message\": \"注册成功\"\n}" + } + ] + }, + { + "name": "GitHub OAuth登录", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"github_id\": \"12345678\",\n \"username\": \"octocat\",\n \"nickname\": \"The Octocat\",\n \"email\": \"octocat@github.com\",\n \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/github", + "host": ["{{baseUrl}}"], + "path": ["auth", "github"] + }, + "description": "GitHub OAuth登录接口,使用GitHub账户登录或注册" + }, + "response": [ + { + "name": "GitHub登录成功", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"github_id\": \"12345678\",\n \"username\": \"octocat\",\n \"nickname\": \"The Octocat\",\n \"email\": \"octocat@github.com\",\n \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/github", + "host": ["{{baseUrl}}"], + "path": ["auth", "github"] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"success\": true,\n \"data\": {\n \"user\": {\n \"id\": \"3\",\n \"username\": \"octocat\",\n \"nickname\": \"The Octocat\",\n \"email\": \"octocat@github.com\",\n \"phone\": null,\n \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n \"role\": 1,\n \"created_at\": \"2025-12-17T10:00:00.000Z\"\n },\n \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"is_new_user\": true,\n \"message\": \"GitHub账户绑定成功\"\n },\n \"message\": \"GitHub账户绑定成功\"\n}" + } + ] + }, + { + "name": "发送密码重置验证码", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"identifier\": \"test@example.com\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/forgot-password", + "host": ["{{baseUrl}}"], + "path": ["auth", "forgot-password"] + }, + "description": "发送密码重置验证码到用户邮箱或手机" + }, + "response": [ + { + "name": "验证码发送成功", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"identifier\": \"test@example.com\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/forgot-password", + "host": ["{{baseUrl}}"], + "path": ["auth", "forgot-password"] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"success\": true,\n \"data\": {\n \"verification_code\": \"123456\"\n },\n \"message\": \"验证码已发送,请查收\"\n}" + } + ] + }, + { + "name": "重置密码", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"identifier\": \"test@example.com\",\n \"verification_code\": \"123456\",\n \"new_password\": \"newpassword123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/reset-password", + "host": ["{{baseUrl}}"], + "path": ["auth", "reset-password"] + }, + "description": "使用验证码重置用户密码" + }, + "response": [ + { + "name": "密码重置成功", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"identifier\": \"test@example.com\",\n \"verification_code\": \"123456\",\n \"new_password\": \"newpassword123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/reset-password", + "host": ["{{baseUrl}}"], + "path": ["auth", "reset-password"] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"success\": true,\n \"message\": \"密码重置成功\"\n}" + } + ] + }, + { + "name": "修改密码", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"user_id\": \"1\",\n \"old_password\": \"oldpassword123\",\n \"new_password\": \"newpassword123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/change-password", + "host": ["{{baseUrl}}"], + "path": ["auth", "change-password"] + }, + "description": "用户修改自己的密码(需要提供旧密码)" + }, + "response": [ + { + "name": "密码修改成功", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"user_id\": \"1\",\n \"old_password\": \"oldpassword123\",\n \"new_password\": \"newpassword123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/change-password", + "host": ["{{baseUrl}}"], + "path": ["auth", "change-password"] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"success\": true,\n \"message\": \"密码修改成功\"\n}" + } + ] + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 1ffa05a..f7cea8f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@nestjs/platform-express": "^10.4.20", "@nestjs/platform-socket.io": "^10.4.20", "@nestjs/schedule": "^4.1.2", + "@nestjs/swagger": "^11.2.3", "@nestjs/typeorm": "^11.0.0", "@nestjs/websockets": "^10.4.20", "@types/bcrypt": "^6.0.0", @@ -39,6 +40,7 @@ "pino": "^10.1.0", "reflect-metadata": "^0.1.14", "rxjs": "^7.8.2", + "swagger-ui-express": "^5.0.1", "typeorm": "^0.3.28" }, "devDependencies": { diff --git a/src/business/login/login-response.dto.ts b/src/business/login/login-response.dto.ts new file mode 100644 index 0000000..d9f2a18 --- /dev/null +++ b/src/business/login/login-response.dto.ts @@ -0,0 +1,267 @@ +/** + * 登录业务响应数据传输对象 + * + * 功能描述: + * - 定义登录相关API的响应数据结构 + * - 提供Swagger文档生成支持 + * - 确保API响应的数据格式一致性 + * + * @author moyin + * @version 1.0.0 + * @since 2025-12-17 + */ + +import { ApiProperty } from '@nestjs/swagger'; + +/** + * 用户信息响应DTO + */ +export class UserInfoDto { + @ApiProperty({ + description: '用户ID', + example: '1' + }) + id: string; + + @ApiProperty({ + description: '用户名', + example: 'testuser' + }) + username: string; + + @ApiProperty({ + description: '用户昵称', + example: '测试用户' + }) + nickname: string; + + @ApiProperty({ + description: '邮箱地址', + example: 'test@example.com', + required: false + }) + email?: string; + + @ApiProperty({ + description: '手机号码', + example: '+8613800138000', + required: false + }) + phone?: string; + + @ApiProperty({ + description: '头像URL', + example: 'https://example.com/avatar.jpg', + required: false + }) + avatar_url?: string; + + @ApiProperty({ + description: '用户角色', + example: 1 + }) + role: number; + + @ApiProperty({ + description: '创建时间', + example: '2025-12-17T10:00:00.000Z' + }) + created_at: Date; +} + +/** + * 登录响应数据DTO + */ +export class LoginResponseDataDto { + @ApiProperty({ + description: '用户信息', + type: UserInfoDto + }) + user: UserInfoDto; + + @ApiProperty({ + description: '访问令牌', + example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' + }) + access_token: string; + + @ApiProperty({ + description: '刷新令牌', + example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', + required: false + }) + refresh_token?: string; + + @ApiProperty({ + description: '是否为新用户', + example: false, + required: false + }) + is_new_user?: boolean; + + @ApiProperty({ + description: '响应消息', + example: '登录成功' + }) + message: string; +} + +/** + * 登录响应DTO + */ +export class LoginResponseDto { + @ApiProperty({ + description: '请求是否成功', + example: true + }) + success: boolean; + + @ApiProperty({ + description: '响应数据', + type: LoginResponseDataDto, + required: false + }) + data?: LoginResponseDataDto; + + @ApiProperty({ + description: '响应消息', + example: '登录成功' + }) + message: string; + + @ApiProperty({ + description: '错误代码', + example: 'LOGIN_FAILED', + required: false + }) + error_code?: string; +} + +/** + * 注册响应DTO + */ +export class RegisterResponseDto { + @ApiProperty({ + description: '请求是否成功', + example: true + }) + success: boolean; + + @ApiProperty({ + description: '响应数据', + type: LoginResponseDataDto, + required: false + }) + data?: LoginResponseDataDto; + + @ApiProperty({ + description: '响应消息', + example: '注册成功' + }) + message: string; + + @ApiProperty({ + description: '错误代码', + example: 'REGISTER_FAILED', + required: false + }) + error_code?: string; +} + +/** + * GitHub OAuth响应DTO + */ +export class GitHubOAuthResponseDto { + @ApiProperty({ + description: '请求是否成功', + example: true + }) + success: boolean; + + @ApiProperty({ + description: '响应数据', + type: LoginResponseDataDto, + required: false + }) + data?: LoginResponseDataDto; + + @ApiProperty({ + description: '响应消息', + example: 'GitHub登录成功' + }) + message: string; + + @ApiProperty({ + description: '错误代码', + example: 'GITHUB_OAUTH_FAILED', + required: false + }) + error_code?: string; +} + +/** + * 忘记密码响应数据DTO + */ +export class ForgotPasswordResponseDataDto { + @ApiProperty({ + description: '验证码(仅用于演示,实际应用中不应返回)', + example: '123456', + required: false + }) + verification_code?: string; +} + +/** + * 忘记密码响应DTO + */ +export class ForgotPasswordResponseDto { + @ApiProperty({ + description: '请求是否成功', + example: true + }) + success: boolean; + + @ApiProperty({ + description: '响应数据', + type: ForgotPasswordResponseDataDto, + required: false + }) + data?: ForgotPasswordResponseDataDto; + + @ApiProperty({ + description: '响应消息', + example: '验证码已发送,请查收' + }) + message: string; + + @ApiProperty({ + description: '错误代码', + example: 'SEND_CODE_FAILED', + required: false + }) + error_code?: string; +} + +/** + * 通用响应DTO(用于重置密码、修改密码等) + */ +export class CommonResponseDto { + @ApiProperty({ + description: '请求是否成功', + example: true + }) + success: boolean; + + @ApiProperty({ + description: '响应消息', + example: '操作成功' + }) + message: string; + + @ApiProperty({ + description: '错误代码', + example: 'OPERATION_FAILED', + required: false + }) + error_code?: string; +} \ No newline at end of file diff --git a/src/business/login/login.controller.ts b/src/business/login/login.controller.ts index 7196805..4aee6af 100644 --- a/src/business/login/login.controller.ts +++ b/src/business/login/login.controller.ts @@ -20,9 +20,18 @@ */ import { Controller, Post, Put, Body, HttpCode, HttpStatus, ValidationPipe, UsePipes, Logger } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse as SwaggerApiResponse, ApiBody } from '@nestjs/swagger'; import { LoginService, ApiResponse, LoginResponse } from './login.service'; import { LoginDto, RegisterDto, GitHubOAuthDto, ForgotPasswordDto, ResetPasswordDto, ChangePasswordDto } from './login.dto'; +import { + LoginResponseDto, + RegisterResponseDto, + GitHubOAuthResponseDto, + ForgotPasswordResponseDto, + CommonResponseDto +} from './login-response.dto'; +@ApiTags('auth') @Controller('auth') export class LoginController { private readonly logger = new Logger(LoginController.name); @@ -35,6 +44,24 @@ export class LoginController { * @param loginDto 登录数据 * @returns 登录结果 */ + @ApiOperation({ + summary: '用户登录', + description: '支持用户名、邮箱或手机号登录' + }) + @ApiBody({ type: LoginDto }) + @SwaggerApiResponse({ + status: 200, + description: '登录成功', + type: LoginResponseDto + }) + @SwaggerApiResponse({ + status: 400, + description: '请求参数错误' + }) + @SwaggerApiResponse({ + status: 401, + description: '用户名或密码错误' + }) @Post('login') @HttpCode(HttpStatus.OK) @UsePipes(new ValidationPipe({ transform: true })) @@ -51,6 +78,24 @@ export class LoginController { * @param registerDto 注册数据 * @returns 注册结果 */ + @ApiOperation({ + summary: '用户注册', + description: '创建新用户账户' + }) + @ApiBody({ type: RegisterDto }) + @SwaggerApiResponse({ + status: 201, + description: '注册成功', + type: RegisterResponseDto + }) + @SwaggerApiResponse({ + status: 400, + description: '请求参数错误' + }) + @SwaggerApiResponse({ + status: 409, + description: '用户名或邮箱已存在' + }) @Post('register') @HttpCode(HttpStatus.CREATED) @UsePipes(new ValidationPipe({ transform: true })) @@ -70,6 +115,24 @@ export class LoginController { * @param githubDto GitHub OAuth数据 * @returns 登录结果 */ + @ApiOperation({ + summary: 'GitHub OAuth登录', + description: '使用GitHub账户登录或注册' + }) + @ApiBody({ type: GitHubOAuthDto }) + @SwaggerApiResponse({ + status: 200, + description: 'GitHub登录成功', + type: GitHubOAuthResponseDto + }) + @SwaggerApiResponse({ + status: 400, + description: '请求参数错误' + }) + @SwaggerApiResponse({ + status: 401, + description: 'GitHub认证失败' + }) @Post('github') @HttpCode(HttpStatus.OK) @UsePipes(new ValidationPipe({ transform: true })) @@ -89,6 +152,24 @@ export class LoginController { * @param forgotPasswordDto 忘记密码数据 * @returns 发送结果 */ + @ApiOperation({ + summary: '发送密码重置验证码', + description: '向用户邮箱或手机发送密码重置验证码' + }) + @ApiBody({ type: ForgotPasswordDto }) + @SwaggerApiResponse({ + status: 200, + description: '验证码发送成功', + type: ForgotPasswordResponseDto + }) + @SwaggerApiResponse({ + status: 400, + description: '请求参数错误' + }) + @SwaggerApiResponse({ + status: 404, + description: '用户不存在' + }) @Post('forgot-password') @HttpCode(HttpStatus.OK) @UsePipes(new ValidationPipe({ transform: true })) @@ -102,6 +183,24 @@ export class LoginController { * @param resetPasswordDto 重置密码数据 * @returns 重置结果 */ + @ApiOperation({ + summary: '重置密码', + description: '使用验证码重置用户密码' + }) + @ApiBody({ type: ResetPasswordDto }) + @SwaggerApiResponse({ + status: 200, + description: '密码重置成功', + type: CommonResponseDto + }) + @SwaggerApiResponse({ + status: 400, + description: '请求参数错误或验证码无效' + }) + @SwaggerApiResponse({ + status: 404, + description: '用户不存在' + }) @Post('reset-password') @HttpCode(HttpStatus.OK) @UsePipes(new ValidationPipe({ transform: true })) @@ -119,6 +218,24 @@ export class LoginController { * @param changePasswordDto 修改密码数据 * @returns 修改结果 */ + @ApiOperation({ + summary: '修改密码', + description: '用户修改自己的密码(需要提供旧密码)' + }) + @ApiBody({ type: ChangePasswordDto }) + @SwaggerApiResponse({ + status: 200, + description: '密码修改成功', + type: CommonResponseDto + }) + @SwaggerApiResponse({ + status: 400, + description: '请求参数错误或旧密码不正确' + }) + @SwaggerApiResponse({ + status: 404, + description: '用户不存在' + }) @Put('change-password') @HttpCode(HttpStatus.OK) @UsePipes(new ValidationPipe({ transform: true })) diff --git a/src/business/login/login.dto.ts b/src/business/login/login.dto.ts index 12ff708..8ab3123 100644 --- a/src/business/login/login.dto.ts +++ b/src/business/login/login.dto.ts @@ -21,6 +21,7 @@ import { Matches, IsNumberString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; /** * 登录请求DTO @@ -30,6 +31,12 @@ export class LoginDto { * 登录标识符 * 支持用户名、邮箱或手机号登录 */ + @ApiProperty({ + description: '登录标识符,支持用户名、邮箱或手机号', + example: 'testuser', + minLength: 1, + maxLength: 100 + }) @IsString({ message: '登录标识符必须是字符串' }) @IsNotEmpty({ message: '登录标识符不能为空' }) @Length(1, 100, { message: '登录标识符长度需在1-100字符之间' }) @@ -38,6 +45,12 @@ export class LoginDto { /** * 密码 */ + @ApiProperty({ + description: '用户密码', + example: 'password123', + minLength: 1, + maxLength: 128 + }) @IsString({ message: '密码必须是字符串' }) @IsNotEmpty({ message: '密码不能为空' }) @Length(1, 128, { message: '密码长度需在1-128字符之间' }) @@ -51,6 +64,13 @@ export class RegisterDto { /** * 用户名 */ + @ApiProperty({ + description: '用户名,只能包含字母、数字和下划线', + example: 'testuser', + minLength: 1, + maxLength: 50, + pattern: '^[a-zA-Z0-9_]+$' + }) @IsString({ message: '用户名必须是字符串' }) @IsNotEmpty({ message: '用户名不能为空' }) @Length(1, 50, { message: '用户名长度需在1-50字符之间' }) @@ -60,6 +80,12 @@ export class RegisterDto { /** * 密码 */ + @ApiProperty({ + description: '密码,必须包含字母和数字,长度8-128字符', + example: 'password123', + minLength: 8, + maxLength: 128 + }) @IsString({ message: '密码必须是字符串' }) @IsNotEmpty({ message: '密码不能为空' }) @Length(8, 128, { message: '密码长度需在8-128字符之间' }) @@ -69,6 +95,12 @@ export class RegisterDto { /** * 昵称 */ + @ApiProperty({ + description: '用户昵称', + example: '测试用户', + minLength: 1, + maxLength: 50 + }) @IsString({ message: '昵称必须是字符串' }) @IsNotEmpty({ message: '昵称不能为空' }) @Length(1, 50, { message: '昵称长度需在1-50字符之间' }) @@ -77,6 +109,11 @@ export class RegisterDto { /** * 邮箱(可选) */ + @ApiProperty({ + description: '邮箱地址(可选)', + example: 'test@example.com', + required: false + }) @IsOptional() @IsEmail({}, { message: '邮箱格式不正确' }) email?: string; @@ -84,6 +121,11 @@ export class RegisterDto { /** * 手机号(可选) */ + @ApiProperty({ + description: '手机号码(可选)', + example: '+8613800138000', + required: false + }) @IsOptional() @IsPhoneNumber(null, { message: '手机号格式不正确' }) phone?: string; @@ -96,6 +138,12 @@ export class GitHubOAuthDto { /** * GitHub用户ID */ + @ApiProperty({ + description: 'GitHub用户ID', + example: '12345678', + minLength: 1, + maxLength: 100 + }) @IsString({ message: 'GitHub ID必须是字符串' }) @IsNotEmpty({ message: 'GitHub ID不能为空' }) @Length(1, 100, { message: 'GitHub ID长度需在1-100字符之间' }) @@ -104,6 +152,12 @@ export class GitHubOAuthDto { /** * 用户名 */ + @ApiProperty({ + description: 'GitHub用户名', + example: 'octocat', + minLength: 1, + maxLength: 50 + }) @IsString({ message: '用户名必须是字符串' }) @IsNotEmpty({ message: '用户名不能为空' }) @Length(1, 50, { message: '用户名长度需在1-50字符之间' }) @@ -112,6 +166,12 @@ export class GitHubOAuthDto { /** * 昵称 */ + @ApiProperty({ + description: 'GitHub显示名称', + example: 'The Octocat', + minLength: 1, + maxLength: 50 + }) @IsString({ message: '昵称必须是字符串' }) @IsNotEmpty({ message: '昵称不能为空' }) @Length(1, 50, { message: '昵称长度需在1-50字符之间' }) @@ -120,6 +180,11 @@ export class GitHubOAuthDto { /** * 邮箱(可选) */ + @ApiProperty({ + description: 'GitHub邮箱地址(可选)', + example: 'octocat@github.com', + required: false + }) @IsOptional() @IsEmail({}, { message: '邮箱格式不正确' }) email?: string; @@ -127,6 +192,11 @@ export class GitHubOAuthDto { /** * 头像URL(可选) */ + @ApiProperty({ + description: 'GitHub头像URL(可选)', + example: 'https://github.com/images/error/octocat_happy.gif', + required: false + }) @IsOptional() @IsString({ message: '头像URL必须是字符串' }) avatar_url?: string; @@ -139,6 +209,12 @@ export class ForgotPasswordDto { /** * 邮箱或手机号 */ + @ApiProperty({ + description: '邮箱或手机号', + example: 'test@example.com', + minLength: 1, + maxLength: 100 + }) @IsString({ message: '标识符必须是字符串' }) @IsNotEmpty({ message: '邮箱或手机号不能为空' }) @Length(1, 100, { message: '标识符长度需在1-100字符之间' }) @@ -152,6 +228,12 @@ export class ResetPasswordDto { /** * 邮箱或手机号 */ + @ApiProperty({ + description: '邮箱或手机号', + example: 'test@example.com', + minLength: 1, + maxLength: 100 + }) @IsString({ message: '标识符必须是字符串' }) @IsNotEmpty({ message: '邮箱或手机号不能为空' }) @Length(1, 100, { message: '标识符长度需在1-100字符之间' }) @@ -160,6 +242,11 @@ export class ResetPasswordDto { /** * 验证码 */ + @ApiProperty({ + description: '6位数字验证码', + example: '123456', + pattern: '^\\d{6}$' + }) @IsString({ message: '验证码必须是字符串' }) @IsNotEmpty({ message: '验证码不能为空' }) @Matches(/^\d{6}$/, { message: '验证码必须是6位数字' }) @@ -168,6 +255,12 @@ export class ResetPasswordDto { /** * 新密码 */ + @ApiProperty({ + description: '新密码,必须包含字母和数字,长度8-128字符', + example: 'newpassword123', + minLength: 8, + maxLength: 128 + }) @IsString({ message: '新密码必须是字符串' }) @IsNotEmpty({ message: '新密码不能为空' }) @Length(8, 128, { message: '新密码长度需在8-128字符之间' }) @@ -183,6 +276,10 @@ export class ChangePasswordDto { * 用户ID * 实际应用中应从JWT令牌中获取,这里为了演示放在请求体中 */ + @ApiProperty({ + description: '用户ID(实际应用中应从JWT令牌中获取)', + example: '1' + }) @IsNumberString({}, { message: '用户ID必须是数字字符串' }) @IsNotEmpty({ message: '用户ID不能为空' }) user_id: string; @@ -190,6 +287,12 @@ export class ChangePasswordDto { /** * 旧密码 */ + @ApiProperty({ + description: '当前密码', + example: 'oldpassword123', + minLength: 1, + maxLength: 128 + }) @IsString({ message: '旧密码必须是字符串' }) @IsNotEmpty({ message: '旧密码不能为空' }) @Length(1, 128, { message: '旧密码长度需在1-128字符之间' }) @@ -198,6 +301,12 @@ export class ChangePasswordDto { /** * 新密码 */ + @ApiProperty({ + description: '新密码,必须包含字母和数字,长度8-128字符', + example: 'newpassword123', + minLength: 8, + maxLength: 128 + }) @IsString({ message: '新密码必须是字符串' }) @IsNotEmpty({ message: '新密码不能为空' }) @Length(8, 128, { message: '新密码长度需在8-128字符之间' }) diff --git a/src/core/utils/logger/logger.config.ts b/src/core/utils/logger/logger.config.ts index 77fc642..5d469ca 100644 --- a/src/core/utils/logger/logger.config.ts +++ b/src/core/utils/logger/logger.config.ts @@ -200,8 +200,8 @@ export class LoggerConfigFactory { statusCode: res.statusCode, statusMessage: res.statusMessage, headers: { - 'content-type': res.getHeader('content-type'), - 'content-length': res.getHeader('content-length'), + 'content-type': res.getHeader ? res.getHeader('content-type') : res.headers?.['content-type'], + 'content-length': res.getHeader ? res.getHeader('content-length') : res.headers?.['content-length'], }, responseTime: res.responseTime, }), diff --git a/src/main.ts b/src/main.ts index 1ea34d4..4f1028d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,11 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { const app = await NestFactory.create(AppModule); + // 全局启用校验管道(核心配置) app.useGlobalPipes( new ValidationPipe({ @@ -12,8 +14,36 @@ async function bootstrap() { transform: true, // 自动把入参转为 DTO 对应的类型(比如前端传的字符串数字 `'1'` 转为数字 `1`) }), ); + + // 配置Swagger文档 + const config = new DocumentBuilder() + .setTitle('Pixel Game Server API') + .setDescription('像素游戏服务器API文档 - 包含用户认证、登录注册等功能') + .setVersion('1.0.0') + .addTag('auth', '用户认证相关接口') + .addBearerAuth( + { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + name: 'JWT', + description: '请输入JWT token', + in: 'header', + }, + 'JWT-auth', + ) + .build(); + + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('api-docs', app, document, { + swaggerOptions: { + persistAuthorization: true, + }, + }); + await app.listen(3000); console.log('Pixel Game Server is running on http://localhost:3000'); + console.log('API Documentation is available at http://localhost:3000/api-docs'); } bootstrap();