Files
whale-town-end/src/business/admin/admin_utils.ts
moyin 6924416bbd feat:实现管理员系统核心功能
- 添加管理员数据库管理控制器和服务
- 实现管理员操作日志记录系统
- 添加数据库异常处理过滤器
- 完善管理员权限验证和响应格式
- 添加全面的属性测试覆盖
2026-01-08 23:05:34 +08:00

316 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 管理员模块工具函数
*
* 功能描述:
* - 提供管理员模块通用的工具函数
* - 消除重复代码,提高代码复用性
* - 统一处理常见的业务逻辑
*
* 职责分离:
* - 工具函数集中管理
* - 重复逻辑抽象
* - 通用功能封装
*
* 最近修改:
* - 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;
}
}
}