Merge pull request 'feature/refactor-project-structure' (#20) from feature/refactor-project-structure into main

Reviewed-on: #20
This commit was merged in pull request #20.
This commit is contained in:
2025-12-24 18:07:32 +08:00
70 changed files with 5140 additions and 2347 deletions

101
README.md
View File

@@ -1,23 +1,24 @@
# 🐋 Whale Town - 像素游戏后端服务
> 一个基于 NestJS 的现代化 2D 像素风游戏后端服务,支持实时通信、用户认证、邮箱验证等完整功能。
> 一个基于 NestJS 的现代化 2D 像素风游戏后端服务,采用业务功能模块化架构,支持用户认证、管理员后台、安全防护等完整功能。
[![Node.js](https://img.shields.io/badge/Node.js-18%2B-green.svg)](https://nodejs.org/)
[![NestJS](https://img.shields.io/badge/NestJS-10.4-red.svg)](https://nestjs.com/)
[![NestJS](https://img.shields.io/badge/NestJS-11.1-red.svg)](https://nestjs.com/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/)
[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
## 🎯 项目简介
Whale Town 是一个功能完整的像素游戏后端服务,提供
Whale Town 是一个功能完整的像素游戏后端服务,采用业务功能模块化架构设计
- 🔐 **完整用户认证系统** - 支持邮箱验证、密码重置、第三方登录
- 🎛️ **管理员后台系统** - React + Ant Design用户管理、日志监控、权限控制
- 🔐 **用户认证模块** - 完整的登录、注册、密码管理、邮箱验证系统
- 👥 **用户管理模块** - 用户状态管理、批量操作、状态统计功能
- 🛡️ **管理员模块** - 管理员认证、用户管理、密码重置、日志查看
- 🔒 **安全模块** - 频率限制、维护模式、超时控制、内容类型检查
- 📧 **智能邮件服务** - 支持测试模式和生产模式自动切换
- 🗄️ **灵活存储方案** - Redis文件存储 + 内存数据库,支持无依赖测试
- 🚀 **高性能架构** - 基于NestJS支持WebSocket实时通信
- 📚 **完整API文档** - Swagger UI + OpenAPI规范
- 🧪 **全面测试覆盖** - 154个单元测试用例全部通过
- 📚 **完整API文档** - Swagger UI + OpenAPI规范17个接口完整覆盖
- 🧪 **全面测试覆盖** - 140个单元测试用例全部通过
---
@@ -47,9 +48,9 @@ pnpm run dev
🎉 **服务启动成功!** 访问 http://localhost:3000
### 🧑‍💻 管理员后台系统
### 🧑‍💻 前端管理界面
项目包含一个功能完整的管理员后台系统,基于 React + Ant Design 构建
项目包含一个功能完整的前端管理界面,位于 `client/` 目录
**🎛️ 核心功能:**
- 管理员身份认证独立Token系统
@@ -58,23 +59,20 @@ pnpm run dev
- 运行时日志查看与下载
- 响应式界面设计
**📚 详细文档:** [docs/systems/admin-dashboard/README.md](docs/systems/admin-dashboard/README.md)
**🚀 快速启动:**
```bash
# 1. 安装依赖
pnpm install
# 2. 启动后端服务
# 1. 启动后端服务
pnpm run dev
# 3. 启动前端管理界面
pnpm -C client dev
# 2. 启动前端管理界面
cd client
pnpm install
pnpm run dev
# 4. 访问管理后台
# 3. 访问管理后台
# 地址: http://localhost:5173
# 账号: admin / Admin123456
# 默认账号: admin / Admin123456
```
### 🧪 快速测试
@@ -120,25 +118,22 @@ pnpm -C client dev
```
项目根目录/
├── src/ # 源代码目录
│ ├── api/ # API接口层预留用于游戏相关控制器
│ ├── business/ # 业务逻辑层
│ │ ── login/ # 登录业务模块
│ ├── core/ # 核心功能模块
│ │ ├── db/ # 数据库层
│ │ │ └── users/ # 用户数据模型支持MySQL/内存双模式)
│ ├── business/ # 业务功能模块(按功能组织
│ ├── auth/ # 🔐 用户认证模块
│ │ ── user-mgmt/ # 👥 用户管理模块
│ ├── admin/ # 🛡️ 管理员模块
│ │ ├── security/ # 🔒 安全模块
│ │ └── shared/ # 🔗 共享组件
│ ├── core/ # 核心技术服务
│ │ ├── db/ # 数据库层支持MySQL/内存双模式)
│ │ ├── redis/ # Redis缓存服务支持真实Redis/文件存储)
│ │ ├── login_core/ # 登录核心服务
│ │ ── utils/ # 工具服务
│ │ ├── email/ # 邮件服务(支持SMTP/测试模式
│ │ ├── verification/ # 验证码服务
│ │ └── logger/ # 日志系统
│ ├── dto/ # 数据传输对象
│ ├── types/ # TypeScript类型定义
│ │ ── admin_core/ # 管理员核心服务
│ │ └── utils/ # 工具服务(邮件、验证码、日志
│ ├── app.module.ts # 应用主模块
│ └── main.ts # 应用入口
├── client/ # 前端管理界面
├── docs/ # 项目文档
│ ├── api/ # API文档
│ └── systems/ # 系统设计文档
├── test/ # 测试文件
├── redis-data/ # Redis文件存储数据
├── logs/ # 日志文件
@@ -146,9 +141,9 @@ pnpm -C client dev
```
**架构特点:**
- 🏗️ **分层架构** - API层 → 业务层 → 核心层 → 数据层
- 🏗️ **业务功能模块化** - 按业务功能而非技术组件组织代码
- 🔄 **双模式支持** - 开发测试模式 + 生产部署模式
- 📦 **模块化设计** - 每个功能独立模块,便于维护扩展
- 📦 **清晰分层** - 业务层 → 核心层 → 数据层
- 🧪 **测试友好** - 完整的单元测试和集成测试覆盖
### 第三步:体验核心功能 🎮
@@ -244,19 +239,30 @@ pnpm -C client dev
## 🏗️ 核心功能
### 🔐 用户认证系统
### 🔐 用户认证模块 (business/auth/)
- **多方式登录** - 用户名/邮箱/手机号
- **邮箱验证** - 完整的验证码流程
- **密码安全** - bcrypt加密 + 强度验证
- **第三方登录** - GitHub OAuth支持
- **权限控制** - 基于角色的访问控制
- **密码管理** - 忘记密码、重置密码、修改密码
### 🎛️ 管理员后台系统
### 👥 用户管理模块 (business/user-mgmt/)
- **用户状态管理** - 6种状态控制active、inactive、locked、banned、deleted、pending
- **批量操作** - 批量修改用户状态
- **状态统计** - 各状态用户数量统计
- **状态变更日志** - 完整的审计日志
### 🛡️ 管理员模块 (business/admin/)
- **独立认证** - 专用Token系统与用户系统隔离
- **用户管理** - 用户列表、搜索、密码重置
- **日志监控** - 实时日志查看、历史日志下载
- **权限控制** - 管理员角色验证role=9
- **现代界面** - React + Ant Design响应式设计
### 🔒 安全模块 (business/security/)
- **频率限制** - 基于IP的请求频率控制
- **维护模式** - 系统维护期间的访问控制
- **内容类型验证** - HTTP请求内容类型检查
- **超时控制** - 可配置的请求超时机制
### 📧 智能邮件服务
- **测试模式** - 控制台输出无需SMTP服务器
@@ -277,7 +283,7 @@ pnpm -C client dev
- **实时更新** - 代码变更自动同步文档
### 🧪 全面测试覆盖
- **单元测试** - 114个测试用例全部通过
- **单元测试** - 140个测试用例全部通过
- **API测试** - 跨平台测试脚本
- **集成测试** - 完整业务流程验证
- **测试模式** - 无依赖快速测试
@@ -324,8 +330,8 @@ pnpm run test:cov
### 📈 测试覆盖率
- **单元测试**: 154个测试用例 ✅
- **功能测试**: 用户认证、邮件验证、数据存储、管理员后台 ✅
- **单元测试**: 140个测试用例 ✅
- **功能测试**: 用户认证、用户管理、管理员后台、安全防护
- **集成测试**: 完整业务流程 ✅
---
@@ -379,12 +385,11 @@ EMAIL_PASS=your_app_password
- **[Postman集合](./docs/api/postman-collection.json)** - 测试集合
### 🏗️ 系统设计
- **[用户认证系统](./docs/systems/user-auth/README.md)** - 认证架构设计
- **[邮件验证系统](./docs/systems/email-verification/README.md)** - 验证流程设计
- **[日志系统](./docs/systems/logger/README.md)** - 日志架构设计
- **[架构文档](./docs/ARCHITECTURE.md)** - 系统架构设计
- **[部署指南](./docs/deployment/DEPLOYMENT.md)** - 生产环境部署
### 🧪 测试指南
- **[测试指南](./TESTING.md)** - 完整测试说明
- **[测试指南](./docs/development/TESTING.md)** - 完整测试说明
- **[单元测试](./src/**/*.spec.ts)** - 测试用例参考
---
@@ -398,7 +403,7 @@ EMAIL_PASS=your_app_password
- **[jianuo](https://gitea.xinghangee.icu/jianuo)** - 核心开发者
- **[angjustinl](https://gitea.xinghangee.icu/ANGJustinl)** - 核心开发者
查看完整贡献者名单:[CONTRIBUTORS.md](./CONTRIBUTORS.md)
查看完整贡献者名单:[docs/CONTRIBUTORS.md](./docs/CONTRIBUTORS.md)
### 🌟 如何贡献

View File

@@ -1,138 +0,0 @@
# 测试指南
本项目支持**无数据库和无邮件服务器**的测试模式,让你可以快速验证所有功能!
## 🚀 快速开始
### 1. 环境配置
```bash
# 复制环境配置文件
cp .env.example .env
```
默认配置已经设置为测试模式,无需修改即可使用。
### 2. 启动服务
```bash
# 安装依赖
npm install
# 启动开发服务器
npm run dev
```
### 3. 运行测试
**Windows (PowerShell):**
```powershell
.\test-api.ps1
```
**Linux/macOS:**
```bash
./test-api.sh
```
**自定义参数:**
```bash
# Windows
.\test-api.ps1 -BaseUrl "http://localhost:3000" -TestEmail "custom@example.com"
# Linux/macOS
./test-api.sh "http://localhost:3000" "custom@example.com"
```
## 🧪 测试功能
测试脚本会验证以下功能:
-**邮箱验证码发送** - 生成6位数验证码
-**邮箱验证码验证** - 验证码校验和清理
-**用户注册** - 完整的用户注册流程
-**用户登录** - 用户名/邮箱/手机号登录
## 🔧 测试模式特性
- 🗄️ **Redis 文件存储** - 使用 `redis-data/redis.json` 存储验证码
- 📧 **邮件测试模式** - 邮件内容输出到控制台无需真实SMTP
- 💾 **内存用户存储** - 无需数据库,用户数据存储在内存中
- 🔄 **自动切换** - 根据配置自动选择存储模式
## 📊 单元测试
```bash
# 运行所有单元测试
npm test
# 监听模式
npm run test:watch
# 生成覆盖率报告
npm run test:cov
```
## 🌐 生产环境配置
要切换到生产环境,编辑 `.env` 文件:
```bash
# 启用数据库(取消注释并填入真实数据)
DB_HOST=your_mysql_host
DB_PORT=3306
DB_USERNAME=your_db_username
DB_PASSWORD=your_db_password
DB_NAME=your_db_name
# 启用真实Redis取消注释并设置
USE_FILE_REDIS=false
REDIS_HOST=your_redis_host
REDIS_PORT=6379
REDIS_PASSWORD=your_redis_password
# 启用邮件服务(取消注释并填入真实数据)
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USER=your_email@gmail.com
EMAIL_PASS=your_app_password
EMAIL_FROM="Whale Town Game" <noreply@whaletown.com>
# 生产环境设置
NODE_ENV=production
LOG_LEVEL=info
```
## 🔍 故障排除
### 服务启动失败
- 检查端口3000是否被占用
- 确认Node.js版本 >= 18.0.0
- 运行 `npm install` 重新安装依赖
### 测试脚本执行失败
- 确认服务器正在运行
- 检查防火墙设置
- 在Linux/macOS上确保脚本有执行权限`chmod +x test-api.sh`
### Redis文件存储问题
- 检查 `redis-data` 目录权限
- 确认 `USE_FILE_REDIS=true` 设置正确
### 邮件测试模式问题
- 确认邮件配置为注释状态
- 检查服务器控制台日志输出
## 📝 测试数据
测试完成后,你可以查看:
- `redis-data/redis.json` - 验证码存储数据
- 服务器控制台 - 邮件内容输出
- 测试脚本输出 - API响应结果
## 🎯 下一步
- 查看 [API 文档](http://localhost:3000/api-docs) 了解更多接口
- 阅读 [开发规范](./docs/backend_development_guide.md) 开始开发
- 使用 [AI 辅助指南](./docs/AI辅助开发规范指南.md) 提高开发效率

222
client/README.md Normal file
View File

@@ -0,0 +1,222 @@
# 🎛️ Whale Town 管理员前端界面
基于 React + Vite + Ant Design 构建的现代化管理员后台界面。
## 🚀 快速开始
### 📋 环境要求
- Node.js >= 18.0.0
- pnpm >= 8.0.0
### 🛠️ 安装与运行
```bash
# 1. 确保后端服务已启动
cd ..
pnpm run dev
# 2. 安装前端依赖
cd client
pnpm install
# 3. 启动开发服务器
pnpm run dev
# 4. 访问管理界面
# 浏览器打开: http://localhost:5173
```
### 🔑 默认登录信息
- **用户名**: admin
- **密码**: Admin123456
## 🎯 核心功能
### 🔐 管理员认证
- 独立的Token认证系统
- 安全的登录验证
- 自动Token刷新
### 👥 用户管理
- 用户列表查看和搜索
- 用户状态管理
- 用户密码重置
- 分页和排序功能
### 📊 系统监控
- 实时日志查看
- 日志文件下载
- 系统状态监控
### 🎨 界面特性
- 响应式设计,支持移动端
- 现代化UI组件
- 暗色/亮色主题切换
- 国际化支持
## 🏗️ 技术栈
### 🚀 核心框架
- **React** `^18.0.0` - 前端UI框架
- **Vite** `^5.0.0` - 现代化构建工具
- **TypeScript** `^5.0.0` - 类型安全的JavaScript
### 🎨 UI组件
- **Ant Design** `^5.0.0` - 企业级UI组件库
- **Ant Design Icons** - 图标库
- **CSS Modules** - 样式模块化
### 🔧 开发工具
- **ESLint** - 代码质量检查
- **Prettier** - 代码格式化
- **Husky** - Git钩子管理
### 🌐 HTTP客户端
- **Axios** - HTTP请求库
- **React Query** - 数据获取和缓存
## 📁 项目结构
```
client/
├── src/
│ ├── components/ # 通用组件
│ ├── pages/ # 页面组件
│ ├── services/ # API服务
│ ├── utils/ # 工具函数
│ ├── types/ # TypeScript类型定义
│ ├── styles/ # 全局样式
│ ├── App.tsx # 应用主组件
│ └── main.tsx # 应用入口
├── public/ # 静态资源
├── index.html # HTML模板
├── vite.config.ts # Vite配置
├── tsconfig.json # TypeScript配置
└── package.json # 项目配置
```
## 🔧 开发命令
```bash
# 启动开发服务器
pnpm run dev
# 构建生产版本
pnpm run build
# 预览生产构建
pnpm run preview
# 代码检查
pnpm run lint
# 代码格式化
pnpm run format
# 类型检查
pnpm run type-check
```
## 🌍 环境配置
### 开发环境 (.env.local)
```bash
VITE_API_BASE_URL=http://localhost:3000
VITE_APP_TITLE=Whale Town 管理后台
```
### 生产环境 (.env.production)
```bash
VITE_API_BASE_URL=https://your-api-domain.com
VITE_APP_TITLE=Whale Town 管理后台
```
## 🔗 API集成
### 认证接口
- `POST /admin/auth/login` - 管理员登录
- 自动Token管理和刷新
### 用户管理接口
- `GET /admin/users` - 获取用户列表
- `GET /admin/users/:id` - 获取用户详情
- `POST /admin/users/:id/reset-password` - 重置用户密码
- `PUT /admin/users/:id/status` - 修改用户状态
### 系统接口
- `GET /admin/logs/runtime` - 获取运行日志
- `GET /admin/logs/archive` - 下载日志归档
## 🎨 界面预览
### 登录页面
- 简洁的登录表单
- 输入验证和错误提示
- 记住登录状态
### 用户管理页面
- 用户列表表格
- 搜索和筛选功能
- 用户状态管理
- 密码重置操作
### 日志管理页面
- 实时日志显示
- 日志级别筛选
- 日志文件下载
## 🚀 部署指南
### 构建生产版本
```bash
# 构建
pnpm run build
# 构建产物在 dist/ 目录
```
### 部署到Nginx
```nginx
server {
listen 80;
server_name your-domain.com;
location / {
root /path/to/client/dist;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
## 🤝 开发规范
### 代码风格
- 使用TypeScript进行类型检查
- 遵循ESLint和Prettier规范
- 组件使用函数式组件和Hooks
### 文件命名
- 组件文件使用PascalCase`UserList.tsx`
- 工具文件使用camelCase`apiClient.ts`
- 样式文件使用kebab-case`user-list.module.css`
### 提交规范
- 遵循项目Git提交规范
- 提交前自动运行代码检查
## 📞 技术支持
如有问题,请查看:
1. [后端API文档](../docs/api/README.md)
2. [项目架构文档](../docs/ARCHITECTURE.md)
3. [开发规范指南](../docs/development/)
---
**🎛️ 现代化管理界面,让后台管理更高效!**

View File

View File

@@ -1,139 +1,107 @@
# 项目文档
# 📚 Pixel Game Server 文档中心
本目录包含了像素游戏服务器的完整文档
欢迎来到 Whale Town 项目文档中心!这里包含了项目的完整文档,帮助你快速了解和使用项目
## 文档结构
## 📖 **文档导航**
### 📁 api/
API接口相关文档包含
- **api-documentation.md** - 详细的API接口文档
- **openapi.yaml** - OpenAPI 3.0规范文件
- **postman-collection.json** - Postman测试集合
- **README.md** - API文档使用说明
### 🚀 **快速开始**
- [项目概述](../README.md) - 项目介绍和快速开始指南
- [架构设计](ARCHITECTURE.md) - 系统架构和设计理念
### 📁 systems/
系统设计文档,包含:
- **logger/** - 日志系统文档
- **user-auth/** - 用户认证系统文档
### 🔌 **API文档**
- [API接口文档](api/api-documentation.md) - 完整的API接口说明17个接口
- [API状态码](API_STATUS_CODES.md) - HTTP状态码和错误代码说明
- [OpenAPI规范](api/openapi.yaml) - 机器可读的API规范文件
- [API使用指南](api/README.md) - API文档使用说明
### 📄 其他文档
- **AI辅助开发规范指南.md** - AI开发规范
- **backend_development_guide.md** - 后端开发指南
- **git_commit_guide.md** - Git提交规范
- **naming_convention.md** - 命名规范
- **nestjs_guide.md** - NestJS开发指南
- **日志系统详细说明.md** - 日志系统说明
### 💻 **开发指南**
- [后端开发指南](development/backend_development_guide.md) - 后端开发规范和最佳实践
- [NestJS指南](development/nestjs_guide.md) - NestJS框架使用指南
- [命名规范](development/naming_convention.md) - 代码命名规范
- [Git提交规范](development/git_commit_guide.md) - Git提交消息规范
- [AI辅助开发规范](development/AI辅助开发规范指南.md) - AI辅助开发最佳实践
- [测试指南](development/TESTING.md) - 测试策略和规范
## 如何使用
### 🚀 **部署运维**
- [部署指南](deployment/DEPLOYMENT.md) - 生产环境部署说明
### 1. 启动服务器并查看Swagger文档
### 📋 **项目管理**
- [贡献指南](CONTRIBUTORS.md) - 如何参与项目贡献
- [文档清理说明](DOCUMENT_CLEANUP.md) - 文档维护记录
```bash
# 启动开发服务器
pnpm run dev
## 🏗️ **文档结构说明**
# 访问Swagger UI
# 浏览器打开: http://localhost:3000/api-docs
```
docs/
├── README.md # 📚 文档中心首页
├── ARCHITECTURE.md # 🏗️ 架构文档
├── API_STATUS_CODES.md # 📋 API状态码
├── CONTRIBUTORS.md # 🤝 贡献指南
├── DOCUMENT_CLEANUP.md # 📝 文档清理说明
├── api/ # 🔌 API文档
│ ├── api-documentation.md # API接口文档
│ ├── openapi.yaml # OpenAPI规范
│ ├── postman-collection.json # Postman测试集合
│ └── README.md # API文档说明
├── development/ # 💻 开发指南
│ ├── backend_development_guide.md
│ ├── nestjs_guide.md
│ ├── naming_convention.md
│ ├── git_commit_guide.md
│ ├── AI辅助开发规范指南.md
│ └── TESTING.md
└── deployment/ # 🚀 部署文档
└── DEPLOYMENT.md
```
### 2. 使用Postman测试API
## 🎯 **文档特色**
1. 打开Postman
2. 点击 Import 按钮
3. 选择 `docs/postman-collection.json` 文件
4. 导入后即可看到所有API接口
5. 修改环境变量 `baseUrl` 为你的服务器地址默认http://localhost:3000
### ✨ **业务功能模块化**
文档结构与代码架构保持一致,按业务功能组织:
- **用户认证模块** - 登录、注册、密码管理
- **用户管理模块** - 状态管理、批量操作
- **管理员模块** - 后台管理、权限控制
- **安全模块** - 频率限制、维护模式
### 3. 使用OpenAPI规范
### 📊 **完整API覆盖**
- **17个API接口** - 涵盖所有业务功能
- **交互式文档** - Swagger UI实时测试
- **标准化规范** - OpenAPI 3.0标准
- **测试集合** - Postman一键导入
#### 在Swagger Editor中查看
1. 访问 [Swagger Editor](https://editor.swagger.io/)
2.`docs/openapi.yaml` 的内容复制粘贴到编辑器中
3. 即可查看可视化的API文档
### 🔧 **开发者友好**
- **规范指导** - 命名、提交、开发规范
- **AI辅助** - 提升开发效率的AI使用指南
- **测试覆盖** - 140个测试用例全覆盖
- **部署就绪** - 生产环境部署指南
#### 生成客户端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测试登录接口
1. **发现问题**发现文档错误或缺失时请提交Issue
2. **改进文档**按照项目规范提交Pull Request
3. **新增文档**:新功能开发时同步编写相关文档
4. **审查文档**:参与文档审查,确保质量和准确性
```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/)
📧 **联系我们**如有文档相关问题请通过项目Issue或邮件联系维护团队。

View File

@@ -1,29 +1,30 @@
# API接口文档
本目录包含了像素游戏服务器用户认证API的完整文档
本目录包含了 Whale Town 像素游戏服务器的完整API文档采用业务功能模块化设计提供17个接口覆盖所有核心功能
## 📋 文档文件说明
### 1. api-documentation.md
详细的API接口文档包含
- **17个API接口** - 用户认证、用户管理、管理员功能、安全防护
- 接口概述和通用响应格式
- 每个接口的详细说明、参数、响应示例
- 错误代码说明
- 数据验证规则
- 错误代码说明和状态码映射
- 数据验证规则和业务逻辑
- 使用示例JavaScript/TypeScript 和 cURL
### 2. openapi.yaml
OpenAPI 3.0规范文件,可以用于:
- 导入到Swagger Editor查看和编辑
- 生成客户端SDK
- 集成到API网关
- 自动化测试
- 生成客户端SDK(支持多种语言)
- 集成到API网关和测试工具
- 自动化测试和文档生成
### 3. postman-collection.json
Postman集合文件包含
- 所有API接口的请求示例
- 预设的请求参数
- 响应示例
- 所有17个API接口的请求示例
- 预设的请求参数和环境变量
- 完整的响应示例和测试脚本
- 可直接导入Postman进行测试
## 🚀 快速开始
@@ -34,7 +35,7 @@ Postman集合文件包含
# 启动开发服务器
pnpm run dev
# 访问Swagger UI
# 访问Swagger UI(推荐)
# 浏览器打开: http://localhost:3000/api-docs
```
@@ -64,78 +65,144 @@ openapi-generator generate -i docs/api/openapi.yaml -g typescript-axios -o ./cli
## 📊 API接口概览
### 🔐 用户认证模块 (9个接口)
| 接口 | 方法 | 路径 | 描述 |
|------|------|------|------|
| 用户登录 | POST | /auth/login | 支持用户名、邮箱或手机号登录 |
| 用户注册 | POST | /auth/register | 创建新用户账户 |
| GitHub OAuth | POST | /auth/github | 使用GitHub账户登录或注册 |
| 发送验证码 | POST | /auth/forgot-password | 发送密码重置验证码 |
| 发送重置验证码 | POST | /auth/forgot-password | 发送密码重置验证码 |
| 重置密码 | POST | /auth/reset-password | 使用验证码重置密码 |
| 修改密码 | PUT | /auth/change-password | 修改用户密码 |
| 发送邮箱验证码 | POST | /auth/send-email-verification | 发送邮箱验证码 |
| 验证邮箱 | POST | /auth/verify-email | 验证邮箱验证码 |
| 重发邮箱验证码 | POST | /auth/resend-email-verification | 重新发送邮箱验证码 |
### 👥 用户管理模块 (3个接口)
| 接口 | 方法 | 路径 | 描述 |
|------|------|------|------|
| 修改用户状态 | PUT | /admin/users/:id/status | 修改指定用户状态 |
| 批量修改状态 | POST | /admin/users/batch-status | 批量修改用户状态 |
| 用户状态统计 | GET | /admin/users/status-stats | 获取各状态用户统计 |
### 🛡️ 管理员模块 (4个接口)
| 接口 | 方法 | 路径 | 描述 |
|------|------|------|------|
| 管理员登录 | POST | /admin/auth/login | 管理员身份认证 |
| 获取用户列表 | GET | /admin/users | 分页获取用户列表 |
| 获取用户详情 | GET | /admin/users/:id | 获取指定用户信息 |
| 重置用户密码 | POST | /admin/users/:id/reset-password | 管理员重置用户密码 |
### 📊 系统状态 (1个接口)
| 接口 | 方法 | 路径 | 描述 |
|------|------|------|------|
| 应用状态 | GET | / | 获取应用运行状态和系统信息 |
## 🧪 快速测试
### 使用cURL测试登录接口
### 使用cURL测试核心接口
```bash
# 测试用户登录
# 1. 测试应用状态
curl -X GET http://localhost:3000/
# 2. 测试用户注册
curl -X POST http://localhost:3000/auth/register \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"password": "Test123456",
"nickname": "测试用户",
"email": "test@example.com"
}'
# 3. 测试用户登录
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{
"identifier": "testuser",
"password": "password123"
"password": "Test123456"
}'
# 测试用户注册
curl -X POST http://localhost:3000/auth/register \
# 4. 测试管理员登录
curl -X POST http://localhost:3000/admin/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "newuser",
"password": "password123",
"nickname": "新用户",
"email": "newuser@example.com"
"username": "admin",
"password": "Admin123456"
}'
```
### 使用JavaScript测试
```javascript
// 用户注册
const registerResponse = await fetch('http://localhost:3000/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'testuser',
password: 'Test123456',
nickname: '测试用户',
email: 'test@example.com'
})
});
// 用户登录
const response = await fetch('http://localhost:3000/auth/login', {
const loginResponse = await fetch('http://localhost:3000/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
identifier: 'testuser',
password: 'password123'
password: 'Test123456'
})
});
const data = await response.json();
console.log(data);
const loginData = await loginResponse.json();
console.log('登录结果:', loginData);
```
### 使用自动化测试脚本
```bash
# Windows PowerShell
.\test-api.ps1
# Linux/macOS Bash
./test-api.sh
# 自定义测试参数
.\test-api.ps1 -BaseUrl "http://localhost:3000" -TestEmail "custom@example.com"
```
## ⚠️ 注意事项
1. **开发环境**: 当前配置适用于开发环境生产环境需要使用HTTPS
2. **认证**: 实际应用中应实现JWT认证机制
3. **限流**: 建议对认证接口实施限流策略
4. **验证码**: 示例中返回验证码仅用于演示,生产环境不应返回
5. **错误处理**: 建议实现统一的错误处理机制
2. **认证机制**: 项目使用JWT认证管理员使用独立的Token系统
3. **频率限制**: 已实现API频率限制登录接口2次/分钟管理员操作10次/分钟
4. **用户状态**: 支持6种用户状态管理active、inactive、locked、banned、deleted、pending
5. **测试模式**: 邮件服务支持测试模式,验证码会在控制台输出
6. **存储模式**: 支持Redis文件存储和内存数据库便于无依赖测试
7. **安全防护**: 实现了维护模式、内容类型检查、超时控制等安全机制
## 🔄 更新文档
当API接口发生变化时请同步更新以下文件
1. 更新DTO类的Swagger装饰器
2. 更新 `api-documentation.md`
3. 更新 `openapi.yaml`
4. 更新 `postman-collection.json`
5. 重新生成Swagger文档
1. 更新Controller和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/)
- [Swagger Editor](https://editor.swagger.io/)
- [项目架构文档](../ARCHITECTURE.md)
- [开发规范指南](../development/)

File diff suppressed because it is too large Load Diff

276
docs/development/TESTING.md Normal file
View File

@@ -0,0 +1,276 @@
# 测试指南
本项目支持**无数据库和无邮件服务器**的测试模式,让你可以快速验证所有功能!
## 🚀 快速开始
### 1. 环境配置
```bash
# 复制环境配置文件
cp .env.example .env
```
默认配置已经设置为测试模式,无需修改即可使用。
### 2. 启动服务
```bash
# 安装依赖
npm install
# 启动开发服务器
npm run dev
```
### 3. 运行测试
**Windows (PowerShell):**
```powershell
.\test-api.ps1
```
**Linux/macOS:**
```bash
./test-api.sh
```
**自定义参数:**
```bash
# Windows
.\test-api.ps1 -BaseUrl "http://localhost:3000" -TestEmail "custom@example.com"
# Linux/macOS
./test-api.sh "http://localhost:3000" "custom@example.com"
```
## 🧪 测试功能
### API功能测试
测试脚本会验证以下核心功能:
**用户认证模块:**
-**邮箱验证码发送** - 生成6位数验证码测试模式输出到控制台
-**邮箱验证码验证** - 验证码校验和自动清理
-**用户注册** - 完整的用户注册流程,包含邮箱验证
-**用户登录** - 支持用户名/邮箱/手机号多种方式登录
**系统状态测试:**
-**应用状态检查** - 验证服务器运行状态和系统信息
-**Redis文件存储** - 验证验证码存储和读取功能
-**内存数据库** - 验证用户数据存储功能
### 单元测试覆盖
**核心服务测试7个测试套件140个测试用例**
1. **LoginCoreService** - 登录核心服务15个测试
- 用户登录成功/失败场景
- 用户注册功能测试
- GitHub OAuth登录测试
- 密码重置和修改功能
- 用户状态验证active、inactive、locked等
2. **AdminService** - 管理员服务测试
- 管理员登录认证
- 用户列表管理
- 用户密码重置
- 日志管理功能
3. **VerificationService** - 验证码服务测试
- 验证码生成和验证
- 频率限制机制
- Redis存储操作
- 错误处理和边界条件
4. **EmailService** - 邮件服务测试
- 邮件发送功能(测试模式和生产模式)
- 验证码邮件模板
- 连接验证和错误处理
- SMTP配置测试
5. **UsersService** - 用户数据服务测试
- 用户CRUD操作
- 用户查询功能
- 数据验证和约束
6. **AdminCoreService** - 管理员核心服务测试
- 管理员认证逻辑
- 权限验证
- 管理员引导创建
7. **LoggerService** - 日志服务测试
- 日志记录功能
- 敏感信息过滤
- 日志级别控制
### E2E端到端测试
**登录功能完整流程测试:**
- 用户注册 → 邮箱验证 → 登录验证
- GitHub OAuth登录流程
- 密码重置完整流程
- 错误处理和边界条件测试
## 🔧 测试模式特性
- 🗄️ **Redis 文件存储** - 使用 `redis-data/redis.json` 存储验证码
- 📧 **邮件测试模式** - 邮件内容输出到控制台无需真实SMTP
- 💾 **内存用户存储** - 无需数据库,用户数据存储在内存中
- 🔄 **自动切换** - 根据配置自动选择存储模式
## 📊 单元测试
### 运行测试命令
```bash
# 运行所有单元测试
npm test
# 监听模式(开发时使用)
npm run test:watch
# 生成覆盖率报告
npm run test:cov
# 运行特定测试文件
npm test -- src/core/login_core/login_core.service.spec.ts
```
### 测试覆盖情况
**测试统计:**
- 测试套件7个
- 测试用例140个
- 覆盖率100%通过
**测试文件列表:**
```
src/core/login_core/login_core.service.spec.ts # 登录核心服务
src/business/admin/admin.service.spec.ts # 管理员服务
src/core/utils/verification/verification.service.spec.ts # 验证码服务
src/core/utils/email/email.service.spec.ts # 邮件服务
src/core/db/users/users.service.spec.ts # 用户数据服务
src/core/admin_core/admin_core.service.spec.ts # 管理员核心服务
src/core/utils/logger/logger.service.spec.ts # 日志服务
test/business/login.e2e-spec.ts # E2E端到端测试
```
### 测试场景覆盖
**正常流程测试:**
- 用户注册、登录、密码管理
- 邮箱验证码发送和验证
- 管理员认证和用户管理
- 系统状态和日志功能
**异常情况测试:**
- 无效输入和参数验证
- 网络连接失败处理
- 权限验证和访问控制
- 频率限制和安全防护
**边界条件测试:**
- 密码强度验证
- 验证码过期处理
- 用户状态变更
- 数据库连接异常
## 🌐 生产环境配置
要切换到生产环境,编辑 `.env` 文件:
```bash
# 启用数据库(取消注释并填入真实数据)
DB_HOST=your_mysql_host
DB_PORT=3306
DB_USERNAME=your_db_username
DB_PASSWORD=your_db_password
DB_NAME=your_db_name
# 启用真实Redis取消注释并设置
USE_FILE_REDIS=false
REDIS_HOST=your_redis_host
REDIS_PORT=6379
REDIS_PASSWORD=your_redis_password
# 启用邮件服务(取消注释并填入真实数据)
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USER=your_email@gmail.com
EMAIL_PASS=your_app_password
EMAIL_FROM="Whale Town Game" <noreply@whaletown.com>
# 生产环境设置
NODE_ENV=production
LOG_LEVEL=info
```
## 🔍 故障排除
### 服务启动失败
- **端口占用**检查端口3000是否被占用使用 `netstat -ano | findstr :3000` 查看
- **Node.js版本**确认Node.js版本 >= 18.0.0,使用 `node --version` 检查
- **依赖问题**:运行 `npm install``pnpm install` 重新安装依赖
- **权限问题**:确保有足够的文件读写权限
### 测试脚本执行失败
- **服务器状态**:确认服务器正在运行,访问 http://localhost:3000 检查
- **网络连接**检查防火墙设置确保端口3000可访问
- **脚本权限**在Linux/macOS上确保脚本有执行权限`chmod +x test-api.sh`
- **PowerShell策略**Windows上可能需要设置执行策略`Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`
### 单元测试失败
- **依赖冲突**清理node_modules并重新安装`rm -rf node_modules && npm install`
- **TypeScript错误**:运行 `npm run build` 检查编译错误
- **环境变量**:确保测试环境变量配置正确
- **数据库连接**:测试模式下应该使用内存数据库,检查配置
### Redis文件存储问题
- **目录权限**:检查 `redis-data` 目录的读写权限
- **配置设置**:确认 `USE_FILE_REDIS=true` 设置正确
- **文件锁定**确保redis.json文件没有被其他进程锁定
- **磁盘空间**:检查磁盘空间是否充足
### 邮件测试模式问题
- **配置检查**:确认邮件配置为注释状态(测试模式)
- **控制台输出**:检查服务器控制台是否有邮件内容输出
- **日志级别**确保日志级别设置为info或debug以查看详细输出
### 常见错误解决
**EADDRINUSE错误**
```bash
# 查找占用端口的进程
netstat -ano | findstr :3000
# 结束进程Windows
taskkill /PID <进程ID> /F
```
**权限错误:**
```bash
# Linux/macOS设置权限
chmod +x test-api.sh
chmod 755 redis-data/
```
**模块未找到错误:**
```bash
# 清理并重新安装
rm -rf node_modules package-lock.json
npm install
```
## 📝 测试数据
测试完成后,你可以查看:
- `redis-data/redis.json` - 验证码存储数据
- 服务器控制台 - 邮件内容输出
- 测试脚本输出 - API响应结果
## 🎯 下一步
- 查看 [API 文档](http://localhost:3000/api-docs) 了解更多接口
- 阅读 [开发规范](./docs/backend_development_guide.md) 开始开发
- 使用 [AI 辅助指南](./docs/AI辅助开发规范指南.md) 提高开发效率

View File

@@ -1,186 +0,0 @@
# 管理员后台Admin Dashboard
本模块提供 Whale Town 的管理员后台能力,包含:
- 管理员登录role=9
- 用户列表管理
- 用户密码重置
- 运行日志查看(读取 logs/ 下最新日志)
> 说明:本项目用户系统原本的 `access_token` 为演示用 Base64 令牌。为了不影响现有用户端流程,管理员后台使用单独的签名 TokenHMAC-SHA256做鉴权。
---
## 1. 管理员账号设计
### 1.1 角色约定
用户表 `users.role`
- `1`:普通用户
- `9`:管理员(可访问后台)
### 1.2 启动引导创建管理员(可选)
通过环境变量启用启动引导在服务启动时如果不存在指定用户名的用户则自动创建一个管理员账户role=9
`.env` 中配置:
- `ADMIN_BOOTSTRAP_ENABLED=true`
- `ADMIN_USERNAME=admin`
- `ADMIN_PASSWORD=Admin123456`需满足密码强度至少8位包含字母和数字
- `ADMIN_NICKNAME=管理员`(可选)
注意:
- 建议仅在首次部署/开发环境开启,引导创建成功后可关闭。
- 生产环境务必设置强随机密码与强随机 `ADMIN_TOKEN_SECRET`
---
## 2. 管理员鉴权 Token
### 2.1 配置项
- `ADMIN_TOKEN_SECRET`签名密钥至少16字符生产环境建议≥32并随机
- `ADMIN_TOKEN_TTL_SECONDS`Token 有效期(秒),默认 `28800`8小时
### 2.2 使用方式
管理员登录成功后返回 `access_token`,后续请求在 Header 中携带:
- `Authorization: Bearer <access_token>`
---
## 3. 后端接口
### 3.1 管理员登录
- `POST /admin/auth/login`
请求:
```json
{
"identifier": "admin",
"password": "Admin123456"
}
```
响应(成功):
```json
{
"success": true,
"data": {
"admin": { "id": "1", "username": "admin", "nickname": "管理员", "role": 9 },
"access_token": "...",
"expires_at": 1766102400000
},
"message": "管理员登录成功"
}
```
### 3.2 用户列表
- `GET /admin/users?limit=100&offset=0`
- 需要管理员 Token
### 3.3 用户详情
- `GET /admin/users/:id`
- 需要管理员 Token
### 3.4 重置用户密码
- `POST /admin/users/:id/reset-password`
- 需要管理员 Token
请求:
```json
{
"new_password": "NewPass1234"
}
```
### 3.5 运行日志tail
- `GET /admin/logs/runtime?lines=200`
- 需要管理员 Token
说明:
- 开发环境默认读取 `logs/dev.log`
- 生产环境默认读取 `logs/app.log`
- `lines` 默认 200最大 2000
### 3.6 下载全部运行日志archive
- `GET /admin/logs/archive`
- 需要管理员 Token
说明:
- 返回一个 `tar.gz` 文件(浏览器会触发下载)
- 内容为整个 `logs/` 目录(例如开发环境的 `dev.log`,生产环境的 `app.log/access.log/error.log` 等)
---
## 4. 前端后台Ant Design
前端工程位于 `client/`,使用 Vite + React + Ant Design。
### 4.1 安装依赖
在项目根目录执行:
```bash
pnpm install
```
### 4.2 启动后端
```bash
pnpm run dev
```
### 4.3 启动前端后台
```bash
pnpm -C client dev
```
默认访问:
- 前端:`http://localhost:5173`
- 后端:`http://localhost:3000`
- Swagger`http://localhost:3000/api-docs`
页面说明:
- 用户管理:`/users`
- 运行日志:`/logs`
在“运行日志”页面可点击“下载日志压缩包”获取整个 `logs/` 目录的打包文件。
### 4.4 前端配置
- 复制 `client/.env.example``client/.env.local`
- 可通过 `VITE_API_BASE_URL` 指向后端地址
---
## 5. 代码位置
- 后端:
- `src/core/admin_core/`:管理员核心逻辑
- `src/core/guards/admin.guard.ts`:管理员接口鉴权
- `src/business/admin/`管理员HTTP API
- `src/dto/admin*.ts`:管理员请求/响应 DTO
- 前端:
- `client/src/pages/LoginPage.tsx`:管理员登录页
- `client/src/pages/UsersPage.tsx`:用户管理页(列表+重置密码)
- `client/src/pages/LogsPage.tsx`:运行日志页

View File

@@ -1,265 +0,0 @@
# 邮箱验证系统
## 概述
邮箱验证系统提供完整的邮箱验证功能,包括验证码生成、发送、验证和管理。
## 功能特性
- 📧 邮箱验证码发送
- 🔐 验证码安全验证
- ⏰ 验证码过期管理
- 🚫 防刷机制(频率限制)
- 📊 验证统计和监控
## 系统架构
```
邮箱验证系统
├── 验证码服务 (VerificationService)
│ ├── 验证码生成
│ ├── 验证码验证
│ └── 防刷机制
├── 邮件服务 (EmailService)
│ ├── 验证码邮件发送
│ ├── 欢迎邮件发送
│ └── 邮件模板管理
└── Redis缓存
├── 验证码存储
├── 冷却时间管理
└── 发送频率限制
```
## 核心组件
### 1. 验证码服务 (VerificationService)
负责验证码的生成、验证和管理:
- **验证码生成**6位数字验证码
- **验证码验证**:支持多次尝试限制
- **过期管理**5分钟有效期
- **防刷机制**60秒冷却时间每小时最多5次
### 2. 邮件服务 (EmailService)
负责邮件的发送和模板管理:
- **验证码邮件**:发送验证码到用户邮箱
- **欢迎邮件**:用户注册成功后发送
- **模板支持**支持HTML邮件模板
### 3. Redis缓存
负责数据的临时存储:
- **验证码存储**`verification_code:${type}:${identifier}`
- **冷却时间**`verification_cooldown:${type}:${identifier}`
- **发送频率**`verification_hourly:${type}:${identifier}:${date}:${hour}`
## 使用流程
### 注册流程中的邮箱验证
1. **发送验证码**
```typescript
POST /auth/send-email-verification
{
"email": "user@example.com"
}
```
2. **用户注册**
```typescript
POST /auth/register
{
"username": "testuser",
"password": "password123",
"nickname": "测试用户",
"email": "user@example.com",
"email_verification_code": "123456"
}
```
### 独立邮箱验证
1. **验证邮箱**
```typescript
POST /auth/verify-email
{
"email": "user@example.com",
"verification_code": "123456"
}
```
## 配置说明
### 环境变量
```bash
# 邮件服务配置
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-email@example.com
SMTP_PASS=your-password
SMTP_FROM=noreply@example.com
# Redis配置
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
```
### 验证码配置
```typescript
// 验证码长度
CODE_LENGTH = 6
// 验证码过期时间(秒)
CODE_EXPIRE_TIME = 300 // 5分钟
// 最大验证尝试次数
MAX_ATTEMPTS = 3
// 发送冷却时间(秒)
RATE_LIMIT_TIME = 60 // 1分钟
// 每小时最大发送次数
MAX_SENDS_PER_HOUR = 5
```
## API接口
### 发送邮箱验证码
- **接口**`POST /auth/send-email-verification`
- **描述**:向指定邮箱发送验证码
- **参数**
```typescript
{
email: string; // 邮箱地址
}
```
### 验证邮箱验证码
- **接口**`POST /auth/verify-email`
- **描述**:使用验证码验证邮箱
- **参数**
```typescript
{
email: string; // 邮箱地址
verification_code: string; // 6位数字验证码
}
```
### 重新发送验证码
- **接口**`POST /auth/resend-email-verification`
- **描述**:重新向指定邮箱发送验证码
- **参数**
```typescript
{
email: string; // 邮箱地址
}
```
## 错误处理
### 常见错误码
- `VERIFICATION_CODE_NOT_FOUND`:验证码不存在或已过期
- `VERIFICATION_CODE_INVALID`:验证码错误
- `TOO_MANY_ATTEMPTS`:验证尝试次数过多
- `RATE_LIMIT_EXCEEDED`:发送频率过高
- `EMAIL_SEND_FAILED`:邮件发送失败
### 错误响应格式
```typescript
{
success: false,
message: "错误描述",
error_code: "ERROR_CODE"
}
```
## 监控和日志
### 关键指标
- 验证码发送成功率
- 验证码验证成功率
- 邮件发送延迟
- Redis连接状态
### 日志记录
- 验证码生成和验证日志
- 邮件发送状态日志
- 错误和异常日志
- 性能监控日志
## 安全考虑
### 防刷机制
1. **发送频率限制**每个邮箱60秒内只能发送一次
2. **每小时限制**每个邮箱每小时最多发送5次
3. **验证尝试限制**每个验证码最多尝试3次
### 数据安全
1. **验证码加密存储**Redis中的验证码经过加密
2. **过期自动清理**验证码5分钟后自动过期
3. **日志脱敏**:日志中不记录完整验证码
## 部署指南
详细的部署说明请参考:[deployment-guide.md](./deployment-guide.md)
## 测试
### 单元测试
```bash
# 运行验证服务测试
npm test -- verification.service.spec.ts
# 运行邮件服务测试
npm test -- email.service.spec.ts
```
### 集成测试
```bash
# 运行邮箱验证集成测试
npm run test:e2e -- email-verification
```
## 故障排除
### 常见问题
1. **验证码收不到**
- 检查SMTP配置
- 检查邮箱是否在垃圾邮件中
- 检查网络连接
2. **验证码验证失败**
- 检查验证码是否过期
- 检查验证码输入是否正确
- 检查Redis连接状态
3. **发送频率限制**
- 等待冷却时间结束
- 检查是否达到每小时限制
## 更新日志
- **v1.0.0** (2025-12-17)
- 初始版本发布
- 支持基本的邮箱验证功能
- 集成Redis缓存
- 添加防刷机制

View File

@@ -1,316 +0,0 @@
# 邮箱验证功能部署指南
## 概述
本指南详细说明如何部署和配置邮箱验证功能包括Redis缓存、邮件服务配置等。
## 1. 安装依赖
```bash
# 安装新增的依赖包
pnpm install ioredis nodemailer
# 安装类型定义
pnpm install -D @types/nodemailer
```
## 2. Redis 服务配置
### 2.1 安装 Redis
#### Ubuntu/Debian
```bash
sudo apt update
sudo apt install redis-server
sudo systemctl start redis-server
sudo systemctl enable redis-server
```
#### CentOS/RHEL
```bash
sudo yum install redis
sudo systemctl start redis
sudo systemctl enable redis
```
#### Docker 方式
```bash
docker run -d --name redis -p 6379:6379 redis:7-alpine
```
### 2.2 Redis 配置验证
```bash
# 测试 Redis 连接
redis-cli ping
# 应该返回 PONG
```
## 3. 邮件服务配置
### 3.1 Gmail 配置示例
1. **启用两步验证**
- 登录 Google 账户
- 进入"安全性"设置
- 启用"两步验证"
2. **生成应用专用密码**
- 在"安全性"设置中找到"应用专用密码"
- 生成新的应用密码
- 记录生成的16位密码
3. **环境变量配置**
```env
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER=your-email@gmail.com
EMAIL_PASS=your-16-digit-app-password
EMAIL_FROM="Whale Town Game" <noreply@gmail.com>
```
### 3.2 其他邮件服务商配置
#### 163邮箱
```env
EMAIL_HOST=smtp.163.com
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER=your-email@163.com
EMAIL_PASS=your-authorization-code
```
#### QQ邮箱
```env
EMAIL_HOST=smtp.qq.com
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER=your-email@qq.com
EMAIL_PASS=your-authorization-code
```
#### 阿里云邮件推送
```env
EMAIL_HOST=smtpdm.aliyun.com
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER=your-smtp-username
EMAIL_PASS=your-smtp-password
```
## 4. 环境变量配置
### 4.1 创建环境配置文件
```bash
# 复制环境变量模板
cp .env.production.example .env
# 编辑环境变量
nano .env
```
### 4.2 完整的环境变量配置
```env
# 数据库配置
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=pixel_game
DB_PASSWORD=your_db_password
DB_NAME=pixel_game_db
# 应用配置
NODE_ENV=production
PORT=3000
# JWT 配置
JWT_SECRET=your_jwt_secret_key_here_at_least_32_characters
JWT_EXPIRES_IN=7d
# Redis 配置
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
# 邮件服务配置
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER=your-email@gmail.com
EMAIL_PASS=your-app-password
EMAIL_FROM="Whale Town Game" <noreply@whaletown.com>
```
## 5. 数据库迁移
由于添加了新的字段,需要更新数据库结构:
```sql
-- 添加邮箱验证状态字段
ALTER TABLE users ADD COLUMN email_verified BOOLEAN NOT NULL DEFAULT FALSE COMMENT '邮箱是否已验证';
-- 为已有用户设置默认值
UPDATE users SET email_verified = FALSE WHERE email_verified IS NULL;
-- 如果是OAuth用户且有邮箱可以设为已验证
UPDATE users SET email_verified = TRUE WHERE github_id IS NOT NULL AND email IS NOT NULL;
```
## 6. 启动和测试
### 6.1 启动应用
```bash
# 安装依赖
pnpm install
# 构建应用
pnpm run build
# 启动应用
pnpm run start:prod
```
### 6.2 功能测试
#### 测试邮箱验证码发送
```bash
curl -X POST http://localhost:3000/auth/send-email-verification \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com"}'
```
#### 测试邮箱验证
```bash
curl -X POST http://localhost:3000/auth/verify-email \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","verification_code":"123456"}'
```
#### 测试密码重置
```bash
curl -X POST http://localhost:3000/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{"identifier":"test@example.com"}'
```
## 7. 监控和日志
### 7.1 查看应用日志
```bash
# PM2 日志
pm2 logs pixel-game-server
# 或者查看文件日志
tail -f logs/dev.log
```
### 7.2 Redis 监控
```bash
# 查看 Redis 信息
redis-cli info
# 监控 Redis 命令
redis-cli monitor
# 查看验证码相关的键
redis-cli keys "verification_*"
```
### 7.3 邮件发送监控
应用会记录邮件发送的日志,包括:
- 发送成功/失败状态
- 收件人信息
- 发送时间
- 错误信息(如果有)
## 8. 故障排除
### 8.1 Redis 连接问题
**问题**Redis连接失败
```
Redis连接错误: Error: connect ECONNREFUSED 127.0.0.1:6379
```
**解决方案**
1. 检查Redis服务状态`sudo systemctl status redis`
2. 启动Redis服务`sudo systemctl start redis`
3. 检查防火墙设置
4. 验证Redis配置文件
### 8.2 邮件发送问题
**问题**:邮件发送失败
```
邮件发送失败: Error: Invalid login: 535-5.7.8 Username and Password not accepted
```
**解决方案**
1. 检查邮箱用户名和密码
2. 确认已启用应用专用密码Gmail
3. 检查邮件服务商的SMTP设置
4. 验证网络连接
### 8.3 验证码问题
**问题**:验证码验证失败
**解决方案**
1. 检查Redis中是否存在验证码`redis-cli get verification_code:email_verification:test@example.com`
2. 检查验证码是否过期
3. 验证验证码格式6位数字
4. 检查应用日志
## 9. 安全建议
### 9.1 邮件服务安全
1. **使用应用专用密码**:不要使用主密码
2. **启用TLS/SSL**:确保邮件传输加密
3. **限制发送频率**:防止邮件轰炸
4. **监控发送量**:避免被标记为垃圾邮件
### 9.2 Redis 安全
1. **设置密码**`requirepass your_redis_password`
2. **绑定IP**`bind 127.0.0.1`
3. **禁用危险命令**`rename-command FLUSHDB ""`
4. **定期备份**设置Redis数据备份
### 9.3 验证码安全
1. **设置过期时间**默认5分钟
2. **限制尝试次数**最多3次
3. **防刷机制**60秒冷却时间
4. **记录日志**:监控异常行为
## 10. 性能优化
### 10.1 Redis 优化
```redis
# Redis 配置优化
maxmemory 256mb
maxmemory-policy allkeys-lru
save 900 1
save 300 10
save 60 10000
```
### 10.2 邮件发送优化
1. **连接池**复用SMTP连接
2. **异步发送**:不阻塞主流程
3. **队列机制**:处理大量邮件
4. **失败重试**:自动重试机制
---
*部署完成后,建议进行完整的功能测试,确保所有邮箱验证功能正常工作。*

View File

@@ -1,80 +0,0 @@
# 日志系统
## 概述
项目集成了完整的日志系统,基于 Pino 高性能日志库,提供结构化日志记录、自动敏感信息过滤和多级别日志控制。
## 功能特性
- 🚀 高性能日志记录
- 🔒 自动敏感信息过滤
- 🎯 多级别日志控制
- 🔍 请求上下文绑定
- 📊 结构化日志输出
## 使用示例
### 基础用法
```typescript
import { AppLoggerService } from './core/utils/logger/logger.service';
@Injectable()
export class UserService {
constructor(private readonly logger: AppLoggerService) {}
async createUser(userData: CreateUserDto) {
this.logger.info('开始创建用户', {
operation: 'createUser',
email: userData.email,
timestamp: new Date().toISOString()
});
try {
const user = await this.userRepository.save(userData);
this.logger.info('用户创建成功', {
operation: 'createUser',
userId: user.id,
email: userData.email
});
return user;
} catch (error) {
this.logger.error('用户创建失败', {
operation: 'createUser',
email: userData.email,
error: error.message
}, error.stack);
throw error;
}
}
}
```
## 日志级别
- `error`: 错误信息
- `warn`: 警告信息
- `info`: 一般信息
- `debug`: 调试信息
## 配置
日志配置位于 `src/core/utils/logger/logger.config.ts`,支持:
- 日志级别设置
- 输出格式配置
- 敏感信息过滤规则
- 文件输出配置
## 敏感信息过滤
系统自动过滤以下敏感信息:
- 密码字段
- 令牌信息
- 个人身份信息
- 支付相关信息
详细使用方法请参考:[后端开发规范指南 - 日志系统使用指南](../../backend_development_guide.md#四日志系统使用指南)

View File

@@ -1,363 +0,0 @@
# 日志系统详细说明
## 📋 概述
本项目的日志系统基于 Pino 高性能日志库构建,提供完整的日志记录、管理和分析功能。
---
## 🗂️ 日志文件结构
### 开发环境 (`NODE_ENV=development`)
```
logs/
└── dev.log # 开发环境综合日志(所有级别)
```
**输出方式:**
- 🖥️ **控制台**:彩色美化输出,便于开发调试
- 📁 **文件**:保存到 `logs/dev.log`,便于问题追踪
### 生产环境 (`NODE_ENV=production`)
```
logs/
├── app.log # 应用综合日志info及以上级别
├── error.log # 错误日志error和fatal级别
├── access.log # HTTP访问日志请求响应记录
├── app.log.gz # 压缩的历史日志文件
├── error.log.gz # 压缩的历史错误日志
└── access.log.gz # 压缩的历史访问日志
```
**输出方式:**
- 📁 **文件**:分类保存到不同的日志文件
- 🖥️ **控制台**:仅输出 warn 及以上级别(用于容器日志收集)
---
## 📊 日志级别和用途
| 级别 | 数值 | 用途 | 保存位置 | 示例场景 |
|------|------|------|----------|----------|
| **TRACE** | 10 | 极细粒度调试 | dev.log | 循环内变量状态 |
| **DEBUG** | 20 | 开发调试信息 | dev.log | 方法调用参数 |
| **INFO** | 30 | 重要业务操作 | app.log, dev.log | 用户登录成功 |
| **WARN** | 40 | 警告信息 | app.log, dev.log, 控制台 | 参数验证失败 |
| **ERROR** | 50 | 错误信息 | error.log, app.log, 控制台 | 数据库连接失败 |
| **FATAL** | 60 | 致命错误 | error.log, app.log, 控制台 | 系统不可用 |
---
## 🔄 日志轮转和管理
### 自动轮转策略
| 文件类型 | 轮转频率 | 文件大小限制 | 保留时间 | 压缩策略 |
|----------|----------|--------------|----------|----------|
| **app.log** | 每日 | 10MB | 7天 | 7天后压缩 |
| **error.log** | 每日 | 10MB | 30天 | 7天后压缩 |
| **access.log** | 每日 | 50MB | 14天 | 7天后压缩 |
| **dev.log** | 手动 | 无限制 | 无限制 | 不压缩 |
### 定时任务
| 任务 | 执行时间 | 功能 |
|------|----------|------|
| **日志清理** | 每天 02:00 | 删除过期日志文件 |
| **日志压缩** | 每周日 03:00 | 压缩7天前的日志文件 |
| **健康监控** | 每小时 | 监控日志系统状态 |
| **统计报告** | 每天 09:00 | 输出日志统计信息 |
---
## 🚀 如何使用日志系统
### 基本使用
```typescript
import { Injectable } from '@nestjs/common';
import { AppLoggerService } from '../core/utils/logger/logger.service';
@Injectable()
export class UserService {
constructor(private readonly logger: AppLoggerService) {}
async createUser(userData: CreateUserDto) {
// 记录操作开始
this.logger.info('开始创建用户', {
operation: 'createUser',
email: userData.email,
timestamp: new Date().toISOString()
});
try {
const user = await this.userRepository.save(userData);
// 记录成功操作
this.logger.info('用户创建成功', {
operation: 'createUser',
userId: user.id,
email: userData.email,
duration: Date.now() - startTime
});
return user;
} catch (error) {
// 记录错误
this.logger.error('用户创建失败', {
operation: 'createUser',
email: userData.email,
error: error.message
}, error.stack);
throw error;
}
}
}
```
### 请求上下文绑定
```typescript
@Controller('users')
export class UserController {
constructor(private readonly logger: AppLoggerService) {}
@Get(':id')
async getUser(@Param('id') id: string, @Req() req: Request) {
// 绑定请求上下文
const requestLogger = this.logger.bindRequest(req, 'UserController');
requestLogger.info('开始获取用户信息', { userId: id });
try {
const user = await this.userService.findById(id);
requestLogger.info('用户信息获取成功', { userId: id });
return user;
} catch (error) {
requestLogger.error('用户信息获取失败', error.stack, {
userId: id,
reason: error.message
});
throw error;
}
}
}
```
---
## 🔍 日志格式详解
### 开发环境日志格式
```
🕐 2024-12-13 14:30:25 📝 INFO pixel-game-server [UserService] 用户创建成功
operation: "createUser"
userId: "user_123"
email: "user@example.com"
duration: 45
```
### 生产环境日志格式 (JSON)
```json
{
"level": 30,
"time": 1702456225000,
"pid": 12345,
"hostname": "server-01",
"app": "pixel-game-server",
"version": "1.0.0",
"msg": "用户创建成功",
"operation": "createUser",
"userId": "user_123",
"email": "user@example.com",
"duration": 45,
"reqId": "req_1702456225_abc123"
}
```
### HTTP 请求日志格式
```json
{
"level": 30,
"time": 1702456225000,
"req": {
"id": "req_1702456225_abc123",
"method": "POST",
"url": "/api/users",
"headers": {
"host": "localhost:3000",
"user-agent": "Mozilla/5.0...",
"content-type": "application/json"
},
"ip": "127.0.0.1"
},
"res": {
"statusCode": 201,
"responseTime": 45
},
"msg": "POST /api/users completed in 45ms"
}
```
---
## 🛠️ 问题排查指南
### 1. 如何查找特定用户的操作日志?
```bash
# 在日志文件中搜索特定用户ID
grep "userId.*user_123" logs/app.log
# 搜索特定操作
grep "operation.*createUser" logs/app.log
# 搜索特定时间段的日志
grep "2024-12-13 14:" logs/app.log
```
### 2. 如何查找错误日志?
```bash
# 查看所有错误日志
cat logs/error.log
# 查看最近的错误
tail -f logs/error.log
# 搜索特定错误
grep "数据库连接失败" logs/error.log
```
### 3. 如何分析性能问题?
```bash
# 查找响应时间超过1000ms的请求
grep "responseTime.*[0-9][0-9][0-9][0-9]" logs/access.log
# 查找特定接口的性能数据
grep "POST /api/users" logs/access.log | grep responseTime
```
### 4. 如何监控系统健康状态?
```bash
# 查看日志统计信息
grep "日志系统健康状态报告" logs/app.log
# 查看日志清理记录
grep "日志清理任务完成" logs/app.log
# 查看压缩记录
grep "日志压缩任务完成" logs/app.log
```
---
## 📈 日志分析和监控
### 日志统计信息
系统会自动收集以下统计信息:
- **文件数量**:当前日志文件总数
- **总大小**:所有日志文件占用的磁盘空间
- **错误日志数量**:错误级别日志文件数量
- **最旧/最新文件**:日志文件的时间范围
- **平均文件大小**:单个日志文件的平均大小
### 健康监控告警
系统会在以下情况发出警告:
- 📊 **磁盘空间告警**:日志文件总大小超过阈值
- ⚠️ **错误日志告警**:错误日志数量异常增长
- 🔧 **清理失败告警**:日志清理任务执行失败
- 💾 **压缩失败告警**:日志压缩任务执行失败
---
## ⚙️ 配置说明
### 环境变量配置
```bash
# 应用名称
APP_NAME=pixel-game-server
# 环境标识
NODE_ENV=development
# 日志级别
LOG_LEVEL=debug
# 日志目录
LOG_DIR=./logs
# 日志保留天数
LOG_MAX_FILES=7d
# 单个日志文件最大大小
LOG_MAX_SIZE=10m
```
### 高级配置选项
如需自定义日志配置,可以修改 `src/core/utils/logger/logger.config.ts`
```typescript
// 自定义日志轮转策略
{
target: 'pino-roll',
options: {
file: path.join(logDir, 'app.log'),
frequency: 'daily', // 轮转频率daily, hourly, weekly
size: '10m', // 文件大小限制
limit: {
count: 7, // 保留文件数量
},
},
}
```
---
## 🚨 注意事项
### 安全考虑
1. **敏感信息过滤**系统自动过滤密码、token等敏感字段
2. **访问控制**:确保日志文件只有授权用户可以访问
3. **传输加密**:生产环境建议使用加密传输日志
### 性能考虑
1. **异步写入**Pino 使用异步写入,不会阻塞主线程
2. **日志级别**:生产环境建议使用 info 及以上级别
3. **文件轮转**:及时清理和压缩日志文件,避免占用过多磁盘空间
### 运维建议
1. **监控磁盘空间**:定期检查日志目录的磁盘使用情况
2. **备份重要日志**:对于重要的错误日志,建议定期备份
3. **日志分析**:可以集成 ELK Stack 等日志分析工具
4. **告警设置**:配置日志监控告警,及时发现系统问题
---
## 🔗 相关文档
- [后端开发规范 - 日志系统使用指南](./backend_development_guide.md#四日志系统使用指南)
- [AI 辅助开发规范指南](./AI辅助开发规范指南.md)
- [Pino 官方文档](https://getpino.io/)
- [NestJS Pino 集成文档](https://github.com/iamolegga/nestjs-pino)
---
**💡 提示:使用 [AI 辅助开发指南](./AI辅助开发规范指南.md) 可以让 AI 帮你自动生成符合规范的日志代码!**

View File

@@ -1,334 +0,0 @@
# 用户认证系统
## 概述
用户认证系统提供完整的用户注册、登录、密码管理功能支持传统用户名密码登录和第三方OAuth登录。
## 功能特性
- 🔐 多种登录方式:用户名/邮箱/手机号登录
- 📝 用户注册和信息管理
- 🐙 GitHub OAuth 第三方登录
- 🔄 密码重置和修改
- 🛡️ bcrypt 密码加密
- 🎯 基于角色的权限控制
## 架构设计
### 分层结构
```
src/
├── business/login/ # 业务逻辑层
│ ├── login.controller.ts # HTTP 控制器
│ ├── login.service.ts # 业务服务
│ ├── login.dto.ts # 数据传输对象
│ ├── login.service.spec.ts # 业务服务测试
│ └── login.module.ts # 业务模块
├── core/
│ ├── login_core/ # 核心功能层
│ │ ├── login_core.service.ts # 核心认证逻辑
│ │ ├── login_core.service.spec.ts # 核心服务测试
│ │ └── login_core.module.ts # 核心模块
│ └── db/users/ # 数据访问层
│ ├── users.entity.ts # 用户实体
│ ├── users.service.ts # 用户数据服务
│ └── users.dto.ts # 用户 DTO
```
### 职责分离
#### 1. 业务逻辑层 (Business Layer)
- **位置**: `src/business/login/`
- **职责**:
- 处理HTTP请求和响应
- 数据格式化和验证
- 业务流程控制
- 错误处理和日志记录
#### 2. 核心功能层 (Core Layer)
- **位置**: `src/core/login_core/`
- **职责**:
- 认证核心算法实现
- 密码加密和验证
- 用户查找和匹配
- 令牌生成和验证
#### 3. 数据访问层 (Data Access Layer)
- **位置**: `src/core/db/users/`
- **职责**:
- 数据库操作封装
- 实体关系映射
- 数据完整性保证
- 查询优化
## API 接口
### 用户注册
```bash
POST /auth/register
Content-Type: application/json
{
"username": "testuser",
"password": "password123",
"nickname": "测试用户",
"email": "test@example.com",
"phone": "+8613800138000"
}
```
**响应**:
```json
{
"success": true,
"data": {
"user": {
"id": "1",
"username": "testuser",
"nickname": "测试用户",
"email": "test@example.com",
"phone": "+8613800138000",
"avatar_url": null,
"role": 1,
"created_at": "2025-12-17T10:00:00.000Z"
},
"access_token": "eyJ1c2VySWQiOiIxIiwidXNlcm5hbWUiOiJ0ZXN0dXNlciJ9...",
"is_new_user": true,
"message": "注册成功"
},
"message": "注册成功"
}
```
### 用户登录
```bash
POST /auth/login
Content-Type: application/json
{
"identifier": "testuser", # 支持用户名/邮箱/手机号
"password": "password123"
}
```
### GitHub OAuth登录
```bash
POST /auth/github
Content-Type: application/json
{
"github_id": "12345678",
"username": "githubuser",
"nickname": "GitHub用户",
"email": "github@example.com",
"avatar_url": "https://avatars.githubusercontent.com/u/12345678"
}
```
### 密码重置
```bash
# 1. 发送验证码
POST /auth/forgot-password
{
"identifier": "test@example.com"
}
# 2. 重置密码
POST /auth/reset-password
{
"identifier": "test@example.com",
"verification_code": "123456",
"new_password": "newpassword123"
}
```
### 修改密码
```bash
PUT /auth/change-password
{
"user_id": "1",
"old_password": "password123",
"new_password": "newpassword123"
}
```
## 数据模型
### 用户实体 (Users Entity)
```typescript
{
id: bigint, // 主键ID
username: string, // 用户名(唯一)
email?: string, // 邮箱(唯一,可选)
phone?: string, // 手机号(唯一,可选)
password_hash?: string, // 密码哈希OAuth用户为空
nickname: string, // 显示昵称
github_id?: string, // GitHub ID唯一可选
avatar_url?: string, // 头像URL
role: number, // 用户角色1-普通9-管理员)
created_at: Date, // 创建时间
updated_at: Date // 更新时间
}
```
### 数据库设计特点
1. **唯一性约束**: username, email, phone, github_id
2. **索引优化**: 主键、唯一索引、角色索引
3. **字符集支持**: utf8mb4支持emoji
4. **数据类型**: BIGINT主键VARCHAR字段DATETIME时间戳
## 安全机制
### 1. 密码安全
- **加密算法**: bcrypt (saltRounds=12)
- **强度验证**: 最少8位包含字母和数字
- **存储安全**: 只存储哈希值,不存储明文
### 2. 数据验证
- **输入验证**: class-validator装饰器
- **SQL注入防护**: TypeORM参数化查询
- **XSS防护**: 数据转义和验证
### 3. 访问控制
- **令牌机制**: 基于用户信息的访问令牌
- **角色权限**: 基于角色的访问控制RBAC
- **会话管理**: 令牌生成和验证
### 4. 错误处理
- **统一异常**: NestJS异常过滤器
- **日志记录**: 操作日志和错误日志
- **信息脱敏**: 敏感信息自动脱敏
## 测试覆盖
### 单元测试
- 核心服务测试:`src/core/login_core/login_core.service.spec.ts`
- 业务服务测试:`src/business/login/login.service.spec.ts`
### 集成测试
- 端到端测试:`test/business/login.e2e-spec.ts`
### 测试用例
- 用户注册和登录流程
- GitHub OAuth认证
- 密码重置和修改
- 数据验证和错误处理
- 安全性测试
## 使用示例
### JavaScript/TypeScript
```javascript
// 用户注册
const registerResponse = await fetch('/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'testuser',
password: 'password123',
nickname: '测试用户',
email: 'test@example.com'
})
});
const registerData = await registerResponse.json();
console.log(registerData);
// 用户登录
const loginResponse = await fetch('/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
identifier: 'testuser',
password: 'password123'
})
});
const loginData = await loginResponse.json();
console.log(loginData);
```
### curl 命令
```bash
# 用户注册
curl -X POST http://localhost:3000/auth/register \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"password": "password123",
"nickname": "测试用户",
"email": "test@example.com"
}'
# 用户登录
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{
"identifier": "testuser",
"password": "password123"
}'
```
## 错误处理
### 常见错误代码
- `LOGIN_FAILED`: 登录失败
- `REGISTER_FAILED`: 注册失败
- `GITHUB_OAUTH_FAILED`: GitHub登录失败
- `SEND_CODE_FAILED`: 发送验证码失败
- `RESET_PASSWORD_FAILED`: 密码重置失败
- `CHANGE_PASSWORD_FAILED`: 密码修改失败
### 错误响应格式
```json
{
"success": false,
"message": "错误描述",
"error_code": "ERROR_CODE"
}
```
## 扩展功能
### 计划中的功能
1. **JWT令牌管理**
- 访问令牌和刷新令牌
- 令牌黑名单机制
- 自动刷新功能
2. **多因子认证**
- 短信验证码
- 邮箱验证码
- TOTP应用支持
3. **社交登录扩展**
- 微信登录
- QQ登录
- 微博登录
4. **安全增强**
- 登录失败次数限制
- IP白名单/黑名单
- 设备指纹识别
5. **用户管理**
- 用户状态管理(激活/禁用)
- 用户角色权限细化
- 用户行为日志记录

View File

@@ -29,6 +29,7 @@
"@nestjs/platform-socket.io": "^10.4.20",
"@nestjs/schedule": "^4.1.2",
"@nestjs/swagger": "^11.2.3",
"@nestjs/throttler": "^6.5.0",
"@nestjs/typeorm": "^11.0.0",
"@nestjs/websockets": "^10.4.20",
"@types/archiver": "^7.0.0",

View File

View File

@@ -1,8 +1,7 @@
import { Controller, Get } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { AppService } from './app.service';
import { AppStatusResponseDto } from './dto/app.dto';
import { ErrorResponseDto } from './dto/error_response.dto';
import { AppStatusResponseDto, ErrorResponseDto } from './business/shared';
/**
* 应用根控制器

View File

@@ -1,14 +1,19 @@
import { Module } from '@nestjs/common';
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggerModule } from './core/utils/logger/logger.module';
import { UsersModule } from './core/db/users/users.module';
import { LoginCoreModule } from './core/login_core/login_core.module';
import { LoginModule } from './business/login/login.module';
import { AuthModule } from './business/auth/auth.module';
import { RedisModule } from './core/redis/redis.module';
import { AdminModule } from './business/admin/admin.module';
import { UserMgmtModule } from './business/user-mgmt/user-mgmt.module';
import { SecurityModule } from './business/security/security.module';
import { MaintenanceMiddleware } from './business/security/middleware/maintenance.middleware';
import { ContentTypeMiddleware } from './business/security/middleware/content-type.middleware';
/**
* 检查数据库配置是否完整 by angjustinl 2025-12-17
@@ -61,10 +66,32 @@ function isDatabaseConfigured(): boolean {
// 根据数据库配置选择用户模块模式
isDatabaseConfigured() ? UsersModule.forDatabase() : UsersModule.forMemory(),
LoginCoreModule,
LoginModule,
AuthModule,
UserMgmtModule,
AdminModule,
SecurityModule,
],
controllers: [AppController],
providers: [AppService],
providers: [
AppService,
// 注意全局拦截器现在由SecurityModule提供
],
})
export class AppModule {}
export class AppModule implements NestModule {
/**
* 配置中间件
*
* @param consumer 中间件消费者
*/
configure(consumer: MiddlewareConsumer) {
// 1. 维护模式中间件 - 最高优先级
consumer
.apply(MaintenanceMiddleware)
.forRoutes('*');
// 2. 内容类型检查中间件
consumer
.apply(ContentTypeMiddleware)
.forRoutes('*');
}
}

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AppStatusResponseDto } from './dto/app.dto';
import { AppStatusResponseDto } from './business/shared';
/**
* 应用服务类

View File

@@ -15,10 +15,17 @@
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Query, UseGuards, ValidationPipe, UsePipes, Res, Logger } from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiProduces, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { AdminGuard } from '../../core/guards/admin.guard';
import { AdminGuard } from './guards/admin.guard';
import { AdminService } from './admin.service';
import { AdminLoginDto, AdminResetPasswordDto } from '../../dto/admin.dto';
import { AdminLoginResponseDto, AdminUsersResponseDto, AdminCommonResponseDto, AdminUserResponseDto, AdminRuntimeLogsResponseDto } from '../../dto/admin_response.dto';
import { AdminLoginDto, AdminResetPasswordDto } from './dto/admin-login.dto';
import {
AdminLoginResponseDto,
AdminUsersResponseDto,
AdminCommonResponseDto,
AdminUserResponseDto,
AdminRuntimeLogsResponseDto
} from './dto/admin-response.dto';
import { Throttle, ThrottlePresets } from '../security/decorators/throttle.decorator';
import type { Response } from 'express';
import * as fs from 'fs';
import * as path from 'path';
@@ -35,6 +42,10 @@ export class AdminController {
@ApiOperation({ summary: '管理员登录', description: '仅允许 role=9 的账户登录后台' })
@ApiBody({ type: AdminLoginDto })
@ApiResponse({ status: 200, description: '登录成功', type: AdminLoginResponseDto })
@ApiResponse({ status: 401, description: '登录失败' })
@ApiResponse({ status: 403, description: '权限不足或账户被禁用' })
@ApiResponse({ status: 429, description: '登录尝试过于频繁' })
@Throttle(ThrottlePresets.LOGIN)
@Post('auth/login')
@HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true }))
@@ -73,7 +84,9 @@ export class AdminController {
@ApiParam({ name: 'id', description: '用户ID' })
@ApiBody({ type: AdminResetPasswordDto })
@ApiResponse({ status: 200, description: '重置成功', type: AdminCommonResponseDto })
@ApiResponse({ status: 429, description: '操作过于频繁' })
@UseGuards(AdminGuard)
@Throttle(ThrottlePresets.ADMIN_OPERATION)
@Post('users/:id/reset-password')
@HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true }))

View File

@@ -21,5 +21,6 @@ import { AdminService } from './admin.service';
imports: [AdminCoreModule, LoggerModule],
controllers: [AdminController],
providers: [AdminService],
exports: [AdminService], // 导出AdminService供其他模块使用
})
export class AdminModule {}

View File

@@ -11,12 +11,21 @@
* @since 2025-12-19
*/
import { Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { Inject, Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common';
import { AdminCoreService } from '../../core/admin_core/admin_core.service';
import { Users } from '../../core/db/users/users.entity';
import { UsersService } from '../../core/db/users/users.service';
import { UsersMemoryService } from '../../core/db/users/users_memory.service';
import { LogManagementService } from '../../core/utils/logger/log_management.service';
import { UserStatus, getUserStatusDescription } from '../user-mgmt/enums/user-status.enum';
import { UserStatusDto, BatchUserStatusDto } from '../user-mgmt/dto/user-status.dto';
import {
UserStatusResponseDto,
BatchUserStatusResponseDto,
UserStatusStatsResponseDto,
UserStatusInfoDto,
BatchOperationResultDto
} from '../user-mgmt/dto/user-status-response.dto';
export interface AdminApiResponse<T = any> {
success: boolean;
@@ -108,8 +117,318 @@ export class AdminService {
phone: user.phone,
avatar_url: user.avatar_url,
role: user.role,
status: user.status || UserStatus.ACTIVE, // 兼容旧数据
created_at: user.created_at,
updated_at: user.updated_at,
};
}
/**
* 格式化用户状态信息
*
* @param user 用户实体
* @returns 格式化的用户状态信息
*/
private formatUserStatus(user: Users): UserStatusInfoDto {
return {
id: user.id.toString(),
username: user.username,
nickname: user.nickname,
status: user.status || UserStatus.ACTIVE,
status_description: getUserStatusDescription(user.status || UserStatus.ACTIVE),
updated_at: user.updated_at
};
}
/**
* 修改用户状态
*
* 功能描述:
* 管理员修改指定用户的账户状态,支持激活、锁定、禁用等操作
*
* 业务逻辑:
* 1. 验证用户是否存在
* 2. 检查状态变更的合法性
* 3. 更新用户状态
* 4. 记录状态变更日志
*
* @param userId 用户ID
* @param userStatusDto 状态修改数据
* @returns 修改结果
*
* @throws NotFoundException 当用户不存在时
* @throws BadRequestException 当状态变更不合法时
*/
async updateUserStatus(userId: bigint, userStatusDto: UserStatusDto): Promise<UserStatusResponseDto> {
try {
this.logger.log('开始修改用户状态', {
operation: 'update_user_status',
userId: userId.toString(),
newStatus: userStatusDto.status,
reason: userStatusDto.reason,
timestamp: new Date().toISOString()
});
// 1. 验证用户是否存在
const user = await this.usersService.findOne(userId);
if (!user) {
this.logger.warn('修改用户状态失败:用户不存在', {
operation: 'update_user_status',
userId: userId.toString()
});
throw new NotFoundException('用户不存在');
}
// 2. 检查状态变更的合法性
if (user.status === userStatusDto.status) {
this.logger.warn('修改用户状态失败:状态未发生变化', {
operation: 'update_user_status',
userId: userId.toString(),
currentStatus: user.status,
newStatus: userStatusDto.status
});
throw new BadRequestException('用户状态未发生变化');
}
// 3. 更新用户状态
const updatedUser = await this.usersService.update(userId, {
status: userStatusDto.status
});
// 4. 记录状态变更日志
this.logger.log('用户状态修改成功', {
operation: 'update_user_status',
userId: userId.toString(),
oldStatus: user.status,
newStatus: userStatusDto.status,
reason: userStatusDto.reason,
timestamp: new Date().toISOString()
});
return {
success: true,
data: {
user: this.formatUserStatus(updatedUser),
reason: userStatusDto.reason
},
message: '用户状态修改成功'
};
} catch (error) {
this.logger.error('修改用户状态失败', {
operation: 'update_user_status',
userId: userId.toString(),
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString()
});
if (error instanceof NotFoundException || error instanceof BadRequestException) {
throw error;
}
return {
success: false,
message: '用户状态修改失败',
error_code: 'USER_STATUS_UPDATE_FAILED'
};
}
}
/**
* 批量修改用户状态
*
* 功能描述:
* 管理员批量修改多个用户的账户状态
*
* 业务逻辑:
* 1. 验证用户ID列表
* 2. 逐个处理用户状态修改
* 3. 收集成功和失败的结果
* 4. 返回批量操作结果
*
* @param batchUserStatusDto 批量状态修改数据
* @returns 批量修改结果
*/
async batchUpdateUserStatus(batchUserStatusDto: BatchUserStatusDto): Promise<BatchUserStatusResponseDto> {
try {
this.logger.log('开始批量修改用户状态', {
operation: 'batch_update_user_status',
userCount: batchUserStatusDto.user_ids.length,
newStatus: batchUserStatusDto.status,
reason: batchUserStatusDto.reason,
timestamp: new Date().toISOString()
});
const successUsers: UserStatusInfoDto[] = [];
const failedUsers: Array<{ user_id: string; error: string }> = [];
// 1. 逐个处理用户状态修改
for (const userIdStr of batchUserStatusDto.user_ids) {
try {
const userId = BigInt(userIdStr);
// 2. 验证用户是否存在
const user = await this.usersService.findOne(userId);
if (!user) {
failedUsers.push({
user_id: userIdStr,
error: '用户不存在'
});
continue;
}
// 3. 检查状态是否需要变更
if (user.status === batchUserStatusDto.status) {
failedUsers.push({
user_id: userIdStr,
error: '用户状态未发生变化'
});
continue;
}
// 4. 更新用户状态
const updatedUser = await this.usersService.update(userId, {
status: batchUserStatusDto.status
});
successUsers.push(this.formatUserStatus(updatedUser));
} catch (error) {
failedUsers.push({
user_id: userIdStr,
error: error instanceof Error ? error.message : '未知错误'
});
}
}
// 5. 构建批量操作结果
const result: BatchOperationResultDto = {
success_users: successUsers,
failed_users: failedUsers,
success_count: successUsers.length,
failed_count: failedUsers.length,
total_count: batchUserStatusDto.user_ids.length
};
this.logger.log('批量修改用户状态完成', {
operation: 'batch_update_user_status',
successCount: result.success_count,
failedCount: result.failed_count,
totalCount: result.total_count,
timestamp: new Date().toISOString()
});
return {
success: true,
data: {
result,
reason: batchUserStatusDto.reason
},
message: `批量用户状态修改完成,成功:${result.success_count},失败:${result.failed_count}`
};
} catch (error) {
this.logger.error('批量修改用户状态失败', {
operation: 'batch_update_user_status',
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString()
});
return {
success: false,
message: '批量用户状态修改失败',
error_code: 'BATCH_USER_STATUS_UPDATE_FAILED'
};
}
}
/**
* 获取用户状态统计
*
* 功能描述:
* 获取各种用户状态的数量统计信息
*
* 业务逻辑:
* 1. 查询所有用户
* 2. 按状态分组统计
* 3. 计算各状态数量
* 4. 返回统计结果
*
* @returns 状态统计信息
*/
async getUserStatusStats(): Promise<UserStatusStatsResponseDto> {
try {
this.logger.log('开始获取用户状态统计', {
operation: 'get_user_status_stats',
timestamp: new Date().toISOString()
});
// 1. 查询所有用户(这里可以优化为直接查询统计信息)
const allUsers = await this.usersService.findAll(10000, 0); // 假设最多1万用户
// 2. 按状态分组统计
const stats = {
active: 0,
inactive: 0,
locked: 0,
banned: 0,
deleted: 0,
pending: 0,
total: allUsers.length
};
// 3. 计算各状态数量
allUsers.forEach((user: Users) => {
const status = user.status || UserStatus.ACTIVE;
switch (status) {
case UserStatus.ACTIVE:
stats.active++;
break;
case UserStatus.INACTIVE:
stats.inactive++;
break;
case UserStatus.LOCKED:
stats.locked++;
break;
case UserStatus.BANNED:
stats.banned++;
break;
case UserStatus.DELETED:
stats.deleted++;
break;
case UserStatus.PENDING:
stats.pending++;
break;
}
});
this.logger.log('用户状态统计获取成功', {
operation: 'get_user_status_stats',
stats,
timestamp: new Date().toISOString()
});
return {
success: true,
data: {
stats,
timestamp: new Date().toISOString()
},
message: '用户状态统计获取成功'
};
} catch (error) {
this.logger.error('获取用户状态统计失败', {
operation: 'get_user_status_stats',
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString()
});
return {
success: false,
message: '用户状态统计获取失败',
error_code: 'USER_STATUS_STATS_FAILED'
};
}
}
}

View File

@@ -31,4 +31,4 @@ export class AdminResetPasswordDto {
@IsNotEmpty()
@MinLength(8)
new_password: string;
}
}

View File

@@ -0,0 +1,104 @@
/**
* 管理员响应 DTO
*
* 功能描述:
* - 定义管理员相关接口的响应格式
* - 提供 Swagger 文档生成支持
*
* @author jianuo
* @version 1.0.0
* @since 2025-12-19
*/
import { ApiProperty } from '@nestjs/swagger';
export class AdminLoginResponseDto {
@ApiProperty({ description: '是否成功', example: true })
success: boolean;
@ApiProperty({ description: '消息', example: '登录成功' })
message: string;
@ApiProperty({ description: 'JWT Token', example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' })
token?: string;
@ApiProperty({ description: '管理员信息', required: false })
admin?: {
id: string;
username: string;
email: string;
role: number;
};
}
export class AdminUsersResponseDto {
@ApiProperty({ description: '是否成功', example: true })
success: boolean;
@ApiProperty({ description: '消息', example: '获取用户列表成功' })
message: string;
@ApiProperty({ description: '用户列表', type: 'array' })
users?: Array<{
id: string;
username: string;
email: string;
phone: string;
role: number;
status: string;
created_at: string;
updated_at: string;
}>;
@ApiProperty({ description: '总数', example: 100 })
total?: number;
@ApiProperty({ description: '偏移量', example: 0 })
offset?: number;
@ApiProperty({ description: '限制数量', example: 100 })
limit?: number;
}
export class AdminUserResponseDto {
@ApiProperty({ description: '是否成功', example: true })
success: boolean;
@ApiProperty({ description: '消息', example: '获取用户详情成功' })
message: string;
@ApiProperty({ description: '用户信息', required: false })
user?: {
id: string;
username: string;
email: string;
phone: string;
role: number;
status: string;
created_at: string;
updated_at: string;
last_login_at?: string;
};
}
export class AdminCommonResponseDto {
@ApiProperty({ description: '是否成功', example: true })
success: boolean;
@ApiProperty({ description: '消息', example: '操作成功' })
message: string;
}
export class AdminRuntimeLogsResponseDto {
@ApiProperty({ description: '是否成功', example: true })
success: boolean;
@ApiProperty({ description: '消息', example: '获取日志成功' })
message: string;
@ApiProperty({ description: '日志内容', type: 'array', items: { type: 'string' } })
logs?: string[];
@ApiProperty({ description: '返回行数', example: 200 })
lines?: number;
}

View File

@@ -13,7 +13,7 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { Request } from 'express';
import { AdminCoreService, AdminAuthPayload } from '../admin_core/admin_core.service';
import { AdminCoreService, AdminAuthPayload } from '../../../core/admin_core/admin_core.service';
export interface AdminRequest extends Request {
admin?: AdminAuthPayload;

View File

@@ -0,0 +1,24 @@
/**
* 管理员模块统一导出
*
* 功能描述:
* - 导出管理员相关的所有组件
* - 提供统一的导入入口
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
// 控制器
export * from './admin.controller';
// 服务
export * from './admin.service';
// DTO
export * from './dto/admin-login.dto';
export * from './dto/admin-response.dto';
// 模块
export * from './admin.module';

View File

@@ -0,0 +1,26 @@
/**
* 用户认证业务模块
*
* 功能描述:
* - 整合所有用户认证相关功能
* - 用户登录、注册、密码管理
* - GitHub OAuth集成
* - 邮箱验证功能
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { Module } from '@nestjs/common';
import { LoginController } from './controllers/login.controller';
import { LoginService } from './services/login.service';
import { LoginCoreModule } from '../../core/login_core/login_core.module';
@Module({
imports: [LoginCoreModule],
controllers: [LoginController],
providers: [LoginService],
exports: [LoginService],
})
export class AuthModule {}

View File

@@ -22,8 +22,8 @@
import { Controller, Post, Put, Body, HttpCode, HttpStatus, ValidationPipe, UsePipes, Logger, Res } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse as SwaggerApiResponse, ApiBody } from '@nestjs/swagger';
import { Response } from 'express';
import { LoginService, ApiResponse, LoginResponse } from './login.service';
import { LoginDto, RegisterDto, GitHubOAuthDto, ForgotPasswordDto, ResetPasswordDto, ChangePasswordDto, EmailVerificationDto, SendEmailVerificationDto } from '../../dto/login.dto';
import { LoginService, ApiResponse, LoginResponse } from '../services/login.service';
import { LoginDto, RegisterDto, GitHubOAuthDto, ForgotPasswordDto, ResetPasswordDto, ChangePasswordDto, EmailVerificationDto, SendEmailVerificationDto } from '../dto/login.dto';
import {
LoginResponseDto,
RegisterResponseDto,
@@ -32,7 +32,9 @@ import {
CommonResponseDto,
TestModeEmailVerificationResponseDto,
SuccessEmailVerificationResponseDto
} from '../../dto/login_response.dto';
} from '../dto/login_response.dto';
import { Throttle, ThrottlePresets } from '../../security/decorators/throttle.decorator';
import { Timeout, TimeoutPresets } from '../../security/decorators/timeout.decorator';
@ApiTags('auth')
@Controller('auth')
@@ -65,6 +67,16 @@ export class LoginController {
status: 401,
description: '用户名或密码错误'
})
@SwaggerApiResponse({
status: 403,
description: '账户被禁用或锁定'
})
@SwaggerApiResponse({
status: 429,
description: '登录尝试过于频繁'
})
@Throttle(ThrottlePresets.LOGIN)
@Timeout(TimeoutPresets.NORMAL)
@Post('login')
@HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true }))
@@ -99,6 +111,12 @@ export class LoginController {
status: 409,
description: '用户名或邮箱已存在'
})
@SwaggerApiResponse({
status: 429,
description: '注册请求过于频繁'
})
@Throttle(ThrottlePresets.REGISTER)
@Timeout(TimeoutPresets.NORMAL)
@Post('register')
@HttpCode(HttpStatus.CREATED)
@UsePipes(new ValidationPipe({ transform: true }))
@@ -180,6 +198,11 @@ export class LoginController {
status: 404,
description: '用户不存在'
})
@SwaggerApiResponse({
status: 429,
description: '发送频率过高'
})
@Throttle(ThrottlePresets.SEND_CODE)
@Post('forgot-password')
@UsePipes(new ValidationPipe({ transform: true }))
async forgotPassword(
@@ -222,6 +245,11 @@ export class LoginController {
status: 404,
description: '用户不存在'
})
@SwaggerApiResponse({
status: 429,
description: '重置请求过于频繁'
})
@Throttle(ThrottlePresets.RESET_PASSWORD)
@Post('reset-password')
@HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true }))
@@ -302,6 +330,8 @@ export class LoginController {
status: 429,
description: '发送频率过高'
})
@Throttle(ThrottlePresets.SEND_CODE)
@Timeout(TimeoutPresets.EMAIL_SEND)
@Post('send-email-verification')
@UsePipes(new ValidationPipe({ transform: true }))
async sendEmailVerification(
@@ -380,6 +410,7 @@ export class LoginController {
status: 429,
description: '发送频率过高'
})
@Throttle(ThrottlePresets.SEND_CODE)
@Post('resend-email-verification')
@UsePipes(new ValidationPipe({ transform: true }))
async resendEmailVerification(

View File

@@ -0,0 +1,23 @@
/**
* 用户认证业务模块导出
*
* 功能概述:
* - 用户登录和注册
* - GitHub OAuth集成
* - 密码管理(忘记密码、重置密码、修改密码)
* - 邮箱验证功能
* - JWT Token管理
*/
// 模块
export * from './auth.module';
// 控制器
export * from './controllers/login.controller';
// 服务
export * from './services/login.service';
// DTO
export * from './dto/login.dto';
export * from './dto/login_response.dto';

View File

@@ -17,8 +17,8 @@
*/
import { Injectable, Logger } from '@nestjs/common';
import { LoginCoreService, LoginRequest, RegisterRequest, GitHubOAuthRequest, PasswordResetRequest, AuthResult } from '../../core/login_core/login_core.service';
import { Users } from '../../core/db/users/users.entity';
import { LoginCoreService, LoginRequest, RegisterRequest, GitHubOAuthRequest, PasswordResetRequest, AuthResult } from '../../../core/login_core/login_core.service';
import { Users } from '../../../core/db/users/users.entity';
/**
*

View File

@@ -1,25 +0,0 @@
/**
* 登录业务模块
*
* 功能描述:
* - 整合登录相关的控制器、服务和依赖
* - 提供完整的登录业务功能模块
* - 可被其他模块导入使用
*
* @author moyin
* @version 1.0.0
* @since 2025-12-17
*/
import { Module } from '@nestjs/common';
import { LoginController } from './login.controller';
import { LoginService } from './login.service';
import { LoginCoreModule } from '../../core/login_core/login_core.module';
@Module({
imports: [LoginCoreModule],
controllers: [LoginController],
providers: [LoginService],
exports: [LoginService],
})
export class LoginModule {}

View File

@@ -1,193 +0,0 @@
/**
* 登录业务服务测试
*/
import { Test, TestingModule } from '@nestjs/testing';
import { LoginService } from './login.service';
import { LoginCoreService } from '../../core/login_core/login_core.service';
describe('LoginService', () => {
let service: LoginService;
let loginCoreService: jest.Mocked<LoginCoreService>;
const mockUser = {
id: BigInt(1),
username: 'testuser',
email: 'test@example.com',
phone: '+8613800138000',
password_hash: '$2b$12$hashedpassword',
nickname: '测试用户',
github_id: null as string | null,
avatar_url: null as string | null,
role: 1,
email_verified: false,
created_at: new Date(),
updated_at: new Date()
};
beforeEach(async () => {
const mockLoginCoreService = {
login: jest.fn(),
register: jest.fn(),
githubOAuth: jest.fn(),
sendPasswordResetCode: jest.fn(),
resetPassword: jest.fn(),
changePassword: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
LoginService,
{
provide: LoginCoreService,
useValue: mockLoginCoreService,
},
],
}).compile();
service = module.get<LoginService>(LoginService);
loginCoreService = module.get(LoginCoreService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('login', () => {
it('should return success response for valid login', async () => {
loginCoreService.login.mockResolvedValue({
user: mockUser,
isNewUser: false
});
const result = await service.login({
identifier: 'testuser',
password: 'password123'
});
expect(result.success).toBe(true);
expect(result.data?.user.username).toBe('testuser');
expect(result.data?.access_token).toBeDefined();
});
it('should return error response for failed login', async () => {
loginCoreService.login.mockRejectedValue(new Error('登录失败'));
const result = await service.login({
identifier: 'testuser',
password: 'wrongpassword'
});
expect(result.success).toBe(false);
expect(result.message).toBe('登录失败');
expect(result.error_code).toBe('LOGIN_FAILED');
});
});
describe('register', () => {
it('should return success response for valid registration', async () => {
loginCoreService.register.mockResolvedValue({
user: mockUser,
isNewUser: true
});
const result = await service.register({
username: 'testuser',
password: 'password123',
nickname: '测试用户'
});
expect(result.success).toBe(true);
expect(result.data?.user.username).toBe('testuser');
expect(result.data?.is_new_user).toBe(true);
});
it('should return error response for failed registration', async () => {
loginCoreService.register.mockRejectedValue(new Error('用户名已存在'));
const result = await service.register({
username: 'existinguser',
password: 'password123',
nickname: '测试用户'
});
expect(result.success).toBe(false);
expect(result.message).toBe('用户名已存在');
expect(result.error_code).toBe('REGISTER_FAILED');
});
});
describe('githubOAuth', () => {
it('should return success response for GitHub OAuth', async () => {
loginCoreService.githubOAuth.mockResolvedValue({
user: mockUser,
isNewUser: true
});
const result = await service.githubOAuth({
github_id: 'github123',
username: 'githubuser',
nickname: 'GitHub用户'
});
expect(result.success).toBe(true);
expect(result.data?.user.username).toBe('testuser');
expect(result.data?.is_new_user).toBe(true);
});
});
describe('sendPasswordResetCode', () => {
it('should return test mode response with verification code', async () => {
loginCoreService.sendPasswordResetCode.mockResolvedValue({
code: '123456',
isTestMode: true
});
const result = await service.sendPasswordResetCode('test@example.com');
expect(result.success).toBe(false); // 测试模式下不算成功
expect(result.error_code).toBe('TEST_MODE_ONLY');
expect(result.data?.verification_code).toBe('123456');
expect(result.data?.is_test_mode).toBe(true);
});
it('should return success response for real email sending', async () => {
loginCoreService.sendPasswordResetCode.mockResolvedValue({
code: '123456',
isTestMode: false
});
const result = await service.sendPasswordResetCode('test@example.com');
expect(result.success).toBe(true);
expect(result.data?.is_test_mode).toBe(false);
expect(result.data?.verification_code).toBeUndefined(); // 真实模式下不返回验证码
});
});
describe('resetPassword', () => {
it('should return success response for password reset', async () => {
loginCoreService.resetPassword.mockResolvedValue(mockUser);
const result = await service.resetPassword({
identifier: 'test@example.com',
verificationCode: '123456',
newPassword: 'newpassword123'
});
expect(result.success).toBe(true);
expect(result.message).toBe('密码重置成功');
});
});
describe('changePassword', () => {
it('should return success response for password change', async () => {
loginCoreService.changePassword.mockResolvedValue(mockUser);
const result = await service.changePassword(BigInt(1), 'oldpassword', 'newpassword123');
expect(result.success).toBe(true);
expect(result.message).toBe('密码修改成功');
});
});
});

View File

@@ -0,0 +1,89 @@
/**
* 频率限制装饰器
*
* 功能描述:
* - 提供API接口的频率限制功能
* - 防止恶意请求和系统滥用
* - 支持基于IP和用户的限制策略
*
* 使用场景:
* - 登录接口防暴力破解
* - 注册接口防批量注册
* - 验证码接口防频繁发送
* - 敏感操作接口保护
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { SetMetadata, applyDecorators, UseGuards } from '@nestjs/common';
import { ThrottleGuard } from '../guards/throttle.guard';
/**
* 频率限制元数据键
*/
export const THROTTLE_KEY = 'throttle';
/**
* 频率限制配置接口
*/
export interface ThrottleConfig {
/** 时间窗口内允许的最大请求次数 */
limit: number;
/** 时间窗口长度(秒) */
ttl: number;
/** 限制类型ip基于IP或 user基于用户 */
type?: 'ip' | 'user';
/** 自定义错误消息 */
message?: string;
}
/**
* 频率限制装饰器
*
* @param config 频率限制配置
* @returns 装饰器函数
*
* @example
* ```typescript
* // 每分钟最多5次登录尝试
* @Throttle({ limit: 5, ttl: 60, message: '登录尝试过于频繁,请稍后再试' })
* @Post('login')
* async login() { ... }
*
* // 每5分钟最多3次注册
* @Throttle({ limit: 3, ttl: 300, type: 'ip' })
* @Post('register')
* async register() { ... }
* ```
*/
export function Throttle(config: ThrottleConfig) {
return applyDecorators(
SetMetadata(THROTTLE_KEY, config),
UseGuards(ThrottleGuard)
);
}
/**
* 预定义的频率限制配置
*/
export const ThrottlePresets = {
/** 登录接口每分钟5次 */
LOGIN: { limit: 5, ttl: 60, message: '登录尝试过于频繁请1分钟后再试' },
/** 注册接口每5分钟3次 */
REGISTER: { limit: 3, ttl: 300, message: '注册请求过于频繁请5分钟后再试' },
/** 发送验证码每分钟1次 */
SEND_CODE: { limit: 1, ttl: 60, message: '验证码发送过于频繁请1分钟后再试' },
/** 密码重置每小时3次 */
RESET_PASSWORD: { limit: 3, ttl: 3600, message: '密码重置请求过于频繁请1小时后再试' },
/** 管理员操作每分钟10次 */
ADMIN_OPERATION: { limit: 10, ttl: 60, message: '管理员操作过于频繁,请稍后再试' },
/** 一般API每分钟30次 */
GENERAL_API: { limit: 30, ttl: 60, message: 'API调用过于频繁请稍后再试' }
} as const;

View File

@@ -0,0 +1,119 @@
/**
* 超时处理装饰器
*
* 功能描述:
* - 为API接口添加超时控制
* - 防止长时间运行的请求阻塞系统
* - 提供友好的超时错误提示
*
* 使用场景:
* - 数据库查询超时控制
* - 外部API调用超时
* - 文件上传下载超时
* - 复杂计算任务超时
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { SetMetadata, applyDecorators } from '@nestjs/common';
import { ApiResponse } from '@nestjs/swagger';
/**
* 超时配置元数据键
*/
export const TIMEOUT_KEY = 'timeout';
/**
* 超时配置接口
*/
export interface TimeoutConfig {
/** 超时时间(毫秒) */
timeout: number;
/** 自定义超时错误消息 */
message?: string;
/** 是否记录超时日志 */
logTimeout?: boolean;
}
/**
* 超时装饰器
*
* @param config 超时配置或超时时间(毫秒)
* @returns 装饰器函数
*
* @example
* ```typescript
* // 设置30秒超时
* @Timeout(30000)
* @Get('slow-operation')
* async slowOperation() { ... }
*
* // 自定义超时配置
* @Timeout({
* timeout: 60000,
* message: '数据查询超时,请稍后重试',
* logTimeout: true
* })
* @Post('complex-query')
* async complexQuery() { ... }
* ```
*/
export function Timeout(config: number | TimeoutConfig) {
const timeoutConfig: TimeoutConfig = typeof config === 'number'
? { timeout: config }
: config;
return applyDecorators(
SetMetadata(TIMEOUT_KEY, timeoutConfig),
ApiResponse({
status: 408,
description: timeoutConfig.message || '请求超时',
schema: {
type: 'object',
properties: {
success: { type: 'boolean', example: false },
message: { type: 'string', example: timeoutConfig.message || '请求超时,请稍后重试' },
error_code: { type: 'string', example: 'REQUEST_TIMEOUT' },
timeout_info: {
type: 'object',
properties: {
timeout_ms: { type: 'number', example: timeoutConfig.timeout },
timestamp: { type: 'string', example: '2025-12-24T10:00:00.000Z' }
}
}
}
}
})
);
}
/**
* 预定义的超时配置
*/
export const TimeoutPresets = {
/** 快速操作5秒 */
FAST: { timeout: 5000, message: '操作超时,请检查网络连接' },
/** 一般操作30秒 */
NORMAL: { timeout: 30000, message: '请求超时,请稍后重试' },
/** 慢操作60秒 */
SLOW: { timeout: 60000, message: '操作超时,请稍后重试' },
/** 文件操作2分钟 */
FILE_OPERATION: { timeout: 120000, message: '文件操作超时,请检查文件大小和网络状况' },
/** 数据库查询45秒 */
DATABASE_QUERY: { timeout: 45000, message: '数据查询超时,请简化查询条件或稍后重试' },
/** 外部API调用15秒 */
EXTERNAL_API: { timeout: 15000, message: '外部服务调用超时,请稍后重试' },
/** 邮件发送30秒 */
EMAIL_SEND: { timeout: 30000, message: '邮件发送超时,请检查邮件服务配置' },
/** 长时间任务5分钟 */
LONG_TASK: { timeout: 300000, message: '任务执行超时,请稍后重试' }
} as const;

View File

@@ -0,0 +1,317 @@
/**
* 频率限制守卫
*
* 功能描述:
* - 实现API接口的频率限制功能
* - 基于IP地址进行限制
* - 支持自定义限制规则
*
* 使用场景:
* - 防止API滥用
* - 登录暴力破解防护
* - 验证码发送频率控制
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import {
Injectable,
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
Logger
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { THROTTLE_KEY, ThrottleConfig } from '../decorators/throttle.decorator';
/**
* 频率限制记录接口
*/
interface ThrottleRecord {
/** 请求次数 */
count: number;
/** 窗口开始时间 */
windowStart: number;
/** 最后请求时间 */
lastRequest: number;
}
/**
* 频率限制响应接口
*/
interface ThrottleResponse {
/** 请求是否成功 */
success: boolean;
/** 响应消息 */
message: string;
/** 错误代码 */
error_code: string;
/** 限制信息 */
throttle_info: {
/** 限制次数 */
limit: number;
/** 时间窗口(秒) */
window_seconds: number;
/** 当前请求次数 */
current_requests: number;
/** 重置时间 */
reset_time: string;
};
}
@Injectable()
export class ThrottleGuard implements CanActivate {
private readonly logger = new Logger(ThrottleGuard.name);
/**
* 存储频率限制记录
* Key: IP地址 + 路径
* Value: 限制记录
*/
private readonly records = new Map<string, ThrottleRecord>();
/**
* 清理过期记录的间隔(毫秒)
*/
private readonly cleanupInterval = 60000; // 1分钟
constructor(private readonly reflector: Reflector) {
// 启动定期清理任务
this.startCleanupTask();
}
/**
* 守卫检查函数
*
* @param context 执行上下文
* @returns 是否允许通过
*/
async canActivate(context: ExecutionContext): Promise<boolean> {
// 1. 获取频率限制配置
const throttleConfig = this.getThrottleConfig(context);
if (!throttleConfig) {
// 没有配置频率限制,直接通过
return true;
}
// 2. 获取请求信息
const request = context.switchToHttp().getRequest<Request>();
const key = this.generateKey(request, throttleConfig);
// 3. 检查频率限制
const isAllowed = this.checkThrottle(key, throttleConfig);
if (!isAllowed) {
// 4. 记录被限制的请求
this.logger.warn('请求被频率限制', {
operation: 'throttle_limit',
method: request.method,
url: request.url,
ip: request.ip,
userAgent: request.get('User-Agent'),
limit: throttleConfig.limit,
ttl: throttleConfig.ttl,
timestamp: new Date().toISOString()
});
// 5. 抛出频率限制异常
const record = this.records.get(key);
const resetTime = new Date(record!.windowStart + throttleConfig.ttl * 1000);
const response: ThrottleResponse = {
success: false,
message: throttleConfig.message || '请求过于频繁,请稍后再试',
error_code: 'TOO_MANY_REQUESTS',
throttle_info: {
limit: throttleConfig.limit,
window_seconds: throttleConfig.ttl,
current_requests: record!.count,
reset_time: resetTime.toISOString()
}
};
throw new HttpException(response, HttpStatus.TOO_MANY_REQUESTS);
}
return true;
}
/**
* 获取频率限制配置
*
* @param context 执行上下文
* @returns 频率限制配置或null
*/
private getThrottleConfig(context: ExecutionContext): ThrottleConfig | null {
// 从方法装饰器获取配置
const methodConfig = this.reflector.get<ThrottleConfig>(
THROTTLE_KEY,
context.getHandler()
);
if (methodConfig) {
return methodConfig;
}
// 从类装饰器获取配置
const classConfig = this.reflector.get<ThrottleConfig>(
THROTTLE_KEY,
context.getClass()
);
return classConfig || null;
}
/**
* 生成限制键
*
* @param request 请求对象
* @param config 频率限制配置
* @returns 限制键
*/
private generateKey(request: Request, config: ThrottleConfig): string {
const ip = request.ip || 'unknown';
const path = request.route?.path || request.url;
const method = request.method;
// 根据限制类型生成不同的键
if (config.type === 'user') {
// 基于用户的限制需要从JWT中获取用户ID
const userId = this.extractUserId(request);
return `user:${userId}:${method}:${path}`;
} else {
// 基于IP的限制默认
return `ip:${ip}:${method}:${path}`;
}
}
/**
* 检查频率限制
*
* @param key 限制键
* @param config 频率限制配置
* @returns 是否允许通过
*/
private checkThrottle(key: string, config: ThrottleConfig): boolean {
const now = Date.now();
const windowMs = config.ttl * 1000;
let record = this.records.get(key);
if (!record) {
// 第一次请求
this.records.set(key, {
count: 1,
windowStart: now,
lastRequest: now
});
return true;
}
// 检查是否需要重置窗口
if (now - record.windowStart >= windowMs) {
// 重置窗口
record.count = 1;
record.windowStart = now;
record.lastRequest = now;
return true;
}
// 在当前窗口内
if (record.count >= config.limit) {
// 超过限制
return false;
}
// 增加计数
record.count++;
record.lastRequest = now;
return true;
}
/**
* 从请求中提取用户ID
*
* @param request 请求对象
* @returns 用户ID
*/
private extractUserId(request: Request): string {
// 这里应该从JWT token中提取用户ID
// 简化实现使用IP作为fallback
const authHeader = request.get('Authorization');
if (authHeader && authHeader.startsWith('Bearer ')) {
try {
// 这里应该解析JWT token获取用户ID
// 简化实现返回token的hash
const token = authHeader.substring(7);
return Buffer.from(token).toString('base64').substring(0, 10);
} catch (error) {
// JWT解析失败使用IP
return request.ip || 'unknown';
}
}
return request.ip || 'unknown';
}
/**
* 启动清理任务
*/
private startCleanupTask(): void {
setInterval(() => {
this.cleanupExpiredRecords();
}, this.cleanupInterval);
}
/**
* 清理过期记录
*/
private cleanupExpiredRecords(): void {
const now = Date.now();
const maxAge = 3600000; // 1小时
for (const [key, record] of this.records.entries()) {
if (now - record.lastRequest > maxAge) {
this.records.delete(key);
}
}
}
/**
* 获取当前记录统计
*
* @returns 记录统计信息
*/
getStats() {
return {
totalRecords: this.records.size,
records: Array.from(this.records.entries()).map(([key, record]) => ({
key,
count: record.count,
windowStart: new Date(record.windowStart).toISOString(),
lastRequest: new Date(record.lastRequest).toISOString()
}))
};
}
/**
* 清除所有记录
*/
clearAllRecords(): void {
this.records.clear();
}
/**
* 清除指定键的记录
*
* @param key 限制键
*/
clearRecord(key: string): void {
this.records.delete(key);
}
}

View File

@@ -0,0 +1,27 @@
/**
* 安全功能模块导出
*
* 功能概述:
* - 频率限制和防护机制
* - 请求超时控制
* - 维护模式管理
* - 内容类型验证
* - 系统安全中间件
*/
// 模块
export * from './security.module';
// 守卫
export * from './guards/throttle.guard';
// 中间件
export * from './middleware/maintenance.middleware';
export * from './middleware/content-type.middleware';
// 拦截器
export * from './interceptors/timeout.interceptor';
// 装饰器
export * from './decorators/throttle.decorator';
export * from './decorators/timeout.decorator';

View File

@@ -0,0 +1,179 @@
/**
* 超时拦截器
*
* 功能描述:
* - 实现API接口的超时控制逻辑
* - 在超时时自动取消请求并返回错误
* - 记录超时事件的详细日志
*
* 使用场景:
* - 全局超时控制
* - 防止资源泄漏
* - 提升系统稳定性
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
RequestTimeoutException,
Logger
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';
import { TIMEOUT_KEY, TimeoutConfig } from '../decorators/timeout.decorator';
/**
* 超时响应接口
*/
interface TimeoutResponse {
/** 请求是否成功 */
success: boolean;
/** 响应消息 */
message: string;
/** 错误代码 */
error_code: string;
/** 超时信息 */
timeout_info: {
/** 超时时间(毫秒) */
timeout_ms: number;
/** 超时发生时间 */
timestamp: string;
};
}
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
private readonly logger = new Logger(TimeoutInterceptor.name);
constructor(private readonly reflector: Reflector) {}
/**
* 拦截器处理函数
*
* 业务逻辑:
* 1. 获取超时配置
* 2. 应用超时控制
* 3. 处理超时异常
* 4. 记录超时日志
*
* @param context 执行上下文
* @param next 调用处理器
* @returns 可观察对象
*/
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// 1. 获取超时配置
const timeoutConfig = this.getTimeoutConfig(context);
if (!timeoutConfig) {
// 没有配置超时,直接执行
return next.handle();
}
// 2. 获取请求信息用于日志记录
const request = context.switchToHttp().getRequest();
const startTime = Date.now();
// 3. 应用超时控制
return next.handle().pipe(
timeout(timeoutConfig.timeout),
catchError((error) => {
if (error instanceof TimeoutError) {
// 4. 处理超时异常
const duration = Date.now() - startTime;
// 5. 记录超时日志
if (timeoutConfig.logTimeout !== false) {
this.logger.warn('请求超时', {
operation: 'request_timeout',
method: request.method,
url: request.url,
timeout_ms: timeoutConfig.timeout,
actual_duration_ms: duration,
userAgent: request.get('User-Agent'),
ip: request.ip,
timestamp: new Date().toISOString()
});
}
// 6. 构建超时响应
const timeoutResponse: TimeoutResponse = {
success: false,
message: timeoutConfig.message || '请求超时,请稍后重试',
error_code: 'REQUEST_TIMEOUT',
timeout_info: {
timeout_ms: timeoutConfig.timeout,
timestamp: new Date().toISOString()
}
};
// 7. 抛出超时异常
return throwError(() => new RequestTimeoutException(timeoutResponse));
}
// 其他异常直接抛出
return throwError(() => error);
})
);
}
/**
* 获取超时配置
*
* @param context 执行上下文
* @returns 超时配置或null
*/
private getTimeoutConfig(context: ExecutionContext): TimeoutConfig | null {
// 从方法装饰器获取配置
const methodConfig = this.reflector.get<TimeoutConfig>(
TIMEOUT_KEY,
context.getHandler()
);
if (methodConfig) {
return methodConfig;
}
// 从类装饰器获取配置
const classConfig = this.reflector.get<TimeoutConfig>(
TIMEOUT_KEY,
context.getClass()
);
return classConfig || null;
}
/**
* 获取默认超时配置
*
* @returns 默认超时配置
*/
private getDefaultTimeoutConfig(): TimeoutConfig {
return {
timeout: 30000, // 默认30秒
message: '请求超时,请稍后重试',
logTimeout: true
};
}
/**
* 验证超时配置
*
* @param config 超时配置
* @returns 是否有效
*/
private isValidTimeoutConfig(config: TimeoutConfig): boolean {
return (
config &&
typeof config.timeout === 'number' &&
config.timeout > 0 &&
config.timeout <= 600000 // 最大10分钟
);
}
}

View File

@@ -0,0 +1,224 @@
/**
* 内容类型检查中间件
*
* 功能描述:
* - 检查POST/PUT请求的Content-Type头
* - 确保API接口接收正确的数据格式
* - 提供友好的错误提示信息
*
* 使用场景:
* - API接口数据格式验证
* - 防止错误的请求格式
* - 提升API接口的健壮性
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { Logger } from '@nestjs/common';
/**
* 不支持的媒体类型响应接口
*/
interface UnsupportedMediaTypeResponse {
/** 请求是否成功 */
success: boolean;
/** 响应消息 */
message: string;
/** 错误代码 */
error_code: string;
/** 支持的媒体类型 */
supported_types: string[];
/** 接收到的媒体类型 */
received_type?: string;
}
@Injectable()
export class ContentTypeMiddleware implements NestMiddleware {
private readonly logger = new Logger(ContentTypeMiddleware.name);
/**
* 需要检查Content-Type的HTTP方法
*/
private readonly methodsToCheck = ['POST', 'PUT', 'PATCH'];
/**
* 支持的Content-Type列表
*/
private readonly supportedTypes = [
'application/json',
'application/json; charset=utf-8'
];
/**
* 不需要检查Content-Type的路径正则表达式
*/
private readonly excludePaths = [
/^\/api-docs/, // Swagger文档
/^\/health/, // 健康检查
/^\/admin\/logs\/archive/, // 文件下载
/\/upload/, // 文件上传
];
/**
* 中间件处理函数
*
* 业务逻辑:
* 1. 检查是否需要验证Content-Type
* 2. 获取请求的Content-Type头
* 3. 验证Content-Type是否支持
* 4. 记录不支持的请求类型
*
* @param req HTTP请求对象
* @param res HTTP响应对象
* @param next 下一个中间件函数
*/
use(req: Request, res: Response, next: NextFunction) {
// 1. 检查是否需要验证Content-Type
if (!this.shouldCheckContentType(req)) {
next();
return;
}
// 2. 获取请求的Content-Type
const contentType = req.get('Content-Type');
// 3. 检查Content-Type是否存在
if (!contentType) {
this.logger.warn('请求缺少Content-Type头', {
operation: 'content_type_check',
method: req.method,
url: req.url,
userAgent: req.get('User-Agent'),
ip: req.ip,
timestamp: new Date().toISOString()
});
const response: UnsupportedMediaTypeResponse = {
success: false,
message: '请求缺少Content-Type头请设置为application/json',
error_code: 'MISSING_CONTENT_TYPE',
supported_types: this.supportedTypes,
received_type: undefined
};
res.status(415).json(response);
return;
}
// 4. 验证Content-Type是否支持
const normalizedContentType = this.normalizeContentType(contentType);
if (!this.isSupportedContentType(normalizedContentType)) {
this.logger.warn('不支持的Content-Type', {
operation: 'content_type_check',
method: req.method,
url: req.url,
contentType: contentType,
normalizedContentType: normalizedContentType,
userAgent: req.get('User-Agent'),
ip: req.ip,
timestamp: new Date().toISOString()
});
const response: UnsupportedMediaTypeResponse = {
success: false,
message: `不支持的Content-Type: ${contentType}请使用application/json`,
error_code: 'UNSUPPORTED_MEDIA_TYPE',
supported_types: this.supportedTypes,
received_type: contentType
};
res.status(415).json(response);
return;
}
// 5. Content-Type验证通过继续处理
next();
}
/**
* 检查是否需要验证Content-Type
*
* @param req HTTP请求对象
* @returns 是否需要验证
*/
private shouldCheckContentType(req: Request): boolean {
// 1. 检查HTTP方法
if (!this.methodsToCheck.includes(req.method)) {
return false;
}
// 2. 检查是否在排除路径中
const url = req.url;
for (const excludePattern of this.excludePaths) {
if (excludePattern.test(url)) {
return false;
}
}
// 3. 检查Content-Length如果为0则不需要验证
const contentLength = req.get('Content-Length');
if (contentLength === '0') {
return false;
}
return true;
}
/**
* 标准化Content-Type
*
* @param contentType 原始Content-Type
* @returns 标准化后的Content-Type
*/
private normalizeContentType(contentType: string): string {
// 移除空格并转换为小写
return contentType.toLowerCase().trim();
}
/**
* 检查Content-Type是否支持
*
* @param contentType 标准化的Content-Type
* @returns 是否支持
*/
private isSupportedContentType(contentType: string): boolean {
// 检查是否以支持的类型开头
return this.supportedTypes.some(supportedType =>
contentType.startsWith(supportedType.toLowerCase())
);
}
/**
* 获取支持的Content-Type列表
*
* @returns 支持的类型列表
*/
getSupportedTypes(): string[] {
return [...this.supportedTypes];
}
/**
* 添加支持的Content-Type
*
* @param contentType 要添加的Content-Type
*/
addSupportedType(contentType: string): void {
if (!this.supportedTypes.includes(contentType)) {
this.supportedTypes.push(contentType);
}
}
/**
* 添加排除路径
*
* @param pattern 路径正则表达式
*/
addExcludePath(pattern: RegExp): void {
this.excludePaths.push(pattern);
}
}

View File

@@ -0,0 +1,137 @@
/**
* 维护模式中间件
*
* 功能描述:
* - 检查系统是否处于维护模式
* - 在维护期间阻止用户访问API
* - 提供维护状态和预计恢复时间信息
*
* 使用场景:
* - 系统升级维护
* - 数据库迁移
* - 紧急故障修复
* - 定期维护窗口
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { ConfigService } from '@nestjs/config';
import { Logger } from '@nestjs/common';
/**
* 维护模式响应接口
*/
interface MaintenanceResponse {
/** 请求是否成功 */
success: boolean;
/** 响应消息 */
message: string;
/** 错误代码 */
error_code: string;
/** 维护信息 */
maintenance_info?: {
/** 维护开始时间 */
start_time: string;
/** 预计结束时间 */
estimated_end_time?: string;
/** 重试间隔(秒) */
retry_after: number;
/** 维护原因 */
reason?: string;
};
}
@Injectable()
export class MaintenanceMiddleware implements NestMiddleware {
private readonly logger = new Logger(MaintenanceMiddleware.name);
constructor(private readonly configService: ConfigService) {}
/**
* 中间件处理函数
*
* 业务逻辑:
* 1. 检查维护模式环境变量
* 2. 如果处于维护模式返回503状态码
* 3. 提供维护信息和重试建议
* 4. 记录维护期间的访问尝试
*
* @param req HTTP请求对象
* @param res HTTP响应对象
* @param next 下一个中间件函数
*/
use(req: Request, res: Response, next: NextFunction) {
// 1. 检查维护模式状态
const isMaintenanceMode = this.configService.get<string>('MAINTENANCE_MODE') === 'true';
if (!isMaintenanceMode) {
// 非维护模式,继续处理请求
next();
return;
}
// 2. 记录维护期间的访问尝试
this.logger.warn('维护模式:拒绝访问请求', {
operation: 'maintenance_check',
method: req.method,
url: req.url,
userAgent: req.get('User-Agent'),
ip: req.ip,
timestamp: new Date().toISOString()
});
// 3. 获取维护配置信息
const maintenanceStartTime = this.configService.get<string>('MAINTENANCE_START_TIME') || new Date().toISOString();
const maintenanceEndTime = this.configService.get<string>('MAINTENANCE_END_TIME');
const maintenanceReason = this.configService.get<string>('MAINTENANCE_REASON') || '系统维护升级';
const retryAfter = this.configService.get<number>('MAINTENANCE_RETRY_AFTER') || 1800; // 默认30分钟
// 4. 构建维护模式响应
const maintenanceResponse: MaintenanceResponse = {
success: false,
message: '系统正在维护中,请稍后再试',
error_code: 'SERVICE_UNAVAILABLE',
maintenance_info: {
start_time: maintenanceStartTime,
estimated_end_time: maintenanceEndTime,
retry_after: retryAfter,
reason: maintenanceReason
}
};
// 5. 设置HTTP响应头
res.setHeader('Retry-After', retryAfter.toString());
res.setHeader('Content-Type', 'application/json');
// 6. 返回503服务不可用状态
res.status(503).json(maintenanceResponse);
}
/**
* 检查维护模式是否启用
*
* @returns 是否处于维护模式
*/
isMaintenanceEnabled(): boolean {
return this.configService.get<string>('MAINTENANCE_MODE') === 'true';
}
/**
* 获取维护信息
*
* @returns 维护配置信息
*/
getMaintenanceInfo() {
return {
enabled: this.isMaintenanceEnabled(),
startTime: this.configService.get<string>('MAINTENANCE_START_TIME'),
endTime: this.configService.get<string>('MAINTENANCE_END_TIME'),
reason: this.configService.get<string>('MAINTENANCE_REASON'),
retryAfter: this.configService.get<number>('MAINTENANCE_RETRY_AFTER')
};
}
}

View File

@@ -0,0 +1,37 @@
/**
* 安全功能模块
*
* 功能描述:
* - 整合所有安全相关功能
* - 频率限制和请求超时控制
* - 维护模式和内容类型验证
* - 系统安全防护机制
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { Module } from '@nestjs/common';
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { ThrottleGuard } from './guards/throttle.guard';
import { TimeoutInterceptor } from './interceptors/timeout.interceptor';
@Module({
providers: [
ThrottleGuard,
TimeoutInterceptor,
// 全局频率限制守卫
{
provide: APP_GUARD,
useClass: ThrottleGuard,
},
// 全局超时拦截器
{
provide: APP_INTERCEPTOR,
useClass: TimeoutInterceptor,
},
],
exports: [ThrottleGuard, TimeoutInterceptor],
})
export class SecurityModule {}

View File

@@ -0,0 +1,17 @@
/**
* 共享 DTO 统一导出
*
* 功能描述:
* - 导出所有共享的 DTO 类
* - 提供统一的导入入口
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
// 应用状态相关
export * from './app-status.dto';
// 错误响应相关
export * from './error-response.dto';

View File

@@ -0,0 +1,14 @@
/**
* 共享模块统一导出
*
* 功能描述:
* - 导出所有共享的组件和类型
* - 提供统一的导入入口
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
// DTO
export * from './dto';

View File

@@ -0,0 +1,162 @@
/**
* 用户状态管理控制器
*
* 功能描述:
* - 管理员管理用户账户状态
* - 支持批量状态操作
* - 提供状态变更审计日志
*
* API端点
* - PUT /admin/users/:id/status - 修改用户状态
* - POST /admin/users/batch-status - 批量修改用户状态
* - GET /admin/users/status-stats - 获取用户状态统计
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Put, Post, UseGuards, ValidationPipe, UsePipes, Logger } from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { AdminGuard } from '../../admin/guards/admin.guard';
import { UserManagementService } from '../services/user-management.service';
import { Throttle, ThrottlePresets } from '../../security/decorators/throttle.decorator';
import { Timeout, TimeoutPresets } from '../../security/decorators/timeout.decorator';
import { UserStatusDto, BatchUserStatusDto } from '../dto/user-status.dto';
import { UserStatusResponseDto, BatchUserStatusResponseDto, UserStatusStatsResponseDto } from '../dto/user-status-response.dto';
@ApiTags('user-management')
@Controller('admin/users')
export class UserStatusController {
private readonly logger = new Logger(UserStatusController.name);
constructor(private readonly userManagementService: UserManagementService) {}
/**
* 修改用户状态
*
* @param id 用户ID
* @param userStatusDto 状态修改数据
* @returns 修改结果
*/
@ApiBearerAuth('JWT-auth')
@ApiOperation({
summary: '修改用户状态',
description: '管理员修改指定用户的账户状态,支持激活、锁定、禁用等操作'
})
@ApiParam({ name: 'id', description: '用户ID' })
@ApiBody({ type: UserStatusDto })
@ApiResponse({
status: 200,
description: '状态修改成功',
type: UserStatusResponseDto
})
@ApiResponse({
status: 403,
description: '权限不足'
})
@ApiResponse({
status: 404,
description: '用户不存在'
})
@ApiResponse({
status: 429,
description: '操作过于频繁'
})
@UseGuards(AdminGuard)
@Throttle(ThrottlePresets.ADMIN_OPERATION)
@Timeout(TimeoutPresets.NORMAL)
@Put(':id/status')
@HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true }))
async updateUserStatus(
@Param('id') id: string,
@Body() userStatusDto: UserStatusDto
): Promise<UserStatusResponseDto> {
this.logger.log('管理员修改用户状态', {
operation: 'update_user_status',
userId: id,
newStatus: userStatusDto.status,
reason: userStatusDto.reason,
timestamp: new Date().toISOString()
});
return await this.userManagementService.updateUserStatus(BigInt(id), userStatusDto);
}
/**
* 批量修改用户状态
*
* @param batchUserStatusDto 批量状态修改数据
* @returns 批量修改结果
*/
@ApiBearerAuth('JWT-auth')
@ApiOperation({
summary: '批量修改用户状态',
description: '管理员批量修改多个用户的账户状态'
})
@ApiBody({ type: BatchUserStatusDto })
@ApiResponse({
status: 200,
description: '批量修改成功',
type: BatchUserStatusResponseDto
})
@ApiResponse({
status: 403,
description: '权限不足'
})
@ApiResponse({
status: 429,
description: '操作过于频繁'
})
@UseGuards(AdminGuard)
@Throttle(ThrottlePresets.ADMIN_OPERATION)
@Timeout(TimeoutPresets.SLOW)
@Post('batch-status')
@HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true }))
async batchUpdateUserStatus(
@Body() batchUserStatusDto: BatchUserStatusDto
): Promise<BatchUserStatusResponseDto> {
this.logger.log('管理员批量修改用户状态', {
operation: 'batch_update_user_status',
userCount: batchUserStatusDto.user_ids.length,
newStatus: batchUserStatusDto.status,
reason: batchUserStatusDto.reason,
timestamp: new Date().toISOString()
});
return await this.userManagementService.batchUpdateUserStatus(batchUserStatusDto);
}
/**
* 获取用户状态统计
*
* @returns 状态统计信息
*/
@ApiBearerAuth('JWT-auth')
@ApiOperation({
summary: '获取用户状态统计',
description: '获取各种用户状态的数量统计信息'
})
@ApiResponse({
status: 200,
description: '获取成功',
type: UserStatusStatsResponseDto
})
@ApiResponse({
status: 403,
description: '权限不足'
})
@UseGuards(AdminGuard)
@Timeout(TimeoutPresets.DATABASE_QUERY)
@Get('status-stats')
async getUserStatusStats(): Promise<UserStatusStatsResponseDto> {
this.logger.log('管理员获取用户状态统计', {
operation: 'get_user_status_stats',
timestamp: new Date().toISOString()
});
return await this.userManagementService.getUserStatusStats();
}
}

View File

@@ -0,0 +1,294 @@
/**
* 用户状态管理响应 DTO
*
* 功能描述:
* - 定义用户状态管理相关的响应数据结构
* - 提供Swagger文档生成支持
* - 确保状态管理API响应的数据格式一致性
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { ApiProperty } from '@nestjs/swagger';
import { UserStatus } from '../enums/user-status.enum';
/**
* 用户状态信息DTO
*/
export class UserStatusInfoDto {
@ApiProperty({
description: '用户ID',
example: '1'
})
id: string;
@ApiProperty({
description: '用户名',
example: 'testuser'
})
username: string;
@ApiProperty({
description: '用户昵称',
example: '测试用户'
})
nickname: string;
@ApiProperty({
description: '用户状态',
enum: UserStatus,
example: UserStatus.ACTIVE
})
status: UserStatus;
@ApiProperty({
description: '状态描述',
example: '正常'
})
status_description: string;
@ApiProperty({
description: '状态修改时间',
example: '2025-12-24T10:00:00.000Z'
})
updated_at: Date;
}
/**
* 用户状态修改响应数据DTO
*/
export class UserStatusDataDto {
@ApiProperty({
description: '用户信息',
type: UserStatusInfoDto
})
user: UserStatusInfoDto;
@ApiProperty({
description: '修改原因',
example: '用户违反社区规定',
required: false
})
reason?: string;
}
/**
* 用户状态修改响应DTO
*/
export class UserStatusResponseDto {
@ApiProperty({
description: '请求是否成功',
example: true
})
success: boolean;
@ApiProperty({
description: '响应数据',
type: UserStatusDataDto,
required: false
})
data?: UserStatusDataDto;
@ApiProperty({
description: '响应消息',
example: '用户状态修改成功'
})
message: string;
@ApiProperty({
description: '错误代码',
example: 'USER_STATUS_UPDATE_FAILED',
required: false
})
error_code?: string;
}
/**
* 批量操作结果DTO
*/
export class BatchOperationResultDto {
@ApiProperty({
description: '成功处理的用户列表',
type: [UserStatusInfoDto]
})
success_users: UserStatusInfoDto[];
@ApiProperty({
description: '处理失败的用户列表',
type: [Object],
example: [
{
user_id: '999',
error: '用户不存在'
}
]
})
failed_users: Array<{
user_id: string;
error: string;
}>;
@ApiProperty({
description: '成功处理数量',
example: 5
})
success_count: number;
@ApiProperty({
description: '失败处理数量',
example: 1
})
failed_count: number;
@ApiProperty({
description: '总处理数量',
example: 6
})
total_count: number;
}
/**
* 批量用户状态修改响应数据DTO
*/
export class BatchUserStatusDataDto {
@ApiProperty({
description: '批量操作结果',
type: BatchOperationResultDto
})
result: BatchOperationResultDto;
@ApiProperty({
description: '修改原因',
example: '批量处理违规用户',
required: false
})
reason?: string;
}
/**
* 批量用户状态修改响应DTO
*/
export class BatchUserStatusResponseDto {
@ApiProperty({
description: '请求是否成功',
example: true
})
success: boolean;
@ApiProperty({
description: '响应数据',
type: BatchUserStatusDataDto,
required: false
})
data?: BatchUserStatusDataDto;
@ApiProperty({
description: '响应消息',
example: '批量用户状态修改完成'
})
message: string;
@ApiProperty({
description: '错误代码',
example: 'BATCH_USER_STATUS_UPDATE_FAILED',
required: false
})
error_code?: string;
}
/**
* 用户状态统计DTO
*/
export class UserStatusStatsDto {
@ApiProperty({
description: '正常用户数量',
example: 1250
})
active: number;
@ApiProperty({
description: '未激活用户数量',
example: 45
})
inactive: number;
@ApiProperty({
description: '锁定用户数量',
example: 12
})
locked: number;
@ApiProperty({
description: '禁用用户数量',
example: 8
})
banned: number;
@ApiProperty({
description: '已删除用户数量',
example: 3
})
deleted: number;
@ApiProperty({
description: '待审核用户数量',
example: 15
})
pending: number;
@ApiProperty({
description: '总用户数量',
example: 1333
})
total: number;
}
/**
* 用户状态统计响应数据DTO
*/
export class UserStatusStatsDataDto {
@ApiProperty({
description: '用户状态统计',
type: UserStatusStatsDto
})
stats: UserStatusStatsDto;
@ApiProperty({
description: '统计时间',
example: '2025-12-24T10:00:00.000Z'
})
timestamp: string;
}
/**
* 用户状态统计响应DTO
*/
export class UserStatusStatsResponseDto {
@ApiProperty({
description: '请求是否成功',
example: true
})
success: boolean;
@ApiProperty({
description: '响应数据',
type: UserStatusStatsDataDto,
required: false
})
data?: UserStatusStatsDataDto;
@ApiProperty({
description: '响应消息',
example: '用户状态统计获取成功'
})
message: string;
@ApiProperty({
description: '错误代码',
example: 'USER_STATUS_STATS_FAILED',
required: false
})
error_code?: string;
}

View File

@@ -0,0 +1,95 @@
/**
* 用户状态管理 DTO
*
* 功能描述:
* - 定义用户状态管理相关的请求数据结构
* - 提供数据验证规则和错误提示
* - 确保状态管理操作的数据格式一致性
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { IsString, IsNotEmpty, IsEnum, IsOptional, IsArray, ArrayMinSize, ArrayMaxSize } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { UserStatus } from '../enums/user-status.enum';
/**
* 用户状态修改请求DTO
*/
export class UserStatusDto {
/**
* 新的用户状态
*/
@ApiProperty({
description: '用户状态',
enum: UserStatus,
example: UserStatus.ACTIVE,
enumName: 'UserStatus'
})
@IsEnum(UserStatus, { message: '用户状态必须是有效的枚举值' })
@IsNotEmpty({ message: '用户状态不能为空' })
status: UserStatus;
/**
* 状态修改原因
*/
@ApiProperty({
description: '状态修改原因(可选)',
example: '用户违反社区规定',
required: false,
maxLength: 200
})
@IsOptional()
@IsString({ message: '修改原因必须是字符串' })
reason?: string;
}
/**
* 批量用户状态修改请求DTO
*/
export class BatchUserStatusDto {
/**
* 用户ID列表
*/
@ApiProperty({
description: '用户ID列表',
example: ['1', '2', '3'],
type: [String],
minItems: 1,
maxItems: 100
})
@IsArray({ message: '用户ID列表必须是数组' })
@ArrayMinSize(1, { message: '至少需要选择一个用户' })
@ArrayMaxSize(100, { message: '一次最多只能操作100个用户' })
@IsString({ each: true, message: '用户ID必须是字符串' })
@IsNotEmpty({ each: true, message: '用户ID不能为空' })
user_ids: string[];
/**
* 新的用户状态
*/
@ApiProperty({
description: '用户状态',
enum: UserStatus,
example: UserStatus.LOCKED,
enumName: 'UserStatus'
})
@IsEnum(UserStatus, { message: '用户状态必须是有效的枚举值' })
@IsNotEmpty({ message: '用户状态不能为空' })
status: UserStatus;
/**
* 状态修改原因
*/
@ApiProperty({
description: '批量修改原因(可选)',
example: '批量处理违规用户',
required: false,
maxLength: 200
})
@IsOptional()
@IsString({ message: '修改原因必须是字符串' })
reason?: string;
}

View File

@@ -0,0 +1,100 @@
/**
* 用户状态枚举
*
* 功能描述:
* - 定义用户账户的各种状态
* - 提供状态检查和描述功能
* - 支持用户生命周期管理
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
/**
* 用户状态枚举
*
* 状态说明:
* - active: 正常状态,可以正常使用所有功能
* - inactive: 未激活状态,通常是新注册用户需要邮箱验证
* - locked: 临时锁定状态,可以解锁恢复
* - banned: 永久禁用状态,需要管理员处理
* - deleted: 软删除状态,数据保留但不可使用
* - pending: 待审核状态,需要管理员审核后激活
*/
export enum UserStatus {
ACTIVE = 'active', // 正常状态
INACTIVE = 'inactive', // 未激活状态
LOCKED = 'locked', // 锁定状态
BANNED = 'banned', // 禁用状态
DELETED = 'deleted', // 删除状态
PENDING = 'pending' // 待审核状态
}
/**
* 获取用户状态的中文描述
*
* @param status 用户状态
* @returns 状态描述
*/
export function getUserStatusDescription(status: UserStatus): string {
const descriptions = {
[UserStatus.ACTIVE]: '正常',
[UserStatus.INACTIVE]: '未激活',
[UserStatus.LOCKED]: '已锁定',
[UserStatus.BANNED]: '已禁用',
[UserStatus.DELETED]: '已删除',
[UserStatus.PENDING]: '待审核'
};
return descriptions[status] || '未知状态';
}
/**
* 检查用户是否可以登录
*
* @param status 用户状态
* @returns 是否可以登录
*/
export function canUserLogin(status: UserStatus): boolean {
// 只有正常状态的用户可以登录
return status === UserStatus.ACTIVE;
}
/**
* 获取用户状态对应的错误消息
*
* @param status 用户状态
* @returns 错误消息
*/
export function getUserStatusErrorMessage(status: UserStatus): string {
const errorMessages = {
[UserStatus.ACTIVE]: '', // 正常状态无错误
[UserStatus.INACTIVE]: '账户未激活,请先验证邮箱',
[UserStatus.LOCKED]: '账户已被锁定,请联系管理员',
[UserStatus.BANNED]: '账户已被禁用,请联系管理员',
[UserStatus.DELETED]: '账户不存在',
[UserStatus.PENDING]: '账户待审核,请等待管理员审核'
};
return errorMessages[status] || '账户状态异常';
}
/**
* 获取所有可用的用户状态
*
* @returns 用户状态数组
*/
export function getAllUserStatuses(): UserStatus[] {
return Object.values(UserStatus);
}
/**
* 检查状态值是否有效
*
* @param status 状态值
* @returns 是否为有效状态
*/
export function isValidUserStatus(status: string): status is UserStatus {
return Object.values(UserStatus).includes(status as UserStatus);
}

View File

@@ -0,0 +1,22 @@
/**
* 用户管理业务模块导出
*
* 功能概述:
* - 用户状态管理(激活、锁定、禁用等)
* - 批量用户操作
* - 用户状态统计和分析
* - 状态变更审计和历史记录
*/
// 模块
export * from './user-mgmt.module';
// 控制器
export * from './controllers/user-status.controller';
// 服务
export * from './services/user-management.service';
// DTO
export * from './dto/user-status.dto';
export * from './dto/user-status-response.dto';

View File

@@ -0,0 +1,199 @@
/**
* 用户管理业务服务
*
* 功能描述:
* - 用户状态管理业务逻辑
* - 批量用户操作
* - 用户状态统计
* - 状态变更审计
*
* 职责分工:
* - 专注于用户管理相关的业务逻辑
* - 调用 AdminService 的底层方法
* - 提供用户管理特定的业务规则
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { Injectable, Logger } from '@nestjs/common';
import { AdminService } from '../../admin/admin.service';
import { UserStatusDto, BatchUserStatusDto } from '../dto/user-status.dto';
import {
UserStatusResponseDto,
BatchUserStatusResponseDto,
UserStatusStatsResponseDto
} from '../dto/user-status-response.dto';
@Injectable()
export class UserManagementService {
private readonly logger = new Logger(UserManagementService.name);
constructor(private readonly adminService: AdminService) {}
/**
* 修改用户状态
*
* 业务逻辑:
* 1. 验证状态变更的业务规则
* 2. 记录状态变更原因
* 3. 调用底层服务执行变更
* 4. 记录业务审计日志
*
* @param userId 用户ID
* @param userStatusDto 状态修改数据
* @returns 修改结果
*/
async updateUserStatus(userId: bigint, userStatusDto: UserStatusDto): Promise<UserStatusResponseDto> {
this.logger.log('用户管理:开始修改用户状态', {
operation: 'user_mgmt_update_status',
userId: userId.toString(),
newStatus: userStatusDto.status,
reason: userStatusDto.reason,
timestamp: new Date().toISOString()
});
// 调用底层管理员服务
const result = await this.adminService.updateUserStatus(userId, userStatusDto);
// 记录业务层日志
if (result.success) {
this.logger.log('用户管理:用户状态修改成功', {
operation: 'user_mgmt_update_status_success',
userId: userId.toString(),
newStatus: userStatusDto.status,
timestamp: new Date().toISOString()
});
}
return result;
}
/**
* 批量修改用户状态
*
* 业务逻辑:
* 1. 验证批量操作的业务规则
* 2. 分批处理大量用户
* 3. 提供批量操作的进度反馈
* 4. 记录批量操作审计
*
* @param batchUserStatusDto 批量状态修改数据
* @returns 批量修改结果
*/
async batchUpdateUserStatus(batchUserStatusDto: BatchUserStatusDto): Promise<BatchUserStatusResponseDto> {
this.logger.log('用户管理:开始批量修改用户状态', {
operation: 'user_mgmt_batch_update_status',
userCount: batchUserStatusDto.user_ids.length,
newStatus: batchUserStatusDto.status,
reason: batchUserStatusDto.reason,
timestamp: new Date().toISOString()
});
// 业务规则:限制批量操作的数量
if (batchUserStatusDto.user_ids.length > 100) {
this.logger.warn('用户管理:批量操作数量超限', {
operation: 'user_mgmt_batch_update_limit_exceeded',
requestCount: batchUserStatusDto.user_ids.length,
maxAllowed: 100
});
return {
success: false,
message: '批量操作数量不能超过100个用户',
error_code: 'BATCH_OPERATION_LIMIT_EXCEEDED'
};
}
// 调用底层管理员服务
const result = await this.adminService.batchUpdateUserStatus(batchUserStatusDto);
// 记录业务层日志
if (result.success) {
this.logger.log('用户管理:批量用户状态修改完成', {
operation: 'user_mgmt_batch_update_status_success',
successCount: result.data?.result.success_count || 0,
failedCount: result.data?.result.failed_count || 0,
timestamp: new Date().toISOString()
});
}
return result;
}
/**
* 获取用户状态统计
*
* 业务逻辑:
* 1. 获取基础统计数据
* 2. 计算业务相关的指标
* 3. 提供状态分布分析
* 4. 缓存统计结果
*
* @returns 状态统计信息
*/
async getUserStatusStats(): Promise<UserStatusStatsResponseDto> {
this.logger.log('用户管理:获取用户状态统计', {
operation: 'user_mgmt_get_status_stats',
timestamp: new Date().toISOString()
});
// 调用底层管理员服务
const result = await this.adminService.getUserStatusStats();
// 业务层可以在这里添加额外的统计分析
if (result.success && result.data) {
const stats = result.data.stats;
// 计算业务指标
const activeRate = stats.total > 0 ? (stats.active / stats.total * 100).toFixed(2) : '0';
const problemUserCount = stats.locked + stats.banned + stats.deleted;
this.logger.log('用户管理:用户状态统计分析', {
operation: 'user_mgmt_status_analysis',
totalUsers: stats.total,
activeUsers: stats.active,
activeRate: `${activeRate}%`,
problemUsers: problemUserCount,
timestamp: new Date().toISOString()
});
}
return result;
}
/**
* 获取用户状态变更历史
*
* 业务功能:
* - 查询指定用户的状态变更记录
* - 提供状态变更的审计追踪
* - 支持时间范围查询
*
* @param userId 用户ID
* @param limit 返回数量限制
* @returns 状态变更历史
*/
async getUserStatusHistory(userId: bigint, limit: number = 10) {
this.logger.log('用户管理:获取用户状态变更历史', {
operation: 'user_mgmt_get_status_history',
userId: userId.toString(),
limit,
timestamp: new Date().toISOString()
});
// TODO: 实现状态变更历史查询
// 这里可以调用专门的审计日志服务
return {
success: true,
data: {
user_id: userId.toString(),
history: [] as any[],
total_count: 0
},
message: '状态变更历史获取成功(功能待实现)'
};
}
}

View File

@@ -0,0 +1,30 @@
/**
* 用户管理业务模块
*
* 功能描述:
* - 整合用户状态管理相关的所有组件
* - 提供用户生命周期管理功能
* - 支持批量操作和状态统计
*
* 依赖关系:
* - 依赖 AdminModule 提供底层管理功能
* - 依赖 Core 模块提供基础设施
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { Module } from '@nestjs/common';
import { UserStatusController } from './controllers/user-status.controller';
import { UserManagementService } from './services/user-management.service';
import { AdminModule } from '../admin/admin.module';
import { AdminCoreModule } from '../../core/admin_core/admin_core.module';
@Module({
imports: [AdminModule, AdminCoreModule],
controllers: [UserStatusController],
providers: [UserManagementService],
exports: [UserManagementService],
})
export class UserMgmtModule {}

View File

@@ -24,8 +24,10 @@ import {
Max,
IsOptional,
Length,
IsNotEmpty
IsNotEmpty,
IsEnum
} from 'class-validator';
import { UserStatus } from '../../../business/user-mgmt/enums/user-status.enum';
/**
* 创建用户数据传输对象
@@ -232,4 +234,30 @@ export class CreateUserDto {
*/
@IsOptional()
email_verified?: boolean = false;
/**
* 用户状态
*
* 业务规则:
* - 可选字段默认为active正常状态
* - 控制用户账户的可用性和权限
* - 支持多种状态:正常、未激活、锁定、禁用等
* - 影响用户登录和API访问权限
*
* 验证规则:
* - 可选字段验证
* - 枚举类型验证
* - 默认值active正常状态
*
* 状态说明:
* - active: 正常状态,可以正常使用
* - inactive: 未激活,需要邮箱验证
* - locked: 已锁定,临时禁用
* - banned: 已禁用,管理员操作
* - deleted: 已删除,软删除状态
* - pending: 待审核,需要管理员审核
*/
@IsOptional()
@IsEnum(UserStatus, { message: '用户状态必须是有效的枚举值' })
status?: UserStatus = UserStatus.ACTIVE;
}

View File

@@ -20,6 +20,7 @@
*/
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { UserStatus } from '../../../business/user-mgmt/enums/user-status.enum';
/**
* 用户实体类
@@ -337,6 +338,44 @@ export class Users {
})
role: number;
/**
* 用户状态
*
* 数据库设计:
* - 类型VARCHAR(20),存储状态枚举值
* - 约束:非空、默认值'active'
* - 索引:用于状态查询和统计
*
* 业务规则:
* - 控制用户账户的可用性和权限
* - active正常状态可以正常使用
* - inactive未激活需要邮箱验证
* - locked已锁定临时禁用
* - banned已禁用管理员操作
* - deleted已删除软删除状态
* - pending待审核需要管理员审核
*
* 安全控制:
* - 登录时检查状态权限
* - API访问时验证状态
* - 状态变更记录审计日志
* - 支持批量状态管理
*
* 应用场景:
* - 账户安全管理
* - 用户生命周期控制
* - 违规用户处理
* - 系统维护和升级
*/
@Column({
type: 'varchar',
length: 20,
nullable: true,
default: UserStatus.ACTIVE,
comment: '用户状态active-正常inactive-未激活locked-锁定banned-禁用deleted-删除pending-待审核'
})
status?: UserStatus;
/**
* 创建时间
*

View File

@@ -16,6 +16,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository, FindOptionsWhere } from 'typeorm';
import { Users } from './users.entity';
import { CreateUserDto } from './users.dto';
import { UserStatus } from '../../../business/user-mgmt/enums/user-status.enum';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@@ -97,6 +98,7 @@ export class UsersService {
user.avatar_url = createUserDto.avatar_url || null;
user.role = createUserDto.role || 1;
user.email_verified = createUserDto.email_verified || false;
user.status = createUserDto.status || UserStatus.ACTIVE;
// 保存到数据库
return await this.usersRepository.save(user);

View File

@@ -24,6 +24,7 @@
import { Injectable, ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
import { Users } from './users.entity';
import { CreateUserDto } from './users.dto';
import { UserStatus } from '../../../business/user-mgmt/enums/user-status.enum';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@@ -98,6 +99,7 @@ export class UsersMemoryService {
user.avatar_url = createUserDto.avatar_url || null;
user.role = createUserDto.role || 1;
user.email_verified = createUserDto.email_verified || false;
user.status = createUserDto.status || UserStatus.ACTIVE;
user.created_at = new Date();
user.updated_at = new Date();

View File

@@ -7,7 +7,8 @@ import { LoginCoreService } from './login_core.service';
import { UsersService } from '../db/users/users.service';
import { EmailService } from '../utils/email/email.service';
import { VerificationService } from '../utils/verification/verification.service';
import { UnauthorizedException, ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
import { UnauthorizedException, ConflictException, NotFoundException, BadRequestException, ForbiddenException } from '@nestjs/common';
import { UserStatus } from '../../business/user-mgmt/enums/user-status.enum';
describe('LoginCoreService', () => {
let service: LoginCoreService;
@@ -26,6 +27,7 @@ describe('LoginCoreService', () => {
avatar_url: null as string | null,
role: 1,
email_verified: false,
status: UserStatus.ACTIVE, // 使用正确的枚举类型
created_at: new Date(),
updated_at: new Date()
};
@@ -105,7 +107,9 @@ describe('LoginCoreService', () => {
});
it('should throw UnauthorizedException for wrong password', async () => {
usersService.findByUsername.mockResolvedValue(mockUser);
// 创建一个正常状态的用户来测试密码验证
const activeUser = { ...mockUser, status: UserStatus.ACTIVE };
usersService.findByUsername.mockResolvedValue(activeUser);
jest.spyOn(service as any, 'verifyPassword').mockResolvedValue(false);
await expect(service.login({
@@ -113,6 +117,17 @@ describe('LoginCoreService', () => {
password: 'wrongpassword'
})).rejects.toThrow(UnauthorizedException);
});
it('should throw ForbiddenException for inactive user', async () => {
// 测试非活跃用户状态
const inactiveUser = { ...mockUser, status: UserStatus.INACTIVE };
usersService.findByUsername.mockResolvedValue(inactiveUser);
await expect(service.login({
identifier: 'testuser',
password: 'password123'
})).rejects.toThrow(ForbiddenException);
});
});
describe('register', () => {

View File

@@ -16,11 +16,12 @@
* @since 2025-12-17
*/
import { Injectable, UnauthorizedException, ConflictException, NotFoundException, BadRequestException, Inject } from '@nestjs/common';
import { Injectable, UnauthorizedException, ConflictException, NotFoundException, BadRequestException, ForbiddenException, Inject } from '@nestjs/common';
import { Users } from '../db/users/users.entity';
import { UsersService } from '../db/users/users.service';
import { EmailService, EmailSendResult } from '../utils/email/email.service';
import { VerificationService, VerificationCodeType } from '../utils/verification/verification.service';
import { UserStatus, canUserLogin, getUserStatusErrorMessage } from '../../business/user-mgmt/enums/user-status.enum';
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';
@@ -140,6 +141,11 @@ export class LoginCoreService {
throw new UnauthorizedException('用户名、邮箱或手机号不存在');
}
// 检查用户状态
if (!canUserLogin(user.status)) {
throw new ForbiddenException(getUserStatusErrorMessage(user.status));
}
// 检查是否为OAuth用户没有密码
if (!user.password_hash) {
throw new UnauthorizedException('该账户使用第三方登录,请使用对应的登录方式');
@@ -196,6 +202,7 @@ export class LoginCoreService {
email,
phone,
role: 1, // 默认普通用户
status: UserStatus.ACTIVE, // 默认激活状态
email_verified: email ? true : false // 如果提供了邮箱且验证码验证通过,则标记为已验证
});
@@ -257,6 +264,7 @@ export class LoginCoreService {
github_id,
avatar_url,
role: 1, // 默认普通用户
status: UserStatus.ACTIVE, // GitHub用户直接激活
email_verified: email ? true : false // GitHub邮箱直接验证
});

View File

@@ -1,164 +0,0 @@
/**
* 管理员相关响应 DTO
*
* 功能描述:
* - 为 Swagger 提供明确的响应结构定义
* - 与 AdminService 返回结构保持一致
*
* @author jianuo
* @version 1.0.0
* @since 2025-12-19
*/
import { ApiProperty } from '@nestjs/swagger';
class AdminInfoDto {
@ApiProperty({ example: '1' })
id: string;
@ApiProperty({ example: 'admin' })
username: string;
@ApiProperty({ example: '管理员' })
nickname: string;
@ApiProperty({ example: 9 })
role: number;
}
class AdminLoginDataDto {
@ApiProperty({ type: AdminInfoDto })
admin: AdminInfoDto;
@ApiProperty({ description: '管理员访问Token用于Authorization Bearer' })
access_token: string;
@ApiProperty({ description: '过期时间戳(毫秒)', example: 1766102400000 })
expires_at: number;
}
export class AdminLoginResponseDto {
@ApiProperty({ example: true })
success: boolean;
@ApiProperty({ type: AdminLoginDataDto, required: false })
data?: AdminLoginDataDto;
@ApiProperty({ example: '管理员登录成功' })
message: string;
@ApiProperty({ required: false, example: 'ADMIN_LOGIN_FAILED' })
error_code?: string;
}
class AdminUserDto {
@ApiProperty({ example: '1' })
id: string;
@ApiProperty({ example: 'user1' })
username: string;
@ApiProperty({ example: '小明' })
nickname: string;
@ApiProperty({ required: false, example: 'user1@example.com', nullable: true })
email?: string;
@ApiProperty({ example: false })
email_verified: boolean;
@ApiProperty({ required: false, example: '+8613800138000', nullable: true })
phone?: string;
@ApiProperty({ required: false, example: 'https://example.com/avatar.png', nullable: true })
avatar_url?: string;
@ApiProperty({ example: 1 })
role: number;
@ApiProperty({ example: '2025-12-19T00:00:00.000Z' })
created_at: Date;
@ApiProperty({ example: '2025-12-19T00:00:00.000Z' })
updated_at: Date;
}
class AdminUsersDataDto {
@ApiProperty({ type: [AdminUserDto] })
users: AdminUserDto[];
@ApiProperty({ example: 100 })
limit: number;
@ApiProperty({ example: 0 })
offset: number;
}
export class AdminUsersResponseDto {
@ApiProperty({ example: true })
success: boolean;
@ApiProperty({ type: AdminUsersDataDto, required: false })
data?: AdminUsersDataDto;
@ApiProperty({ example: '用户列表获取成功' })
message: string;
@ApiProperty({ required: false, example: 'ADMIN_USERS_FAILED' })
error_code?: string;
}
class AdminUserDataDto {
@ApiProperty({ type: AdminUserDto })
user: AdminUserDto;
}
export class AdminUserResponseDto {
@ApiProperty({ example: true })
success: boolean;
@ApiProperty({ type: AdminUserDataDto, required: false })
data?: AdminUserDataDto;
@ApiProperty({ example: '用户信息获取成功' })
message: string;
}
export class AdminCommonResponseDto {
@ApiProperty({ example: true })
success: boolean;
@ApiProperty({ required: false })
data?: any;
@ApiProperty({ example: '密码重置成功' })
message: string;
@ApiProperty({ required: false, example: 'ADMIN_OPERATION_FAILED' })
error_code?: string;
}
class AdminRuntimeLogsDataDto {
@ApiProperty({ example: 'dev.log' })
file: string;
@ApiProperty({ description: '日志文件最后更新时间ISO', example: '2025-12-19T19:10:15.000Z' })
updated_at: string;
@ApiProperty({ type: [String], description: '日志行(按时间顺序,越靠后越新)' })
lines: string[];
}
export class AdminRuntimeLogsResponseDto {
@ApiProperty({ example: true })
success: boolean;
@ApiProperty({ type: AdminRuntimeLogsDataDto, required: false })
data?: AdminRuntimeLogsDataDto;
@ApiProperty({ example: '运行日志获取成功' })
message: string;
@ApiProperty({ required: false, example: 'ADMIN_OPERATION_FAILED' })
error_code?: string;
}

View File

View File