import fs from 'fs'; import path from 'path'; import fetch from 'node-fetch'; import { logInfo, logDebug, logError } from './logger.js'; import { getNextProxyAgent } from './proxy-manager.js'; /** * Account Manager - 管理多个 OAuth 账号的选择、刷新和统计 * * 设计思路: * 1. 支持多个 refresh_token 账号池 * 2. 基于健康度加权轮询选择账号 * 3. 自动刷新 access_token * 4. 401/402 时自动禁用异常账号 */ const REFRESH_URL = 'https://api.workos.com/user_management/authenticate'; const REFRESH_INTERVAL_HOURS = 6; const CLIENT_ID = 'client_01HNM792M5G5G1A2THWPXKFMXB'; class AccountManager { constructor() { this.accounts = []; this.settings = { algorithm: 'weighted', // 'weighted' or 'simple' refresh_interval_hours: REFRESH_INTERVAL_HOURS, disable_on_401: true, disable_on_402: true }; this.simpleIndex = 0; this.configPath = null; this.endpointStats = {}; // 端点统计 } /** * 从配置文件加载账号 */ async loadAccounts(configPath = null) { // 优先检查环境变量中的多账号配置 const envAccounts = process.env.OAUTH_ACCOUNTS; if (envAccounts) { try { const parsed = JSON.parse(envAccounts); this.accounts = this._normalizeAccounts(parsed); logInfo(`AccountManager: 从环境变量加载了 ${this.accounts.length} 个账号`); return true; } catch (e) { logError('AccountManager: 解析 OAUTH_ACCOUNTS 环境变量失败', e); } } // 检查配置文件 const possiblePaths = [ configPath, path.join(process.cwd(), 'accounts.json'), path.join(process.cwd(), 'oauth_accounts.json') ].filter(Boolean); for (const p of possiblePaths) { if (fs.existsSync(p)) { try { const content = fs.readFileSync(p, 'utf-8'); const data = JSON.parse(content); this.accounts = this._normalizeAccounts(data.accounts || data); if (data.settings) { this.settings = { ...this.settings, ...data.settings }; } this.configPath = p; logInfo(`AccountManager: 从 ${p} 加载了 ${this.accounts.length} 个账号`); return true; } catch (e) { logError(`AccountManager: 读取配置文件 ${p} 失败`, e); } } } logInfo('AccountManager: 未找到多账号配置,将回退到单账号模式'); return false; } /** * 标准化账号数据结构 */ _normalizeAccounts(accounts) { if (!Array.isArray(accounts)) { accounts = [accounts]; } return accounts.map((acc, index) => ({ id: acc.id || `account_${index + 1}`, name: acc.name || `账号${index + 1}`, refresh_token: acc.refresh_token, access_token: acc.access_token || null, last_refresh: acc.last_refresh ? new Date(acc.last_refresh).getTime() : null, status: acc.status || 'active', // 'active', 'disabled', 'rate_limited' disable_reason: acc.disable_reason || null, stats: acc.stats || { success: 0, fail: 0 } })); } /** * 获取活跃账号列表 */ getActiveAccounts() { return this.accounts.filter(acc => acc.status === 'active'); } /** * 选择一个账号(基于配置的算法) */ selectAccount() { const activeAccounts = this.getActiveAccounts(); if (activeAccounts.length === 0) { throw new Error('AccountManager: 没有可用账号 - 所有账号已被禁用'); } if (activeAccounts.length === 1) { return activeAccounts[0]; } if (this.settings.algorithm === 'simple') { return this._simpleSelect(activeAccounts); } else { return this._weightedSelect(activeAccounts); } } /** * 简单轮询算法 */ _simpleSelect(accounts) { const account = accounts[this.simpleIndex % accounts.length]; this.simpleIndex = (this.simpleIndex + 1) % accounts.length; logDebug(`AccountManager: 简单轮询选择账号 ${account.id}`); return account; } /** * 基于健康度的加权选择算法 */ _weightedSelect(accounts) { const weights = accounts.map(acc => { const total = acc.stats.success + acc.stats.fail; if (total === 0) { return 1; // 新账号默认权重 } // 成功率 + 0.1(确保失败账号也有机会恢复) return (acc.stats.success / total) + 0.1; }); const totalWeight = weights.reduce((sum, w) => sum + w, 0); let random = Math.random() * totalWeight; for (let i = 0; i < weights.length; i++) { random -= weights[i]; if (random <= 0) { const acc = accounts[i]; logDebug(`AccountManager: 加权选择账号 ${acc.id}, 健康度: ${((weights[i] - 0.1) * 100).toFixed(1)}%`); return acc; } } return accounts[accounts.length - 1]; } /** * 获取账号的 access_token(自动刷新) */ async getAccessToken(account) { // 检查是否需要刷新 const needsRefresh = !account.access_token || this._shouldRefresh(account); if (needsRefresh) { await this._refreshToken(account); } return account.access_token; } /** * 检查是否需要刷新 token */ _shouldRefresh(account) { if (!account.last_refresh) { return true; } const hoursSinceRefresh = (Date.now() - account.last_refresh) / (1000 * 60 * 60); return hoursSinceRefresh >= this.settings.refresh_interval_hours; } /** * 刷新账号的 access_token */ async _refreshToken(account) { logInfo(`AccountManager: 刷新账号 ${account.id} 的 token...`); try { const formData = new URLSearchParams(); formData.append('grant_type', 'refresh_token'); formData.append('refresh_token', account.refresh_token); formData.append('client_id', CLIENT_ID); const proxyAgentInfo = getNextProxyAgent(REFRESH_URL); const fetchOptions = { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: formData.toString() }; if (proxyAgentInfo?.agent) { fetchOptions.agent = proxyAgentInfo.agent; } const response = await fetch(REFRESH_URL, fetchOptions); if (!response.ok) { const errorText = await response.text(); throw new Error(`刷新失败: ${response.status} ${errorText}`); } const data = await response.json(); // 更新账号信息 account.access_token = data.access_token; account.refresh_token = data.refresh_token; // 更新 refresh_token account.last_refresh = Date.now(); // 记录用户信息 if (data.user) { account.email = data.user.email; logInfo(`AccountManager: 账号 ${account.id} 刷新成功 - ${data.user.email}`); } // 保存配置 this._saveConfig(); return data.access_token; } catch (error) { logError(`AccountManager: 账号 ${account.id} 刷新失败`, error); // 刷新失败可能是 refresh_token 失效,禁用账号 if (error.message.includes('401') || error.message.includes('invalid')) { this.disableAccount(account.id, 'refresh_token_invalid'); } throw error; } } /** * 记录请求结果 */ recordResult(accountId, endpoint, success, statusCode) { const account = this.accounts.find(acc => acc.id === accountId); if (!account) return; if (success) { account.stats.success++; } else { account.stats.fail++; // 检查是否需要禁用账号 if (statusCode === 401 && this.settings.disable_on_401) { this.disableAccount(accountId, '401_unauthorized'); } else if (statusCode === 402 && this.settings.disable_on_402) { this.disableAccount(accountId, '402_payment_required'); } } // 更新端点统计 if (!this.endpointStats[endpoint]) { this.endpointStats[endpoint] = { success: 0, fail: 0 }; } if (success) { this.endpointStats[endpoint].success++; } else { this.endpointStats[endpoint].fail++; } } /** * 禁用账号 */ disableAccount(accountId, reason) { const account = this.accounts.find(acc => acc.id === accountId); if (!account) return; account.status = 'disabled'; account.disable_reason = reason; account.disabled_at = new Date().toISOString(); logError(`AccountManager: 账号 ${accountId} 已禁用,原因: ${reason}`); // 保存配置 this._saveConfig(); // 记录到废弃账号文件 this._logDeprecatedAccount(account); } /** * 记录废弃账号到文件 */ _logDeprecatedAccount(account) { try { const logPath = path.join(process.cwd(), 'deprecated_accounts.txt'); const logEntry = `[${new Date().toISOString()}] ${account.id} (${account.email || 'unknown'}) - ${account.disable_reason}\n`; fs.appendFileSync(logPath, logEntry, 'utf-8'); } catch (e) { logError('AccountManager: 记录废弃账号失败', e); } } /** * 保存配置到文件 */ _saveConfig() { if (!this.configPath) return; try { const data = { accounts: this.accounts.map(acc => ({ id: acc.id, name: acc.name, refresh_token: acc.refresh_token, access_token: acc.access_token, last_refresh: acc.last_refresh ? new Date(acc.last_refresh).toISOString() : null, status: acc.status, disable_reason: acc.disable_reason, email: acc.email, stats: acc.stats })), settings: this.settings, last_updated: new Date().toISOString() }; fs.writeFileSync(this.configPath, JSON.stringify(data, null, 2), 'utf-8'); logDebug(`AccountManager: 配置已保存到 ${this.configPath}`); } catch (e) { logError('AccountManager: 保存配置失败', e); } } /** * 获取状态信息(用于 /status 页面) */ getStatus() { return { total_accounts: this.accounts.length, active_accounts: this.getActiveAccounts().length, algorithm: this.settings.algorithm, accounts: this.accounts.map(acc => ({ id: acc.id, name: acc.name, email: acc.email || 'unknown', status: acc.status, disable_reason: acc.disable_reason, stats: acc.stats, health: this._calculateHealth(acc), last_refresh: acc.last_refresh ? new Date(acc.last_refresh).toISOString() : null })), endpoint_stats: this.endpointStats }; } /** * 计算账号健康度 */ _calculateHealth(account) { const total = account.stats.success + account.stats.fail; if (total === 0) return 100; return Math.round((account.stats.success / total) * 100); } /** * 检查是否有多账号配置 */ hasMultipleAccounts() { return this.accounts.length > 1; } /** * 获取账号数量 */ getAccountCount() { return this.accounts.length; } } // 单例实例 let accountManagerInstance = null; /** * 获取 AccountManager 实例 */ export function getAccountManager() { if (!accountManagerInstance) { accountManagerInstance = new AccountManager(); } return accountManagerInstance; } /** * 初始化 AccountManager */ export async function initializeAccountManager() { const manager = getAccountManager(); const loaded = await manager.loadAccounts(); return loaded; } export default AccountManager;