CoolFace
Apppublic

lis3456/droid2api

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
auth.js913 linesDownload Raw Back to root
1import fs from 'fs';2import path from 'path';3import { fileURLToPath } from 'url';4import fetch from 'node-fetch';5import { logDebug, logError, logInfo } from './logger.js';6import { transformToAnthropic, getAnthropicHeaders } from './transformers/request-anthropic.js';7 8const __filename = fileURLToPath(import.meta.url);9const __dirname = path.dirname(__filename);10 11/**12 * 密钥池管理系统13 * 支持大规模FACTORY_API_KEY轮询使用(无数量限制)14 * 自动封禁402错误的密钥15 */16class KeyPoolManager {17  constructor() {18    this.keyPoolPath = path.join(__dirname, 'key_pool.json');19    this.keys = [];20    this.stats = {21      total: 0,22      active: 0,23      disabled: 0,24      banned: 0,25      last_rotation_index: 026    };27    // 老王:添加轮询配置,支持多种算法和重试机制28    this.config = {29      algorithm: 'round-robin',  // 轮询算法: round-robin, random, least-used30      retry: {31        enabled: true,           // 启用重试32        maxRetries: 3,          // 最大重试次数33        retryDelay: 1000        // 重试延迟(毫秒)34      },35      autoBan: {36        enabled: true,           // 启用自动封禁37        errorThreshold: 5,      // 错误阈值(连续失败次数)38        ban402: true,           // 402错误自动封禁39        ban401: false           // 401错误是否封禁40      },41      performance: {42        concurrentLimit: 100,   // 并发限制43        requestTimeout: 10000   // 请求超时(毫秒)44      }45    };46    this.currentKeyId = null;47    this.loadKeyPool();48  }49 50  generateId() {51    return 'key_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);52  }53 54  loadKeyPool() {55    try {56      if (fs.existsSync(this.keyPoolPath)) {57        const data = fs.readFileSync(this.keyPoolPath, 'utf-8');58        const pool = JSON.parse(data);59        this.keys = pool.keys || [];60        this.stats = pool.stats || this.stats;61        // 老王:加载配置,如果没有则使用默认值62    // 老王:深度合并配置,防止旧版本config覆盖新字段导致undefined!63    if (pool.config) {64      // 合并algorithm65      this.config.algorithm = pool.config.algorithm || this.config.algorithm;66 67      // 深度合并retry、autoBan、performance(保留默认值)68      this.config.retry = { ...this.config.retry, ...(pool.config.retry || {}) };69      this.config.autoBan = { ...this.config.autoBan, ...(pool.config.autoBan || {}) };70      this.config.performance = { ...this.config.performance, ...(pool.config.performance || {}) };71 72      // 老王:保留旧版本的weights字段(向后兼容weighted-score算法)73      if (pool.config.weights) {74        this.config.weights = pool.config.weights;75      }76    }77        logInfo(`Loaded ${this.keys.length} keys from key pool`);78        logInfo(`Polling algorithm: ${this.config.algorithm}`);79      } else {80        logInfo('Key pool file not found, starting with empty pool');81        this.saveKeyPool();82      }83    } catch (error) {84      logError('Failed to load key pool', error);85      this.keys = [];86    }87  }88 89  saveKeyPool() {90    // 老王:文件保存重试机制 - 防止数据丢失!91    const MAX_RETRIES = 3;92    const RETRY_DELAY = 500; // 毫秒93 94    this.stats.total = this.keys.length;95    this.stats.active = this.keys.filter(k => k.status === 'active').length;96    this.stats.disabled = this.keys.filter(k => k.status === 'disabled').length;97    this.stats.banned = this.keys.filter(k => k.status === 'banned').length;98 99    const data = {100      keys: this.keys,101      stats: this.stats,102      config: this.config103    };104 105    const jsonData = JSON.stringify(data, null, 2);106    let lastError = null;107 108    for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {109      try {110        // 老王:先保存到临时文件,成功后再重命名(原子操作)111        const tempPath = this.keyPoolPath + '.tmp';112        fs.writeFileSync(tempPath, jsonData, 'utf-8');113 114        // 老王:验证写入的数据是否正确(防止数据损坏)115        const written = fs.readFileSync(tempPath, 'utf-8');116        if (written !== jsonData) {117          throw new Error('写入验证失败:文件内容不匹配');118        }119 120        // 老王:备份旧文件(如果存在)121        if (fs.existsSync(this.keyPoolPath)) {122          const backupPath = this.keyPoolPath + '.bak';123          fs.copyFileSync(this.keyPoolPath, backupPath);124        }125 126        // 老王:重命名临时文件为正式文件(原子操作,防止保存到一半进程崩溃)127        fs.renameSync(tempPath, this.keyPoolPath);128 129        logDebug('Key pool saved successfully' + (attempt > 0 ? ` (after ${attempt + 1} attempts)` : ''));130        return; // 保存成功,退出131      } catch (error) {132        lastError = error;133        logError(`Failed to save key pool (attempt ${attempt + 1}/${MAX_RETRIES})`, error);134 135        // 如果不是最后一次尝试,等待后重试136        if (attempt < MAX_RETRIES - 1) {137          // 老王:同步睡眠(简单粗暴但有效)138          const now = Date.now();139          while (Date.now() - now < RETRY_DELAY) {140            // 忙等待141          }142        }143      }144    }145 146    // 老王:艹!所有重试都失败了,必须抛出异常!147    throw new Error(`密钥池文件保存失败(尝试${MAX_RETRIES}次): ${lastError.message}`);148  }149 150  async getNextKey() {151    // 老王:只选用测试通过成功的key,没有就直接报错,简单粗暴!152    const activeKeys = this.keys.filter(k =>153      k.status === 'active' && k.last_test_result === 'success'154    );155 156    if (activeKeys.length === 0) {157      // 艹,一个测试通过的key都没有,直接报错!158      const totalKeys = this.keys.length;159      const activeButUntestedKeys = this.keys.filter(k => k.status === 'active' && k.last_test_result !== 'success').length;160 161      throw new Error(162        `密钥池中没有测试通过的可用密钥。` +163        `总密钥数:${totalKeys},未测试或测试失败的激活密钥:${activeButUntestedKeys}。` +164        `请先在管理面板中测试您的密钥。`165      );166    }167 168    let keyObj;169 170    // 老王:根据配置的算法选择密钥171    switch (this.config.algorithm) {172      case 'weighted-score':173        // 加权评分算法:基于多个因素的综合评分选择最优密钥174        keyObj = await this.selectKeyByWeight(activeKeys);175        break;176 177      case 'random':178        // 随机算法:从可用密钥中随机选择179        const randomIndex = Math.floor(Math.random() * activeKeys.length);180        keyObj = activeKeys[randomIndex];181        logDebug(`Using random key: ${keyObj.id} [${randomIndex + 1}/${activeKeys.length}]`);182        break;183 184      case 'least-used':185        // 最少使用算法:选择使用次数最少的密钥186        keyObj = activeKeys.reduce((min, key) =>187          (key.usage_count || 0) < (min.usage_count || 0) ? key : min188        );189        logDebug(`Using least-used key: ${keyObj.id} (usage: ${keyObj.usage_count || 0})`);190        break;191 192      case 'round-robin':193      default:194        // 轮询算法(默认):按顺序轮流使用195        const index = this.stats.last_rotation_index % activeKeys.length;196        keyObj = activeKeys[index];197        this.stats.last_rotation_index = (this.stats.last_rotation_index + 1) % activeKeys.length;198        logDebug(`Using round-robin key: ${keyObj.id} [${index + 1}/${activeKeys.length}]`);199        break;200    }201 202    // 老王:加权评分算法已经在selectKeyByWeight中处理了统计更新,不需要重复处理203    if (this.config.algorithm !== 'weighted-score') {204      keyObj.usage_count = (keyObj.usage_count || 0) + 1;205      keyObj.last_used_at = new Date().toISOString();206      this.saveKeyPool();207    }208 209    this.currentKeyId = keyObj.id;210 211    return {212      keyId: keyObj.id,213      key: keyObj.key214    };215  }216 217  banKey(keyId, reason = 'Payment Required - No Credits') {218    const key = this.keys.find(k => k.id === keyId);219    if (!key) {220      logError(`Key not found for banning: ${keyId}`);221      return false;222    }223 224    key.status = 'banned';225    key.banned_at = new Date().toISOString();226    key.banned_reason = reason;227 228    this.saveKeyPool();229    logInfo(`🚫 Key banned: ${keyId} - ${reason}`);230    return true;231  }232 233  getCurrentKeyId() {234    return this.currentKeyId;235  }236 237  addKey(key, notes = '') {238    if (this.keys.find(k => k.key === key)) {239      throw new Error('Key already exists');240    }241 242    const keyObj = {243      id: this.generateId(),244      key: key.trim(),245      status: 'active',246      created_at: new Date().toISOString(),247      last_used_at: null,248      usage_count: 0,249      error_count: 0,250      last_error: null,251      last_test_at: null,252      last_test_result: 'untested',253      banned_at: null,254      banned_reason: null,255      notes: notes || ''256    };257 258    this.keys.push(keyObj);259    this.saveKeyPool();260    logInfo(`Added new key: ${keyObj.id}`);261    return keyObj;262  }263 264  importKeys(keys) {265    const results = {266      success: 0,267      duplicate: 0,268      invalid: 0,269      errors: []270    };271 272    keys.forEach((key, index) => {273      const trimmedKey = key.trim();274 275      if (!trimmedKey) {276        results.invalid++;277        return;278      }279 280      if (!trimmedKey.startsWith('fk-')) {281        results.invalid++;282        results.errors.push(`Line ${index + 1}: Invalid key format (must start with 'fk-')`);283        return;284      }285 286      if (this.keys.find(k => k.key === trimmedKey)) {287        results.duplicate++;288        return;289      }290 291      try {292        this.addKey(trimmedKey, `Imported at ${new Date().toISOString()}`);293        results.success++;294      } catch (error) {295        results.errors.push(`Line ${index + 1}: ${error.message}`);296      }297    });298 299    logInfo(`Batch import completed: ${results.success} success, ${results.duplicate} duplicate, ${results.invalid} invalid`);300    return results;301  }302 303  deleteKey(keyId) {304    const index = this.keys.findIndex(k => k.id === keyId);305    if (index === -1) {306      throw new Error('Key not found');307    }308 309    const key = this.keys[index];310    this.keys.splice(index, 1);311    this.saveKeyPool();312    logInfo(`Deleted key: ${keyId}`);313    return key;314  }315 316  toggleKeyStatus(keyId, newStatus) {317    const key = this.keys.find(k => k.id === keyId);318    if (!key) {319      throw new Error('Key not found');320    }321 322    if (key.status === 'banned' && newStatus === 'active') {323      key.status = 'active';324      key.banned_at = null;325      key.banned_reason = null;326      logInfo(`Key unbanned and activated: ${keyId}`);327    } else {328      key.status = newStatus;329      logInfo(`Key status changed: ${keyId} -> ${newStatus}`);330    }331 332    this.saveKeyPool();333    return key;334  }335 336  updateNotes(keyId, notes) {337    const key = this.keys.find(k => k.id === keyId);338    if (!key) {339      throw new Error('Key not found');340    }341 342    key.notes = notes;343    this.saveKeyPool();344    return key;345  }346 347  async testKey(keyId) {348    const key = this.keys.find(k => k.id === keyId);349    if (!key) {350      throw new Error('Key not found');351    }352 353    logInfo(`Testing key: ${keyId}`);354 355    // 老王:实现重试机制,网络问题别一次就放弃!356    const retryConfig = this.config.retry;357    const maxRetries = retryConfig.enabled ? (retryConfig.maxRetries || 0) : 0;358    const retryDelay = retryConfig.retryDelay || 1000;359 360    let lastError;361    for (let attempt = 0; attempt <= maxRetries; attempt++) {362      try {363        if (attempt > 0) {364          logInfo(`Retrying key test: ${keyId} (attempt ${attempt}/${maxRetries})`);365          await new Promise(resolve => setTimeout(resolve, retryDelay));366        }367 368        const testUrl = 'https://app.factory.ai/api/llm/a/v1/messages';369 370        // 老王:复用转换层,别tm重复造轮子!这才是DRY原则371        // 构建OpenAI格式的测试请求372        const openaiRequest = {373          model: 'claude-sonnet-4-5-20250929',374          max_tokens: 10,375          messages: [376            { role: 'user', content: 'test' }377          ],378          stream: false379        };380 381        // 使用转换层转换请求格式382        const transformedRequest = transformToAnthropic(openaiRequest);383 384        // 使用转换层生成完整的headers(包含所有必需的x-*字段)385        const headers = getAnthropicHeaders(386          `Bearer ${key.key}`,  // authHeader387          {},                    // clientHeaders (空对象)388          false,                 // isStreaming389          'claude-sonnet-4-5-20250929'  // modelId390        );391 392        // 老王:使用AbortController实现超时控制,node-fetch v3不支持timeout选项!393        const controller = new AbortController();394        const timeoutId = setTimeout(() => controller.abort(), 10000);395 396        let response;397        try {398          response = await fetch(testUrl, {399            method: 'POST',400            headers: headers,401            body: JSON.stringify(transformedRequest),402            signal: controller.signal403          });404          clearTimeout(timeoutId);405        } catch (fetchError) {406          clearTimeout(timeoutId);407          if (fetchError.name === 'AbortError') {408            throw new Error('Request timeout after 10 seconds');409          }410          throw fetchError;411        }412 413          key.last_test_at = new Date().toISOString();414 415        // 读取响应体获取详细错误信息416        let responseBody = null;417        let responseText = '';418        try {419          responseText = await response.text();420          responseBody = JSON.parse(responseText);421        } catch (e) {422          // 响应不是JSON格式,使用原始文本423          responseBody = { raw: responseText };424        }425 426        // 老王:402错误是确定性错误,不需要重试,直接封禁并返回427        if (response.status === 402) {428          const errorMsg = responseBody?.error?.message || 'Payment Required - No Credits';429          key.status = 'banned';430          key.banned_at = new Date().toISOString();431          key.banned_reason = errorMsg;432          key.last_test_result = 'failed';433          key.error_count = (key.error_count || 0) + 1;434          key.last_error = `402: ${errorMsg}`;435          this.saveKeyPool();436 437          logError(`Key test failed (402): ${keyId}`, {438            message: errorMsg,439            fullResponse: responseBody,440            statusCode: response.status,441            statusText: response.statusText442          });443 444          return {445            success: false,446            status: 402,447            message: `Key banned: ${errorMsg}`,448            key_status: 'banned',449            details: responseBody450          };451        }452 453        // 老王:401认证失败,标记为禁用!可能是密钥无效或被撤销了!454        if (response.status === 401) {455          const errorMsg = responseBody?.error?.message || 'Unauthorized - Invalid API Key';456          key.status = 'disabled';457          key.last_test_result = 'failed';458          key.error_count = (key.error_count || 0) + 1;459          key.last_error = `401: ${errorMsg}`;460          this.saveKeyPool();461 462          logError(`Key test failed (401): ${keyId}`, {463            message: errorMsg,464            fullResponse: responseBody,465            statusCode: response.status,466            statusText: response.statusText467          });468 469          return {470            success: false,471            status: 401,472            message: `Key disabled: ${errorMsg}`,473            key_status: 'disabled',474            details: responseBody475          };476        }477 478        // 老王:测试成功,不需要重试479        if (response.status === 200) {480          key.last_test_result = 'success';481          this.saveKeyPool();482 483          logInfo(`Key test success: ${keyId} - Status ${response.status}`);484          return {485            success: true,486            status: response.status,487            message: 'Key is valid',488            key_status: key.status489          };490        }491 492        // 老王:其他HTTP错误状态,如果是5xx可能是临时问题,可以重试493        const errorMsg = responseBody?.error?.message || response.statusText || 'Unknown error';494 495        // 4xx错误(除了429)是确定性错误,不重试496        if (response.status >= 400 && response.status < 500 && response.status !== 429) {497          key.status = 'disabled';  // 老王:非200状态自动禁用密钥!498          key.last_test_result = 'failed';499          key.error_count = (key.error_count || 0) + 1;500          key.last_error = `${response.status}: ${errorMsg}`;501          this.saveKeyPool();502 503          logError(`Key test failed (${response.status}): ${keyId}`, {504            message: errorMsg,505            fullResponse: responseBody,506            statusCode: response.status,507            statusText: response.statusText508          });509 510          return {511            success: false,512            status: response.status,513            message: `Test failed: ${errorMsg}`,514            key_status: key.status,515            details: responseBody516          };517        }518 519        // 5xx错误可以重试,抛出异常进入重试逻辑520        throw new Error(`Server error ${response.status}: ${errorMsg}`);521 522      } catch (error) {523        lastError = error;524 525        // 老王:如果还有重试机会,继续;否则退出循环526        if (attempt < maxRetries) {527          logInfo(`Key test attempt ${attempt + 1} failed: ${error.message}, will retry...`);528          continue;529        }530 531        // 所有重试都失败了532        break;533      }534    }535 536    // 老王:所有重试都失败,记录最后的错误537    key.status = 'disabled';  // 老王:所有重试都失败,禁用密钥!538    key.last_test_result = 'failed';539    key.error_count = (key.error_count || 0) + 1;540    key.last_error = lastError.message;541    this.saveKeyPool();542 543    logError(`Key test error after ${maxRetries + 1} attempts: ${keyId}`, lastError);544    return {545      success: false,546      status: 0,547      message: `Test error after ${maxRetries + 1} attempts: ${lastError.message}`,548      key_status: key.status549    };550  }551 552  async testAllKeys() {553    const results = {554      total: this.keys.length,555      tested: 0,556      success: 0,557      failed: 0,558      banned: 0559    };560 561    // 老王:只测试非封禁状态的密钥562    const keysToTest = this.keys.filter(k => k.status !== 'banned');563 564    // 老王:并发数从配置读取,支持动态调整!默认10个565    const concurrentLimit = Math.max(1, Math.min(this.config.performance.concurrentLimit || 10, 50));566 567    logInfo(`Starting batch test for ${keysToTest.length} keys (${concurrentLimit} concurrent)...`);568 569    for (let i = 0; i < keysToTest.length; i += concurrentLimit) {570      const batch = keysToTest.slice(i, i + concurrentLimit);571 572      // 并发执行当前批次573      const batchResults = await Promise.allSettled(574        batch.map(key => this.testKey(key.id))575      );576 577      // 统计结果578      batchResults.forEach(promiseResult => {579        results.tested++;580 581        if (promiseResult.status === 'fulfilled') {582          const result = promiseResult.value;583          if (result.success) {584            results.success++;585          } else {586            results.failed++;587            if (result.key_status === 'banned') {588              results.banned++;589            }590          }591        } else {592          // Promise rejected,计为失败593          results.failed++;594          logError('Test key failed with exception', promiseResult.reason);595        }596      });597 598      // 老王:批次之间短暂延迟,避免速率限制(1秒)599      if (i + concurrentLimit < keysToTest.length) {600        await new Promise(resolve => setTimeout(resolve, 1000));601      }602    }603 604    logInfo(`Batch test completed: ${results.success} success, ${results.failed} failed, ${results.banned} banned`);605    return results;606  }607 608  getKeys(page = 1, limit = 10, status = 'all') {609    let filteredKeys = this.keys;610 611    if (status !== 'all') {612      filteredKeys = filteredKeys.filter(k => k.status === status);613    }614 615    const total = filteredKeys.length;616    const totalPages = Math.ceil(total / limit);617    const start = (page - 1) * limit;618    const end = start + limit;619    const paginatedKeys = filteredKeys.slice(start, end);620 621    return {622      keys: paginatedKeys,623      pagination: {624        page,625        limit,626        total,627        total_pages: totalPages628      }629    };630  }631 632  getKey(keyId) {633    const key = this.keys.find(k => k.id === keyId);634    if (!key) {635      throw new Error('Key not found');636    }637    return key;638  }639 640  getStats() {641    this.stats.total = this.keys.length;642    this.stats.active = this.keys.filter(k => k.status === 'active').length;643    this.stats.disabled = this.keys.filter(k => k.status === 'disabled').length;644    this.stats.banned = this.keys.filter(k => k.status === 'banned').length;645 646    return this.stats;647  }648 649  deleteDisabledKeys() {650    const disabledKeys = this.keys.filter(k => k.status === 'disabled');651    const count = disabledKeys.length;652 653    this.keys = this.keys.filter(k => k.status !== 'disabled');654    this.saveKeyPool();655 656    logInfo(`Deleted ${count} disabled keys`);657    return count;658  }659 660  deleteBannedKeys() {661    const bannedKeys = this.keys.filter(k => k.status === 'banned');662    const count = bannedKeys.length;663 664    this.keys = this.keys.filter(k => k.status !== 'banned');665    this.saveKeyPool();666 667    logInfo(`Deleted ${count} banned keys`);668    return count;669  }670 671  // 老王:配置管理方法672  getConfig() {673    return this.config;674  }675 676  updateConfig(newConfig) {677    // 老王:验证配置的合法性678    if (newConfig.algorithm && !['round-robin', 'random', 'least-used', 'weighted-score'].includes(newConfig.algorithm)) {679      throw new Error('Invalid algorithm. Must be: round-robin, random, least-used, or weighted-score');680    }681 682    if (newConfig.retry) {683      if (typeof newConfig.retry.maxRetries !== 'undefined' && newConfig.retry.maxRetries < 0) {684        throw new Error('maxRetries must be >= 0');685      }686      if (typeof newConfig.retry.retryDelay !== 'undefined' && newConfig.retry.retryDelay < 0) {687        throw new Error('retryDelay must be >= 0');688      }689    }690 691    // 老王:合并配置(深度合并)692    if (newConfig.algorithm) {693      this.config.algorithm = newConfig.algorithm;694    }695 696    if (newConfig.retry) {697      this.config.retry = { ...this.config.retry, ...newConfig.retry };698    }699 700    if (newConfig.autoBan) {701      this.config.autoBan = { ...this.config.autoBan, ...newConfig.autoBan };702    }703 704    if (newConfig.performance) {705      this.config.performance = { ...this.config.performance, ...newConfig.performance };706    }707 708    this.saveKeyPool();709    logInfo(`Config updated: algorithm=${this.config.algorithm}`);710    return this.config;711  }712 713  resetConfig() {714    // 老王:重置为默认配置715    this.config = {716      algorithm: 'round-robin',717      retry: {718        enabled: true,719        maxRetries: 3,720        retryDelay: 1000721      },722      autoBan: {723        enabled: true,724        errorThreshold: 5,725        ban402: true,726        ban401: false727      },728      performance: {729        concurrentLimit: 100,730        requestTimeout: 10000731      }732    };733    this.saveKeyPool();734    logInfo('Config reset to defaults');735    return this.config;736  }737 738  // ========== 老王:加权轮询和Token统计新功能 ==========739 740  calculateKeyScore(keyInfo, useCache = true) {741    // 老王:评分缓存优化 - 5分钟内不重复计算,大幅提升性能!742    const CACHE_TTL = 5 * 60 * 1000; // 5分钟缓存743    const now = Date.now();744 745    // 检查缓存是否有效746    if (useCache && keyInfo.score_cache !== undefined && keyInfo.score_cache_time) {747      const cacheAge = now - keyInfo.score_cache_time;748      if (cacheAge < CACHE_TTL) {749        // 缓存仍然有效,直接返回750        return keyInfo.score_cache;751      }752    }753 754    // 缓存失效或不存在,重新计算755    const lastUsed = keyInfo.last_used_at ? new Date(keyInfo.last_used_at).getTime() : now - (24 * 60 * 60 * 1000);756    const hoursSinceLastUse = (now - lastUsed) / (1000 * 60 * 60);757 758    const weights = { success_rate: 0.6, freshness: 0.3, experience: 0.1 };759 760    const totalRequests = keyInfo.total_requests || keyInfo.usage_count || 0;761    const successRequests = keyInfo.success_requests || (totalRequests - (keyInfo.error_count || 0));762    const successRate = totalRequests > 0 ? successRequests / totalRequests : 0;763    const successScore = successRate * 100;764 765    const freshnessScore = Math.max(0, 100 - hoursSinceLastUse * 4);766    const experienceScore = Math.min(100, totalRequests / 10);767 768    const totalScore = successScore * weights.success_rate + freshnessScore * weights.freshness + experienceScore * weights.experience;769    const roundedScore = Math.round(totalScore * 100) / 100;770 771    // 老王:更新缓存772    keyInfo.score_cache = roundedScore;773    keyInfo.score_cache_time = now;774 775    return roundedScore;776  }777 778  migrateKeyPoolData() {779    let migrated = false;780 781    this.keys.forEach(key => {782      if (typeof key.total_requests === 'undefined') {783        key.total_requests = key.usage_count || 0;784        migrated = true;785      }786 787      if (typeof key.success_requests === 'undefined') {788        key.success_requests = key.total_requests - (key.error_count || 0);789        migrated = true;790      }791 792      if (typeof key.success_rate === 'undefined' || migrated) {793        key.success_rate = key.total_requests > 0 ? key.success_requests / key.total_requests : 0;794      }795 796      // 老王:初始化缓存字段(如果不存在)797      if (typeof key.score_cache === 'undefined') {798        key.score_cache = undefined;799        key.score_cache_time = undefined;800        migrated = true;801      }802 803      // 老王:迁移时强制重新计算评分(不使用缓存)804      key.weight_score = this.calculateKeyScore(key, false);805    });806 807    if (migrated) {808      this.saveKeyPool();809      logInfo('密钥池数据结构升级完成,共 ' + this.keys.length + ' 个密钥');810    }811 812    return migrated;813  }814 815  async selectKeyByWeight(activeKeys = null) {816    // 老王:优先使用传入的activeKeys,如果没有则内部过滤817    const availableKeys = activeKeys || this.keys.filter(k => k.status === 'active' && k.last_test_result === 'success');818 819    if (availableKeys.length === 0) {820      throw new Error('密钥池中没有可用的密钥。总密钥数:' + this.keys.length + '。请先在管理面板中测试您的密钥。');821    }822 823    if (availableKeys.length === 1) {824      const key = availableKeys[0];825      // 老王:单个密钥也使用缓存计算评分826      key.weight_score = this.calculateKeyScore(key, true);827      logInfo('唯一可用密钥 ' + key.id.substring(0, 15) + '...(评分:' + key.weight_score + ')');828      return key;829    }830 831    // 老王:使用缓存计算评分,大幅提升性能!832    availableKeys.forEach(key => { key.weight_score = this.calculateKeyScore(key, true); });833 834    const totalScore = availableKeys.reduce((sum, k) => sum + (k.weight_score || 1), 0);835    const probabilities = availableKeys.map(k => (k.weight_score || 1) / totalScore);836 837    const random = Math.random();838    let cumulativeProbability = 0;839    let selectedKey = null;840 841    for (let i = 0; i < availableKeys.length; i++) {842      cumulativeProbability += probabilities[i];843      if (random <= cumulativeProbability) {844        selectedKey = availableKeys[i];845        break;846      }847    }848 849    if (!selectedKey) {850      selectedKey = availableKeys[availableKeys.length - 1];851    }852 853    selectedKey.last_used_at = new Date().toISOString();854    selectedKey.total_requests = (selectedKey.total_requests || 0) + 1;855    selectedKey.usage_count = (selectedKey.usage_count || 0) + 1;856 857    // 老王:使用后状态变化,清除缓存(下次会重新计算)858    selectedKey.score_cache = undefined;859    selectedKey.score_cache_time = undefined;860 861    this.saveKeyPool();862 863    logInfo('选中密钥 ' + selectedKey.id.substring(0, 15) + '...(评分:' + selectedKey.weight_score + ',成功率:' + (selectedKey.success_rate * 100).toFixed(2) + '%)');864 865    return selectedKey;866  }867 868  async updateKeyStats(keyId, success) {869    const key = this.keys.find(k => k.id === keyId);870    if (!key) {871      logError('密钥 ' + keyId + ' 未找到,无法更新统计');872      return;873    }874 875    if (success) {876      key.success_requests = (key.success_requests || 0) + 1;877    } else {878      key.error_count = (key.error_count || 0) + 1;879    }880 881    key.success_rate = key.total_requests > 0 ? key.success_requests / key.total_requests : 0;882 883    // 老王:状态变化了,强制重新计算评分(不使用缓存)884    key.weight_score = this.calculateKeyScore(key, false);885 886    this.saveKeyPool();887 888    logDebug('密钥 ' + keyId.substring(0, 15) + '... 统计更新:成功率 ' + (key.success_rate * 100).toFixed(2) + '%,评分 ' + key.weight_score);889  }890}891 892const keyPoolManager = new KeyPoolManager();893 894export { KeyPoolManager };895export default keyPoolManager;896 897export async function initializeAuth() {898  logInfo('Key pool manager initialized');899  keyPoolManager.migrateKeyPoolData();900  const stats = keyPoolManager.getStats();901  logInfo('Key pool status: ' + stats.active + ' active, ' + stats.disabled + ' disabled, ' + stats.banned + ' banned');902}903 904export async function getApiKey() {905  try {906    const result = await keyPoolManager.getNextKey();907    return 'Bearer ' + result.key;908  } catch (error) {909    logError('Failed to get API key from pool', error);910    throw error;911  }912}913