# Request/Response Flow Architecture ## New Request Flow (After Refactor) ``` ┌─────────────────┐ │ HTTP Client │ │ (Frontend/API) │ └────────┬────────┘ │ │ 1. POST /mgnt/auth/login │ Headers: x-request-id (optional) │ Body: { username, password } │ ▼ ┌─────────────────────────────────────────────────────┐ │ Fastify Server │ └─────────────────────────────────────────────────────┘ │ │ 2. Request enters NestJS pipeline │ ▼ ┌─────────────────────────────────────────────────────┐ │ CorrelationInterceptor (NEW) │ │ • Extract/generate x-request-id │ │ • Attach to req.correlationId │ │ • Add to response headers │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ LoggingInterceptor │ │ • Log: "[uuid] +++ 请求:POST -> /mgnt/auth/login" │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Guards (Executed in Order) │ │ 1. RateLimitGuard (NEW) │ │ • Check IP + endpoint bucket │ │ • Increment counter │ │ • Throw 429 if over limit (10/min) │ │ │ │ 2. LocalAuthGuard │ │ • Validate credentials │ │ • Call AuthService.validateUser() │ │ • Attach user to request │ │ │ │ 3. MfaGuard (if applied) (NEW) │ │ • Check if user.twoFA enabled │ │ • Verify req.mfaVerified === true │ │ • Throw 401 if MFA required but not verified │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Controller Method │ │ @Post('login') │ │ async login(@AuthUser() user, @Req() req) { │ │ return this.authService.login(user, req) │ │ } │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Service Layer │ │ AuthService.login() │ │ • Create login log │ │ • Fetch roles & menus (parallel) │ │ • Generate JWT token │ │ • Update user.jwtToken in DB │ │ • Return: { account, token, avatar, ... } │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ OperationLogInterceptor │ │ • Capture method call (on success/error) │ │ • Log operation to sys_operation_log │ │ • Include req.body, response, correlation ID │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ ResponseInterceptor (UPDATED) │ │ Transform: │ │ { account, token, avatar, ... } │ │ Into: │ │ { │ │ success: true, │ │ code: "OK", │ │ message: "success", │ │ data: { account, token, avatar, ... }, │ │ timestamp: "2025-11-20T12:34:56.789Z" │ │ } │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ LoggingInterceptor │ │ • Log: "[uuid] --- 响应:POST -> /mgnt/auth/login │ │ +45ms" │ └─────────────────────────────────────────────────────┘ │ │ 3. HTTP Response │ Status: 200 OK │ Headers: │ x-request-id: 550e8400-e29b... │ x-correlation-id: 550e8400-e29b... │ Body: │ { success: true, code: "OK", ... } │ ▼ ┌─────────────────┐ │ HTTP Client │ │ (Receives) │ └─────────────────┘ ``` --- ## Error Flow (After Refactor) ``` ┌─────────────────┐ │ HTTP Client │ └────────┬────────┘ │ │ POST /mgnt/auth/login │ { username: "test", password: "wrong" } │ ▼ ... (same interceptors/guards) ... │ ▼ ┌─────────────────────────────────────────────────────┐ │ AuthService.validateUser() │ │ • Compare password │ │ • ❌ Password mismatch │ │ • Throw: new BadRequestException({ │ │ statusCode: 400, │ │ message: '用户名或密码错误' │ │ }) │ └─────────────────────────────────────────────────────┘ │ │ Exception thrown │ ▼ ┌─────────────────────────────────────────────────────┐ │ OperationLogInterceptor (error handler) │ │ • Catch exception │ │ • Call exceptionService.getHttpResponse(error) │ │ • Log to sys_operation_log with status: false │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ HttpExceptionFilter (NEW) │ │ • Catch BadRequestException │ │ • Extract status: 400 (was 200 before!) │ │ • Map to code: "BAD_REQUEST" │ │ • Extract message: "用户名或密码错误" │ │ • Build ApiResponse: │ │ { │ │ success: false, │ │ code: "BAD_REQUEST", │ │ message: "用户名或密码错误", │ │ data: null, │ │ timestamp: "2025-11-20T12:34:56.789Z" │ │ } │ │ • Log: logger.warn("[uuid] POST /mgnt/auth/login │ │ - 400 - 用户名或密码错误") │ │ • response.status(400).send(apiResponse) │ └─────────────────────────────────────────────────────┘ │ │ 4. HTTP Response │ Status: 400 Bad Request (was 200!) │ Headers: x-request-id, x-correlation-id │ Body: { success: false, code: "BAD_REQUEST", ... } │ ▼ ┌─────────────────┐ │ HTTP Client │ │ (Handles 400) │ └─────────────────┘ ``` --- ## Rate Limit Flow (New) ``` ┌─────────────────┐ │ HTTP Client │ │ (IP: 1.2.3.4) │ └────────┬────────┘ │ │ Request #1-10: POST /mgnt/auth/login │ ▼ ┌─────────────────────────────────────────────────────┐ │ RateLimitGuard │ │ │ │ buckets = Map { │ │ "1.2.3.4:POST:/mgnt/auth/login": { │ │ count: 10, │ │ resetAt: timestamp + 60000ms │ │ } │ │ } │ │ │ │ • Check bucket count │ │ • count <= 10 → ✅ ALLOW │ └─────────────────────────────────────────────────────┘ │ ▼ ... (continues to controller) ... │ Request #11: POST /mgnt/auth/login │ ▼ ┌─────────────────────────────────────────────────────┐ │ RateLimitGuard │ │ │ │ • Increment: count = 11 │ │ • count > 10 → ❌ BLOCK │ │ • Calculate retryAfter = (resetAt - now) / 1000 │ │ • Throw: new HttpException({ │ │ statusCode: 429, │ │ message: "Too many requests. Please try │ │ again in 45 seconds.", │ │ code: "RATE_LIMITED", │ │ retryAfter: 45 │ │ }, HttpStatus.TOO_MANY_REQUESTS) │ └─────────────────────────────────────────────────────┘ │ │ Exception thrown │ ▼ ┌─────────────────────────────────────────────────────┐ │ HttpExceptionFilter │ │ • Status: 429 │ │ • Code: "RATE_LIMITED" │ │ • Message: "Too many requests..." │ └─────────────────────────────────────────────────────┘ │ │ HTTP 429 Response │ ▼ ┌─────────────────┐ │ HTTP Client │ │ (Rate limited) │ └─────────────────┘ After 60 seconds... │ Request #12: POST /mgnt/auth/login │ ▼ ┌─────────────────────────────────────────────────────┐ │ RateLimitGuard │ │ │ │ • now > bucket.resetAt │ │ • Create new bucket: │ │ { │ │ count: 1, │ │ resetAt: now + 60000ms │ │ } │ │ • count <= 10 → ✅ ALLOW │ └─────────────────────────────────────────────────────┘ ``` --- ## MFA Flow (New) ``` ┌─────────────────┐ │ HTTP Client │ └────────┬────────┘ │ │ 1. POST /mgnt/auth/login │ { username: "admin", password: "correct" } │ ▼ ... (guards pass) ... │ ▼ ┌─────────────────────────────────────────────────────┐ │ AuthService.login2fa() │ │ │ │ • Check: user.twoFA === "encrypted_secret" │ │ • twoFAEnabled = true │ │ • Check: req.mfaVerified === undefined │ │ • ❌ MFA not verified yet │ │ │ │ • Generate MFA stage token: │ │ payload = { │ │ userId, username, roleIds, │ │ stage: 'mfa' // ⚠️ Special flag │ │ } │ │ mfaToken = jwt.sign(payload, { expiresIn: '3m' })│ │ │ │ • Return: │ │ { │ │ twoFARequired: true, │ │ mfaToken, │ │ account, avatar, nick, roleInfo, menus │ │ } │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────┐ │ HTTP Client │ │ • Store mfaToken│ │ • Show 2FA UI │ │ • User enters │ │ TOTP code │ └────────┬────────┘ │ │ 2. POST /mgnt/auth/2fa/verify │ Headers: Authorization: Bearer │ Body: { code: "123456" } │ ▼ ... (JWT guard validates mfaToken) ... │ ▼ ┌─────────────────────────────────────────────────────┐ │ TwoFAService.verifyAtLogin() │ │ │ │ • Decrypt user.twoFA to get secret │ │ • Generate TOTP from secret │ │ • Compare with user input: "123456" │ │ • Check not recently used (prevent replay) │ │ • ✅ Code valid │ │ • Set: req.mfaVerified = true │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ AuthService.login() - Second Call │ │ │ │ • Check: req.mfaVerified === true ✅ │ │ • Generate final token: │ │ payload = { │ │ userId, username, roleIds, │ │ mfa: true // ✅ MFA verified │ │ } │ │ token = jwt.sign(payload, { expiresIn: '12h' }) │ │ │ │ • Update: user.jwtToken = token │ │ • Return: { account, token, avatar, ... } │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────┐ │ HTTP Client │ │ • Store token │ │ • Navigate to │ │ dashboard │ └─────────────────┘ Later request to protected endpoint... │ DELETE /mgnt/users/123 │ Headers: Authorization: Bearer │ ▼ ┌─────────────────────────────────────────────────────┐ │ JwtAuthGuard │ │ • Validate token │ │ • Extract payload: { userId, mfa: true } │ │ • Attach user to request │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ MfaGuard (NEW) │ │ • Check: user.twoFA exists → true │ │ • Check: payload.mfa === true → ✅ PASS │ │ • Allow request │ └─────────────────────────────────────────────────────┘ │ ▼ ... (continues to controller) ... ``` --- ## Configuration Validation Flow (New) ``` ┌─────────────────┐ │ Application │ │ Startup │ └────────┬────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ ConfigModule.forRoot() │ │ • Load .env.mgnt.dev │ │ • Load .env │ │ • Merge with process.env │ │ • Call: validate(config) │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ validateEnvironment(config) │ │ │ │ • Transform to EnvironmentVariables class │ │ • Run class-validator decorators: │ │ - @IsUrl() MYSQL_URL │ │ - @IsUrl() MONGO_URL │ │ - @IsString() JWT_SECRET │ │ - @IsInt() @Min(60) JWT_EXPIRES_IN_SECONDS │ │ - etc. │ │ │ │ • Check validation results │ └─────────────────────────────────────────────────────┘ │ ├─── ✅ All valid │ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Application Continues Startup │ │ • ConfigService populated with validated config │ │ • Type-safe access: configService.get('JWT_SECRET')│ └─────────────────────────────────────────────────────┘ │ ▼ [App Running] │ ├─── ❌ Validation failed │ ▼ ┌─────────────────────────────────────────────────────┐ │ Throw ValidationError │ │ │ │ Error: Environment validation failed: │ │ - JWT_SECRET should not be empty │ │ - MYSQL_URL must be a URL address │ │ - JWT_EXPIRES_IN_SECONDS must be at least 60 │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Application Exits │ │ • Exit code: 1 │ │ • Error logged to console │ │ • ❌ App does NOT start with invalid config │ └─────────────────────────────────────────────────────┘ ``` --- ## Comparison: Before vs After ### Before (Old Flow) ``` Request → Guards → Controller → Service → ResponseInterceptor (wrap in {error, status, data}) → AllExceptionsFilter (always HTTP 200) → Response ``` ### After (New Flow) ``` Request → CorrelationInterceptor (add UUID) → LoggingInterceptor → RateLimitGuard (check limit) → Guards → MfaGuard (if applied) → Controller → Service → OperationLogInterceptor → ResponseInterceptor (wrap in ApiResponse) → LoggingInterceptor → HttpExceptionFilter (preserve status) → Response (with correlation ID headers) ``` --- ## Key Architectural Changes 1. **Request Identification** - Every request has a unique correlation ID - Logged at every stage for traceability 2. **Early Rejection** - Rate limiting happens before authentication - Saves resources on brute force attempts 3. **Layered Security** - Multiple guards can be composed - Each guard has single responsibility 4. **Proper HTTP Semantics** - Status codes match HTTP standards - Clients can handle errors properly - Monitoring tools work correctly 5. **Type Safety** - Generic `ApiResponse` ensures type correctness - Configuration validated at compile time and runtime 6. **Observability** - Correlation IDs enable request tracing - Structured logging with context - Operation logs capture full request/response