Commit Graph
2 Commits
Author SHA1 Message Date
tts-stage1 b8fee37078 fix(route): 不在 router 覆盖 template.VoiceKey,避免对外 voice 名误透传给上游
bug 描述:
  原 router.go:169 写 req.VoiceKey = voice,把"对外 voice 名"(OpenAI 风格
  字符串,如 "chun"/"alloy")覆盖到 template.VoiceKey,导致上游(provider)
  拿到的 VoiceKey 是对外字符串而非上游音色 ID(如 "BV001_streaming")。
  火山侧 optionsFromRequest (adapter/volcano/provider.go:55) 直接把
  VoiceKey 当 speaker 塞进 HTTP body,结果 speaker="chun" 在火山查不到
  -> 55000000。

修法:
  - router 只换"账号身份"(Credentials),其它字段(尤其 VoiceKey)由
    controller 维护;文档 §5 的设计本就是这样,代码是实现走样
  - 删 router.go:169 那行,加注释明确"不动 template.VoiceKey"
  - 同步修 SelectAndSynthesize 函数注释,把"调用方契约"写清楚:
      * voice (第 2 参数) = 对外 voice 名,用于过滤 Channel.Voices 白名单
      * template.VoiceKey 必须是上游 speaker ID,已由 controller 解析 voices 表填好
      * router 不修改 template 任何字段,只换 Credentials

回归保护(本地测试,不入库):
  - 新增 TestSelectAndSynthesize_VoiceKey_NotOverwrittenByExternalName:
    模拟完整链路(对外 voice="chun" + 模板 VoiceKey="BV001_streaming"),
    断言 router 调用 Synthesize 时 req.VoiceKey 仍是 "BV001_streaming"
  - 顺手重构 mockProvider:加 lastVoiceKey + failOn 字段,清理 FirstFailsSecondSucceeds
    测试的随机性陷阱(pickByWeight 是随机的,不应固定 chosen ID)
  - 全包 16 个测试通过;go build/vet/test 全绿

影响:
  - 客户端语义不变(对外 voice 名仍按 Channel.Voices 白名单过滤)
  - controller 行为不变(它原本就在 voice 路由后把 v.Speaker 填到 ttsReq.VoiceKey)
  - 上游调用正确(从错误地传对外字符串 -> 正确地传上游 speaker ID)
2026-10-11 16:27:18 +08:00
tts-stage1 a614ab55c9 feat(route): 阶段 1 多渠道路由分发层 (Channel + Router)
- store: 新增 channels 表 (name unique, credentials_json, voices CSV,
  priority, weight, status, auto_ban) + CRUD (List/Get/Insert/SetStatus/Delete)
  + ErrChannelDuplicate 区分 (避免与 ErrDuplicate 互相误报)
- store/db.go: schemaVersion 1 -> 2; channels 表 CREATE IF NOT EXISTS + 复合索引
- adapter/route: 新包,Channel (含 BuildRequest 覆盖 template.Credentials)
  + Router (Select / SelectAndSynthesize / pickByWeight 纯函数)
  + 错误区分 ErrNoChannels (走兜底) vs ErrVoiceNotFound (400)
  + AggregateError 聚合多渠道失败, 供 controller 日志逐个打印
- controller/router.go: SetRouter/GetRouter 句柄 (与 SetAdminStore 同一模式)
- controller/tts.go: 接入 Router, 零 channel 走 setting 兜底 (行为=阶段 0);
  voice 不被任何 channel 接受 -> 400 + voice_not_found; 抽出 finalizeSynth
  收敛合成结果->响应+metrics+日志三件套
- main.go: 启动期从 store 加载 channels, 转 route.Channel, 注入 controller;
  credentials_json 加载失败 -> fail-fast (避免一个错渠道拖崩全部请求)
- .gitignore: 补 .gotmp/ .gomodcache/ .dsh-acl/

验收 (设计文档 §9):
  - 零 channel 兜底, 行为=阶段 0
  - 加 1 个 channel: 走该渠道
  - 加 2+ 个同档不同 weight: pickByWeight weight-1-to-3 分布 ±5% (单测覆盖)
  - 不同 priority: 高优先级优先 (单测覆盖)
  - disabled 不参与选择 (单测覆盖)
  - 失败降级: 同档换下一个 -> 降档, 全失败返 AggregateError (单测覆盖)
  - go build ./... + go vet ./... 全绿
  - go test ./... (adapter/route 15 个, store 2 个, 全部通过)
2026-10-11 15:33:22 +08:00