mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-01-08 20:39:11 +08:00
* feat: 统一并标准化eslint * lint: napcat.webui * lint: napcat.webui * lint: napcat.core * build: fix * lint: napcat.webui * refactor: 重构eslint * Update README.md
281 lines
8.0 KiB
TypeScript
281 lines
8.0 KiB
TypeScript
export interface ResourceConfig<T extends any[], R> {
|
|
/** 资源获取函数 */
|
|
resourceFn: (...args: T) => Promise<R>;
|
|
/** 失败后禁用时间(毫秒),默认 30 秒 */
|
|
disableTime?: number;
|
|
/** 最大重试次数,默认 3 次 */
|
|
maxRetries?: number;
|
|
/** 主动测试间隔(毫秒),默认 60 秒 */
|
|
healthCheckInterval?: number;
|
|
/** 最大健康检查失败次数,超过后永久禁用,默认 5 次 */
|
|
maxHealthCheckFailures?: number;
|
|
/** 健康检查函数,如果提供则优先使用此函数进行健康检查 */
|
|
healthCheckFn?: (...args: T) => Promise<boolean>;
|
|
/** 测试参数(用于健康检查) */
|
|
testArgs?: T;
|
|
}
|
|
|
|
interface ResourceTypeState {
|
|
/** 资源配置 */
|
|
config: {
|
|
resourceFn: (...args: any[]) => Promise<any>;
|
|
healthCheckFn?: (...args: any[]) => Promise<boolean>;
|
|
disableTime: number;
|
|
maxRetries: number;
|
|
healthCheckInterval: number;
|
|
maxHealthCheckFailures: number;
|
|
testArgs?: any[];
|
|
};
|
|
/** 是否启用 */
|
|
isEnabled: boolean;
|
|
/** 禁用截止时间 */
|
|
disableUntil: number;
|
|
/** 当前重试次数 */
|
|
currentRetries: number;
|
|
/** 健康检查失败次数 */
|
|
healthCheckFailureCount: number;
|
|
/** 是否永久禁用 */
|
|
isPermanentlyDisabled: boolean;
|
|
/** 上次健康检查时间 */
|
|
lastHealthCheckTime: number;
|
|
/** 成功次数统计 */
|
|
successCount: number;
|
|
/** 失败次数统计 */
|
|
failureCount: number;
|
|
}
|
|
|
|
export class ResourceManager {
|
|
private resourceTypes = new Map<string, ResourceTypeState>();
|
|
private destroyed = false;
|
|
|
|
/**
|
|
* 调用资源(自动注册或复用已有配置)
|
|
*/
|
|
async callResource<T extends any[], R>(
|
|
type: string,
|
|
config: ResourceConfig<T, R>,
|
|
...args: T
|
|
): Promise<R> {
|
|
if (this.destroyed) {
|
|
throw new Error('ResourceManager has been destroyed');
|
|
}
|
|
|
|
// 获取或创建资源类型状态
|
|
let state = this.resourceTypes.get(type);
|
|
|
|
if (!state) {
|
|
// 首次注册
|
|
state = {
|
|
config: {
|
|
resourceFn: config.resourceFn as (...args: any[]) => Promise<any>,
|
|
healthCheckFn: config.healthCheckFn as ((...args: any[]) => Promise<boolean>) | undefined,
|
|
disableTime: config.disableTime ?? 30000,
|
|
maxRetries: config.maxRetries ?? 3,
|
|
healthCheckInterval: config.healthCheckInterval ?? 60000,
|
|
maxHealthCheckFailures: config.maxHealthCheckFailures ?? 20,
|
|
testArgs: config.testArgs as any[] | undefined,
|
|
},
|
|
isEnabled: true,
|
|
disableUntil: 0,
|
|
currentRetries: 0,
|
|
healthCheckFailureCount: 0,
|
|
isPermanentlyDisabled: false,
|
|
lastHealthCheckTime: 0,
|
|
successCount: 0,
|
|
failureCount: 0,
|
|
};
|
|
this.resourceTypes.set(type, state);
|
|
}
|
|
|
|
// 在调用前检查是否需要进行健康检查
|
|
await this.checkAndPerformHealthCheck(state);
|
|
|
|
// 检查资源状态
|
|
if (state.isPermanentlyDisabled) {
|
|
throw new Error(`Resource type '${type}' is permanently disabled (success: ${state.successCount}, failure: ${state.failureCount})`);
|
|
}
|
|
|
|
if (!this.isResourceAvailable(type)) {
|
|
const disableUntilDate = new Date(state.disableUntil).toISOString();
|
|
throw new Error(`Resource type '${type}' is currently disabled until ${disableUntilDate} (success: ${state.successCount}, failure: ${state.failureCount})`);
|
|
}
|
|
|
|
// 调用资源
|
|
try {
|
|
const result = await config.resourceFn(...args);
|
|
this.onResourceSuccess(state);
|
|
return result;
|
|
} catch (error) {
|
|
this.onResourceFailure(state);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查资源类型是否可用
|
|
*/
|
|
isResourceAvailable (type: string): boolean {
|
|
const state = this.resourceTypes.get(type);
|
|
if (!state) {
|
|
return true; // 未注册的资源类型视为可用
|
|
}
|
|
|
|
if (state.isPermanentlyDisabled || !state.isEnabled) {
|
|
return false;
|
|
}
|
|
|
|
return Date.now() >= state.disableUntil;
|
|
}
|
|
|
|
/**
|
|
* 获取资源类型统计信息
|
|
*/
|
|
getResourceStats (type: string): { successCount: number; failureCount: number; isEnabled: boolean; isPermanentlyDisabled: boolean } | null {
|
|
const state = this.resourceTypes.get(type);
|
|
if (!state) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
successCount: state.successCount,
|
|
failureCount: state.failureCount,
|
|
isEnabled: state.isEnabled,
|
|
isPermanentlyDisabled: state.isPermanentlyDisabled,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 获取所有资源类型统计
|
|
*/
|
|
getAllResourceStats (): Map<string, { successCount: number; failureCount: number; isEnabled: boolean; isPermanentlyDisabled: boolean }> {
|
|
const stats = new Map();
|
|
for (const [type, state] of this.resourceTypes) {
|
|
stats.set(type, {
|
|
successCount: state.successCount,
|
|
failureCount: state.failureCount,
|
|
isEnabled: state.isEnabled,
|
|
isPermanentlyDisabled: state.isPermanentlyDisabled,
|
|
});
|
|
}
|
|
return stats;
|
|
}
|
|
|
|
/**
|
|
* 注销资源类型
|
|
*/
|
|
unregister (type: string): boolean {
|
|
return this.resourceTypes.delete(type);
|
|
}
|
|
|
|
/**
|
|
* 销毁管理器
|
|
*/
|
|
destroy (): void {
|
|
if (this.destroyed) {
|
|
return;
|
|
}
|
|
|
|
this.resourceTypes.clear();
|
|
this.destroyed = true;
|
|
}
|
|
|
|
/**
|
|
* 检查并执行健康检查(如果需要)
|
|
*/
|
|
private async checkAndPerformHealthCheck (state: ResourceTypeState): Promise<void> {
|
|
// 如果资源可用或已永久禁用,无需健康检查
|
|
if (state.isEnabled && Date.now() >= state.disableUntil) {
|
|
return;
|
|
}
|
|
|
|
if (state.isPermanentlyDisabled) {
|
|
return;
|
|
}
|
|
|
|
const now = Date.now();
|
|
|
|
// 检查是否还在禁用期内
|
|
if (now < state.disableUntil) {
|
|
return;
|
|
}
|
|
|
|
// 检查是否需要进行健康检查(根据间隔时间)
|
|
if (now - state.lastHealthCheckTime < state.config.healthCheckInterval) {
|
|
return;
|
|
}
|
|
|
|
// 执行健康检查
|
|
await this.performHealthCheck(state);
|
|
}
|
|
|
|
private async performHealthCheck (state: ResourceTypeState): Promise<void> {
|
|
state.lastHealthCheckTime = Date.now();
|
|
|
|
try {
|
|
let healthCheckResult: boolean;
|
|
|
|
if (state.config.healthCheckFn) {
|
|
const testArgs = state.config.testArgs || [];
|
|
healthCheckResult = await state.config.healthCheckFn(...testArgs);
|
|
} else {
|
|
const testArgs = state.config.testArgs || [];
|
|
await state.config.resourceFn(...testArgs);
|
|
healthCheckResult = true;
|
|
}
|
|
|
|
if (healthCheckResult) {
|
|
// 健康检查成功,重新启用
|
|
state.isEnabled = true;
|
|
state.disableUntil = 0;
|
|
state.currentRetries = 0;
|
|
state.healthCheckFailureCount = 0;
|
|
} else {
|
|
throw new Error('Health check function returned false');
|
|
}
|
|
} catch {
|
|
// 健康检查失败,增加失败计数
|
|
state.healthCheckFailureCount++;
|
|
|
|
// 检查是否达到最大健康检查失败次数
|
|
if (state.healthCheckFailureCount >= state.config.maxHealthCheckFailures) {
|
|
// 永久禁用资源
|
|
state.isPermanentlyDisabled = true;
|
|
state.disableUntil = 0;
|
|
} else {
|
|
// 继续禁用一段时间
|
|
state.disableUntil = Date.now() + state.config.disableTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
private onResourceSuccess (state: ResourceTypeState): void {
|
|
state.currentRetries = 0;
|
|
state.disableUntil = 0;
|
|
state.healthCheckFailureCount = 0;
|
|
state.successCount++;
|
|
}
|
|
|
|
private onResourceFailure (state: ResourceTypeState): void {
|
|
state.currentRetries++;
|
|
state.failureCount++;
|
|
|
|
// 如果重试次数达到上限,禁用资源
|
|
if (state.currentRetries >= state.config.maxRetries) {
|
|
state.disableUntil = Date.now() + state.config.disableTime;
|
|
state.currentRetries = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 创建全局实例
|
|
export const resourceManager = new ResourceManager();
|
|
|
|
// 便捷函数
|
|
export async function registerResource<T extends any[], R> (
|
|
type: string,
|
|
config: ResourceConfig<T, R>,
|
|
...args: T
|
|
): Promise<R> {
|
|
return resourceManager.callResource(type, config, ...args);
|
|
}
|