/** * 管理员操作日志服务 * * 功能描述: * - 记录管理员的所有数据库操作 * - 提供操作日志的查询和统计功能 * - 支持敏感操作的特殊标记 * - 实现日志的自动清理和归档 * * 职责分离: * - 日志记录:记录操作的详细信息 * - 日志查询:提供灵活的日志查询接口 * - 日志统计:生成操作统计报告 * - 日志管理:自动清理和归档功能 * * 最近修改: * - 2026-01-09: 代码质量优化 - 使用常量替代硬编码字符串,提高代码一致性 (修改者: moyin) * - 2026-01-09: 代码质量优化 - 拆分getStatistics长方法为多个私有方法,提高可读性 (修改者: moyin) * - 2026-01-08: 注释规范优化 - 修正@author字段,更新版本号和修改记录 (修改者: moyin) * - 2026-01-08: 注释规范优化 - 为接口添加注释,完善文档说明 (修改者: moyin) * - 2026-01-08: 注释规范优化 - 添加类注释,完善服务文档说明 (修改者: moyin) * - 2026-01-08: 代码质量优化 - 提取魔法数字为常量,重构长方法,补充导入 (修改者: moyin) * - 2026-01-08: 功能新增 - 创建管理员操作日志服务 (修改者: assistant) * * @author moyin * @version 1.4.0 * @since 2026-01-08 * @lastModified 2026-01-09 */ import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { AdminOperationLog } from './admin_operation_log.entity'; import { LOG_QUERY_LIMITS, USER_QUERY_LIMITS, LOG_RETENTION, OPERATION_TYPES, OPERATION_RESULTS } from './admin_constants'; /** * 创建日志参数接口 * * 功能描述: * 定义创建管理员操作日志所需的所有参数 * * 使用场景: * - AdminOperationLogService.createLog()方法的参数类型 * - 记录管理员操作的详细信息 */ export interface CreateLogParams { adminUserId: string; adminUsername: string; operationType: keyof typeof OPERATION_TYPES; targetType: string; targetId?: string; operationDescription: string; httpMethodPath: string; requestParams?: Record; beforeData?: Record; afterData?: Record; operationResult: keyof typeof OPERATION_RESULTS; errorMessage?: string; errorCode?: string; durationMs: number; clientIp?: string; userAgent?: string; requestId: string; context?: Record; isSensitive?: boolean; affectedRecords?: number; batchId?: string; } /** * 日志查询参数接口 * * 功能描述: * 定义查询管理员操作日志的过滤条件 * * 使用场景: * - AdminOperationLogService.queryLogs()方法的参数类型 * - 支持多维度的日志查询和过滤 */ export interface LogQueryParams { adminUserId?: string; operationType?: string; targetType?: string; operationResult?: string; startDate?: Date; endDate?: Date; isSensitive?: boolean; limit?: number; offset?: number; } /** * 日志统计信息接口 * * 功能描述: * 定义管理员操作日志的统计数据结构 * * 使用场景: * - AdminOperationLogService.getStatistics()方法的返回类型 * - 提供操作统计和分析数据 */ export interface LogStatistics { totalOperations: number; successfulOperations: number; failedOperations: number; operationsByType: Record; operationsByTarget: Record; operationsByAdmin: Record; averageDuration: number; sensitiveOperations: number; uniqueAdmins: number; } /** * 管理员操作日志服务 * * 功能描述: * - 记录管理员的所有数据库操作 * - 提供操作日志的查询和统计功能 * - 支持敏感操作的特殊标记 * - 实现日志的自动清理和归档 * * 职责分离: * - 日志记录:记录操作的详细信息 * - 日志查询:提供灵活的日志查询接口 * - 日志统计:生成操作统计报告 * - 日志管理:自动清理和归档功能 * * 主要方法: * - createLog() - 创建操作日志记录 * - queryLogs() - 查询操作日志 * - getLogById() - 获取单个日志详情 * - getStatistics() - 获取操作统计 * - getSensitiveOperations() - 获取敏感操作日志 * - getAdminOperationHistory() - 获取管理员操作历史 * - cleanupExpiredLogs() - 清理过期日志 * * 使用场景: * - 管理员操作审计 * - 安全监控和异常检测 * - 系统操作统计分析 */ @Injectable() export class AdminOperationLogService { private readonly logger = new Logger(AdminOperationLogService.name); constructor( @InjectRepository(AdminOperationLog) private readonly logRepository: Repository, ) { this.logger.log('AdminOperationLogService初始化完成'); } /** * 创建操作日志 * * @param params 日志参数 * @returns 创建的日志记录 */ async createLog(params: CreateLogParams): Promise { try { const log = this.logRepository.create({ admin_user_id: params.adminUserId, admin_username: params.adminUsername, operation_type: params.operationType, target_type: params.targetType, target_id: params.targetId, operation_description: params.operationDescription, http_method_path: params.httpMethodPath, request_params: params.requestParams, before_data: params.beforeData, after_data: params.afterData, operation_result: params.operationResult, error_message: params.errorMessage, error_code: params.errorCode, duration_ms: params.durationMs, client_ip: params.clientIp, user_agent: params.userAgent, request_id: params.requestId, context: params.context, is_sensitive: params.isSensitive || false, affected_records: params.affectedRecords || 0, batch_id: params.batchId, }); const savedLog = await this.logRepository.save(log); this.logger.log('操作日志记录成功', { logId: savedLog.id, adminUserId: params.adminUserId, operationType: params.operationType, targetType: params.targetType, operationResult: params.operationResult }); return savedLog; } catch (error) { this.logger.error('操作日志记录失败', { error: error instanceof Error ? error.message : String(error), params }); throw error; } } /** * 构建查询条件 * * @param queryBuilder 查询构建器 * @param params 查询参数 */ private buildQueryConditions(queryBuilder: any, params: LogQueryParams): void { if (params.adminUserId) { queryBuilder.andWhere('log.admin_user_id = :adminUserId', { adminUserId: params.adminUserId }); } if (params.operationType) { queryBuilder.andWhere('log.operation_type = :operationType', { operationType: params.operationType }); } if (params.targetType) { queryBuilder.andWhere('log.target_type = :targetType', { targetType: params.targetType }); } if (params.operationResult) { queryBuilder.andWhere('log.operation_result = :operationResult', { operationResult: params.operationResult }); } if (params.startDate && params.endDate) { queryBuilder.andWhere('log.created_at BETWEEN :startDate AND :endDate', { startDate: params.startDate, endDate: params.endDate }); } if (params.isSensitive !== undefined) { queryBuilder.andWhere('log.is_sensitive = :isSensitive', { isSensitive: params.isSensitive }); } } /** * 查询操作日志 * * @param params 查询参数 * @returns 日志列表和总数 */ async queryLogs(params: LogQueryParams): Promise<{ logs: AdminOperationLog[]; total: number }> { try { const queryBuilder = this.logRepository.createQueryBuilder('log'); // 构建查询条件 this.buildQueryConditions(queryBuilder, params); // 排序 queryBuilder.orderBy('log.created_at', 'DESC'); // 分页 const limit = params.limit || LOG_QUERY_LIMITS.DEFAULT_LOG_QUERY_LIMIT; const offset = params.offset || 0; queryBuilder.limit(limit).offset(offset); const [logs, total] = await queryBuilder.getManyAndCount(); this.logger.log('操作日志查询成功', { total, returned: logs.length, params }); return { logs, total }; } catch (error) { this.logger.error('操作日志查询失败', { error: error instanceof Error ? error.message : String(error), params }); throw error; } } /** * 根据ID获取操作日志详情 * * @param id 日志ID * @returns 日志详情 */ async getLogById(id: string): Promise { try { const log = await this.logRepository.findOne({ where: { id } }); if (log) { this.logger.log('操作日志详情获取成功', { logId: id }); } else { this.logger.warn('操作日志不存在', { logId: id }); } return log; } catch (error) { this.logger.error('操作日志详情获取失败', { error: error instanceof Error ? error.message : String(error), logId: id }); throw error; } } /** * 获取基础统计数据 * * @param queryBuilder 查询构建器 * @returns 基础统计数据 */ private async getBasicStatistics(queryBuilder: any): Promise<{ totalOperations: number; successfulOperations: number; failedOperations: number; sensitiveOperations: number; }> { const totalOperations = await queryBuilder.getCount(); const successfulOperations = await queryBuilder .clone() .andWhere('log.operation_result = :result', { result: OPERATION_RESULTS.SUCCESS }) .getCount(); const failedOperations = totalOperations - successfulOperations; const sensitiveOperations = await queryBuilder .clone() .andWhere('log.is_sensitive = :sensitive', { sensitive: true }) .getCount(); return { totalOperations, successfulOperations, failedOperations, sensitiveOperations }; } /** * 获取操作类型统计 * * @param queryBuilder 查询构建器 * @returns 操作类型统计 */ private async getOperationTypeStatistics(queryBuilder: any): Promise> { const operationTypeStats = await queryBuilder .clone() .select('log.operation_type', 'type') .addSelect('COUNT(*)', 'count') .groupBy('log.operation_type') .getRawMany(); return operationTypeStats.reduce((acc, stat) => { acc[stat.type] = parseInt(stat.count); return acc; }, {} as Record); } /** * 获取目标类型统计 * * @param queryBuilder 查询构建器 * @returns 目标类型统计 */ private async getTargetTypeStatistics(queryBuilder: any): Promise> { const targetTypeStats = await queryBuilder .clone() .select('log.target_type', 'type') .addSelect('COUNT(*)', 'count') .groupBy('log.target_type') .getRawMany(); return targetTypeStats.reduce((acc, stat) => { acc[stat.type] = parseInt(stat.count); return acc; }, {} as Record); } /** * 获取管理员统计 * * @param queryBuilder 查询构建器 * @returns 管理员统计 */ private async getAdminStatistics(queryBuilder: any): Promise> { const adminStats = await queryBuilder .clone() .select('log.admin_user_id', 'admin') .addSelect('COUNT(*)', 'count') .groupBy('log.admin_user_id') .getRawMany(); if (!adminStats || !Array.isArray(adminStats)) { return {}; } return adminStats.reduce((acc, stat) => { acc[stat.admin] = parseInt(stat.count); return acc; }, {} as Record); } /** * 获取性能统计 * * @param queryBuilder 查询构建器 * @returns 性能统计 */ private async getPerformanceStatistics(queryBuilder: any): Promise<{ averageDuration: number; uniqueAdmins: number; }> { // 平均耗时 const avgDurationResult = await queryBuilder .clone() .select('AVG(log.duration_ms)', 'avgDuration') .getRawOne(); const averageDuration = parseFloat(avgDurationResult?.avgDuration || '0'); // 唯一管理员数量 const uniqueAdminsResult = await queryBuilder .clone() .select('COUNT(DISTINCT log.admin_user_id)', 'uniqueAdmins') .getRawOne(); const uniqueAdmins = parseInt(uniqueAdminsResult?.uniqueAdmins || '0'); return { averageDuration, uniqueAdmins }; } /** * 获取操作统计信息 * * @param startDate 开始日期 * @param endDate 结束日期 * @returns 统计信息 */ async getStatistics(startDate?: Date, endDate?: Date): Promise { try { const queryBuilder = this.logRepository.createQueryBuilder('log'); if (startDate && endDate) { queryBuilder.where('log.created_at BETWEEN :startDate AND :endDate', { startDate, endDate }); } // 获取各类统计数据 const basicStats = await this.getBasicStatistics(queryBuilder); const operationsByType = await this.getOperationTypeStatistics(queryBuilder); const operationsByTarget = await this.getTargetTypeStatistics(queryBuilder); const operationsByAdmin = await this.getAdminStatistics(queryBuilder); const performanceStats = await this.getPerformanceStatistics(queryBuilder); const statistics: LogStatistics = { ...basicStats, operationsByType, operationsByTarget, operationsByAdmin, ...performanceStats }; this.logger.log('操作统计获取成功', statistics); return statistics; } catch (error) { this.logger.error('操作统计获取失败', { error: error instanceof Error ? error.message : String(error), startDate, endDate }); throw error; } } /** * 清理过期日志 * * @param daysToKeep 保留天数 * @returns 清理的记录数 */ async cleanupExpiredLogs(daysToKeep: number = LOG_RETENTION.DEFAULT_DAYS): Promise { try { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysToKeep); const result = await this.logRepository .createQueryBuilder() .delete() .where('created_at < :cutoffDate', { cutoffDate }) .andWhere('is_sensitive = :sensitive', { sensitive: false }) // 保留敏感操作日志 .execute(); const deletedCount = result.affected || 0; this.logger.log('过期日志清理完成', { deletedCount, cutoffDate, daysToKeep }); return deletedCount; } catch (error) { this.logger.error('过期日志清理失败', { error: error instanceof Error ? error.message : String(error), daysToKeep }); throw error; } } /** * 获取管理员操作历史 * * @param adminUserId 管理员用户ID * @param limit 限制数量 * @returns 操作历史 */ async getAdminOperationHistory(adminUserId: string, limit: number = USER_QUERY_LIMITS.ADMIN_HISTORY_DEFAULT_LIMIT): Promise { try { const logs = await this.logRepository.find({ where: { admin_user_id: adminUserId }, order: { created_at: 'DESC' }, take: limit }); this.logger.log('管理员操作历史获取成功', { adminUserId, count: logs.length }); return logs; } catch (error) { this.logger.error('管理员操作历史获取失败', { error: error instanceof Error ? error.message : String(error), adminUserId }); throw error; } } /** * 获取敏感操作日志 * * @param limit 限制数量 * @param offset 偏移量 * @returns 敏感操作日志 */ async getSensitiveOperations(limit: number = LOG_QUERY_LIMITS.SENSITIVE_LOG_DEFAULT_LIMIT, offset: number = 0): Promise<{ logs: AdminOperationLog[]; total: number }> { try { const [logs, total] = await this.logRepository.findAndCount({ where: { is_sensitive: true }, order: { created_at: 'DESC' }, take: limit, skip: offset }); this.logger.log('敏感操作日志获取成功', { total, returned: logs.length }); return { logs, total }; } catch (error) { this.logger.error('敏感操作日志获取失败', { error: error instanceof Error ? error.message : String(error) }); throw error; } } }