diff --git a/src/app.module.ts b/src/app.module.ts index 62a3cf3..3a5e5c8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,7 +7,7 @@ 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 { AuthModule } from './business/auth/auth.module'; +import { AuthGatewayModule } from './gateway/auth/auth.gateway.module'; import { ZulipModule } from './business/zulip/zulip.module'; import { RedisModule } from './core/redis/redis.module'; import { AdminModule } from './business/admin/admin.module'; @@ -69,7 +69,7 @@ function isDatabaseConfigured(): boolean { // 根据数据库配置选择用户模块模式 isDatabaseConfigured() ? UsersModule.forDatabase() : UsersModule.forMemory(), LoginCoreModule, - AuthModule, + AuthGatewayModule, // 使用网关层模块替代业务层模块 ZulipModule, UserMgmtModule, AdminModule, diff --git a/src/business/auth/index.ts b/src/business/auth/index.ts index cf74d65..6499524 100644 --- a/src/business/auth/index.ts +++ b/src/business/auth/index.ts @@ -2,38 +2,30 @@ * 用户认证业务模块导出 * * 功能概述: - * - 用户登录和注册 + * - 用户登录和注册业务逻辑 * - GitHub OAuth集成 * - 密码管理(忘记密码、重置密码、修改密码) * - 邮箱验证功能 * - JWT Token管理 * * 职责分离: - * - 专注于模块导出和接口暴露 - * - 提供统一的模块入口点 + * - 专注于业务层模块导出 + * - 提供统一的业务服务入口点 * - 简化外部模块的引用方式 * * 最近修改: + * - 2026-01-14: 架构重构 - 移除Controller和DTO导出(已移至Gateway层)(修改者: moyin) * - 2026-01-07: 代码规范优化 - 文件夹扁平化,移除单文件文件夹结构 - * - 2026-01-07: 代码规范优化 - 更新注释规范 * * @author moyin - * @version 1.0.2 + * @version 2.0.0 * @since 2025-12-17 - * @lastModified 2026-01-07 + * @lastModified 2026-01-14 */ // 模块 export * from './auth.module'; -// 控制器 -export * from './login.controller'; -export * from './register.controller'; - -// 服务 +// 服务(业务层) export { LoginService } from './login.service'; -export { RegisterService } from './register.service'; - -// DTO -export * from './login.dto'; -export * from './login_response.dto'; \ No newline at end of file +export { RegisterService } from './register.service'; \ No newline at end of file diff --git a/src/business/location_broadcast/controllers/location_broadcast.controller.ts b/src/business/location_broadcast/controllers/location_broadcast.controller.ts index 75392fd..1f2c182 100644 --- a/src/business/location_broadcast/controllers/location_broadcast.controller.ts +++ b/src/business/location_broadcast/controllers/location_broadcast.controller.ts @@ -51,8 +51,8 @@ import { ApiBearerAuth, ApiBody, } from '@nestjs/swagger'; -import { JwtAuthGuard } from '../../auth/jwt_auth.guard'; -import { CurrentUser } from '../../auth/current_user.decorator'; +import { JwtAuthGuard } from '../../../gateway/auth/jwt_auth.guard'; +import { CurrentUser } from '../../../gateway/auth/current_user.decorator'; import { JwtPayload } from '../../../core/login_core/login_core.service'; // 导入业务服务 diff --git a/src/business/location_broadcast/location_broadcast.controller.ts b/src/business/location_broadcast/location_broadcast.controller.ts index 0835fce..30ecb8a 100644 --- a/src/business/location_broadcast/location_broadcast.controller.ts +++ b/src/business/location_broadcast/location_broadcast.controller.ts @@ -51,8 +51,8 @@ import { ApiBearerAuth, ApiBody, } from '@nestjs/swagger'; -import { JwtAuthGuard, AuthenticatedRequest } from '../auth/jwt_auth.guard'; -import { CurrentUser } from '../auth/current_user.decorator'; +import { JwtAuthGuard, AuthenticatedRequest } from '../../gateway/auth/jwt_auth.guard'; +import { CurrentUser } from '../../gateway/auth/current_user.decorator'; import { JwtPayload } from '../../core/login_core/login_core.service'; // 导入业务服务 diff --git a/src/business/notice/notice.controller.ts b/src/business/notice/notice.controller.ts index 5b809c1..96e99de 100644 --- a/src/business/notice/notice.controller.ts +++ b/src/business/notice/notice.controller.ts @@ -13,8 +13,8 @@ import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagg import { NoticeService } from './notice.service'; import { CreateNoticeDto } from './dto/create-notice.dto'; import { NoticeResponseDto } from './dto/notice-response.dto'; -import { JwtAuthGuard } from '../auth/jwt_auth.guard'; -import { CurrentUser } from '../auth/current_user.decorator'; +import { JwtAuthGuard } from '../../gateway/auth/jwt_auth.guard'; +import { CurrentUser } from '../../gateway/auth/current_user.decorator'; @ApiTags('通知管理') @Controller('api/notices') diff --git a/src/business/zulip/chat.controller.spec.ts b/src/business/zulip/chat.controller.spec.ts index 4c19cec..87f0374 100644 --- a/src/business/zulip/chat.controller.spec.ts +++ b/src/business/zulip/chat.controller.spec.ts @@ -29,7 +29,7 @@ import { ChatController } from './chat.controller'; import { ZulipService } from './zulip.service'; import { MessageFilterService } from './services/message_filter.service'; import { CleanWebSocketGateway } from './clean_websocket.gateway'; -import { JwtAuthGuard } from '../auth/jwt_auth.guard'; +import { JwtAuthGuard } from '../../gateway/auth/jwt_auth.guard'; // Mock JwtAuthGuard const mockJwtAuthGuard = { diff --git a/src/business/zulip/chat.controller.ts b/src/business/zulip/chat.controller.ts index d83f8f2..a352751 100644 --- a/src/business/zulip/chat.controller.ts +++ b/src/business/zulip/chat.controller.ts @@ -40,7 +40,7 @@ import { ApiBearerAuth, ApiQuery, } from '@nestjs/swagger'; -import { JwtAuthGuard } from '../auth/jwt_auth.guard'; +import { JwtAuthGuard } from '../../gateway/auth/jwt_auth.guard'; import { ZulipService } from './zulip.service'; import { CleanWebSocketGateway } from './clean_websocket.gateway'; import { diff --git a/src/business/zulip/zulip_accounts.controller.spec.ts b/src/business/zulip/zulip_accounts.controller.spec.ts index 36bbceb..8b04671 100644 --- a/src/business/zulip/zulip_accounts.controller.spec.ts +++ b/src/business/zulip/zulip_accounts.controller.spec.ts @@ -26,7 +26,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { HttpException, HttpStatus } from '@nestjs/common'; 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 { ZulipAccountsBusinessService } from './services/zulip_accounts_business.service'; diff --git a/src/business/zulip/zulip_accounts.controller.ts b/src/business/zulip/zulip_accounts.controller.ts index 9cc6c13..021065c 100644 --- a/src/business/zulip/zulip_accounts.controller.ts +++ b/src/business/zulip/zulip_accounts.controller.ts @@ -50,7 +50,7 @@ import { ApiQuery, } from '@nestjs/swagger'; 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 { ZulipAccountsMemoryService } from '../../core/db/zulip_accounts/zulip_accounts_memory.service'; import { AppLoggerService } from '../../core/utils/logger/logger.service'; diff --git a/src/gateway/auth/README.md b/src/gateway/auth/README.md new file mode 100644 index 0000000..fd655d6 --- /dev/null +++ b/src/gateway/auth/README.md @@ -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 { + 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 { + // 验证用户 + 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 +处理用户登录请求,支持用户名、邮箱或手机号多种方式登录。 + +#### githubOAuth(githubDto: GitHubOAuthDto, res: Response): Promise +处理GitHub OAuth登录请求,支持使用GitHub账户登录或注册。 + +#### refreshToken(refreshTokenDto: RefreshTokenDto, res: Response): Promise +刷新访问令牌,使用有效的刷新令牌生成新的访问令牌。 + +#### forgotPassword(forgotPasswordDto: ForgotPasswordDto, res: Response): Promise +发送密码重置验证码到用户邮箱或手机。 + +#### resetPassword(resetPasswordDto: ResetPasswordDto, res: Response): Promise +使用验证码重置用户密码。 + +#### changePassword(changePasswordDto: ChangePasswordDto, res: Response): Promise +修改用户密码,需要提供旧密码验证。 + +#### verificationCodeLogin(verificationCodeLoginDto: VerificationCodeLoginDto, res: Response): Promise +使用邮箱或手机号和验证码进行登录,无需密码。 + +#### sendLoginVerificationCode(sendLoginVerificationCodeDto: SendLoginVerificationCodeDto, res: Response): Promise +向用户邮箱或手机发送登录验证码。 + +#### debugVerificationCode(sendEmailVerificationDto: SendEmailVerificationDto, res: Response): Promise +获取验证码的详细调试信息,仅用于开发环境。 + +### RegisterController + +#### register(registerDto: RegisterDto, res: Response): Promise +处理用户注册请求,创建新用户账户。 + +#### sendEmailVerification(sendEmailVerificationDto: SendEmailVerificationDto, res: Response): Promise +向指定邮箱发送验证码。 + +#### verifyEmail(emailVerificationDto: EmailVerificationDto, res: Response): Promise +使用验证码验证邮箱。 + +#### resendEmailVerification(sendEmailVerificationDto: SendEmailVerificationDto, res: Response): Promise +重新向指定邮箱发送验证码。 + +### JwtAuthGuard + +#### canActivate(context: ExecutionContext): Promise +验证请求中的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 { + 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层实现 diff --git a/src/gateway/auth/auth.gateway.module.ts b/src/gateway/auth/auth.gateway.module.ts new file mode 100644 index 0000000..c3b08b1 --- /dev/null +++ b/src/gateway/auth/auth.gateway.module.ts @@ -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 {} diff --git a/src/business/auth/current_user.decorator.ts b/src/gateway/auth/current_user.decorator.ts similarity index 100% rename from src/business/auth/current_user.decorator.ts rename to src/gateway/auth/current_user.decorator.ts diff --git a/src/business/auth/login.dto.ts b/src/gateway/auth/dto/login.dto.ts similarity index 100% rename from src/business/auth/login.dto.ts rename to src/gateway/auth/dto/login.dto.ts diff --git a/src/business/auth/login_response.dto.ts b/src/gateway/auth/dto/login_response.dto.ts similarity index 100% rename from src/business/auth/login_response.dto.ts rename to src/gateway/auth/dto/login_response.dto.ts diff --git a/src/business/auth/jwt_auth.guard.spec.ts b/src/gateway/auth/jwt_auth.guard.spec.ts similarity index 97% rename from src/business/auth/jwt_auth.guard.spec.ts rename to src/gateway/auth/jwt_auth.guard.spec.ts index bd49168..abb26f7 100644 --- a/src/business/auth/jwt_auth.guard.spec.ts +++ b/src/gateway/auth/jwt_auth.guard.spec.ts @@ -7,12 +7,13 @@ * - 测试认证失败的异常处理 * * 最近修改: + * - 2026-01-14: 架构重构 - 从business层移动到gateway层 (修改者: moyin) * - 2026-01-12: 代码规范优化 - 创建缺失的守卫测试文件 (修改者: moyin) * * @author moyin - * @version 1.0.0 + * @version 1.1.0 * @since 2026-01-12 - * @lastModified 2026-01-12 + * @lastModified 2026-01-14 */ import { Test, TestingModule } from '@nestjs/testing'; @@ -160,4 +161,4 @@ describe('JwtAuthGuard', () => { expect(loginCoreService.verifyToken).not.toHaveBeenCalled(); }); }); -}); \ No newline at end of file +}); diff --git a/src/business/auth/jwt_auth.guard.ts b/src/gateway/auth/jwt_auth.guard.ts similarity index 100% rename from src/business/auth/jwt_auth.guard.ts rename to src/gateway/auth/jwt_auth.guard.ts diff --git a/src/business/auth/jwt_usage_example.ts b/src/gateway/auth/jwt_usage_example.ts similarity index 86% rename from src/business/auth/jwt_usage_example.ts rename to src/gateway/auth/jwt_usage_example.ts index f1b34ee..4be4509 100644 --- a/src/business/auth/jwt_usage_example.ts +++ b/src/gateway/auth/jwt_usage_example.ts @@ -1,6 +1,8 @@ /** * JWT 使用示例 * + * 架构层级:Gateway Layer(网关层) + * * 功能描述: * - 展示如何在控制器中使用 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: 代码规范优化 - 文件重命名为snake_case格式,更新注释规范 * * @author moyin - * @version 1.0.2 + * @version 1.2.0 * @since 2025-01-05 - * @lastModified 2026-01-07 + * @lastModified 2026-01-14 */ import { Controller, Get, UseGuards, Post, Body } from '@nestjs/common'; diff --git a/src/business/auth/login.controller.spec.ts b/src/gateway/auth/login.controller.spec.ts similarity index 96% rename from src/business/auth/login.controller.spec.ts rename to src/gateway/auth/login.controller.spec.ts index 8de3f8a..6396f51 100644 --- a/src/business/auth/login.controller.spec.ts +++ b/src/gateway/auth/login.controller.spec.ts @@ -7,19 +7,20 @@ * - 测试错误处理和异常情况 * * 最近修改: + * - 2026-01-14: 架构重构 - 从business层移动到gateway层 (修改者: moyin) * - 2026-01-12: 代码规范优化 - 创建缺失的控制器测试文件 (修改者: moyin) * * @author moyin - * @version 1.0.0 + * @version 1.1.0 * @since 2026-01-12 - * @lastModified 2026-01-12 + * @lastModified 2026-01-14 */ import { Test, TestingModule } from '@nestjs/testing'; import { Response } from 'express'; import { HttpStatus } from '@nestjs/common'; import { LoginController } from './login.controller'; -import { LoginService } from './login.service'; +import { LoginService } from '../../business/auth/login.service'; describe('LoginController', () => { let controller: LoginController; @@ -205,4 +206,4 @@ describe('LoginController', () => { expect(mockResponse.json).toHaveBeenCalledWith(mockResult); }); }); -}); \ No newline at end of file +}); diff --git a/src/business/auth/login.controller.ts b/src/gateway/auth/login.controller.ts similarity index 70% rename from src/business/auth/login.controller.ts rename to src/gateway/auth/login.controller.ts index 8aeb9e0..c53f016 100644 --- a/src/business/auth/login.controller.ts +++ b/src/gateway/auth/login.controller.ts @@ -1,47 +1,79 @@ /** - * 登录控制器 + * 登录网关控制器 + * + * 架构层级:Gateway Layer(网关层) * * 功能描述: * - 处理登录相关的HTTP请求和响应 * - 提供RESTful API接口 * - 数据验证和格式化 + * - 协议处理和错误响应 * * 职责分离: - * - 专注于HTTP请求处理和响应格式化 - * - 调用业务服务完成具体功能 + * - 专注于HTTP协议处理和请求响应 + * - 调用业务层服务完成具体功能 * - 处理API文档和参数验证 + * - 不包含业务逻辑,只做数据转换和路由 + * + * 依赖关系: + * - 依赖 Business Layer 的 LoginService + * - 使用 DTO 进行数据验证 + * - 使用 Guard 进行认证保护 * * API端点: * - POST /auth/login - 用户登录 - * - POST /auth/register - 用户注册 * - POST /auth/github - GitHub OAuth登录 * - POST /auth/forgot-password - 发送密码重置验证码 * - POST /auth/reset-password - 重置密码 * - PUT /auth/change-password - 修改密码 * - POST /auth/refresh-token - 刷新访问令牌 - * - * 最近修改: - * - 2026-01-07: 代码规范优化 - 文件夹扁平化,移除单文件文件夹结构 - * - 2026-01-07: 代码规范优化 - 更新注释规范,修正文件引用路径 + * - POST /auth/verification-code-login - 验证码登录 + * - POST /auth/send-login-verification-code - 发送登录验证码 * * @author moyin - * @version 1.0.2 - * @since 2025-12-17 - * @lastModified 2026-01-07 + * @version 2.0.0 + * @since 2026-01-14 + * @lastModified 2026-01-14 */ -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 { + 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, GitHubOAuthDto, ForgotPasswordDto, ResetPasswordDto, ChangePasswordDto, VerificationCodeLoginDto, SendLoginVerificationCodeDto, RefreshTokenDto, SendEmailVerificationDto } from './login.dto'; +import { LoginService } from '../../business/auth/login.service'; +import { + LoginDto, + GitHubOAuthDto, + ForgotPasswordDto, + ResetPasswordDto, + ChangePasswordDto, + VerificationCodeLoginDto, + SendLoginVerificationCodeDto, + RefreshTokenDto, + SendEmailVerificationDto +} from './dto/login.dto'; import { LoginResponseDto, GitHubOAuthResponseDto, ForgotPasswordResponseDto, CommonResponseDto, RefreshTokenResponseDto -} from './login_response.dto'; +} from './dto/login_response.dto'; import { Throttle, ThrottlePresets } from '../../core/security_core/throttle.decorator'; import { Timeout, TimeoutPresets } from '../../core/security_core/timeout.decorator'; @@ -68,10 +100,10 @@ export class LoginController { /** * 通用响应处理方法 * - * 业务逻辑: - * 1. 根据业务结果设置HTTP状态码 - * 2. 处理不同类型的错误响应 - * 3. 统一响应格式和错误处理 + * 职责: + * - 根据业务结果设置HTTP状态码 + * - 处理不同类型的错误响应 + * - 统一响应格式和错误处理 * * @param result 业务服务返回的结果 * @param res Express响应对象 @@ -84,7 +116,6 @@ export class LoginController { return; } - // 根据错误代码获取状态码 const statusCode = this.getErrorStatusCode(result); res.status(statusCode).json(result); } @@ -97,12 +128,10 @@ export class LoginController { * @private */ private getErrorStatusCode(result: any): HttpStatus { - // 优先使用错误代码映射 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]; } - // 根据消息内容判断 if (result.message?.includes('已存在') || result.message?.includes('已被注册')) { return HttpStatus.CONFLICT; } @@ -115,7 +144,6 @@ export class LoginController { return HttpStatus.NOT_FOUND; } - // 默认返回400 return HttpStatus.BAD_REQUEST; } @@ -123,7 +151,7 @@ export class LoginController { * 用户登录 * * @param loginDto 登录数据 - * @returns 登录结果 + * @param res Express响应对象 */ @ApiOperation({ summary: '用户登录', @@ -168,7 +196,7 @@ export class LoginController { * GitHub OAuth登录 * * @param githubDto GitHub OAuth数据 - * @returns 登录结果 + * @param res Express响应对象 */ @ApiOperation({ summary: 'GitHub OAuth登录', @@ -207,7 +235,6 @@ export class LoginController { * * @param forgotPasswordDto 忘记密码数据 * @param res Express响应对象 - * @returns 发送结果 */ @ApiOperation({ summary: '发送密码重置验证码', @@ -251,7 +278,7 @@ export class LoginController { * 重置密码 * * @param resetPasswordDto 重置密码数据 - * @returns 重置结果 + * @param res Express响应对象 */ @ApiOperation({ summary: '重置密码', @@ -292,7 +319,7 @@ export class LoginController { * 修改密码 * * @param changePasswordDto 修改密码数据 - * @returns 修改结果 + * @param res Express响应对象 */ @ApiOperation({ summary: '修改密码', @@ -315,8 +342,6 @@ export class LoginController { @Put('change-password') @UsePipes(new ValidationPipe({ transform: true })) async changePassword(@Body() changePasswordDto: ChangePasswordDto, @Res() res: Response): Promise { - // 实际应用中应从JWT令牌中获取用户ID - // 这里为了演示,使用请求体中的用户ID const userId = BigInt(changePasswordDto.user_id); const result = await this.loginService.changePassword( @@ -332,7 +357,7 @@ export class LoginController { * 验证码登录 * * @param verificationCodeLoginDto 验证码登录数据 - * @returns 登录结果 + * @param res Express响应对象 */ @ApiOperation({ summary: '验证码登录', @@ -359,11 +384,16 @@ export class LoginController { @Post('verification-code-login') @HttpCode(HttpStatus.OK) @UsePipes(new ValidationPipe({ transform: true })) - async verificationCodeLogin(@Body() verificationCodeLoginDto: VerificationCodeLoginDto): Promise> { - return await this.loginService.verificationCodeLogin({ + async verificationCodeLogin( + @Body() verificationCodeLoginDto: VerificationCodeLoginDto, + @Res() res: Response + ): Promise { + const result = await this.loginService.verificationCodeLogin({ identifier: verificationCodeLoginDto.identifier, verificationCode: verificationCodeLoginDto.verification_code }); + + this.handleResponse(result, res); } /** @@ -371,11 +401,10 @@ export class LoginController { * * @param sendLoginVerificationCodeDto 发送验证码数据 * @param res Express响应对象 - * @returns 发送结果 */ @ApiOperation({ summary: '发送登录验证码', - description: '向用户邮箱或手机发送登录验证码。邮件使用专门的登录验证码模板,内容明确标识为登录验证而非密码重置。' + description: '向用户邮箱或手机发送登录验证码' }) @ApiBody({ type: SendLoginVerificationCodeDto }) @SwaggerApiResponse({ @@ -410,63 +439,15 @@ export class LoginController { 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 { - 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 { - // 注入ThrottleGuard并清除记录 - // 这里需要通过依赖注入获取ThrottleGuard实例 - res.status(HttpStatus.OK).json({ - success: true, - message: '限流记录已清除' - }); - } - /** * 刷新访问令牌 * - * 功能描述: - * 使用有效的刷新令牌生成新的访问令牌,实现无感知的令牌续期 - * - * 业务逻辑: - * 1. 验证刷新令牌的有效性和格式 - * 2. 检查用户状态是否正常 - * 3. 生成新的JWT令牌对 - * 4. 返回新的访问令牌和刷新令牌 - * * @param refreshTokenDto 刷新令牌数据 * @param res Express响应对象 - * @returns 新的令牌对 */ @ApiOperation({ summary: '刷新访问令牌', - description: '使用有效的刷新令牌生成新的访问令牌,实现无感知的令牌续期。建议在访问令牌即将过期时调用此接口。' + description: '使用有效的刷新令牌生成新的访问令牌' }) @ApiBody({ type: RefreshTokenDto }) @SwaggerApiResponse({ @@ -495,73 +476,28 @@ export class LoginController { @Post('refresh-token') @UsePipes(new ValidationPipe({ transform: true })) async refreshToken(@Body() refreshTokenDto: RefreshTokenDto, @Res() res: Response): Promise { - const startTime = Date.now(); - - try { - this.logRefreshTokenStart(); - const result = await this.loginService.refreshAccessToken(refreshTokenDto.refresh_token); - this.handleRefreshTokenResponse(result, res, startTime); - } catch (error) { - this.handleRefreshTokenError(error, res, startTime); - } + const result = await this.loginService.refreshAccessToken(refreshTokenDto.refresh_token); + this.handleResponse(result, res); } /** - * 记录令牌刷新开始日志 - * @private + * 调试验证码信息(仅开发环境) + * + * @param sendEmailVerificationDto 邮箱信息 + * @param res Express响应对象 */ - private logRefreshTokenStart(): void { - this.logger.log('令牌刷新请求', { - operation: 'refreshToken', - timestamp: new Date().toISOString(), - }); + @ApiOperation({ + summary: '调试验证码信息', + description: '获取验证码的详细调试信息(仅开发环境)' + }) + @ApiBody({ type: SendEmailVerificationDto }) + @Post('debug-verification-code') + @UsePipes(new ValidationPipe({ transform: true })) + async debugVerificationCode( + @Body() sendEmailVerificationDto: SendEmailVerificationDto, + @Res() res: Response + ): Promise { + const result = await this.loginService.debugVerificationCode(sendEmailVerificationDto.email); + res.status(HttpStatus.OK).json(result); } - - /** - * 处理令牌刷新响应 - * @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); - } - } - - /** - * 处理令牌刷新异常 - * @private - */ - private handleRefreshTokenError(error: unknown, res: Response, startTime: number): void { - const duration = Date.now() - startTime; - const err = error as Error; - - this.logger.error('令牌刷新异常', { - operation: 'refreshToken', - error: err.message, - duration, - timestamp: new Date().toISOString(), - }, err.stack); - - res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - success: false, - message: '服务器内部错误', - error_code: 'INTERNAL_SERVER_ERROR' - }); - } -} \ No newline at end of file +} diff --git a/src/business/auth/register.controller.spec.ts b/src/gateway/auth/register.controller.spec.ts similarity index 97% rename from src/business/auth/register.controller.spec.ts rename to src/gateway/auth/register.controller.spec.ts index 6702daf..c8be301 100644 --- a/src/business/auth/register.controller.spec.ts +++ b/src/gateway/auth/register.controller.spec.ts @@ -7,19 +7,20 @@ * - 测试邮箱验证流程 * * 最近修改: + * - 2026-01-14: 架构重构 - 从business层移动到gateway层 (修改者: moyin) * - 2026-01-12: 代码规范优化 - 创建缺失的控制器测试文件 (修改者: moyin) * * @author moyin - * @version 1.0.0 + * @version 1.1.0 * @since 2026-01-12 - * @lastModified 2026-01-12 + * @lastModified 2026-01-14 */ import { Test, TestingModule } from '@nestjs/testing'; import { Response } from 'express'; import { HttpStatus } from '@nestjs/common'; import { RegisterController } from './register.controller'; -import { RegisterService } from './register.service'; +import { RegisterService } from '../../business/auth/register.service'; describe('RegisterController', () => { let controller: RegisterController; @@ -227,4 +228,4 @@ describe('RegisterController', () => { expect(mockResponse.json).toHaveBeenCalledWith(mockResult); }); }); -}); \ No newline at end of file +}); diff --git a/src/business/auth/register.controller.ts b/src/gateway/auth/register.controller.ts similarity index 84% rename from src/business/auth/register.controller.ts rename to src/gateway/auth/register.controller.ts index 60759a1..c70f69c 100644 --- a/src/business/auth/register.controller.ts +++ b/src/gateway/auth/register.controller.ts @@ -1,5 +1,7 @@ /** - * 注册控制器 + * 注册网关控制器 + * + * 架构层级:Gateway Layer(网关层) * * 功能描述: * - 处理用户注册相关的HTTP请求和响应 @@ -8,9 +10,14 @@ * - 邮箱验证功能 * * 职责分离: - * - 专注于HTTP请求处理和响应格式化 - * - 调用注册业务服务完成具体功能 + * - 专注于HTTP协议处理和请求响应 + * - 调用业务层服务完成具体功能 * - 处理API文档和参数验证 + * - 不包含业务逻辑,只做数据转换和路由 + * + * 依赖关系: + * - 依赖 Business Layer 的 RegisterService + * - 使用 DTO 进行数据验证 * * API端点: * - POST /auth/register - 用户注册 @@ -18,26 +25,41 @@ * - POST /auth/verify-email - 验证邮箱验证码 * - POST /auth/resend-email-verification - 重新发送邮箱验证码 * - * 最近修改: - * - 2026-01-12: 代码分离 - 从login.controller.ts中分离注册相关功能 - * * @author moyin - * @version 1.0.0 - * @since 2026-01-12 - * @lastModified 2026-01-12 + * @version 2.0.0 + * @since 2026-01-14 + * @lastModified 2026-01-14 */ -import { Controller, Post, Body, HttpCode, HttpStatus, ValidationPipe, UsePipes, Logger, Res } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiResponse as SwaggerApiResponse, ApiBody } from '@nestjs/swagger'; +import { + 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 { RegisterService, ApiResponse, RegisterResponse } from './register.service'; -import { RegisterDto, EmailVerificationDto, SendEmailVerificationDto } from './login.dto'; +import { RegisterService } from '../../business/auth/register.service'; +import { + RegisterDto, + EmailVerificationDto, + SendEmailVerificationDto +} from './dto/login.dto'; import { RegisterResponseDto, CommonResponseDto, TestModeEmailVerificationResponseDto, SuccessEmailVerificationResponseDto -} from './login_response.dto'; +} from './dto/login_response.dto'; import { Throttle, ThrottlePresets } from '../../core/security_core/throttle.decorator'; import { Timeout, TimeoutPresets } from '../../core/security_core/timeout.decorator'; @@ -61,10 +83,10 @@ export class RegisterController { /** * 通用响应处理方法 * - * 业务逻辑: - * 1. 根据业务结果设置HTTP状态码 - * 2. 处理不同类型的错误响应 - * 3. 统一响应格式和错误处理 + * 职责: + * - 根据业务结果设置HTTP状态码 + * - 处理不同类型的错误响应 + * - 统一响应格式和错误处理 * * @param result 业务服务返回的结果 * @param res Express响应对象 @@ -77,7 +99,6 @@ export class RegisterController { return; } - // 根据错误代码获取状态码 const statusCode = this.getErrorStatusCode(result); res.status(statusCode).json(result); } @@ -90,12 +111,10 @@ export class RegisterController { * @private */ private getErrorStatusCode(result: any): HttpStatus { - // 优先使用错误代码映射 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]; } - // 根据消息内容判断 if (result.message?.includes('已存在') || result.message?.includes('已被注册')) { return HttpStatus.CONFLICT; } @@ -104,7 +123,6 @@ export class RegisterController { return HttpStatus.NOT_FOUND; } - // 默认返回400 return HttpStatus.BAD_REQUEST; } @@ -112,11 +130,11 @@ export class RegisterController { * 用户注册 * * @param registerDto 注册数据 - * @returns 注册结果 + * @param res Express响应对象 */ @ApiOperation({ summary: '用户注册', - description: '创建新用户账户。如果提供邮箱,需要先调用发送验证码接口获取验证码,然后在注册时提供验证码进行验证。' + description: '创建新用户账户' }) @ApiBody({ type: RegisterDto }) @SwaggerApiResponse({ @@ -158,7 +176,6 @@ export class RegisterController { * * @param sendEmailVerificationDto 发送验证码数据 * @param res Express响应对象 - * @returns 发送结果 */ @ApiOperation({ summary: '发送邮箱验证码', @@ -199,7 +216,7 @@ export class RegisterController { * 验证邮箱验证码 * * @param emailVerificationDto 邮箱验证数据 - * @returns 验证结果 + * @param res Express响应对象 */ @ApiOperation({ summary: '验证邮箱验证码', @@ -231,7 +248,6 @@ export class RegisterController { * * @param sendEmailVerificationDto 发送验证码数据 * @param res Express响应对象 - * @returns 发送结果 */ @ApiOperation({ summary: '重新发送邮箱验证码', @@ -266,4 +282,4 @@ export class RegisterController { const result = await this.registerService.resendEmailVerification(sendEmailVerificationDto.email); this.handleResponse(result, res); } -} \ No newline at end of file +}