- 前端
- AI 应用
- 本地部署
【免费下载链接】FluentRead
An open-source browser extension for bilingual translation. 一款开源的浏览器双语翻译插件。
FluentRead(流畅阅读)是一款开源的浏览器双语翻译插件。本文聚焦其多 API Key 管理机制:同一翻译服务可逐行添加多个 Key,日常翻译由后台自动轮询分担请求并避开失效 Key,而连接检查则逐行明确执行。本文以 多 Key 自动轮换与逐项检查报告 为主体,结合仓库源码、单元测试与浏览器专项测试,完整还原该机制的配置方式、轮换规则、实现原理与验证证据。读者读完可掌握:多 Key 的配置入口与逐项检查操作、平滑加权轮询的权重奖惩规则、恢复窗口的调节方法,以及该机制在真实生产扩展中的验证手段与边界。
该机制对应 FluentRead 的 Issue #161 与 #171:同一翻译服务可以逐行添加多个 Key;翻译时自动分担请求并避开失败的 Key,检查连接时则明确检查每一行。实现基于提交6499537e5138e640ecd263a778bff9574e595447,并整合了主分支64cc5e1c10b331bd0adc371e1aaae7308d148db9;此后仅补充浏览器测试场景与本报告,未再修改运行时代码。本文引用的是当前仓库中可检索到的实现与测试,可作为该报告的源码级延伸。
用户流程:如何配置多 Key 并逐项检查
多 Key 管理在「服务配置 → 密钥与认证」区域完成,界面组件位于 ServiceConfiguration.vue。整体流程如下:
- 逐行添加:在服务配置中填写第一行 Key,点击「添加一个 Key」继续添加。现有空行会直接获得焦点,不堆积空白输入框;不要求使用分隔符(逗号不会被拆分,见下文配置模型)。每个服务可添加的 Key 数量没有硬性上限,报告中验证过十行 Key 的场景。
- 逐行独立管理:Key 默认隐藏。每行可以独立删除、显示或隐藏、检查或重新检查。空行不参与请求,重复项会明确提示并跳过。
- 检查所有 Key:点击「检查所有 Key」后依次检测,逐行显示「等待 → 检查中 → 成功勾及耗时」状态,失败则显示具体原因。某一行失败不会借用另一行来显示成功,避免误判。
- 可中断、结果可失效:可停止后续检查,已完成结果保留;当前已发送的请求可能继续完成。修改 Key、服务地址、模型等配置后,旧结果失效,迟到的响应不会重新点亮旧的成功勾(实现上由配置指纹保证,见下文)。
- 翻译自动选择 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:
| 验证 | 结果 |
|---|---|
| 全量 Vitest | 305 个文件,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. 一款开源的浏览器双语翻译插件。
相关推荐
Apache DolphinScheduler 负载均衡机制全解析:从加权随机、平滑轮询到动态加权调度
Apache DolphinScheduler 负载均衡机制全解析:从加权随机、平滑轮询到动态加权调度 本篇技术指南以 Apache DolphinSchedu
任务调度大数据后端前端Apache DolphinScheduler Worker 负载均衡算法深度解析:从随机分配到动态平滑加权轮询
Apache DolphinScheduler Worker 负载均衡算法深度解析:从随机分配到动态平滑加权轮询 导读 本文聚焦 Apache DolphinS
任务调度数据编排工作流自动化后端大数据Apache DolphinScheduler Worker 负载均衡机制全解析:从加权随机、平滑轮询到线性负载与动态加权实现
Apache DolphinScheduler Worker 负载均衡机制全解析:从加权随机、平滑轮询到线性负载与动态加权实现 导读 在 Apache Dolp
任务调度大数据后端前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考