feat:实现管理员系统核心功能
- 添加管理员数据库管理控制器和服务 - 实现管理员操作日志记录系统 - 添加数据库异常处理过滤器 - 完善管理员权限验证和响应格式 - 添加全面的属性测试覆盖
This commit is contained in:
316
src/business/admin/admin_utils.ts
Normal file
316
src/business/admin/admin_utils.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user