/** * 管理员操作日志控制器 * * 功能描述: * - 提供管理员操作日志的查询和管理接口 * - 支持日志的分页查询和过滤 * - 提供操作统计和分析功能 * - 支持敏感操作日志的特殊查询 * * 职责分离: * - HTTP请求处理:接收和验证HTTP请求参数 * - 权限控制:通过AdminGuard确保只有管理员可以访问 * - 业务委托:将业务逻辑委托给AdminOperationLogService处理 * - 响应格式化:返回统一格式的HTTP响应 * * API端点: * - GET /admin/operation-logs 获取操作日志列表 * - GET /admin/operation-logs/:id 获取操作日志详情 * - GET /admin/operation-logs/statistics 获取操作统计 * - GET /admin/operation-logs/sensitive 获取敏感操作日志 * - DELETE /admin/operation-logs/cleanup 清理过期日志 * * 最近修改: * - 2026-01-08: 功能新增 - 创建管理员操作日志控制器 (修改者: moyin) * * @author moyin * @version 1.0.0 * @since 2026-01-08 * @lastModified 2026-01-08 */ import { Controller, Get, Delete, Param, Query, UseGuards, UseFilters, UseInterceptors, ParseIntPipe, DefaultValuePipe, BadRequestException } from '@nestjs/common'; import { ApiTags, ApiBearerAuth, ApiOperation, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger'; import { AdminGuard } from './admin.guard'; import { AdminDatabaseExceptionFilter } from './admin_database_exception.filter'; import { AdminOperationLogInterceptor } from './admin_operation_log.interceptor'; import { LogAdminOperation } from './log_admin_operation.decorator'; import { AdminOperationLogService, LogQueryParams } from './admin_operation_log.service'; import { PAGINATION_LIMITS, LOG_RETENTION, USER_QUERY_LIMITS } from './admin_constants'; import { safeLimitValue, safeOffsetValue, safeDaysToKeep, createSuccessResponse, createListResponse } from './admin_utils'; @ApiTags('admin-operation-logs') @Controller('admin/operation-logs') @UseGuards(AdminGuard) @UseFilters(AdminDatabaseExceptionFilter) @UseInterceptors(AdminOperationLogInterceptor) @ApiBearerAuth('JWT-auth') export class AdminOperationLogController { constructor( private readonly logService: AdminOperationLogService ) {} /** * 获取操作日志列表 * * 功能描述: * 分页获取管理员操作日志,支持多种过滤条件 * * 业务逻辑: * 1. 验证查询参数 * 2. 构建查询条件 * 3. 调用日志服务查询 * 4. 返回分页结果 * * @param limit 返回数量,默认50,最大200 * @param offset 偏移量,默认0 * @param adminUserId 管理员用户ID过滤,可选 * @param operationType 操作类型过滤,可选 * @param targetType 目标类型过滤,可选 * @param operationResult 操作结果过滤,可选 * @param startDate 开始日期过滤,可选 * @param endDate 结束日期过滤,可选 * @param isSensitive 是否敏感操作过滤,可选 * @returns 操作日志列表和分页信息 * * @example * ```typescript * // 获取最近50条操作日志 * GET /admin/operation-logs?limit=50&offset=0 * * // 获取特定管理员的操作日志 * GET /admin/operation-logs?adminUserId=123&limit=20 * * // 获取敏感操作日志 * GET /admin/operation-logs?isSensitive=true * ``` */ @ApiOperation({ summary: '获取操作日志列表', description: '分页获取管理员操作日志,支持多种过滤条件' }) @ApiQuery({ name: 'limit', required: false, description: '返回数量(默认50,最大200)', example: 50 }) @ApiQuery({ name: 'offset', required: false, description: '偏移量(默认0)', example: 0 }) @ApiQuery({ name: 'adminUserId', required: false, description: '管理员用户ID过滤', example: '123' }) @ApiQuery({ name: 'operationType', required: false, description: '操作类型过滤', example: 'CREATE' }) @ApiQuery({ name: 'targetType', required: false, description: '目标类型过滤', example: 'users' }) @ApiQuery({ name: 'operationResult', required: false, description: '操作结果过滤', example: 'SUCCESS' }) @ApiQuery({ name: 'startDate', required: false, description: '开始日期(ISO格式)', example: '2026-01-01T00:00:00.000Z' }) @ApiQuery({ name: 'endDate', required: false, description: '结束日期(ISO格式)', example: '2026-01-08T23:59:59.999Z' }) @ApiQuery({ name: 'isSensitive', required: false, description: '是否敏感操作', example: true }) @ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 401, description: '未授权访问' }) @ApiResponse({ status: 403, description: '权限不足' }) @LogAdminOperation({ operationType: 'QUERY', targetType: 'admin_logs', description: '获取操作日志列表', isSensitive: false }) @Get() async getOperationLogs( @Query('limit', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_LIMIT), ParseIntPipe) limit: number, @Query('offset', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_OFFSET), ParseIntPipe) offset: number, @Query('adminUserId') adminUserId?: string, @Query('operationType') operationType?: string, @Query('targetType') targetType?: string, @Query('operationResult') operationResult?: string, @Query('startDate') startDate?: string, @Query('endDate') endDate?: string, @Query('isSensitive') isSensitive?: string ) { const safeLimit = safeLimitValue(limit, PAGINATION_LIMITS.LOG_LIST_MAX_LIMIT); const safeOffset = safeOffsetValue(offset); const queryParams: LogQueryParams = { limit: safeLimit, offset: safeOffset }; if (adminUserId) queryParams.adminUserId = adminUserId; if (operationType) queryParams.operationType = operationType; if (targetType) queryParams.targetType = targetType; if (operationResult) queryParams.operationResult = operationResult; if (isSensitive !== undefined) queryParams.isSensitive = isSensitive === 'true'; if (startDate && endDate) { queryParams.startDate = new Date(startDate); queryParams.endDate = new Date(endDate); if (isNaN(queryParams.startDate.getTime()) || isNaN(queryParams.endDate.getTime())) { throw new BadRequestException('日期格式无效,请使用ISO格式'); } } const { logs, total } = await this.logService.queryLogs(queryParams); return createListResponse( logs, total, safeLimit, safeOffset, '操作日志列表获取成功' ); } /** * 获取操作日志详情 * * 功能描述: * 根据日志ID获取操作日志的详细信息 * * 业务逻辑: * 1. 验证日志ID格式 * 2. 查询日志详细信息 * 3. 返回日志详情 * * @param id 日志ID * @returns 操作日志详细信息 * * @throws NotFoundException 当日志不存在时 * * @example * ```typescript * const result = await controller.getOperationLogById('uuid-123'); * ``` */ @ApiOperation({ summary: '获取操作日志详情', description: '根据日志ID获取操作日志的详细信息' }) @ApiParam({ name: 'id', description: '日志ID', example: 'uuid-123' }) @ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 404, description: '日志不存在' }) @Get(':id') async getOperationLogById(@Param('id') id: string) { const log = await this.logService.getLogById(id); if (!log) { throw new BadRequestException('操作日志不存在'); } return createSuccessResponse(log, '操作日志详情获取成功'); } /** * 获取操作统计信息 * * 功能描述: * 获取管理员操作的统计信息,包括操作数量、类型分布等 * * 业务逻辑: * 1. 解析时间范围参数 * 2. 调用统计服务 * 3. 返回统计结果 * * @param startDate 开始日期,可选 * @param endDate 结束日期,可选 * @returns 操作统计信息 * * @example * ```typescript * // 获取全部统计 * GET /admin/operation-logs/statistics * * // 获取指定时间范围的统计 * GET /admin/operation-logs/statistics?startDate=2026-01-01&endDate=2026-01-08 * ``` */ @ApiOperation({ summary: '获取操作统计信息', description: '获取管理员操作的统计信息,包括操作数量、类型分布等' }) @ApiQuery({ name: 'startDate', required: false, description: '开始日期(ISO格式)', example: '2026-01-01T00:00:00.000Z' }) @ApiQuery({ name: 'endDate', required: false, description: '结束日期(ISO格式)', example: '2026-01-08T23:59:59.999Z' }) @ApiResponse({ status: 200, description: '获取成功' }) @Get('statistics') async getOperationStatistics( @Query('startDate') startDate?: string, @Query('endDate') endDate?: string ) { let parsedStartDate: Date | undefined; let parsedEndDate: Date | undefined; if (startDate && endDate) { parsedStartDate = new Date(startDate); parsedEndDate = new Date(endDate); if (isNaN(parsedStartDate.getTime()) || isNaN(parsedEndDate.getTime())) { throw new BadRequestException('日期格式无效,请使用ISO格式'); } } const statistics = await this.logService.getStatistics(parsedStartDate, parsedEndDate); return createSuccessResponse(statistics, '操作统计信息获取成功'); } /** * 获取敏感操作日志 * * 功能描述: * 获取标记为敏感的操作日志,用于安全审计 * * 业务逻辑: * 1. 验证查询参数 * 2. 查询敏感操作日志 * 3. 返回分页结果 * * @param limit 返回数量,默认50,最大200 * @param offset 偏移量,默认0 * @returns 敏感操作日志列表 * * @example * ```typescript * // 获取最近50条敏感操作日志 * GET /admin/operation-logs/sensitive?limit=50 * ``` */ @ApiOperation({ summary: '获取敏感操作日志', description: '获取标记为敏感的操作日志,用于安全审计' }) @ApiQuery({ name: 'limit', required: false, description: '返回数量(默认50,最大200)', example: 50 }) @ApiQuery({ name: 'offset', required: false, description: '偏移量(默认0)', example: 0 }) @ApiResponse({ status: 200, description: '获取成功' }) @LogAdminOperation({ operationType: 'QUERY', targetType: 'admin_logs', description: '获取敏感操作日志', isSensitive: true }) @Get('sensitive') async getSensitiveOperations( @Query('limit', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_LIMIT), ParseIntPipe) limit: number, @Query('offset', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_OFFSET), ParseIntPipe) offset: number ) { const safeLimit = safeLimitValue(limit, PAGINATION_LIMITS.LOG_LIST_MAX_LIMIT); const safeOffset = safeOffsetValue(offset); const { logs, total } = await this.logService.getSensitiveOperations(safeLimit, safeOffset); return createListResponse( logs, total, safeLimit, safeOffset, '敏感操作日志获取成功' ); } /** * 清理过期日志 * * 功能描述: * 清理超过指定天数的操作日志,释放存储空间 * * 业务逻辑: * 1. 验证保留天数参数 * 2. 调用清理服务 * 3. 返回清理结果 * * @param daysToKeep 保留天数,默认90天,最少7天,最多365天 * @returns 清理结果,包含删除的记录数 * * @throws BadRequestException 当保留天数超出范围时 * * @example * ```typescript * // 清理90天前的日志 * DELETE /admin/operation-logs/cleanup?daysToKeep=90 * ``` */ @ApiOperation({ summary: '清理过期日志', description: '清理超过指定天数的操作日志,释放存储空间' }) @ApiQuery({ name: 'daysToKeep', required: false, description: '保留天数(默认90,最少7,最多365)', example: 90 }) @ApiResponse({ status: 200, description: '清理成功' }) @ApiResponse({ status: 400, description: '参数错误' }) @LogAdminOperation({ operationType: 'DELETE', targetType: 'admin_logs', description: '清理过期操作日志', isSensitive: true }) @Delete('cleanup') async cleanupExpiredLogs( @Query('daysToKeep', new DefaultValuePipe(LOG_RETENTION.DEFAULT_DAYS), ParseIntPipe) daysToKeep: number ) { const safeDays = safeDaysToKeep(daysToKeep, LOG_RETENTION.MIN_DAYS, LOG_RETENTION.MAX_DAYS); if (safeDays !== daysToKeep) { throw new BadRequestException(`保留天数必须在${LOG_RETENTION.MIN_DAYS}-${LOG_RETENTION.MAX_DAYS}天之间`); } const deletedCount = await this.logService.cleanupExpiredLogs(safeDays); return createSuccessResponse({ deleted_count: deletedCount, days_to_keep: safeDays, cleanup_date: new Date().toISOString() }, `过期日志清理完成,删除了${deletedCount}条记录`); } }