feat:实现管理员系统核心功能

- 添加管理员数据库管理控制器和服务
- 实现管理员操作日志记录系统
- 添加数据库异常处理过滤器
- 完善管理员权限验证和响应格式
- 添加全面的属性测试覆盖
This commit is contained in:
moyin
2026-01-08 23:05:34 +08:00
parent 0f37130832
commit 6924416bbd
34 changed files with 9481 additions and 199 deletions

View File

@@ -0,0 +1,373 @@
/**
* 管理员操作日志控制器
*
* 功能描述:
* - 提供管理员操作日志的查询和管理接口
* - 支持日志的分页查询和过滤
* - 提供操作统计和分析功能
* - 支持敏感操作日志的特殊查询
*
* 职责分离:
* - 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}条记录`);
}
}