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,316 @@
/**
* 管理员模块工具函数
*
* 功能描述:
* - 提供管理员模块通用的工具函数
* - 消除重复代码,提高代码复用性
* - 统一处理常见的业务逻辑
*
* 职责分离:
* - 工具函数集中管理
* - 重复逻辑抽象
* - 通用功能封装
*
* 最近修改:
* - 2026-01-08: 重构 - 文件夹扁平化移动到上级目录并更新import路径 (修改者: moyin)
* - 2026-01-08: 代码质量优化 - 提取魔法数字为常量,添加用户格式化工具和操作监控工具 (修改者: moyin)
* - 2026-01-08: 功能新增 - 创建管理员模块工具函数 (修改者: moyin)
*
* @author moyin
* @version 1.3.0
* @since 2026-01-08
* @lastModified 2026-01-08
*/
import { PAGINATION_LIMITS, REQUEST_ID_PREFIXES, SENSITIVE_FIELDS } from './admin_constants';
/**
* 请求ID生成常量
*/
const REQUEST_ID_RANDOM_LENGTH = 9; // 随机字符串长度
const REQUEST_ID_RANDOM_START = 2; // 跳过'0.'前缀
/**
* 安全限制查询数量
*
* @param limit 请求的限制数量
* @param maxLimit 最大允许的限制数量
* @returns 安全的限制数量
*/
export function safeLimitValue(limit: number, maxLimit: number): number {
return Math.min(Math.max(limit, 1), maxLimit);
}
/**
* 安全限制偏移量
*
* @param offset 请求的偏移量
* @returns 安全的偏移量不小于0
*/
export function safeOffsetValue(offset: number): number {
return Math.max(offset, PAGINATION_LIMITS.DEFAULT_OFFSET);
}
/**
* 生成唯一的请求ID
*
* @param prefix 请求ID前缀
* @returns 唯一的请求ID
*/
export function generateRequestId(prefix: string = REQUEST_ID_PREFIXES.GENERAL): string {
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(REQUEST_ID_RANDOM_START, REQUEST_ID_RANDOM_START + REQUEST_ID_RANDOM_LENGTH)}`;
}
/**
* 获取当前时间戳字符串
*
* @returns ISO格式的时间戳字符串
*/
export function getCurrentTimestamp(): string {
return new Date().toISOString();
}
/**
* 清理请求体中的敏感信息
*
* @param body 请求体对象
* @returns 清理后的请求体
*/
export function sanitizeRequestBody(body: any): any {
if (!body || typeof body !== 'object') {
return body;
}
const sanitized = { ...body };
for (const field of SENSITIVE_FIELDS) {
if (sanitized[field]) {
sanitized[field] = '***REDACTED***';
}
}
return sanitized;
}
/**
* 提取客户端IP地址
*
* @param request 请求对象
* @returns 客户端IP地址
*/
export function extractClientIp(request: any): string {
return request.ip ||
request.connection?.remoteAddress ||
request.socket?.remoteAddress ||
(request.connection?.socket as any)?.remoteAddress ||
request.headers['x-forwarded-for']?.split(',')[0] ||
request.headers['x-real-ip'] ||
'unknown';
}
/**
* 创建标准的成功响应
*
* @param data 响应数据
* @param message 响应消息
* @param requestIdPrefix 请求ID前缀
* @returns 标准格式的成功响应
*/
export function createSuccessResponse<T>(
data: T,
message: string,
requestIdPrefix?: string
): {
success: true;
data: T;
message: string;
timestamp: string;
request_id: string;
} {
return {
success: true,
data,
message,
timestamp: getCurrentTimestamp(),
request_id: generateRequestId(requestIdPrefix)
};
}
/**
* 创建标准的错误响应
*
* @param message 错误消息
* @param errorCode 错误码
* @param requestIdPrefix 请求ID前缀
* @returns 标准格式的错误响应
*/
export function createErrorResponse(
message: string,
errorCode?: string,
requestIdPrefix?: string
): {
success: false;
message: string;
error_code?: string;
timestamp: string;
request_id: string;
} {
return {
success: false,
message,
error_code: errorCode,
timestamp: getCurrentTimestamp(),
request_id: generateRequestId(requestIdPrefix)
};
}
/**
* 创建标准的列表响应
*
* @param items 列表项
* @param total 总数
* @param limit 限制数量
* @param offset 偏移量
* @param message 响应消息
* @param requestIdPrefix 请求ID前缀
* @returns 标准格式的列表响应
*/
export function createListResponse<T>(
items: T[],
total: number,
limit: number,
offset: number,
message: string,
requestIdPrefix?: string
): {
success: true;
data: {
items: T[];
total: number;
limit: number;
offset: number;
has_more: boolean;
};
message: string;
timestamp: string;
request_id: string;
} {
return {
success: true,
data: {
items,
total,
limit,
offset,
has_more: offset + items.length < total
},
message,
timestamp: getCurrentTimestamp(),
request_id: generateRequestId(requestIdPrefix)
};
}
/**
* 限制保留天数在合理范围内
*
* @param daysToKeep 请求的保留天数
* @param minDays 最少保留天数
* @param maxDays 最多保留天数
* @returns 安全的保留天数
*/
export function safeDaysToKeep(daysToKeep: number, minDays: number, maxDays: number): number {
return Math.max(minDays, Math.min(daysToKeep, maxDays));
}
/**
* 用户数据格式化工具
*/
export class UserFormatter {
/**
* 格式化用户基本信息
*
* @param user 用户实体
* @returns 格式化的用户信息
*/
static formatBasicUser(user: any) {
return {
id: user.id.toString(),
username: user.username,
nickname: user.nickname,
email: user.email,
phone: user.phone,
role: user.role,
status: user.status,
email_verified: user.email_verified,
avatar_url: user.avatar_url,
created_at: user.created_at,
updated_at: user.updated_at
};
}
/**
* 格式化用户详细信息包含GitHub ID
*
* @param user 用户实体
* @returns 格式化的用户详细信息
*/
static formatDetailedUser(user: any) {
return {
...this.formatBasicUser(user),
github_id: user.github_id
};
}
}
/**
* 操作性能监控工具
*/
export class OperationMonitor {
/**
* 执行带性能监控的操作
*
* @param operationName 操作名称
* @param context 操作上下文
* @param operation 要执行的操作
* @param logger 日志记录器
* @returns 操作结果
*/
static async executeWithMonitoring<T>(
operationName: string,
context: Record<string, any>,
operation: () => Promise<T>,
logger: (level: 'log' | 'warn' | 'error', message: string, context: Record<string, any>) => void
): Promise<T> {
const startTime = Date.now();
logger('log', `开始${operationName}`, {
operation: operationName,
...context
});
try {
const result = await operation();
const duration = Date.now() - startTime;
logger('log', `${operationName}成功`, {
operation: operationName,
duration,
...context
});
return result;
} catch (error) {
const duration = Date.now() - startTime;
logger('error', `${operationName}失败`, {
operation: operationName,
duration,
error: error instanceof Error ? error.message : String(error),
...context
});
throw error;
}
}
}