☰
FluentRead 多 API Key 自动轮换与逐项检查机制全解析:配置、平滑加权轮询算法与浏览器专项验证
2026/9/28 20:57:22 网站建设 项目流程
  • 前端
  • AI 应用
  • 本地部署

【免费下载链接】FluentRead

An open-source browser extension for bilingual translation. 一款开源的浏览器双语翻译插件。

项目地址:https://gitcode.com/gh_mirrors/fl/FluentRead
点击查看免费下载

FluentRead(流畅阅读)是一款开源的浏览器双语翻译插件。本文聚焦其多 API Key 管理机制:同一翻译服务可逐行添加多个 Key,日常翻译由后台自动轮询分担请求并避开失效 Key,而连接检查则逐行明确执行。本文以 多 Key 自动轮换与逐项检查报告 为主体,结合仓库源码、单元测试与浏览器专项测试,完整还原该机制的配置方式、轮换规则、实现原理与验证证据。读者读完可掌握:多 Key 的配置入口与逐项检查操作、平滑加权轮询的权重奖惩规则、恢复窗口的调节方法,以及该机制在真实生产扩展中的验证手段与边界。

该机制对应 FluentRead 的 Issue #161 与 #171:同一翻译服务可以逐行添加多个 Key;翻译时自动分担请求并避开失败的 Key,检查连接时则明确检查每一行。实现基于提交6499537e5138e640ecd263a778bff9574e595447,并整合了主分支64cc5e1c10b331bd0adc371e1aaae7308d148db9;此后仅补充浏览器测试场景与本报告,未再修改运行时代码。本文引用的是当前仓库中可检索到的实现与测试,可作为该报告的源码级延伸。

用户流程:如何配置多 Key 并逐项检查

多 Key 管理在「服务配置 → 密钥与认证」区域完成,界面组件位于 ServiceConfiguration.vue。整体流程如下:

  1. 逐行添加:在服务配置中填写第一行 Key,点击「添加一个 Key」继续添加。现有空行会直接获得焦点,不堆积空白输入框;不要求使用分隔符(逗号不会被拆分,见下文配置模型)。每个服务可添加的 Key 数量没有硬性上限,报告中验证过十行 Key 的场景。
  2. 逐行独立管理:Key 默认隐藏。每行可以独立删除、显示或隐藏、检查或重新检查。空行不参与请求,重复项会明确提示并跳过。
  3. 检查所有 Key:点击「检查所有 Key」后依次检测,逐行显示「等待 → 检查中 → 成功勾及耗时」状态,失败则显示具体原因。某一行失败不会借用另一行来显示成功,避免误判。
  4. 可中断、结果可失效:可停止后续检查,已完成结果保留;当前已发送的请求可能继续完成。修改 Key、服务地址、模型等配置后,旧结果失效,迟到的响应不会重新点亮旧的成功勾(实现上由配置指纹保证,见下文)。
  5. 翻译自动选择 Key:日常翻译自动轮询选择 Key,无需用户维护优先级。成对使用 Access Key ID / Secret 的服务继续保留一组完整凭据;只需一个密钥的云服务支持多行 Key。

多 Key 开关:apiKeyRotationEnabled是每个服务的独立开关,由设置页中的el-switch控制(ServiceConfiguration.vue)。从源码看,开启后才显示完整 Key 列表(displayedApiKeys),关闭时仅展示首个 Key,便于用户随时停用轮询而保留其余 Key。

共用配置与隔离边界:每行 Key 共用当前服务的地址、模型、区域及自定义请求头等设置;不同服务或不同自定义服务之间不共享 Key。原有单 Key 配置自动保留为第一项;完整备份保留凭据,公开配置和历史记录不包含 Key 列表。

轮换规则:平滑加权轮询与失败惩罚

多 Key 轮询的核心状态机位于 apiKeyPool.ts。其完整规则如下表,这是报告原文的权威定义:

情况后续行为
新增 Key / 初始状态权重均为 4,使用平滑加权轮询分担请求
网络、超时、服务端临时故障失败 Key 权重减半并向下取整,本次请求尝试其他 Key
鉴权失败、额度不足、限流失败 Key 权重归零,暂时跳过,本次请求尝试其他 Key
成功请求权重增加 1,最高恢复到初始值
恢复窗口到期默认距最近失败 1 分钟后恢复初始权重;用户可在高级请求限制中调整为 1–60 分钟;服务端提供重试时间时遵循该时间
单独检查成功将该 Key 恢复为初始权重
取消或一般模型、参数配置错误不扣减 Key 权重;不会靠轮换反复尝试相同的配置问题
全部 Key 暂时不可用返回失败及重试信息,不无限循环

源码中的权重状态机

从源码看,规则表由三组常量驱动,全部定义在 apiKeyPool.ts:

  • 初始权重:API_KEY_POOL_DEFAULT_WEIGHT = 4,所有 Key 以相同权重进入轮询池,测试starts equal and uses smooth weighted rotation验证了三个 Key 在连续六次请求中按['a','b','c','a','b','c']均匀分配(tests/apiKeyPool.test.ts)。
  • 临时故障降权:transient / network / server归入penalty类,reportFailure中执行weight = Math.floor(weight / 2),即减半向下取整。
  • 致命故障清零:auth / quota / rate-limit归入ZERO_WEIGHT_FAILURES,权重直接置 0,进入冷却跳过。
  • 不惩罚:config / cancelled归入NON_PENALIZING_FAILURES,权重不变——因为模型或参数配置错误属于配置问题,靠轮换重试其他 Key 无意义。

平滑加权轮询算法(lease方法)是「平滑」的来源:每个候选 Key 先累加自己的权重(current += weight),选出current最大的 Key,再从中减去总权重。这样既按权重比例分配请求,又避免同一 Key 被连续打中,从结构上保证了请求的平滑分散。

失败分类与冷却

classifyApiKeyFailure综合错误类型、状态码、错误 code 与 message 文本判断失败类别(apiKeyPool.ts):

  • 401 / 403 / 429 状态码、invalid_api_key、quota_exceeded等 code,以及「api key invalid / expired」类消息 →cooldown(清零冷却);
  • 408 / 425 / 5xx、网络与超时 →penalty(减半);
  • 其余(如 400 参数错误、AbortError 取消)→ 不惩罚。

该分类在 tests/apiKeyPool.test.ts 中有大量边界用例覆盖,例如invalid max_tokens(参数配置问题)不会被误判为 Key 故障。

单次翻译中的尝试预算

调度封装在 apiKeyRotation.ts 的runWithApiKeyRotation中:

  • 一次翻译中每个 Key 最多尝试一次(excluded列表记录已尝试的 Key,循环直至排除完毕);
  • 所有尝试共用总超时预算(deadlineAt),且按剩余 Key 数把单次预算最多切三份,避免大量 Key 把单次请求切得过短;
  • 继续遵守现有并发与速率限制(默认并发上限、每秒/每分钟请求上限定义于 scheduling.ts);
  • 全部不可用时:抛出携带retryAfterMs与「其他 Key 暂时不可用,请稍后重试或逐项检查」提示的错误,不会无限循环;
  • 流式能力(写作等):流建立前可以切换 Key;已经开始输出后不会重放请求,避免产生重复内容。

健康状态的存储边界

健康权重只保存在后台进程内存中(rotationsMap,仅存 Key 的 SHA-256 摘要而非明文),后台进程重启后所有 Key 重新从等权开始。因此成功勾仅代表当前配置的一次检查成功;且检查会产生小额测试请求,不代表该 Key 持续可用或免费。

恢复窗口与请求限制配置

Key 冷却后的恢复时间在 scheduling.ts 中定义并规范化:

  • DEFAULT_API_KEY_RECOVERY_MS = 60_000(默认 1 分钟);
  • 可调范围MIN_API_KEY_RECOVERY_MS到MAX_API_KEY_RECOVERY_MS,即1–60 分钟;
  • normalizeApiKeyRecoveryMs按整分钟保存(四舍五入到分钟并夹取到合法范围),避免产生难以理解的半分钟值;
  • 若服务端响应携带重试时间(Retry-After),冷却窗口遵循该时间,且最小不低于 1 秒(normalizeCooldownMs)。

设置项「API Key 冷却恢复时间」位于请求限制设置区,由 SettingsSections.vue 渲染,模型值经normalizeApiKeyRecoveryMs换算为分钟数后写入配置(同文件 L1444-L1450)。由于配置范围被限制为 1–60 分钟,apiKeyRotation.ts 中的策略缓存最多只保留 60 个实例,避免动态配置导致内存无界增长。

关键实现细节:scope 隔离、密钥脱敏与陈旧结果失效

服务身份隔离(scope)

不同服务、不同自定义服务的 Key 互不共享,由scopeFor实现:它对服务名、请求模型、代理、请求体模板、自定义请求头、计费路由(如deeplx、newapi、azureOpenai、minimax、mimo、deepseek等)以及自定义 provider 端点生成SHA-256 摘要作为 scope 身份(apiKeyRotation.ts)。createApiKeyRotation按 scope 维护有界轮询池(默认最多 64 个 scope,LRU 淘汰),同一服务的 Key 列表变化时通过sync增删 Key 状态。编辑其他服务的配置不会重置本服务的权重,这正是「不同服务不共享 Key」的底层保证。

密钥脱敏

redactApiKeyError会在错误序列化后,把message / code / requestId中出现的任何 Key 明文及其 URL 编码形式替换为[已隐藏的密钥],确保 provider 回显的任意 Key 都不会进入运行时错误或日志(apiKeyRotation.ts)。

单项检查:绕过冷却、精确到行

「检查或重新检查」单行 Key 时,runWithApiKeyRotation接收keyIndex参数:它只初始化/同步轮询池但不领取普通轮询租约,直接把该行的原始 Key 绑定到请求上(withServiceApiKey冻结配置快照并替换该服务 token),从而绕过冷却阻挡;成功则调用rotation.success(scope, id)将该 Key 恢复初始权重,失败则按类别记入该 Key。若该行已为空或被修改,会明确报错「这个 API Key 已更改或为空,请重新检查」。

陈旧结果失效:配置指纹

逐项检查的「旧结果失效」由 apiKeyCheckIdentity.ts 保证:它按服务提取 endpoint、代理、模型、请求参数、计费路由、自定义 provider 与原始 Key 行,计算稳定的64 位 SHA-256 配置指纹(createApiKeyCheckRevision)。任何配置或 Key 行变化都会改变指纹,迟到的响应因指纹不匹配而不会重新点亮旧的成功勾;指纹格式通过/^[a-f0-9]{64}$/校验。

配置模型与旧版单 Key 兼容

多 Key 的纯配置层在 apiKeys.ts,不读写存储、不发起网络、不参与 provider 编排:

  • 配置形态为ApiKeys = Record<service, string[]>,按服务保存有序 Key 列表;
  • normalizeApiKeys规范化:非字符串项被过滤、每项trim()、逗号按字符串原样保留不被拆分;
  • getServiceApiKeys返回去重且非空的服务 Key 列表;getServiceApiKeyRows供 UI 保留空行;
  • 旧版兼容:apiKeysToToken为旧 provider 路径生成「首个非空 Key」的 token 镜像;旧token字段仅作为未迁移服务的单 Key 兼容来源,新字段优先(ApiKeyConfigSource的解析顺序)。

因此升级前只有一个 Key 的配置,会在新模型下自动保留为第一项,无需用户迁移。

验证结果:全量测试与浏览器专项

该机制在报告对应的提交上通过了完整的质量门禁,命令输出摘录保存在 validation.txt,浏览器专项原始报告为 browser-report.json:

验证结果
全量 Vitest305 个文件,6,143 个用例通过
严格覆盖率套件248 个文件,5,017 个用例通过;统计范围内 statements / branches / functions / lines 均为 100%
测试审计305 个文件归类有效,未发现重复、遗漏、违规跳过或覆盖率忽略
TypeScript / Vue 类型检查通过
Chrome / Firefox 构建与 manifest verifier通过
Userscript 构建与 verifier通过
文档构建通过
生产扩展多 Key 浏览器专项13 / 13 通过,控制台未捕获页面错误

浏览器专项覆盖的 13 个场景(来自 browser-report.json 的cases列表):空行复用、重复项跳过、实际翻译消息经过 broker 后从失败的 A 切换到 B/C、逐项检查不代偿、单项重测、删除第一项、停止后续检查、检查中编辑、十行 Key、深色窄屏、重新打开后持久化、成对云凭据保持完整、单密钥云服务的多行编辑。两个云服务场景仅验证配置界面,没有向外部供应商发送请求。

自动化入口为 run-api-keys-ui-test.cjs:它在本地启动 HTTP fixture,对fixture-A返回 401、对其他合成 Key 返回成功,并把生产 Chrome MV3 产物(.output/chrome-mv3)加载到隔离的 Edge 临时 profile 中执行全部场景,同时记录请求、截图与控制台错误。从脚本头部注释可见,浏览器启动模式为macos-background-cdp,焦点策略为launchservices-no-foreground,窗口位于第二块屏幕,模式为background-visible-no-focus;报告记录browserFrontmost=false,确保验证不被前台焦点干扰。

复现命令

报告给出了完整的复现命令,可在当前仓库根目录执行:

pnpm exec vitest run --maxWorkers=2 --minWorkers=1 --testTimeout=20000 pnpm exec vitest run --config vitest.coverage.config.ts --maxWorkers=2 --minWorkers=1 --testTimeout=20000 --coverage.reportsDirectory=/private/tmp/multikey-delivery-coverage pnpm compile pnpm build pnpm build:firefox pnpm verify:extension-manifests pnpm build:userscript node scripts/verify-userscript-build.mjs pnpm docs:build pnpm test:audit

证据范围与限制

  • 浏览器专项验证的是实际生产扩展、消息链、HTTP 传输和 UI,但服务响应来自本地模拟端点(local-http-401-fixture),未验证任何真实付费供应商的账号、额度或持续可用性。
  • Firefox 与 Userscript 完成了构建验证,但本次没有运行对应真实浏览器 UI 或用户脚本端到端测试。
  • 原有 full UI 技能脚本在run-ui-test.cjs:533等待旧 popup 标题「让阅读自然地流动 / 翻译功能已暂停」时超时(当前标题已改变),该套件未计为通过;主分支此前已有相同的基线失败记录,本次命令输出未重定向保存,所指定的证据目录为空。

此外,本报告对应的交互界面在后续提交中已重整为多 Key 界面重整报告所述的新版密钥管理界面,本页保留的是初版机制及其回归证据——本文所述的轮询算法与配置模型仍是新版界面的底层基础。

  • 前端
  • AI 应用
  • 本地部署

【免费下载链接】FluentRead

An open-source browser extension for bilingual translation. 一款开源的浏览器双语翻译插件。

项目地址:https://gitcode.com/gh_mirrors/fl/FluentRead
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询