Merge pull request 'feat:实现完整的API文档系统' (#4) from feature/api-documentation-system into main

Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
2025-12-17 15:23:20 +08:00
12 changed files with 2173 additions and 4 deletions

View File

@@ -173,10 +173,18 @@ export class PlayerService {
- [命名规范](./docs/naming_convention.md) - 项目命名规范和最佳实践 - [命名规范](./docs/naming_convention.md) - 项目命名规范和最佳实践
- [NestJS 使用指南](./docs/nestjs_guide.md) - 详细的 NestJS 开发指南,包含实战案例 - [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 帮助遵循规范 1. **开发前**:先读 AI 辅助指南,了解如何用 AI 帮助遵循规范
2. **开发中**:参考具体规范文档,使用 AI 实时检查代码质量 2. **开发中**:参考具体规范文档,使用 AI 实时检查代码质量
3. **提交前**:用 AI 检查代码和提交信息是否符合规范 3. **API 开发**使Swagger UI 进行接口测试,参考 API 文档进行开发
4. **提交前**:用 AI 检查代码和提交信息是否符合规范
## 前置要求 ## 前置要求
@@ -262,10 +270,19 @@ test/
├── api/ # API 测试 ├── api/ # API 测试
└── service/ # 服务测试 └── service/ # 服务测试
docs/ # 项目文档 docs/ # 项目文档
├── api/ # API 接口文档
│ ├── README.md # API 文档使用指南
│ ├── api-documentation.md # 详细接口文档
│ ├── openapi.yaml # OpenAPI 规范文件
│ └── postman-collection.json # Postman 测试集合
├── systems/ # 系统设计文档
│ ├── logger/ # 日志系统文档
│ └── user-auth/ # 用户认证系统文档
├── backend_development_guide.md # 后端开发规范 ├── backend_development_guide.md # 后端开发规范
├── git_commit_guide.md # Git 提交规范 ├── git_commit_guide.md # Git 提交规范
├── naming_convention.md # 命名规范 ├── 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) **详细文档**: [用户认证系统文档](./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 的高性能日志系统,提供结构化日志记录: 基于 Pino 的高性能日志系统,提供结构化日志记录:

139
docs/README.md Normal file
View File

@@ -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/)

141
docs/api/README.md Normal file
View File

@@ -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/)

View File

@@ -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): 初始版本,包含基础的用户认证功能

568
docs/api/openapi.yaml Normal file
View File

@@ -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

View File

@@ -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}"
}
]
}
]
}

View File

@@ -28,6 +28,7 @@
"@nestjs/platform-express": "^10.4.20", "@nestjs/platform-express": "^10.4.20",
"@nestjs/platform-socket.io": "^10.4.20", "@nestjs/platform-socket.io": "^10.4.20",
"@nestjs/schedule": "^4.1.2", "@nestjs/schedule": "^4.1.2",
"@nestjs/swagger": "^11.2.3",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
"@nestjs/websockets": "^10.4.20", "@nestjs/websockets": "^10.4.20",
"@types/bcrypt": "^6.0.0", "@types/bcrypt": "^6.0.0",
@@ -39,6 +40,7 @@
"pino": "^10.1.0", "pino": "^10.1.0",
"reflect-metadata": "^0.1.14", "reflect-metadata": "^0.1.14",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"swagger-ui-express": "^5.0.1",
"typeorm": "^0.3.28" "typeorm": "^0.3.28"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -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;
}

View File

@@ -20,9 +20,18 @@
*/ */
import { Controller, Post, Put, Body, HttpCode, HttpStatus, ValidationPipe, UsePipes, Logger } from '@nestjs/common'; 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 { LoginService, ApiResponse, LoginResponse } from './login.service';
import { LoginDto, RegisterDto, GitHubOAuthDto, ForgotPasswordDto, ResetPasswordDto, ChangePasswordDto } from './login.dto'; import { LoginDto, RegisterDto, GitHubOAuthDto, ForgotPasswordDto, ResetPasswordDto, ChangePasswordDto } from './login.dto';
import {
LoginResponseDto,
RegisterResponseDto,
GitHubOAuthResponseDto,
ForgotPasswordResponseDto,
CommonResponseDto
} from './login-response.dto';
@ApiTags('auth')
@Controller('auth') @Controller('auth')
export class LoginController { export class LoginController {
private readonly logger = new Logger(LoginController.name); private readonly logger = new Logger(LoginController.name);
@@ -35,6 +44,24 @@ export class LoginController {
* @param loginDto 登录数据 * @param loginDto 登录数据
* @returns 登录结果 * @returns 登录结果
*/ */
@ApiOperation({
summary: '用户登录',
description: '支持用户名、邮箱或手机号登录'
})
@ApiBody({ type: LoginDto })
@SwaggerApiResponse({
status: 200,
description: '登录成功',
type: LoginResponseDto
})
@SwaggerApiResponse({
status: 400,
description: '请求参数错误'
})
@SwaggerApiResponse({
status: 401,
description: '用户名或密码错误'
})
@Post('login') @Post('login')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
@@ -51,6 +78,24 @@ export class LoginController {
* @param registerDto 注册数据 * @param registerDto 注册数据
* @returns 注册结果 * @returns 注册结果
*/ */
@ApiOperation({
summary: '用户注册',
description: '创建新用户账户'
})
@ApiBody({ type: RegisterDto })
@SwaggerApiResponse({
status: 201,
description: '注册成功',
type: RegisterResponseDto
})
@SwaggerApiResponse({
status: 400,
description: '请求参数错误'
})
@SwaggerApiResponse({
status: 409,
description: '用户名或邮箱已存在'
})
@Post('register') @Post('register')
@HttpCode(HttpStatus.CREATED) @HttpCode(HttpStatus.CREATED)
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
@@ -70,6 +115,24 @@ export class LoginController {
* @param githubDto GitHub OAuth数据 * @param githubDto GitHub OAuth数据
* @returns 登录结果 * @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') @Post('github')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
@@ -89,6 +152,24 @@ export class LoginController {
* @param forgotPasswordDto 忘记密码数据 * @param forgotPasswordDto 忘记密码数据
* @returns 发送结果 * @returns 发送结果
*/ */
@ApiOperation({
summary: '发送密码重置验证码',
description: '向用户邮箱或手机发送密码重置验证码'
})
@ApiBody({ type: ForgotPasswordDto })
@SwaggerApiResponse({
status: 200,
description: '验证码发送成功',
type: ForgotPasswordResponseDto
})
@SwaggerApiResponse({
status: 400,
description: '请求参数错误'
})
@SwaggerApiResponse({
status: 404,
description: '用户不存在'
})
@Post('forgot-password') @Post('forgot-password')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
@@ -102,6 +183,24 @@ export class LoginController {
* @param resetPasswordDto 重置密码数据 * @param resetPasswordDto 重置密码数据
* @returns 重置结果 * @returns 重置结果
*/ */
@ApiOperation({
summary: '重置密码',
description: '使用验证码重置用户密码'
})
@ApiBody({ type: ResetPasswordDto })
@SwaggerApiResponse({
status: 200,
description: '密码重置成功',
type: CommonResponseDto
})
@SwaggerApiResponse({
status: 400,
description: '请求参数错误或验证码无效'
})
@SwaggerApiResponse({
status: 404,
description: '用户不存在'
})
@Post('reset-password') @Post('reset-password')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
@@ -119,6 +218,24 @@ export class LoginController {
* @param changePasswordDto 修改密码数据 * @param changePasswordDto 修改密码数据
* @returns 修改结果 * @returns 修改结果
*/ */
@ApiOperation({
summary: '修改密码',
description: '用户修改自己的密码(需要提供旧密码)'
})
@ApiBody({ type: ChangePasswordDto })
@SwaggerApiResponse({
status: 200,
description: '密码修改成功',
type: CommonResponseDto
})
@SwaggerApiResponse({
status: 400,
description: '请求参数错误或旧密码不正确'
})
@SwaggerApiResponse({
status: 404,
description: '用户不存在'
})
@Put('change-password') @Put('change-password')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))

View File

@@ -21,6 +21,7 @@ import {
Matches, Matches,
IsNumberString IsNumberString
} from 'class-validator'; } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
/** /**
* 登录请求DTO * 登录请求DTO
@@ -30,6 +31,12 @@ export class LoginDto {
* 登录标识符 * 登录标识符
* 支持用户名、邮箱或手机号登录 * 支持用户名、邮箱或手机号登录
*/ */
@ApiProperty({
description: '登录标识符,支持用户名、邮箱或手机号',
example: 'testuser',
minLength: 1,
maxLength: 100
})
@IsString({ message: '登录标识符必须是字符串' }) @IsString({ message: '登录标识符必须是字符串' })
@IsNotEmpty({ message: '登录标识符不能为空' }) @IsNotEmpty({ message: '登录标识符不能为空' })
@Length(1, 100, { message: '登录标识符长度需在1-100字符之间' }) @Length(1, 100, { message: '登录标识符长度需在1-100字符之间' })
@@ -38,6 +45,12 @@ export class LoginDto {
/** /**
* 密码 * 密码
*/ */
@ApiProperty({
description: '用户密码',
example: 'password123',
minLength: 1,
maxLength: 128
})
@IsString({ message: '密码必须是字符串' }) @IsString({ message: '密码必须是字符串' })
@IsNotEmpty({ message: '密码不能为空' }) @IsNotEmpty({ message: '密码不能为空' })
@Length(1, 128, { message: '密码长度需在1-128字符之间' }) @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: '用户名必须是字符串' }) @IsString({ message: '用户名必须是字符串' })
@IsNotEmpty({ message: '用户名不能为空' }) @IsNotEmpty({ message: '用户名不能为空' })
@Length(1, 50, { message: '用户名长度需在1-50字符之间' }) @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: '密码必须是字符串' }) @IsString({ message: '密码必须是字符串' })
@IsNotEmpty({ message: '密码不能为空' }) @IsNotEmpty({ message: '密码不能为空' })
@Length(8, 128, { message: '密码长度需在8-128字符之间' }) @Length(8, 128, { message: '密码长度需在8-128字符之间' })
@@ -69,6 +95,12 @@ export class RegisterDto {
/** /**
* 昵称 * 昵称
*/ */
@ApiProperty({
description: '用户昵称',
example: '测试用户',
minLength: 1,
maxLength: 50
})
@IsString({ message: '昵称必须是字符串' }) @IsString({ message: '昵称必须是字符串' })
@IsNotEmpty({ message: '昵称不能为空' }) @IsNotEmpty({ message: '昵称不能为空' })
@Length(1, 50, { message: '昵称长度需在1-50字符之间' }) @Length(1, 50, { message: '昵称长度需在1-50字符之间' })
@@ -77,6 +109,11 @@ export class RegisterDto {
/** /**
* 邮箱(可选) * 邮箱(可选)
*/ */
@ApiProperty({
description: '邮箱地址(可选)',
example: 'test@example.com',
required: false
})
@IsOptional() @IsOptional()
@IsEmail({}, { message: '邮箱格式不正确' }) @IsEmail({}, { message: '邮箱格式不正确' })
email?: string; email?: string;
@@ -84,6 +121,11 @@ export class RegisterDto {
/** /**
* 手机号(可选) * 手机号(可选)
*/ */
@ApiProperty({
description: '手机号码(可选)',
example: '+8613800138000',
required: false
})
@IsOptional() @IsOptional()
@IsPhoneNumber(null, { message: '手机号格式不正确' }) @IsPhoneNumber(null, { message: '手机号格式不正确' })
phone?: string; phone?: string;
@@ -96,6 +138,12 @@ export class GitHubOAuthDto {
/** /**
* GitHub用户ID * GitHub用户ID
*/ */
@ApiProperty({
description: 'GitHub用户ID',
example: '12345678',
minLength: 1,
maxLength: 100
})
@IsString({ message: 'GitHub ID必须是字符串' }) @IsString({ message: 'GitHub ID必须是字符串' })
@IsNotEmpty({ message: 'GitHub ID不能为空' }) @IsNotEmpty({ message: 'GitHub ID不能为空' })
@Length(1, 100, { message: 'GitHub ID长度需在1-100字符之间' }) @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: '用户名必须是字符串' }) @IsString({ message: '用户名必须是字符串' })
@IsNotEmpty({ message: '用户名不能为空' }) @IsNotEmpty({ message: '用户名不能为空' })
@Length(1, 50, { message: '用户名长度需在1-50字符之间' }) @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: '昵称必须是字符串' }) @IsString({ message: '昵称必须是字符串' })
@IsNotEmpty({ message: '昵称不能为空' }) @IsNotEmpty({ message: '昵称不能为空' })
@Length(1, 50, { message: '昵称长度需在1-50字符之间' }) @Length(1, 50, { message: '昵称长度需在1-50字符之间' })
@@ -120,6 +180,11 @@ export class GitHubOAuthDto {
/** /**
* 邮箱(可选) * 邮箱(可选)
*/ */
@ApiProperty({
description: 'GitHub邮箱地址可选',
example: 'octocat@github.com',
required: false
})
@IsOptional() @IsOptional()
@IsEmail({}, { message: '邮箱格式不正确' }) @IsEmail({}, { message: '邮箱格式不正确' })
email?: string; email?: string;
@@ -127,6 +192,11 @@ export class GitHubOAuthDto {
/** /**
* 头像URL可选 * 头像URL可选
*/ */
@ApiProperty({
description: 'GitHub头像URL可选',
example: 'https://github.com/images/error/octocat_happy.gif',
required: false
})
@IsOptional() @IsOptional()
@IsString({ message: '头像URL必须是字符串' }) @IsString({ message: '头像URL必须是字符串' })
avatar_url?: string; avatar_url?: string;
@@ -139,6 +209,12 @@ export class ForgotPasswordDto {
/** /**
* 邮箱或手机号 * 邮箱或手机号
*/ */
@ApiProperty({
description: '邮箱或手机号',
example: 'test@example.com',
minLength: 1,
maxLength: 100
})
@IsString({ message: '标识符必须是字符串' }) @IsString({ message: '标识符必须是字符串' })
@IsNotEmpty({ message: '邮箱或手机号不能为空' }) @IsNotEmpty({ message: '邮箱或手机号不能为空' })
@Length(1, 100, { message: '标识符长度需在1-100字符之间' }) @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: '标识符必须是字符串' }) @IsString({ message: '标识符必须是字符串' })
@IsNotEmpty({ message: '邮箱或手机号不能为空' }) @IsNotEmpty({ message: '邮箱或手机号不能为空' })
@Length(1, 100, { message: '标识符长度需在1-100字符之间' }) @Length(1, 100, { message: '标识符长度需在1-100字符之间' })
@@ -160,6 +242,11 @@ export class ResetPasswordDto {
/** /**
* 验证码 * 验证码
*/ */
@ApiProperty({
description: '6位数字验证码',
example: '123456',
pattern: '^\\d{6}$'
})
@IsString({ message: '验证码必须是字符串' }) @IsString({ message: '验证码必须是字符串' })
@IsNotEmpty({ message: '验证码不能为空' }) @IsNotEmpty({ message: '验证码不能为空' })
@Matches(/^\d{6}$/, { message: '验证码必须是6位数字' }) @Matches(/^\d{6}$/, { message: '验证码必须是6位数字' })
@@ -168,6 +255,12 @@ export class ResetPasswordDto {
/** /**
* 新密码 * 新密码
*/ */
@ApiProperty({
description: '新密码必须包含字母和数字长度8-128字符',
example: 'newpassword123',
minLength: 8,
maxLength: 128
})
@IsString({ message: '新密码必须是字符串' }) @IsString({ message: '新密码必须是字符串' })
@IsNotEmpty({ message: '新密码不能为空' }) @IsNotEmpty({ message: '新密码不能为空' })
@Length(8, 128, { message: '新密码长度需在8-128字符之间' }) @Length(8, 128, { message: '新密码长度需在8-128字符之间' })
@@ -183,6 +276,10 @@ export class ChangePasswordDto {
* 用户ID * 用户ID
* 实际应用中应从JWT令牌中获取这里为了演示放在请求体中 * 实际应用中应从JWT令牌中获取这里为了演示放在请求体中
*/ */
@ApiProperty({
description: '用户ID实际应用中应从JWT令牌中获取',
example: '1'
})
@IsNumberString({}, { message: '用户ID必须是数字字符串' }) @IsNumberString({}, { message: '用户ID必须是数字字符串' })
@IsNotEmpty({ message: '用户ID不能为空' }) @IsNotEmpty({ message: '用户ID不能为空' })
user_id: string; user_id: string;
@@ -190,6 +287,12 @@ export class ChangePasswordDto {
/** /**
* 旧密码 * 旧密码
*/ */
@ApiProperty({
description: '当前密码',
example: 'oldpassword123',
minLength: 1,
maxLength: 128
})
@IsString({ message: '旧密码必须是字符串' }) @IsString({ message: '旧密码必须是字符串' })
@IsNotEmpty({ message: '旧密码不能为空' }) @IsNotEmpty({ message: '旧密码不能为空' })
@Length(1, 128, { message: '旧密码长度需在1-128字符之间' }) @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: '新密码必须是字符串' }) @IsString({ message: '新密码必须是字符串' })
@IsNotEmpty({ message: '新密码不能为空' }) @IsNotEmpty({ message: '新密码不能为空' })
@Length(8, 128, { message: '新密码长度需在8-128字符之间' }) @Length(8, 128, { message: '新密码长度需在8-128字符之间' })

View File

@@ -200,8 +200,8 @@ export class LoggerConfigFactory {
statusCode: res.statusCode, statusCode: res.statusCode,
statusMessage: res.statusMessage, statusMessage: res.statusMessage,
headers: { headers: {
'content-type': res.getHeader('content-type'), 'content-type': res.getHeader ? res.getHeader('content-type') : res.headers?.['content-type'],
'content-length': res.getHeader('content-length'), 'content-length': res.getHeader ? res.getHeader('content-length') : res.headers?.['content-length'],
}, },
responseTime: res.responseTime, responseTime: res.responseTime,
}), }),

View File

@@ -1,9 +1,11 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common'; import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
// 全局启用校验管道(核心配置) // 全局启用校验管道(核心配置)
app.useGlobalPipes( app.useGlobalPipes(
new ValidationPipe({ new ValidationPipe({
@@ -12,8 +14,36 @@ async function bootstrap() {
transform: true, // 自动把入参转为 DTO 对应的类型(比如前端传的字符串数字 `'1'` 转为数字 `1` 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); await app.listen(3000);
console.log('Pixel Game Server is running on http://localhost: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(); bootstrap();