forked from datawhale/whale-town-end
373 lines
13 KiB
TypeScript
373 lines
13 KiB
TypeScript
/**
|
||
* 管理员操作日志控制器
|
||
*
|
||
* 功能描述:
|
||
* - 提供管理员操作日志的查询和管理接口
|
||
* - 支持日志的分页查询和过滤
|
||
* - 提供操作统计和分析功能
|
||
* - 支持敏感操作日志的特殊查询
|
||
*
|
||
* 职责分离:
|
||
* - 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}条记录`);
|
||
}
|
||
} |