316 lines
7.2 KiB
TypeScript
316 lines
7.2 KiB
TypeScript
/**
|
||
* 管理员模块工具函数
|
||
*
|
||
* 功能描述:
|
||
* - 提供管理员模块通用的工具函数
|
||
* - 消除重复代码,提高代码复用性
|
||
* - 统一处理常见的业务逻辑
|
||
*
|
||
* 职责分离:
|
||
* - 工具函数集中管理
|
||
* - 重复逻辑抽象
|
||
* - 通用功能封装
|
||
*
|
||
* 最近修改:
|
||
* - 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;
|
||
}
|
||
}
|
||
} |