/** * 管理员操作日志拦截器 * * 功能描述: * - 自动拦截管理员操作并记录日志 * - 记录操作前后的数据状态 * - 监控操作性能和错误 * - 支持敏感操作的特殊处理 * * 职责分离: * - 操作拦截:拦截控制器方法的执行 * - 数据捕获:记录请求参数和响应数据 * - 日志记录:调用日志服务记录操作 * - 错误处理:记录操作异常信息 * * 最近修改: * - 2026-01-08: 注释规范优化 - 修正@author字段,更新版本号和修改记录 (修改者: moyin) * - 2026-01-08: 功能新增 - 创建管理员操作日志拦截器 (修改者: assistant) * * @author moyin * @version 1.0.1 * @since 2026-01-08 * @lastModified 2026-01-08 */ import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Observable, throwError } from 'rxjs'; import { tap, catchError } from 'rxjs/operators'; import { AdminOperationLogService } from './admin_operation_log.service'; import { LOG_ADMIN_OPERATION_KEY, LogAdminOperationOptions } from './log_admin_operation.decorator'; import { SENSITIVE_FIELDS } from './admin_constants'; import { extractClientIp, generateRequestId, sanitizeRequestBody } from './admin_utils'; @Injectable() export class AdminOperationLogInterceptor implements NestInterceptor { private readonly logger = new Logger(AdminOperationLogInterceptor.name); constructor( private readonly reflector: Reflector, private readonly logService: AdminOperationLogService, ) {} intercept(context: ExecutionContext, next: CallHandler): Observable { const logOptions = this.reflector.get( LOG_ADMIN_OPERATION_KEY, context.getHandler(), ); // 如果没有日志配置,直接执行 if (!logOptions) { return next.handle(); } const request = context.switchToHttp().getRequest(); const response = context.switchToHttp().getResponse(); const startTime = Date.now(); // 提取请求信息 const adminUser = request.user; const clientIp = extractClientIp(request); const userAgent = request.headers['user-agent'] || 'unknown'; const httpMethodPath = `${request.method} ${request.route?.path || request.url}`; const requestId = generateRequestId(); // 提取请求参数 const requestParams = logOptions.captureRequestParams !== false ? { params: request.params, query: request.query, body: sanitizeRequestBody(request.body) } : undefined; // 提取目标ID(如果存在) const targetId = request.params?.id || request.body?.id || request.query?.id; let beforeData: any = undefined; let operationError: any = null; return next.handle().pipe( tap((responseData) => { // 操作成功,记录日志 this.recordLog({ logOptions, adminUser, clientIp, userAgent, httpMethodPath, requestId, requestParams, targetId, beforeData, afterData: logOptions.captureAfterData !== false ? responseData : undefined, operationResult: 'SUCCESS', durationMs: Date.now() - startTime, affectedRecords: this.extractAffectedRecords(responseData), }); }), catchError((error) => { // 操作失败,记录错误日志 operationError = error; this.recordLog({ logOptions, adminUser, clientIp, userAgent, httpMethodPath, requestId, requestParams, targetId, beforeData, operationResult: 'FAILED', errorMessage: error.message || String(error), errorCode: error.code || error.status || 'UNKNOWN_ERROR', durationMs: Date.now() - startTime, }); return throwError(() => error); }), ); } /** * 记录操作日志 */ private async recordLog(params: { logOptions: LogAdminOperationOptions; adminUser: any; clientIp: string; userAgent: string; httpMethodPath: string; requestId: string; requestParams?: any; targetId?: string; beforeData?: any; afterData?: any; operationResult: 'SUCCESS' | 'FAILED'; errorMessage?: string; errorCode?: string; durationMs: number; affectedRecords?: number; }) { try { await this.logService.createLog({ adminUserId: params.adminUser?.id || 'unknown', adminUsername: params.adminUser?.username || 'unknown', operationType: params.logOptions.operationType, targetType: params.logOptions.targetType, targetId: params.targetId, operationDescription: params.logOptions.description, httpMethodPath: params.httpMethodPath, requestParams: params.requestParams, beforeData: params.beforeData, afterData: params.afterData, operationResult: params.operationResult, errorMessage: params.errorMessage, errorCode: params.errorCode, durationMs: params.durationMs, clientIp: params.clientIp, userAgent: params.userAgent, requestId: params.requestId, isSensitive: params.logOptions.isSensitive || false, affectedRecords: params.affectedRecords || 0, }); } catch (error) { this.logger.error('记录操作日志失败', { error: error instanceof Error ? error.message : String(error), adminUserId: params.adminUser?.id, operationType: params.logOptions.operationType, targetType: params.logOptions.targetType, }); } } /** * 提取影响的记录数量 */ private extractAffectedRecords(responseData: any): number { if (!responseData || typeof responseData !== 'object') { return 0; } // 从响应数据中提取影响的记录数 if (responseData.data) { if (Array.isArray(responseData.data.items)) { return responseData.data.items.length; } if (responseData.data.total !== undefined) { return responseData.data.total; } if (responseData.data.success !== undefined && responseData.data.failed !== undefined) { return responseData.data.success + responseData.data.failed; } } return 1; // 默认为1条记录 } }