This commit is contained in:
手瓜一十雪 2025-10-16 21:13:50 +08:00
parent cbacc89907
commit 42abc2b9cb

View File

@ -9,41 +9,50 @@ export interface ResourceConfig<T extends any[], R> {
healthCheckInterval?: number; healthCheckInterval?: number;
/** 最大健康检查失败次数,超过后永久禁用,默认 5 次 */ /** 最大健康检查失败次数,超过后永久禁用,默认 5 次 */
maxHealthCheckFailures?: number; maxHealthCheckFailures?: number;
/** 资源名称(用于日志) */
name?: string;
/** 测试参数(用于健康检查) */
testArgs?: T;
/** 健康检查函数,如果提供则优先使用此函数进行健康检查 */ /** 健康检查函数,如果提供则优先使用此函数进行健康检查 */
healthCheckFn?: (...args: T) => Promise<boolean>; healthCheckFn?: (...args: T) => Promise<boolean>;
/** 测试参数(用于健康检查) */
testArgs?: T;
} }
interface ResourceState<T extends any[], R> { interface ResourceTypeState {
config: ResourceConfig<T, R>; /** 资源配置 */
config: {
resourceFn: (...args: any[]) => Promise<any>;
healthCheckFn?: (...args: any[]) => Promise<boolean>;
disableTime: number;
maxRetries: number;
healthCheckInterval: number;
maxHealthCheckFailures: number;
testArgs?: any[];
};
/** 是否启用 */
isEnabled: boolean; isEnabled: boolean;
/** 禁用截止时间 */
disableUntil: number; disableUntil: number;
/** 当前重试次数 */
currentRetries: number; currentRetries: number;
/** 健康检查失败次数 */
healthCheckFailureCount: number; healthCheckFailureCount: number;
/** 是否永久禁用 */
isPermanentlyDisabled: boolean; isPermanentlyDisabled: boolean;
lastError?: Error; /** 上次健康检查时间 */
lastHealthCheckTime: number; lastHealthCheckTime: number;
registrationKey: string; /** 成功次数统计 */
successCount: number;
/** 失败次数统计 */
failureCount: number;
} }
export class ResourceManager { export class ResourceManager {
private resources = new Map<string, ResourceState<any, any>>(); private resourceTypes = new Map<string, ResourceTypeState>();
private destroyed = false; private destroyed = false;
private healthCheckTimer?: NodeJS.Timeout;
private readonly HEALTH_CHECK_TASK_INTERVAL = 5000; // 5秒执行一次健康检查任务
constructor() {
this.startHealthCheckTask();
}
/** /**
* *
*/ */
async register<T extends any[], R>( async callResource<T extends any[], R>(
key: string, type: string,
config: ResourceConfig<T, R>, config: ResourceConfig<T, R>,
...args: T ...args: T
): Promise<R> { ): Promise<R> {
@ -51,81 +60,64 @@ export class ResourceManager {
throw new Error('ResourceManager has been destroyed'); throw new Error('ResourceManager has been destroyed');
} }
const registrationKey = this.generateRegistrationKey(key, config); // 获取或创建资源类型状态
let state = this.resourceTypes.get(type);
// 检查是否已经注册
if (this.resources.has(key)) {
const existingState = this.resources.get(key)!;
// 如果是相同的配置,直接调用
if (existingState.registrationKey === registrationKey) {
return this.callResource<T, R>(key, ...args);
}
// 配置不同,清理旧的并重新注册
this.unregister(key);
}
// 创建新的资源状态
const state: ResourceState<T, R> = {
config: {
disableTime: 30000,
maxRetries: 3,
healthCheckInterval: 60000,
maxHealthCheckFailures: 5,
name: key,
...config
},
isEnabled: true,
disableUntil: 0,
currentRetries: 0,
healthCheckFailureCount: 0,
isPermanentlyDisabled: false,
lastHealthCheckTime: 0,
registrationKey
};
this.resources.set(key, state);
// 注册即调用
return await this.callResource<T, R>(key, ...args);
}
/**
*
*/
async callResource<T extends any[], R>(key: string, ...args: T): Promise<R> {
const state = this.resources.get(key) as ResourceState<T, R> | undefined;
if (!state) { if (!state) {
throw new Error(`Resource ${key} not registered`); // 首次注册
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) { if (state.isPermanentlyDisabled) {
throw new Error(`Resource ${key} is permanently disabled due to repeated health check failures`); throw new Error(`Resource type '${type}' is permanently disabled (success: ${state.successCount}, failure: ${state.failureCount})`);
} }
if (!this.isResourceAvailable(key)) { if (!this.isResourceAvailable(type)) {
const disableUntilDate = new Date(state.disableUntil).toISOString(); const disableUntilDate = new Date(state.disableUntil).toISOString();
throw new Error(`Resource ${key} is currently disabled until ${disableUntilDate}`); throw new Error(`Resource type '${type}' is currently disabled until ${disableUntilDate} (success: ${state.successCount}, failure: ${state.failureCount})`);
} }
// 调用资源
try { try {
const result = await state.config.resourceFn(...args); const result = await config.resourceFn(...args);
this.onResourceSuccess(state); this.onResourceSuccess(state);
return result; return result;
} catch (error) { } catch (error) {
this.onResourceFailure(state, error as Error); this.onResourceFailure(state);
throw error; throw error;
} }
} }
/** /**
* *
*/ */
isResourceAvailable(key: string): boolean { isResourceAvailable(type: string): boolean {
const state = this.resources.get(key); const state = this.resourceTypes.get(type);
if (!state) { if (!state) {
return false; return true; // 未注册的资源类型视为可用
} }
if (state.isPermanentlyDisabled || !state.isEnabled) { if (state.isPermanentlyDisabled || !state.isEnabled) {
@ -136,128 +128,97 @@ export class ResourceManager {
} }
/** /**
* *
*/ */
unregister(key: string): boolean { getResourceStats(type: string): { successCount: number; failureCount: number; isEnabled: boolean; isPermanentlyDisabled: boolean } | null {
return this.resources.delete(key); 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 { destroy(): void {
if (this.destroyed) { if (this.destroyed) {
return; return;
} }
this.stopHealthCheckTask(); this.resourceTypes.clear();
this.resources.clear();
this.destroyed = true; this.destroyed = true;
} }
private generateRegistrationKey<T extends any[], R>(key: string, config: ResourceConfig<T, R>): string { /**
const configStr = JSON.stringify({ *
name: config.name, */
disableTime: config.disableTime, private async checkAndPerformHealthCheck(state: ResourceTypeState): Promise<void> {
maxRetries: config.maxRetries, // 如果资源可用或已永久禁用,无需健康检查
healthCheckInterval: config.healthCheckInterval, if (state.isEnabled && Date.now() >= state.disableUntil) {
maxHealthCheckFailures: config.maxHealthCheckFailures,
functionStr: config.resourceFn.toString(),
healthCheckFnStr: config.healthCheckFn?.toString()
});
return `${key}_${this.simpleHash(configStr)}`;
}
private simpleHash(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return Math.abs(hash).toString(36);
}
private onResourceSuccess<T extends any[], R>(state: ResourceState<T, R>): void {
state.currentRetries = 0;
state.disableUntil = 0;
state.healthCheckFailureCount = 0;
state.lastError = undefined;
}
private onResourceFailure<T extends any[], R>(state: ResourceState<T, R>, error: Error): void {
state.currentRetries++;
state.lastError = error;
// 如果重试次数达到上限,禁用资源
if (state.currentRetries >= state.config.maxRetries!) {
state.disableUntil = Date.now() + state.config.disableTime!;
state.currentRetries = 0;
}
}
private startHealthCheckTask(): void {
if (this.healthCheckTimer) {
return; return;
} }
this.healthCheckTimer = setInterval(() => { if (state.isPermanentlyDisabled) {
this.runHealthCheckTask();
}, this.HEALTH_CHECK_TASK_INTERVAL);
}
private stopHealthCheckTask(): void {
if (this.healthCheckTimer) {
clearInterval(this.healthCheckTimer);
this.healthCheckTimer = undefined;
}
}
private async runHealthCheckTask(): Promise<void> {
if (this.destroyed) {
return; return;
} }
const now = Date.now(); const now = Date.now();
for (const [key, state] of this.resources) { // 检查是否还在禁用期内
// 跳过永久禁用或可用的资源 if (now < state.disableUntil) {
if (state.isPermanentlyDisabled || this.isResourceAvailable(key)) { return;
continue;
}
// 跳过还在禁用期内的资源
if (now < state.disableUntil) {
continue;
}
// 检查是否需要进行健康检查(根据间隔时间)
const lastHealthCheck = state.lastHealthCheckTime || 0;
const healthCheckInterval = state.config.healthCheckInterval!;
if (now - lastHealthCheck < healthCheckInterval) {
continue;
}
// 执行健康检查
await this.performHealthCheck(state);
} }
// 检查是否需要进行健康检查(根据间隔时间)
if (now - state.lastHealthCheckTime < state.config.healthCheckInterval) {
return;
}
// 执行健康检查
await this.performHealthCheck(state);
} }
private async performHealthCheck<T extends any[], R>(state: ResourceState<T, R>): Promise<void> { private async performHealthCheck(state: ResourceTypeState): Promise<void> {
state.lastHealthCheckTime = Date.now(); state.lastHealthCheckTime = Date.now();
try { try {
let healthCheckResult: boolean; let healthCheckResult: boolean;
// 如果有专门的健康检查函数,使用它
if (state.config.healthCheckFn) { if (state.config.healthCheckFn) {
const testArgs = state.config.testArgs || [] as unknown as T; const testArgs = state.config.testArgs || [];
healthCheckResult = await state.config.healthCheckFn(...testArgs); healthCheckResult = await state.config.healthCheckFn(...testArgs);
} else { } else {
// 否则使用原始函数进行检查 const testArgs = state.config.testArgs || [];
const testArgs = state.config.testArgs || [] as unknown as T;
await state.config.resourceFn(...testArgs); await state.config.resourceFn(...testArgs);
healthCheckResult = true; healthCheckResult = true;
} }
@ -268,26 +229,42 @@ export class ResourceManager {
state.disableUntil = 0; state.disableUntil = 0;
state.currentRetries = 0; state.currentRetries = 0;
state.healthCheckFailureCount = 0; state.healthCheckFailureCount = 0;
state.lastError = undefined;
} else { } else {
throw new Error('Health check function returned false'); throw new Error('Health check function returned false');
} }
} catch (error) { } catch {
// 健康检查失败,增加失败计数 // 健康检查失败,增加失败计数
state.healthCheckFailureCount++; state.healthCheckFailureCount++;
state.lastError = error as Error;
// 检查是否达到最大健康检查失败次数 // 检查是否达到最大健康检查失败次数
if (state.healthCheckFailureCount >= state.config.maxHealthCheckFailures!) { if (state.healthCheckFailureCount >= state.config.maxHealthCheckFailures) {
// 永久禁用资源 // 永久禁用资源
state.isPermanentlyDisabled = true; state.isPermanentlyDisabled = true;
state.disableUntil = 0; state.disableUntil = 0;
} else { } else {
// 继续禁用一段时间 // 继续禁用一段时间
state.disableUntil = Date.now() + state.config.disableTime!; 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;
}
}
} }
// 创建全局实例 // 创建全局实例
@ -295,34 +272,9 @@ export const resourceManager = new ResourceManager();
// 便捷函数 // 便捷函数
export async function registerResource<T extends any[], R>( export async function registerResource<T extends any[], R>(
key: string, type: string,
config: ResourceConfig<T, R>, config: ResourceConfig<T, R>,
...args: T ...args: T
): Promise<R> { ): Promise<R> {
return resourceManager.register(key, config, ...args); return resourceManager.callResource(type, config, ...args);
} }
// 使用示例:
/*
await registerResource(
'api-with-health-check',
{
resourceFn: async (id: string) => {
const response = await fetch(`https://api.example.com/data/${id}`);
return response.json();
},
healthCheckFn: async (id: string) => {
try {
const response = await fetch(`https://api.example.com/health`);
return response.ok;
} catch {
return false;
}
},
testArgs: ['health-check-id'],
healthCheckInterval: 30000,
maxHealthCheckFailures: 3
},
'user123'
);
*/