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