refactor(auth): 重构认证模块架构 - 将Gateway层组件从Business层分离
范围:src/gateway/auth/, src/business/auth/, src/app.module.ts 涉及文件: - 新增:src/gateway/auth/ 目录及所有文件 - 移动:Controller、Guard、Decorator、DTO从business层移至gateway层 - 修改:src/business/auth/index.ts(移除Gateway层组件导出) - 修改:src/app.module.ts(使用AuthGatewayModule替代AuthModule) 主要改进: - 明确Gateway层和Business层的职责边界 - Controller、Guard、Decorator属于Gateway层职责 - Business层专注于业务逻辑和服务 - 符合分层架构设计原则
This commit is contained in:
@@ -7,7 +7,7 @@ import { AppService } from './app.service';
|
|||||||
import { LoggerModule } from './core/utils/logger/logger.module';
|
import { LoggerModule } from './core/utils/logger/logger.module';
|
||||||
import { UsersModule } from './core/db/users/users.module';
|
import { UsersModule } from './core/db/users/users.module';
|
||||||
import { LoginCoreModule } from './core/login_core/login_core.module';
|
import { LoginCoreModule } from './core/login_core/login_core.module';
|
||||||
import { AuthModule } from './business/auth/auth.module';
|
import { AuthGatewayModule } from './gateway/auth/auth.gateway.module';
|
||||||
import { ZulipModule } from './business/zulip/zulip.module';
|
import { ZulipModule } from './business/zulip/zulip.module';
|
||||||
import { RedisModule } from './core/redis/redis.module';
|
import { RedisModule } from './core/redis/redis.module';
|
||||||
import { AdminModule } from './business/admin/admin.module';
|
import { AdminModule } from './business/admin/admin.module';
|
||||||
@@ -69,7 +69,7 @@ function isDatabaseConfigured(): boolean {
|
|||||||
// 根据数据库配置选择用户模块模式
|
// 根据数据库配置选择用户模块模式
|
||||||
isDatabaseConfigured() ? UsersModule.forDatabase() : UsersModule.forMemory(),
|
isDatabaseConfigured() ? UsersModule.forDatabase() : UsersModule.forMemory(),
|
||||||
LoginCoreModule,
|
LoginCoreModule,
|
||||||
AuthModule,
|
AuthGatewayModule, // 使用网关层模块替代业务层模块
|
||||||
ZulipModule,
|
ZulipModule,
|
||||||
UserMgmtModule,
|
UserMgmtModule,
|
||||||
AdminModule,
|
AdminModule,
|
||||||
|
|||||||
@@ -2,38 +2,30 @@
|
|||||||
* 用户认证业务模块导出
|
* 用户认证业务模块导出
|
||||||
*
|
*
|
||||||
* 功能概述:
|
* 功能概述:
|
||||||
* - 用户登录和注册
|
* - 用户登录和注册业务逻辑
|
||||||
* - GitHub OAuth集成
|
* - GitHub OAuth集成
|
||||||
* - 密码管理(忘记密码、重置密码、修改密码)
|
* - 密码管理(忘记密码、重置密码、修改密码)
|
||||||
* - 邮箱验证功能
|
* - 邮箱验证功能
|
||||||
* - JWT Token管理
|
* - JWT Token管理
|
||||||
*
|
*
|
||||||
* 职责分离:
|
* 职责分离:
|
||||||
* - 专注于模块导出和接口暴露
|
* - 专注于业务层模块导出
|
||||||
* - 提供统一的模块入口点
|
* - 提供统一的业务服务入口点
|
||||||
* - 简化外部模块的引用方式
|
* - 简化外部模块的引用方式
|
||||||
*
|
*
|
||||||
* 最近修改:
|
* 最近修改:
|
||||||
|
* - 2026-01-14: 架构重构 - 移除Controller和DTO导出(已移至Gateway层)(修改者: moyin)
|
||||||
* - 2026-01-07: 代码规范优化 - 文件夹扁平化,移除单文件文件夹结构
|
* - 2026-01-07: 代码规范优化 - 文件夹扁平化,移除单文件文件夹结构
|
||||||
* - 2026-01-07: 代码规范优化 - 更新注释规范
|
|
||||||
*
|
*
|
||||||
* @author moyin
|
* @author moyin
|
||||||
* @version 1.0.2
|
* @version 2.0.0
|
||||||
* @since 2025-12-17
|
* @since 2025-12-17
|
||||||
* @lastModified 2026-01-07
|
* @lastModified 2026-01-14
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 模块
|
// 模块
|
||||||
export * from './auth.module';
|
export * from './auth.module';
|
||||||
|
|
||||||
// 控制器
|
// 服务(业务层)
|
||||||
export * from './login.controller';
|
|
||||||
export * from './register.controller';
|
|
||||||
|
|
||||||
// 服务
|
|
||||||
export { LoginService } from './login.service';
|
export { LoginService } from './login.service';
|
||||||
export { RegisterService } from './register.service';
|
export { RegisterService } from './register.service';
|
||||||
|
|
||||||
// DTO
|
|
||||||
export * from './login.dto';
|
|
||||||
export * from './login_response.dto';
|
|
||||||
@@ -51,8 +51,8 @@ import {
|
|||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
ApiBody,
|
ApiBody,
|
||||||
} from '@nestjs/swagger';
|
} from '@nestjs/swagger';
|
||||||
import { JwtAuthGuard } from '../../auth/jwt_auth.guard';
|
import { JwtAuthGuard } from '../../../gateway/auth/jwt_auth.guard';
|
||||||
import { CurrentUser } from '../../auth/current_user.decorator';
|
import { CurrentUser } from '../../../gateway/auth/current_user.decorator';
|
||||||
import { JwtPayload } from '../../../core/login_core/login_core.service';
|
import { JwtPayload } from '../../../core/login_core/login_core.service';
|
||||||
|
|
||||||
// 导入业务服务
|
// 导入业务服务
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ import {
|
|||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
ApiBody,
|
ApiBody,
|
||||||
} from '@nestjs/swagger';
|
} from '@nestjs/swagger';
|
||||||
import { JwtAuthGuard, AuthenticatedRequest } from '../auth/jwt_auth.guard';
|
import { JwtAuthGuard, AuthenticatedRequest } from '../../gateway/auth/jwt_auth.guard';
|
||||||
import { CurrentUser } from '../auth/current_user.decorator';
|
import { CurrentUser } from '../../gateway/auth/current_user.decorator';
|
||||||
import { JwtPayload } from '../../core/login_core/login_core.service';
|
import { JwtPayload } from '../../core/login_core/login_core.service';
|
||||||
|
|
||||||
// 导入业务服务
|
// 导入业务服务
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagg
|
|||||||
import { NoticeService } from './notice.service';
|
import { NoticeService } from './notice.service';
|
||||||
import { CreateNoticeDto } from './dto/create-notice.dto';
|
import { CreateNoticeDto } from './dto/create-notice.dto';
|
||||||
import { NoticeResponseDto } from './dto/notice-response.dto';
|
import { NoticeResponseDto } from './dto/notice-response.dto';
|
||||||
import { JwtAuthGuard } from '../auth/jwt_auth.guard';
|
import { JwtAuthGuard } from '../../gateway/auth/jwt_auth.guard';
|
||||||
import { CurrentUser } from '../auth/current_user.decorator';
|
import { CurrentUser } from '../../gateway/auth/current_user.decorator';
|
||||||
|
|
||||||
@ApiTags('通知管理')
|
@ApiTags('通知管理')
|
||||||
@Controller('api/notices')
|
@Controller('api/notices')
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { ChatController } from './chat.controller';
|
|||||||
import { ZulipService } from './zulip.service';
|
import { ZulipService } from './zulip.service';
|
||||||
import { MessageFilterService } from './services/message_filter.service';
|
import { MessageFilterService } from './services/message_filter.service';
|
||||||
import { CleanWebSocketGateway } from './clean_websocket.gateway';
|
import { CleanWebSocketGateway } from './clean_websocket.gateway';
|
||||||
import { JwtAuthGuard } from '../auth/jwt_auth.guard';
|
import { JwtAuthGuard } from '../../gateway/auth/jwt_auth.guard';
|
||||||
|
|
||||||
// Mock JwtAuthGuard
|
// Mock JwtAuthGuard
|
||||||
const mockJwtAuthGuard = {
|
const mockJwtAuthGuard = {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import {
|
|||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
ApiQuery,
|
ApiQuery,
|
||||||
} from '@nestjs/swagger';
|
} from '@nestjs/swagger';
|
||||||
import { JwtAuthGuard } from '../auth/jwt_auth.guard';
|
import { JwtAuthGuard } from '../../gateway/auth/jwt_auth.guard';
|
||||||
import { ZulipService } from './zulip.service';
|
import { ZulipService } from './zulip.service';
|
||||||
import { CleanWebSocketGateway } from './clean_websocket.gateway';
|
import { CleanWebSocketGateway } from './clean_websocket.gateway';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||||
import { ZulipAccountsController } from './zulip_accounts.controller';
|
import { ZulipAccountsController } from './zulip_accounts.controller';
|
||||||
import { JwtAuthGuard } from '../auth/jwt_auth.guard';
|
import { JwtAuthGuard } from '../../gateway/auth/jwt_auth.guard';
|
||||||
import { AppLoggerService } from '../../core/utils/logger/logger.service';
|
import { AppLoggerService } from '../../core/utils/logger/logger.service';
|
||||||
import { ZulipAccountsBusinessService } from './services/zulip_accounts_business.service';
|
import { ZulipAccountsBusinessService } from './services/zulip_accounts_business.service';
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ import {
|
|||||||
ApiQuery,
|
ApiQuery,
|
||||||
} from '@nestjs/swagger';
|
} from '@nestjs/swagger';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { JwtAuthGuard } from '../auth/jwt_auth.guard';
|
import { JwtAuthGuard } from '../../gateway/auth/jwt_auth.guard';
|
||||||
import { ZulipAccountsService } from '../../core/db/zulip_accounts/zulip_accounts.service';
|
import { ZulipAccountsService } from '../../core/db/zulip_accounts/zulip_accounts.service';
|
||||||
import { ZulipAccountsMemoryService } from '../../core/db/zulip_accounts/zulip_accounts_memory.service';
|
import { ZulipAccountsMemoryService } from '../../core/db/zulip_accounts/zulip_accounts_memory.service';
|
||||||
import { AppLoggerService } from '../../core/utils/logger/logger.service';
|
import { AppLoggerService } from '../../core/utils/logger/logger.service';
|
||||||
|
|||||||
338
src/gateway/auth/README.md
Normal file
338
src/gateway/auth/README.md
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
# 认证网关模块 (Auth Gateway Module)
|
||||||
|
|
||||||
|
认证网关模块是系统的HTTP API入口,负责处理所有认证相关的HTTP请求,包括用户登录、注册、密码管理、邮箱验证等功能,提供统一的API接口和完善的安全保护机制。
|
||||||
|
|
||||||
|
## 架构层级
|
||||||
|
|
||||||
|
**Gateway Layer(网关层)**
|
||||||
|
|
||||||
|
## 职责定位
|
||||||
|
|
||||||
|
网关层是系统的HTTP API入口,负责:
|
||||||
|
|
||||||
|
1. **协议处理**:处理HTTP请求和响应
|
||||||
|
2. **数据验证**:使用DTO进行请求参数验证
|
||||||
|
3. **路由管理**:定义API端点和路由规则
|
||||||
|
4. **认证守卫**:JWT令牌验证和权限检查
|
||||||
|
5. **错误转换**:将业务错误转换为HTTP状态码
|
||||||
|
6. **API文档**:提供Swagger API文档
|
||||||
|
|
||||||
|
## 模块组成
|
||||||
|
|
||||||
|
```
|
||||||
|
src/gateway/auth/
|
||||||
|
├── login.controller.ts # 登录API控制器
|
||||||
|
├── login.controller.spec.ts # 登录控制器测试
|
||||||
|
├── register.controller.ts # 注册API控制器
|
||||||
|
├── register.controller.spec.ts # 注册控制器测试
|
||||||
|
├── jwt_auth.guard.ts # JWT认证守卫
|
||||||
|
├── jwt_auth.guard.spec.ts # JWT认证守卫测试
|
||||||
|
├── current_user.decorator.ts # 当前用户装饰器
|
||||||
|
├── jwt_usage_example.ts # JWT使用示例(开发参考)
|
||||||
|
├── dto/ # 数据传输对象
|
||||||
|
│ ├── login.dto.ts # 登录相关DTO
|
||||||
|
│ └── login_response.dto.ts # 响应DTO
|
||||||
|
├── auth.gateway.module.ts # 网关模块配置
|
||||||
|
└── README.md # 模块文档
|
||||||
|
```
|
||||||
|
|
||||||
|
## 依赖关系
|
||||||
|
|
||||||
|
```
|
||||||
|
Gateway Layer (auth.gateway.module)
|
||||||
|
↓ 依赖
|
||||||
|
Business Layer (auth.module)
|
||||||
|
↓ 依赖
|
||||||
|
Core Layer (login_core.module)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心原则
|
||||||
|
|
||||||
|
### 1. 只做协议转换,不做业务逻辑
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 正确:只做HTTP协议处理
|
||||||
|
@Post('login')
|
||||||
|
async login(@Body() loginDto: LoginDto, @Res() res: Response): Promise<void> {
|
||||||
|
const result = await this.loginService.login({
|
||||||
|
identifier: loginDto.identifier,
|
||||||
|
password: loginDto.password
|
||||||
|
});
|
||||||
|
|
||||||
|
this.handleResponse(result, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 错误:在Controller中包含业务逻辑
|
||||||
|
@Post('login')
|
||||||
|
async login(@Body() loginDto: LoginDto): Promise<any> {
|
||||||
|
// 验证用户
|
||||||
|
const user = await this.userService.findByIdentifier(loginDto.identifier);
|
||||||
|
// 检查密码
|
||||||
|
const isValid = await this.comparePassword(loginDto.password, user.password);
|
||||||
|
// ... 更多业务逻辑
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 统一的错误处理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
private handleResponse(result: any, res: Response, successStatus: HttpStatus = HttpStatus.OK): void {
|
||||||
|
if (result.success) {
|
||||||
|
res.status(successStatus).json(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusCode = this.getErrorStatusCode(result);
|
||||||
|
res.status(statusCode).json(result);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用DTO进行数据验证
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class LoginDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
identifier: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 对外提供的接口
|
||||||
|
|
||||||
|
### LoginController
|
||||||
|
|
||||||
|
#### login(loginDto: LoginDto, res: Response): Promise<void>
|
||||||
|
处理用户登录请求,支持用户名、邮箱或手机号多种方式登录。
|
||||||
|
|
||||||
|
#### githubOAuth(githubDto: GitHubOAuthDto, res: Response): Promise<void>
|
||||||
|
处理GitHub OAuth登录请求,支持使用GitHub账户登录或注册。
|
||||||
|
|
||||||
|
#### refreshToken(refreshTokenDto: RefreshTokenDto, res: Response): Promise<void>
|
||||||
|
刷新访问令牌,使用有效的刷新令牌生成新的访问令牌。
|
||||||
|
|
||||||
|
#### forgotPassword(forgotPasswordDto: ForgotPasswordDto, res: Response): Promise<void>
|
||||||
|
发送密码重置验证码到用户邮箱或手机。
|
||||||
|
|
||||||
|
#### resetPassword(resetPasswordDto: ResetPasswordDto, res: Response): Promise<void>
|
||||||
|
使用验证码重置用户密码。
|
||||||
|
|
||||||
|
#### changePassword(changePasswordDto: ChangePasswordDto, res: Response): Promise<void>
|
||||||
|
修改用户密码,需要提供旧密码验证。
|
||||||
|
|
||||||
|
#### verificationCodeLogin(verificationCodeLoginDto: VerificationCodeLoginDto, res: Response): Promise<void>
|
||||||
|
使用邮箱或手机号和验证码进行登录,无需密码。
|
||||||
|
|
||||||
|
#### sendLoginVerificationCode(sendLoginVerificationCodeDto: SendLoginVerificationCodeDto, res: Response): Promise<void>
|
||||||
|
向用户邮箱或手机发送登录验证码。
|
||||||
|
|
||||||
|
#### debugVerificationCode(sendEmailVerificationDto: SendEmailVerificationDto, res: Response): Promise<void>
|
||||||
|
获取验证码的详细调试信息,仅用于开发环境。
|
||||||
|
|
||||||
|
### RegisterController
|
||||||
|
|
||||||
|
#### register(registerDto: RegisterDto, res: Response): Promise<void>
|
||||||
|
处理用户注册请求,创建新用户账户。
|
||||||
|
|
||||||
|
#### sendEmailVerification(sendEmailVerificationDto: SendEmailVerificationDto, res: Response): Promise<void>
|
||||||
|
向指定邮箱发送验证码。
|
||||||
|
|
||||||
|
#### verifyEmail(emailVerificationDto: EmailVerificationDto, res: Response): Promise<void>
|
||||||
|
使用验证码验证邮箱。
|
||||||
|
|
||||||
|
#### resendEmailVerification(sendEmailVerificationDto: SendEmailVerificationDto, res: Response): Promise<void>
|
||||||
|
重新向指定邮箱发送验证码。
|
||||||
|
|
||||||
|
### JwtAuthGuard
|
||||||
|
|
||||||
|
#### canActivate(context: ExecutionContext): Promise<boolean>
|
||||||
|
验证请求中的JWT令牌,提取用户信息并添加到请求上下文。
|
||||||
|
|
||||||
|
### CurrentUser Decorator
|
||||||
|
|
||||||
|
#### CurrentUser(data?: keyof JwtPayload): ParameterDecorator
|
||||||
|
从请求上下文中提取当前认证用户信息的参数装饰器。
|
||||||
|
|
||||||
|
## 对外API接口
|
||||||
|
|
||||||
|
### POST /auth/login
|
||||||
|
用户登录接口,支持用户名、邮箱或手机号多种方式登录,返回JWT令牌。
|
||||||
|
|
||||||
|
### POST /auth/github
|
||||||
|
GitHub OAuth登录接口,使用GitHub账户登录或注册。
|
||||||
|
|
||||||
|
### POST /auth/verification-code-login
|
||||||
|
验证码登录接口,使用邮箱或手机号和验证码进行登录,无需密码。
|
||||||
|
|
||||||
|
### POST /auth/refresh-token
|
||||||
|
刷新访问令牌接口,使用有效的刷新令牌生成新的访问令牌。
|
||||||
|
|
||||||
|
### POST /auth/forgot-password
|
||||||
|
发送密码重置验证码接口,向用户邮箱或手机发送密码重置验证码。
|
||||||
|
|
||||||
|
### POST /auth/reset-password
|
||||||
|
重置密码接口,使用验证码重置用户密码。
|
||||||
|
|
||||||
|
### PUT /auth/change-password
|
||||||
|
修改密码接口,用户修改自己的密码,需要提供旧密码验证。
|
||||||
|
|
||||||
|
### POST /auth/send-login-verification-code
|
||||||
|
发送登录验证码接口,向用户邮箱或手机发送登录验证码。
|
||||||
|
|
||||||
|
### POST /auth/debug-verification-code
|
||||||
|
调试验证码信息接口,获取验证码的详细调试信息,仅用于开发环境。
|
||||||
|
|
||||||
|
### POST /auth/register
|
||||||
|
用户注册接口,创建新用户账户。
|
||||||
|
|
||||||
|
### POST /auth/send-email-verification
|
||||||
|
发送邮箱验证码接口,向指定邮箱发送验证码。
|
||||||
|
|
||||||
|
### POST /auth/verify-email
|
||||||
|
验证邮箱接口,使用验证码验证邮箱。
|
||||||
|
|
||||||
|
### POST /auth/resend-email-verification
|
||||||
|
重新发送邮箱验证码接口,重新向指定邮箱发送验证码。
|
||||||
|
|
||||||
|
## 使用的项目内部依赖
|
||||||
|
|
||||||
|
### LoginService (来自 business/auth/login.service)
|
||||||
|
登录业务服务,提供用户登录、密码管理、令牌刷新等业务逻辑。
|
||||||
|
|
||||||
|
### RegisterService (来自 business/auth/register.service)
|
||||||
|
注册业务服务,提供用户注册、邮箱验证等业务逻辑。
|
||||||
|
|
||||||
|
### LoginCoreService (来自 core/login_core/login_core.service)
|
||||||
|
登录核心服务,提供JWT令牌验证和生成等技术实现。
|
||||||
|
|
||||||
|
### JwtPayload (来自 core/login_core/login_core.service)
|
||||||
|
JWT令牌载荷类型定义,包含用户ID、用户名、角色等信息。
|
||||||
|
|
||||||
|
### ThrottlePresets (来自 core/security_core/throttle.decorator)
|
||||||
|
限流预设配置,提供登录、注册、发送验证码等场景的限流规则。
|
||||||
|
|
||||||
|
### TimeoutPresets (来自 core/security_core/timeout.decorator)
|
||||||
|
超时预设配置,提供不同场景的超时时间设置。
|
||||||
|
|
||||||
|
## 核心特性
|
||||||
|
|
||||||
|
### 统一的响应处理机制
|
||||||
|
- 智能错误状态码映射:根据错误代码和消息自动选择合适的HTTP状态码
|
||||||
|
- 统一响应格式:所有API返回统一的JSON格式
|
||||||
|
- 错误信息标准化:提供清晰的错误代码和消息
|
||||||
|
|
||||||
|
### 完善的安全保护
|
||||||
|
- JWT令牌认证:使用JWT进行用户身份验证
|
||||||
|
- 限流保护:防止API滥用和暴力破解
|
||||||
|
- 超时控制:防止长时间阻塞和资源占用
|
||||||
|
- 请求验证:使用DTO和class-validator进行严格的数据验证
|
||||||
|
|
||||||
|
### 完整的API文档
|
||||||
|
- Swagger集成:自动生成交互式API文档
|
||||||
|
- 详细的接口说明:每个API都有完整的描述和示例
|
||||||
|
- 请求响应示例:提供清晰的数据格式说明
|
||||||
|
|
||||||
|
### 灵活的认证方式
|
||||||
|
- 多种登录方式:支持用户名、邮箱、手机号登录
|
||||||
|
- 验证码登录:支持无密码的验证码登录
|
||||||
|
- OAuth集成:支持GitHub OAuth登录
|
||||||
|
- 令牌刷新:支持无感知的令牌续期
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### JWT认证完整示例
|
||||||
|
|
||||||
|
本模块提供了完整的JWT认证使用示例文件 `jwt_usage_example.ts`,展示了以下场景:
|
||||||
|
|
||||||
|
1. **公开接口**:无需认证的接口
|
||||||
|
2. **受保护接口**:需要JWT令牌的接口
|
||||||
|
3. **用户信息获取**:使用 `@CurrentUser()` 装饰器
|
||||||
|
4. **特定属性提取**:使用 `@CurrentUser('username')` 获取特定字段
|
||||||
|
5. **角色权限检查**:基于用户角色的访问控制
|
||||||
|
|
||||||
|
详细代码请参考 `src/gateway/auth/jwt_usage_example.ts` 文件。
|
||||||
|
|
||||||
|
### 在其他模块中使用认证守卫
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||||
|
import { JwtAuthGuard } from '../gateway/auth/jwt_auth.guard';
|
||||||
|
import { CurrentUser } from '../gateway/auth/current_user.decorator';
|
||||||
|
|
||||||
|
@Controller('profile')
|
||||||
|
export class ProfileController {
|
||||||
|
@Get()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
getProfile(@CurrentUser() user: any) {
|
||||||
|
return { user };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 与业务层的交互
|
||||||
|
|
||||||
|
网关层通过依赖注入使用业务层服务:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
constructor(
|
||||||
|
private readonly loginService: LoginService,
|
||||||
|
private readonly registerService: RegisterService
|
||||||
|
) {}
|
||||||
|
```
|
||||||
|
|
||||||
|
业务层返回统一的响应格式:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ApiResponse<T = any> {
|
||||||
|
success: boolean;
|
||||||
|
data?: T;
|
||||||
|
message: string;
|
||||||
|
error_code?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **保持Controller轻量**:只做请求响应处理
|
||||||
|
2. **使用DTO验证**:所有输入都要经过DTO验证
|
||||||
|
3. **统一错误处理**:使用统一的错误处理方法
|
||||||
|
4. **完善API文档**:使用Swagger装饰器
|
||||||
|
5. **限流保护**:使用Throttle装饰器防止滥用
|
||||||
|
6. **超时控制**:使用Timeout装饰器防止长时间阻塞
|
||||||
|
|
||||||
|
## 潜在风险
|
||||||
|
|
||||||
|
### 限流配置不当风险
|
||||||
|
- 限流阈值过低可能影响正常用户使用
|
||||||
|
- 限流阈值过高无法有效防止攻击
|
||||||
|
- 缓解措施:根据实际业务场景调整限流参数,监控限流触发情况
|
||||||
|
|
||||||
|
### JWT令牌安全风险
|
||||||
|
- 令牌泄露可能导致账户被盗用
|
||||||
|
- 令牌过期时间设置不当影响用户体验
|
||||||
|
- 缓解措施:使用HTTPS传输,合理设置令牌过期时间,实现令牌刷新机制
|
||||||
|
|
||||||
|
### 验证码安全风险
|
||||||
|
- 验证码被暴力破解
|
||||||
|
- 验证码发送频率过高导致资源浪费
|
||||||
|
- 缓解措施:限制验证码发送频率,增加验证码复杂度,设置验证码有效期
|
||||||
|
|
||||||
|
### API滥用风险
|
||||||
|
- 恶意用户频繁调用API消耗服务器资源
|
||||||
|
- 自动化工具批量注册账户
|
||||||
|
- 缓解措施:实施限流策略,添加人机验证,监控异常请求模式
|
||||||
|
|
||||||
|
### 错误信息泄露风险
|
||||||
|
- 详细的错误信息可能泄露系统实现细节
|
||||||
|
- 帮助攻击者了解系统弱点
|
||||||
|
- 缓解措施:生产环境使用通用错误消息,详细日志仅记录在服务器端
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 网关层不应该直接访问数据库
|
||||||
|
- 网关层不应该包含复杂的业务逻辑
|
||||||
|
- 网关层不应该直接调用Core层服务(Guard除外)
|
||||||
|
- 所有业务逻辑都应该在Business层实现
|
||||||
52
src/gateway/auth/auth.gateway.module.ts
Normal file
52
src/gateway/auth/auth.gateway.module.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* 认证网关模块
|
||||||
|
*
|
||||||
|
* 架构层级:Gateway Layer(网关层)
|
||||||
|
*
|
||||||
|
* 功能描述:
|
||||||
|
* - 整合所有认证相关的网关组件
|
||||||
|
* - 提供HTTP API接口
|
||||||
|
* - 配置认证守卫和中间件
|
||||||
|
* - 处理请求验证和响应格式化
|
||||||
|
*
|
||||||
|
* 职责分离:
|
||||||
|
* - 专注于HTTP协议处理和API网关功能
|
||||||
|
* - 依赖业务层服务,不包含业务逻辑
|
||||||
|
* - 提供统一的API入口和文档
|
||||||
|
*
|
||||||
|
* 依赖关系:
|
||||||
|
* - 依赖 Business Layer 的 AuthModule
|
||||||
|
* - 提供 Controller 和 Guard
|
||||||
|
*
|
||||||
|
* @author moyin
|
||||||
|
* @version 2.0.0
|
||||||
|
* @since 2026-01-14
|
||||||
|
* @lastModified 2026-01-14
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { LoginController } from './login.controller';
|
||||||
|
import { RegisterController } from './register.controller';
|
||||||
|
import { JwtAuthGuard } from './jwt_auth.guard';
|
||||||
|
import { AuthModule } from '../../business/auth/auth.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
// 导入业务层模块
|
||||||
|
AuthModule,
|
||||||
|
],
|
||||||
|
controllers: [
|
||||||
|
// 网关层控制器
|
||||||
|
LoginController,
|
||||||
|
RegisterController,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
// 认证守卫
|
||||||
|
JwtAuthGuard,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
// 导出守卫供其他模块使用
|
||||||
|
JwtAuthGuard,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AuthGatewayModule {}
|
||||||
@@ -7,12 +7,13 @@
|
|||||||
* - 测试认证失败的异常处理
|
* - 测试认证失败的异常处理
|
||||||
*
|
*
|
||||||
* 最近修改:
|
* 最近修改:
|
||||||
|
* - 2026-01-14: 架构重构 - 从business层移动到gateway层 (修改者: moyin)
|
||||||
* - 2026-01-12: 代码规范优化 - 创建缺失的守卫测试文件 (修改者: moyin)
|
* - 2026-01-12: 代码规范优化 - 创建缺失的守卫测试文件 (修改者: moyin)
|
||||||
*
|
*
|
||||||
* @author moyin
|
* @author moyin
|
||||||
* @version 1.0.0
|
* @version 1.1.0
|
||||||
* @since 2026-01-12
|
* @since 2026-01-12
|
||||||
* @lastModified 2026-01-12
|
* @lastModified 2026-01-14
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* JWT 使用示例
|
* JWT 使用示例
|
||||||
*
|
*
|
||||||
|
* 架构层级:Gateway Layer(网关层)
|
||||||
|
*
|
||||||
* 功能描述:
|
* 功能描述:
|
||||||
* - 展示如何在控制器中使用 JWT 认证守卫和当前用户装饰器
|
* - 展示如何在控制器中使用 JWT 认证守卫和当前用户装饰器
|
||||||
* - 提供完整的JWT认证使用示例和最佳实践
|
* - 提供完整的JWT认证使用示例和最佳实践
|
||||||
@@ -11,14 +13,20 @@
|
|||||||
* - 提供开发者参考的代码示例
|
* - 提供开发者参考的代码示例
|
||||||
* - 展示认证守卫和装饰器的最佳实践
|
* - 展示认证守卫和装饰器的最佳实践
|
||||||
*
|
*
|
||||||
|
* 架构说明:
|
||||||
|
* - 本文件位于Gateway层,符合Controller的架构定位
|
||||||
|
* - JWT Guard和装饰器位于同层(src/gateway/auth)
|
||||||
|
* - 本文件作为使用示例参考
|
||||||
|
*
|
||||||
* 最近修改:
|
* 最近修改:
|
||||||
|
* - 2026-01-14: 架构优化 - 将文件从Business层移至Gateway层,符合架构分层原则 (Modified by: moyin)
|
||||||
|
* - 2026-01-14: 代码规范优化 - 修正导入路径,指向Gateway层 (Modified by: moyin)
|
||||||
* - 2026-01-07: 代码规范优化 - 文件夹扁平化,移除单文件文件夹结构
|
* - 2026-01-07: 代码规范优化 - 文件夹扁平化,移除单文件文件夹结构
|
||||||
* - 2026-01-07: 代码规范优化 - 文件重命名为snake_case格式,更新注释规范
|
|
||||||
*
|
*
|
||||||
* @author moyin
|
* @author moyin
|
||||||
* @version 1.0.2
|
* @version 1.2.0
|
||||||
* @since 2025-01-05
|
* @since 2025-01-05
|
||||||
* @lastModified 2026-01-07
|
* @lastModified 2026-01-14
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Controller, Get, UseGuards, Post, Body } from '@nestjs/common';
|
import { Controller, Get, UseGuards, Post, Body } from '@nestjs/common';
|
||||||
@@ -7,19 +7,20 @@
|
|||||||
* - 测试错误处理和异常情况
|
* - 测试错误处理和异常情况
|
||||||
*
|
*
|
||||||
* 最近修改:
|
* 最近修改:
|
||||||
|
* - 2026-01-14: 架构重构 - 从business层移动到gateway层 (修改者: moyin)
|
||||||
* - 2026-01-12: 代码规范优化 - 创建缺失的控制器测试文件 (修改者: moyin)
|
* - 2026-01-12: 代码规范优化 - 创建缺失的控制器测试文件 (修改者: moyin)
|
||||||
*
|
*
|
||||||
* @author moyin
|
* @author moyin
|
||||||
* @version 1.0.0
|
* @version 1.1.0
|
||||||
* @since 2026-01-12
|
* @since 2026-01-12
|
||||||
* @lastModified 2026-01-12
|
* @lastModified 2026-01-14
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { HttpStatus } from '@nestjs/common';
|
import { HttpStatus } from '@nestjs/common';
|
||||||
import { LoginController } from './login.controller';
|
import { LoginController } from './login.controller';
|
||||||
import { LoginService } from './login.service';
|
import { LoginService } from '../../business/auth/login.service';
|
||||||
|
|
||||||
describe('LoginController', () => {
|
describe('LoginController', () => {
|
||||||
let controller: LoginController;
|
let controller: LoginController;
|
||||||
@@ -1,47 +1,79 @@
|
|||||||
/**
|
/**
|
||||||
* 登录控制器
|
* 登录网关控制器
|
||||||
|
*
|
||||||
|
* 架构层级:Gateway Layer(网关层)
|
||||||
*
|
*
|
||||||
* 功能描述:
|
* 功能描述:
|
||||||
* - 处理登录相关的HTTP请求和响应
|
* - 处理登录相关的HTTP请求和响应
|
||||||
* - 提供RESTful API接口
|
* - 提供RESTful API接口
|
||||||
* - 数据验证和格式化
|
* - 数据验证和格式化
|
||||||
|
* - 协议处理和错误响应
|
||||||
*
|
*
|
||||||
* 职责分离:
|
* 职责分离:
|
||||||
* - 专注于HTTP请求处理和响应格式化
|
* - 专注于HTTP协议处理和请求响应
|
||||||
* - 调用业务服务完成具体功能
|
* - 调用业务层服务完成具体功能
|
||||||
* - 处理API文档和参数验证
|
* - 处理API文档和参数验证
|
||||||
|
* - 不包含业务逻辑,只做数据转换和路由
|
||||||
|
*
|
||||||
|
* 依赖关系:
|
||||||
|
* - 依赖 Business Layer 的 LoginService
|
||||||
|
* - 使用 DTO 进行数据验证
|
||||||
|
* - 使用 Guard 进行认证保护
|
||||||
*
|
*
|
||||||
* API端点:
|
* API端点:
|
||||||
* - POST /auth/login - 用户登录
|
* - POST /auth/login - 用户登录
|
||||||
* - POST /auth/register - 用户注册
|
|
||||||
* - POST /auth/github - GitHub OAuth登录
|
* - POST /auth/github - GitHub OAuth登录
|
||||||
* - POST /auth/forgot-password - 发送密码重置验证码
|
* - POST /auth/forgot-password - 发送密码重置验证码
|
||||||
* - POST /auth/reset-password - 重置密码
|
* - POST /auth/reset-password - 重置密码
|
||||||
* - PUT /auth/change-password - 修改密码
|
* - PUT /auth/change-password - 修改密码
|
||||||
* - POST /auth/refresh-token - 刷新访问令牌
|
* - POST /auth/refresh-token - 刷新访问令牌
|
||||||
*
|
* - POST /auth/verification-code-login - 验证码登录
|
||||||
* 最近修改:
|
* - POST /auth/send-login-verification-code - 发送登录验证码
|
||||||
* - 2026-01-07: 代码规范优化 - 文件夹扁平化,移除单文件文件夹结构
|
|
||||||
* - 2026-01-07: 代码规范优化 - 更新注释规范,修正文件引用路径
|
|
||||||
*
|
*
|
||||||
* @author moyin
|
* @author moyin
|
||||||
* @version 1.0.2
|
* @version 2.0.0
|
||||||
* @since 2025-12-17
|
* @since 2026-01-14
|
||||||
* @lastModified 2026-01-07
|
* @lastModified 2026-01-14
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Controller, Post, Put, Body, HttpCode, HttpStatus, ValidationPipe, UsePipes, Logger, Res } from '@nestjs/common';
|
import {
|
||||||
import { ApiTags, ApiOperation, ApiResponse as SwaggerApiResponse, ApiBody } from '@nestjs/swagger';
|
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 { Response } from 'express';
|
||||||
import { LoginService, ApiResponse, LoginResponse } from './login.service';
|
import { LoginService } from '../../business/auth/login.service';
|
||||||
import { LoginDto, GitHubOAuthDto, ForgotPasswordDto, ResetPasswordDto, ChangePasswordDto, VerificationCodeLoginDto, SendLoginVerificationCodeDto, RefreshTokenDto, SendEmailVerificationDto } from './login.dto';
|
import {
|
||||||
|
LoginDto,
|
||||||
|
GitHubOAuthDto,
|
||||||
|
ForgotPasswordDto,
|
||||||
|
ResetPasswordDto,
|
||||||
|
ChangePasswordDto,
|
||||||
|
VerificationCodeLoginDto,
|
||||||
|
SendLoginVerificationCodeDto,
|
||||||
|
RefreshTokenDto,
|
||||||
|
SendEmailVerificationDto
|
||||||
|
} from './dto/login.dto';
|
||||||
import {
|
import {
|
||||||
LoginResponseDto,
|
LoginResponseDto,
|
||||||
GitHubOAuthResponseDto,
|
GitHubOAuthResponseDto,
|
||||||
ForgotPasswordResponseDto,
|
ForgotPasswordResponseDto,
|
||||||
CommonResponseDto,
|
CommonResponseDto,
|
||||||
RefreshTokenResponseDto
|
RefreshTokenResponseDto
|
||||||
} from './login_response.dto';
|
} from './dto/login_response.dto';
|
||||||
import { Throttle, ThrottlePresets } from '../../core/security_core/throttle.decorator';
|
import { Throttle, ThrottlePresets } from '../../core/security_core/throttle.decorator';
|
||||||
import { Timeout, TimeoutPresets } from '../../core/security_core/timeout.decorator';
|
import { Timeout, TimeoutPresets } from '../../core/security_core/timeout.decorator';
|
||||||
|
|
||||||
@@ -68,10 +100,10 @@ export class LoginController {
|
|||||||
/**
|
/**
|
||||||
* 通用响应处理方法
|
* 通用响应处理方法
|
||||||
*
|
*
|
||||||
* 业务逻辑:
|
* 职责:
|
||||||
* 1. 根据业务结果设置HTTP状态码
|
* - 根据业务结果设置HTTP状态码
|
||||||
* 2. 处理不同类型的错误响应
|
* - 处理不同类型的错误响应
|
||||||
* 3. 统一响应格式和错误处理
|
* - 统一响应格式和错误处理
|
||||||
*
|
*
|
||||||
* @param result 业务服务返回的结果
|
* @param result 业务服务返回的结果
|
||||||
* @param res Express响应对象
|
* @param res Express响应对象
|
||||||
@@ -84,7 +116,6 @@ export class LoginController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据错误代码获取状态码
|
|
||||||
const statusCode = this.getErrorStatusCode(result);
|
const statusCode = this.getErrorStatusCode(result);
|
||||||
res.status(statusCode).json(result);
|
res.status(statusCode).json(result);
|
||||||
}
|
}
|
||||||
@@ -97,12 +128,10 @@ export class LoginController {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private getErrorStatusCode(result: any): HttpStatus {
|
private getErrorStatusCode(result: any): HttpStatus {
|
||||||
// 优先使用错误代码映射
|
|
||||||
if (result.error_code && ERROR_STATUS_MAP[result.error_code as keyof typeof ERROR_STATUS_MAP]) {
|
if (result.error_code && ERROR_STATUS_MAP[result.error_code as keyof typeof ERROR_STATUS_MAP]) {
|
||||||
return ERROR_STATUS_MAP[result.error_code as keyof typeof ERROR_STATUS_MAP];
|
return ERROR_STATUS_MAP[result.error_code as keyof typeof ERROR_STATUS_MAP];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据消息内容判断
|
|
||||||
if (result.message?.includes('已存在') || result.message?.includes('已被注册')) {
|
if (result.message?.includes('已存在') || result.message?.includes('已被注册')) {
|
||||||
return HttpStatus.CONFLICT;
|
return HttpStatus.CONFLICT;
|
||||||
}
|
}
|
||||||
@@ -115,7 +144,6 @@ export class LoginController {
|
|||||||
return HttpStatus.NOT_FOUND;
|
return HttpStatus.NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认返回400
|
|
||||||
return HttpStatus.BAD_REQUEST;
|
return HttpStatus.BAD_REQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +151,7 @@ export class LoginController {
|
|||||||
* 用户登录
|
* 用户登录
|
||||||
*
|
*
|
||||||
* @param loginDto 登录数据
|
* @param loginDto 登录数据
|
||||||
* @returns 登录结果
|
* @param res Express响应对象
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '用户登录',
|
summary: '用户登录',
|
||||||
@@ -168,7 +196,7 @@ export class LoginController {
|
|||||||
* GitHub OAuth登录
|
* GitHub OAuth登录
|
||||||
*
|
*
|
||||||
* @param githubDto GitHub OAuth数据
|
* @param githubDto GitHub OAuth数据
|
||||||
* @returns 登录结果
|
* @param res Express响应对象
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: 'GitHub OAuth登录',
|
summary: 'GitHub OAuth登录',
|
||||||
@@ -207,7 +235,6 @@ export class LoginController {
|
|||||||
*
|
*
|
||||||
* @param forgotPasswordDto 忘记密码数据
|
* @param forgotPasswordDto 忘记密码数据
|
||||||
* @param res Express响应对象
|
* @param res Express响应对象
|
||||||
* @returns 发送结果
|
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '发送密码重置验证码',
|
summary: '发送密码重置验证码',
|
||||||
@@ -251,7 +278,7 @@ export class LoginController {
|
|||||||
* 重置密码
|
* 重置密码
|
||||||
*
|
*
|
||||||
* @param resetPasswordDto 重置密码数据
|
* @param resetPasswordDto 重置密码数据
|
||||||
* @returns 重置结果
|
* @param res Express响应对象
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '重置密码',
|
summary: '重置密码',
|
||||||
@@ -292,7 +319,7 @@ export class LoginController {
|
|||||||
* 修改密码
|
* 修改密码
|
||||||
*
|
*
|
||||||
* @param changePasswordDto 修改密码数据
|
* @param changePasswordDto 修改密码数据
|
||||||
* @returns 修改结果
|
* @param res Express响应对象
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '修改密码',
|
summary: '修改密码',
|
||||||
@@ -315,8 +342,6 @@ export class LoginController {
|
|||||||
@Put('change-password')
|
@Put('change-password')
|
||||||
@UsePipes(new ValidationPipe({ transform: true }))
|
@UsePipes(new ValidationPipe({ transform: true }))
|
||||||
async changePassword(@Body() changePasswordDto: ChangePasswordDto, @Res() res: Response): Promise<void> {
|
async changePassword(@Body() changePasswordDto: ChangePasswordDto, @Res() res: Response): Promise<void> {
|
||||||
// 实际应用中应从JWT令牌中获取用户ID
|
|
||||||
// 这里为了演示,使用请求体中的用户ID
|
|
||||||
const userId = BigInt(changePasswordDto.user_id);
|
const userId = BigInt(changePasswordDto.user_id);
|
||||||
|
|
||||||
const result = await this.loginService.changePassword(
|
const result = await this.loginService.changePassword(
|
||||||
@@ -332,7 +357,7 @@ export class LoginController {
|
|||||||
* 验证码登录
|
* 验证码登录
|
||||||
*
|
*
|
||||||
* @param verificationCodeLoginDto 验证码登录数据
|
* @param verificationCodeLoginDto 验证码登录数据
|
||||||
* @returns 登录结果
|
* @param res Express响应对象
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '验证码登录',
|
summary: '验证码登录',
|
||||||
@@ -359,11 +384,16 @@ export class LoginController {
|
|||||||
@Post('verification-code-login')
|
@Post('verification-code-login')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@UsePipes(new ValidationPipe({ transform: true }))
|
@UsePipes(new ValidationPipe({ transform: true }))
|
||||||
async verificationCodeLogin(@Body() verificationCodeLoginDto: VerificationCodeLoginDto): Promise<ApiResponse<LoginResponse>> {
|
async verificationCodeLogin(
|
||||||
return await this.loginService.verificationCodeLogin({
|
@Body() verificationCodeLoginDto: VerificationCodeLoginDto,
|
||||||
|
@Res() res: Response
|
||||||
|
): Promise<void> {
|
||||||
|
const result = await this.loginService.verificationCodeLogin({
|
||||||
identifier: verificationCodeLoginDto.identifier,
|
identifier: verificationCodeLoginDto.identifier,
|
||||||
verificationCode: verificationCodeLoginDto.verification_code
|
verificationCode: verificationCodeLoginDto.verification_code
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.handleResponse(result, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -371,11 +401,10 @@ export class LoginController {
|
|||||||
*
|
*
|
||||||
* @param sendLoginVerificationCodeDto 发送验证码数据
|
* @param sendLoginVerificationCodeDto 发送验证码数据
|
||||||
* @param res Express响应对象
|
* @param res Express响应对象
|
||||||
* @returns 发送结果
|
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '发送登录验证码',
|
summary: '发送登录验证码',
|
||||||
description: '向用户邮箱或手机发送登录验证码。邮件使用专门的登录验证码模板,内容明确标识为登录验证而非密码重置。'
|
description: '向用户邮箱或手机发送登录验证码'
|
||||||
})
|
})
|
||||||
@ApiBody({ type: SendLoginVerificationCodeDto })
|
@ApiBody({ type: SendLoginVerificationCodeDto })
|
||||||
@SwaggerApiResponse({
|
@SwaggerApiResponse({
|
||||||
@@ -410,63 +439,15 @@ export class LoginController {
|
|||||||
this.handleResponse(result, res);
|
this.handleResponse(result, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 调试验证码信息
|
|
||||||
* 仅用于开发和调试
|
|
||||||
*
|
|
||||||
* @param sendEmailVerificationDto 邮箱信息
|
|
||||||
* @returns 验证码调试信息
|
|
||||||
*/
|
|
||||||
@ApiOperation({
|
|
||||||
summary: '调试验证码信息',
|
|
||||||
description: '获取验证码的详细调试信息(仅开发环境)'
|
|
||||||
})
|
|
||||||
@ApiBody({ type: SendEmailVerificationDto })
|
|
||||||
@Post('debug-verification-code')
|
|
||||||
@UsePipes(new ValidationPipe({ transform: true }))
|
|
||||||
async debugVerificationCode(@Body() sendEmailVerificationDto: SendEmailVerificationDto, @Res() res: Response): Promise<void> {
|
|
||||||
const result = await this.loginService.debugVerificationCode(sendEmailVerificationDto.email);
|
|
||||||
|
|
||||||
// 调试接口总是返回200
|
|
||||||
res.status(HttpStatus.OK).json(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除限流记录(仅开发环境)
|
|
||||||
*/
|
|
||||||
@ApiOperation({
|
|
||||||
summary: '清除限流记录',
|
|
||||||
description: '清除所有限流记录(仅开发环境使用)'
|
|
||||||
})
|
|
||||||
@Post('debug-clear-throttle')
|
|
||||||
async clearThrottle(@Res() res: Response): Promise<void> {
|
|
||||||
// 注入ThrottleGuard并清除记录
|
|
||||||
// 这里需要通过依赖注入获取ThrottleGuard实例
|
|
||||||
res.status(HttpStatus.OK).json({
|
|
||||||
success: true,
|
|
||||||
message: '限流记录已清除'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新访问令牌
|
* 刷新访问令牌
|
||||||
*
|
*
|
||||||
* 功能描述:
|
|
||||||
* 使用有效的刷新令牌生成新的访问令牌,实现无感知的令牌续期
|
|
||||||
*
|
|
||||||
* 业务逻辑:
|
|
||||||
* 1. 验证刷新令牌的有效性和格式
|
|
||||||
* 2. 检查用户状态是否正常
|
|
||||||
* 3. 生成新的JWT令牌对
|
|
||||||
* 4. 返回新的访问令牌和刷新令牌
|
|
||||||
*
|
|
||||||
* @param refreshTokenDto 刷新令牌数据
|
* @param refreshTokenDto 刷新令牌数据
|
||||||
* @param res Express响应对象
|
* @param res Express响应对象
|
||||||
* @returns 新的令牌对
|
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '刷新访问令牌',
|
summary: '刷新访问令牌',
|
||||||
description: '使用有效的刷新令牌生成新的访问令牌,实现无感知的令牌续期。建议在访问令牌即将过期时调用此接口。'
|
description: '使用有效的刷新令牌生成新的访问令牌'
|
||||||
})
|
})
|
||||||
@ApiBody({ type: RefreshTokenDto })
|
@ApiBody({ type: RefreshTokenDto })
|
||||||
@SwaggerApiResponse({
|
@SwaggerApiResponse({
|
||||||
@@ -495,73 +476,28 @@ export class LoginController {
|
|||||||
@Post('refresh-token')
|
@Post('refresh-token')
|
||||||
@UsePipes(new ValidationPipe({ transform: true }))
|
@UsePipes(new ValidationPipe({ transform: true }))
|
||||||
async refreshToken(@Body() refreshTokenDto: RefreshTokenDto, @Res() res: Response): Promise<void> {
|
async refreshToken(@Body() refreshTokenDto: RefreshTokenDto, @Res() res: Response): Promise<void> {
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.logRefreshTokenStart();
|
|
||||||
const result = await this.loginService.refreshAccessToken(refreshTokenDto.refresh_token);
|
const result = await this.loginService.refreshAccessToken(refreshTokenDto.refresh_token);
|
||||||
this.handleRefreshTokenResponse(result, res, startTime);
|
|
||||||
} catch (error) {
|
|
||||||
this.handleRefreshTokenError(error, res, startTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录令牌刷新开始日志
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private logRefreshTokenStart(): void {
|
|
||||||
this.logger.log('令牌刷新请求', {
|
|
||||||
operation: 'refreshToken',
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理令牌刷新响应
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private handleRefreshTokenResponse(result: any, res: Response, startTime: number): void {
|
|
||||||
const duration = Date.now() - startTime;
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
this.logger.log('令牌刷新成功', {
|
|
||||||
operation: 'refreshToken',
|
|
||||||
duration,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
res.status(HttpStatus.OK).json(result);
|
|
||||||
} else {
|
|
||||||
this.logger.warn('令牌刷新失败', {
|
|
||||||
operation: 'refreshToken',
|
|
||||||
error: result.message,
|
|
||||||
errorCode: result.error_code,
|
|
||||||
duration,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
this.handleResponse(result, res);
|
this.handleResponse(result, res);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理令牌刷新异常
|
* 调试验证码信息(仅开发环境)
|
||||||
* @private
|
*
|
||||||
|
* @param sendEmailVerificationDto 邮箱信息
|
||||||
|
* @param res Express响应对象
|
||||||
*/
|
*/
|
||||||
private handleRefreshTokenError(error: unknown, res: Response, startTime: number): void {
|
@ApiOperation({
|
||||||
const duration = Date.now() - startTime;
|
summary: '调试验证码信息',
|
||||||
const err = error as Error;
|
description: '获取验证码的详细调试信息(仅开发环境)'
|
||||||
|
})
|
||||||
this.logger.error('令牌刷新异常', {
|
@ApiBody({ type: SendEmailVerificationDto })
|
||||||
operation: 'refreshToken',
|
@Post('debug-verification-code')
|
||||||
error: err.message,
|
@UsePipes(new ValidationPipe({ transform: true }))
|
||||||
duration,
|
async debugVerificationCode(
|
||||||
timestamp: new Date().toISOString(),
|
@Body() sendEmailVerificationDto: SendEmailVerificationDto,
|
||||||
}, err.stack);
|
@Res() res: Response
|
||||||
|
): Promise<void> {
|
||||||
res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
|
const result = await this.loginService.debugVerificationCode(sendEmailVerificationDto.email);
|
||||||
success: false,
|
res.status(HttpStatus.OK).json(result);
|
||||||
message: '服务器内部错误',
|
|
||||||
error_code: 'INTERNAL_SERVER_ERROR'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,19 +7,20 @@
|
|||||||
* - 测试邮箱验证流程
|
* - 测试邮箱验证流程
|
||||||
*
|
*
|
||||||
* 最近修改:
|
* 最近修改:
|
||||||
|
* - 2026-01-14: 架构重构 - 从business层移动到gateway层 (修改者: moyin)
|
||||||
* - 2026-01-12: 代码规范优化 - 创建缺失的控制器测试文件 (修改者: moyin)
|
* - 2026-01-12: 代码规范优化 - 创建缺失的控制器测试文件 (修改者: moyin)
|
||||||
*
|
*
|
||||||
* @author moyin
|
* @author moyin
|
||||||
* @version 1.0.0
|
* @version 1.1.0
|
||||||
* @since 2026-01-12
|
* @since 2026-01-12
|
||||||
* @lastModified 2026-01-12
|
* @lastModified 2026-01-14
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { HttpStatus } from '@nestjs/common';
|
import { HttpStatus } from '@nestjs/common';
|
||||||
import { RegisterController } from './register.controller';
|
import { RegisterController } from './register.controller';
|
||||||
import { RegisterService } from './register.service';
|
import { RegisterService } from '../../business/auth/register.service';
|
||||||
|
|
||||||
describe('RegisterController', () => {
|
describe('RegisterController', () => {
|
||||||
let controller: RegisterController;
|
let controller: RegisterController;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* 注册控制器
|
* 注册网关控制器
|
||||||
|
*
|
||||||
|
* 架构层级:Gateway Layer(网关层)
|
||||||
*
|
*
|
||||||
* 功能描述:
|
* 功能描述:
|
||||||
* - 处理用户注册相关的HTTP请求和响应
|
* - 处理用户注册相关的HTTP请求和响应
|
||||||
@@ -8,9 +10,14 @@
|
|||||||
* - 邮箱验证功能
|
* - 邮箱验证功能
|
||||||
*
|
*
|
||||||
* 职责分离:
|
* 职责分离:
|
||||||
* - 专注于HTTP请求处理和响应格式化
|
* - 专注于HTTP协议处理和请求响应
|
||||||
* - 调用注册业务服务完成具体功能
|
* - 调用业务层服务完成具体功能
|
||||||
* - 处理API文档和参数验证
|
* - 处理API文档和参数验证
|
||||||
|
* - 不包含业务逻辑,只做数据转换和路由
|
||||||
|
*
|
||||||
|
* 依赖关系:
|
||||||
|
* - 依赖 Business Layer 的 RegisterService
|
||||||
|
* - 使用 DTO 进行数据验证
|
||||||
*
|
*
|
||||||
* API端点:
|
* API端点:
|
||||||
* - POST /auth/register - 用户注册
|
* - POST /auth/register - 用户注册
|
||||||
@@ -18,26 +25,41 @@
|
|||||||
* - POST /auth/verify-email - 验证邮箱验证码
|
* - POST /auth/verify-email - 验证邮箱验证码
|
||||||
* - POST /auth/resend-email-verification - 重新发送邮箱验证码
|
* - POST /auth/resend-email-verification - 重新发送邮箱验证码
|
||||||
*
|
*
|
||||||
* 最近修改:
|
|
||||||
* - 2026-01-12: 代码分离 - 从login.controller.ts中分离注册相关功能
|
|
||||||
*
|
|
||||||
* @author moyin
|
* @author moyin
|
||||||
* @version 1.0.0
|
* @version 2.0.0
|
||||||
* @since 2026-01-12
|
* @since 2026-01-14
|
||||||
* @lastModified 2026-01-12
|
* @lastModified 2026-01-14
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Controller, Post, Body, HttpCode, HttpStatus, ValidationPipe, UsePipes, Logger, Res } from '@nestjs/common';
|
import {
|
||||||
import { ApiTags, ApiOperation, ApiResponse as SwaggerApiResponse, ApiBody } from '@nestjs/swagger';
|
Controller,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
HttpStatus,
|
||||||
|
ValidationPipe,
|
||||||
|
UsePipes,
|
||||||
|
Logger,
|
||||||
|
Res
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ApiTags,
|
||||||
|
ApiOperation,
|
||||||
|
ApiResponse as SwaggerApiResponse,
|
||||||
|
ApiBody
|
||||||
|
} from '@nestjs/swagger';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { RegisterService, ApiResponse, RegisterResponse } from './register.service';
|
import { RegisterService } from '../../business/auth/register.service';
|
||||||
import { RegisterDto, EmailVerificationDto, SendEmailVerificationDto } from './login.dto';
|
import {
|
||||||
|
RegisterDto,
|
||||||
|
EmailVerificationDto,
|
||||||
|
SendEmailVerificationDto
|
||||||
|
} from './dto/login.dto';
|
||||||
import {
|
import {
|
||||||
RegisterResponseDto,
|
RegisterResponseDto,
|
||||||
CommonResponseDto,
|
CommonResponseDto,
|
||||||
TestModeEmailVerificationResponseDto,
|
TestModeEmailVerificationResponseDto,
|
||||||
SuccessEmailVerificationResponseDto
|
SuccessEmailVerificationResponseDto
|
||||||
} from './login_response.dto';
|
} from './dto/login_response.dto';
|
||||||
import { Throttle, ThrottlePresets } from '../../core/security_core/throttle.decorator';
|
import { Throttle, ThrottlePresets } from '../../core/security_core/throttle.decorator';
|
||||||
import { Timeout, TimeoutPresets } from '../../core/security_core/timeout.decorator';
|
import { Timeout, TimeoutPresets } from '../../core/security_core/timeout.decorator';
|
||||||
|
|
||||||
@@ -61,10 +83,10 @@ export class RegisterController {
|
|||||||
/**
|
/**
|
||||||
* 通用响应处理方法
|
* 通用响应处理方法
|
||||||
*
|
*
|
||||||
* 业务逻辑:
|
* 职责:
|
||||||
* 1. 根据业务结果设置HTTP状态码
|
* - 根据业务结果设置HTTP状态码
|
||||||
* 2. 处理不同类型的错误响应
|
* - 处理不同类型的错误响应
|
||||||
* 3. 统一响应格式和错误处理
|
* - 统一响应格式和错误处理
|
||||||
*
|
*
|
||||||
* @param result 业务服务返回的结果
|
* @param result 业务服务返回的结果
|
||||||
* @param res Express响应对象
|
* @param res Express响应对象
|
||||||
@@ -77,7 +99,6 @@ export class RegisterController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据错误代码获取状态码
|
|
||||||
const statusCode = this.getErrorStatusCode(result);
|
const statusCode = this.getErrorStatusCode(result);
|
||||||
res.status(statusCode).json(result);
|
res.status(statusCode).json(result);
|
||||||
}
|
}
|
||||||
@@ -90,12 +111,10 @@ export class RegisterController {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private getErrorStatusCode(result: any): HttpStatus {
|
private getErrorStatusCode(result: any): HttpStatus {
|
||||||
// 优先使用错误代码映射
|
|
||||||
if (result.error_code && ERROR_STATUS_MAP[result.error_code as keyof typeof ERROR_STATUS_MAP]) {
|
if (result.error_code && ERROR_STATUS_MAP[result.error_code as keyof typeof ERROR_STATUS_MAP]) {
|
||||||
return ERROR_STATUS_MAP[result.error_code as keyof typeof ERROR_STATUS_MAP];
|
return ERROR_STATUS_MAP[result.error_code as keyof typeof ERROR_STATUS_MAP];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据消息内容判断
|
|
||||||
if (result.message?.includes('已存在') || result.message?.includes('已被注册')) {
|
if (result.message?.includes('已存在') || result.message?.includes('已被注册')) {
|
||||||
return HttpStatus.CONFLICT;
|
return HttpStatus.CONFLICT;
|
||||||
}
|
}
|
||||||
@@ -104,7 +123,6 @@ export class RegisterController {
|
|||||||
return HttpStatus.NOT_FOUND;
|
return HttpStatus.NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认返回400
|
|
||||||
return HttpStatus.BAD_REQUEST;
|
return HttpStatus.BAD_REQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +130,11 @@ export class RegisterController {
|
|||||||
* 用户注册
|
* 用户注册
|
||||||
*
|
*
|
||||||
* @param registerDto 注册数据
|
* @param registerDto 注册数据
|
||||||
* @returns 注册结果
|
* @param res Express响应对象
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '用户注册',
|
summary: '用户注册',
|
||||||
description: '创建新用户账户。如果提供邮箱,需要先调用发送验证码接口获取验证码,然后在注册时提供验证码进行验证。'
|
description: '创建新用户账户'
|
||||||
})
|
})
|
||||||
@ApiBody({ type: RegisterDto })
|
@ApiBody({ type: RegisterDto })
|
||||||
@SwaggerApiResponse({
|
@SwaggerApiResponse({
|
||||||
@@ -158,7 +176,6 @@ export class RegisterController {
|
|||||||
*
|
*
|
||||||
* @param sendEmailVerificationDto 发送验证码数据
|
* @param sendEmailVerificationDto 发送验证码数据
|
||||||
* @param res Express响应对象
|
* @param res Express响应对象
|
||||||
* @returns 发送结果
|
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '发送邮箱验证码',
|
summary: '发送邮箱验证码',
|
||||||
@@ -199,7 +216,7 @@ export class RegisterController {
|
|||||||
* 验证邮箱验证码
|
* 验证邮箱验证码
|
||||||
*
|
*
|
||||||
* @param emailVerificationDto 邮箱验证数据
|
* @param emailVerificationDto 邮箱验证数据
|
||||||
* @returns 验证结果
|
* @param res Express响应对象
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '验证邮箱验证码',
|
summary: '验证邮箱验证码',
|
||||||
@@ -231,7 +248,6 @@ export class RegisterController {
|
|||||||
*
|
*
|
||||||
* @param sendEmailVerificationDto 发送验证码数据
|
* @param sendEmailVerificationDto 发送验证码数据
|
||||||
* @param res Express响应对象
|
* @param res Express响应对象
|
||||||
* @returns 发送结果
|
|
||||||
*/
|
*/
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '重新发送邮箱验证码',
|
summary: '重新发送邮箱验证码',
|
||||||
Reference in New Issue
Block a user