mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-01-09 21:09:14 +08:00
* feat: pnpm new * Refactor build and release workflows, update dependencies Switch build scripts and workflows from npm to pnpm, update build and artifact paths, and simplify release workflow by removing version detection and changelog steps. Add new dependencies (silk-wasm, express, ws, node-pty-prebuilt-multiarch), update exports in package.json files, and add vite config for napcat-framework. Also, rename manifest.json for framework package and fix static asset copying in shell build config.
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);
|
|
}
|