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,498 @@
/**
* 管理员操作日志服务
*
* 功能描述:
* - 记录管理员的所有数据库操作
* - 提供操作日志的查询和统计功能
* - 支持敏感操作的特殊标记
* - 实现日志的自动清理和归档
*
* 职责分离:
* - 日志记录:记录操作的详细信息
* - 日志查询:提供灵活的日志查询接口
* - 日志统计:生成操作统计报告
* - 日志管理:自动清理和归档功能
*
* 最近修改:
* - 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.2.0
* @since 2026-01-08
* @lastModified 2026-01-08
*/
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 } from './admin_constants';
/**
* 创建日志参数接口
*
* 功能描述:
* 定义创建管理员操作日志所需的所有参数
*
* 使用场景:
* - AdminOperationLogService.createLog()方法的参数类型
* - 记录管理员操作的详细信息
*/
export interface CreateLogParams {
adminUserId: string;
adminUsername: string;
operationType: 'CREATE' | 'UPDATE' | 'DELETE' | 'QUERY' | 'BATCH';
targetType: string;
targetId?: string;
operationDescription: string;
httpMethodPath: string;
requestParams?: Record<string, any>;
beforeData?: Record<string, any>;
afterData?: Record<string, any>;
operationResult: 'SUCCESS' | 'FAILED';
errorMessage?: string;
errorCode?: string;
durationMs: number;
clientIp?: string;
userAgent?: string;
requestId: string;
context?: Record<string, any>;
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<string, number>;
operationsByTarget: Record<string, number>;
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<AdminOperationLog>,
) {
this.logger.log('AdminOperationLogService初始化完成');
}
/**
* 创建操作日志
*
* @param params 日志参数
* @returns 创建的日志记录
*/
async createLog(params: CreateLogParams): Promise<AdminOperationLog> {
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<AdminOperationLog | null> {
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 startDate 开始日期
* @param endDate 结束日期
* @returns 统计信息
*/
async getStatistics(startDate?: Date, endDate?: Date): Promise<LogStatistics> {
try {
const queryBuilder = this.logRepository.createQueryBuilder('log');
if (startDate && endDate) {
queryBuilder.where('log.created_at BETWEEN :startDate AND :endDate', {
startDate,
endDate
});
}
// 基础统计
const totalOperations = await queryBuilder.getCount();
const successfulOperations = await queryBuilder
.clone()
.andWhere('log.operation_result = :result', { result: 'SUCCESS' })
.getCount();
const failedOperations = totalOperations - successfulOperations;
const sensitiveOperations = await queryBuilder
.clone()
.andWhere('log.is_sensitive = :sensitive', { sensitive: true })
.getCount();
// 按操作类型统计
const operationTypeStats = await queryBuilder
.clone()
.select('log.operation_type', 'type')
.addSelect('COUNT(*)', 'count')
.groupBy('log.operation_type')
.getRawMany();
const operationsByType = operationTypeStats.reduce((acc, stat) => {
acc[stat.type] = parseInt(stat.count);
return acc;
}, {} as Record<string, number>);
// 按目标类型统计
const targetTypeStats = await queryBuilder
.clone()
.select('log.target_type', 'type')
.addSelect('COUNT(*)', 'count')
.groupBy('log.target_type')
.getRawMany();
const operationsByTarget = targetTypeStats.reduce((acc, stat) => {
acc[stat.type] = parseInt(stat.count);
return acc;
}, {} as Record<string, 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');
const statistics: LogStatistics = {
totalOperations,
successfulOperations,
failedOperations,
operationsByType,
operationsByTarget,
averageDuration,
sensitiveOperations,
uniqueAdmins
};
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<number> {
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<AdminOperationLog[]> {
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;
}
}
}