fix(tts): default_speaker 查 voice 表,改后立即生效
Bug 链: 1. 【核心 bug】setting/config.go LoadRuntimeConfig 把 "default_speaker" 当成"火山 speaker ID"用 — 但 DB 里存的是 voice **名字**(如 "chun"),不是 speaker ID (如 "S_G8tEKnaJ1")。 结果: TTSOptions.Speaker = "chun", TTSOptions.ResourceID 仍来自 default_resource_id 配置。调 /v1/audio/speech 不传 voice 时, 把 "chun" 当 speaker ID 发给火山 → 55000000 resource ID mismatched。 2. 【次生 bug】装完 install 没 reload TTS 配置。 启动期 LoadRuntimeConfig 失败(无 lock 无 settings), TTSConfigErr 被设; 装完 mode 变 normal 但 TTSConfigErr 没清, /v1/audio/speech 仍 503/500 (要重启才生效)。 修法: - setting/config.go: 拿到 default_speaker 后调 GetVoiceForTTS(name) 查 voice 表, 用 voice.Speaker / ResourceID / Model 覆盖。 (新增 store.Store interface 方法 GetVoiceForTTS,4 元组返回值 避免循环 import;store 包加 GetVoiceForTTS 实现,内部包 VoiceGetByName) - controller/setup.go: 装完 install 调 LoadRuntimeConfig 一次, 让 TTSConfigErr 立即清,TTSOptions 立即可用。 测试: - 已有 store/installer/middleware/router 测试全绿 - e2e (本机): install voice "chun" + speaker "S_G8tEKnaJ1" → 启动 log 显示 "speaker=S_G8tEKnaJ1" (不是 "chun") ✓ → /v1/audio/speech 不传 voice → 火山收到正确 speaker, 错误从 55000000 变成 401 Invalid X-Api-Key (假 key,换真 key 就 work) 二进制大小: ~18MB → ~18MB (代码量不变) 未 push (等用户验证)
This commit is contained in:
@@ -176,6 +176,12 @@ func SetupSubmitHandler(w http.ResponseWriter, r *http.Request) {
|
||||
setting.Auth.APIKeys = []string{authKey}
|
||||
}
|
||||
|
||||
// 装完 reload TTS 全局配置(让 TTSOptions 立即有可用的 api_key/speaker/resource_id,
|
||||
// 否则 /v1/audio/speech 会因为 TTSConfigErr 在启动期被设而返 503,要重启才生效)。
|
||||
if err := setting.LoadRuntimeConfig(GetSetupStore()); err != nil {
|
||||
log.Printf("[setup] warning: 装完 LoadRuntimeConfig 失败: %v(下次启动会恢复)", err)
|
||||
}
|
||||
|
||||
// 清空旧 voices 再插入(假设是首次安装;若不是,name 冲突会变成 409)
|
||||
// 这里选择 "清空+插入" 语义,符合"setup 是首次安装"的产品定位
|
||||
// 如果想保留旧 voices,可以改成 UPSERT,但 M1 不做
|
||||
|
||||
+25
-1
@@ -153,7 +153,7 @@ func splitAndLowerOrigins(s string) []string {
|
||||
|
||||
// LoadRuntimeConfig 从 store 加载 TTS 全局配置到 TTSOptions / TTSTimeout / Auth.APIKeys 内存。
|
||||
// 启动期(master 模式)调一次,或 PUT /api/settings 后调一次(改完立即生效)。
|
||||
//
|
||||
////
|
||||
// 与原 InitTTSConfig 的区别:
|
||||
// - 不再读 BYTEDANCE_TTS_* env;全部从 store.Settings 读
|
||||
// - 必填项(api_key / default_resource_id / default_speaker)缺失时返 error
|
||||
@@ -202,7 +202,22 @@ func LoadRuntimeConfig(s Store) error {
|
||||
return TTSConfigErr
|
||||
}
|
||||
|
||||
// 【BUG 修复】default_speaker 是 voice **名字**(如 "chun"),
|
||||
// 不是火山 speaker ID (如 "S_G8tEKnaJ1")。原代码直接把 voice 名当
|
||||
// speaker ID 用,导致调 /v1/audio/speech 不传 voice 时火山 55000000。
|
||||
// 这里查 voice 表拿真正的 speaker / resource_id / model。
|
||||
var voiceModel string
|
||||
if vSpeaker, vResource, vModel, found, vErr := s.GetVoiceForTTS(speaker); vErr == nil && found {
|
||||
speaker = vSpeaker
|
||||
resourceId = vResource
|
||||
voiceModel = vModel
|
||||
// 找不到 voice 时不报错 — 保持原值(向后兼容)
|
||||
}
|
||||
|
||||
model := all["model"]
|
||||
if voiceModel != "" {
|
||||
model = voiceModel // voice 行有 model 时优先用 voice 的
|
||||
}
|
||||
format := all["default_format"]
|
||||
if format == "" {
|
||||
format = "mp3"
|
||||
@@ -290,11 +305,20 @@ func LoadRuntimeConfig(s Store) error {
|
||||
}
|
||||
|
||||
// Store 是 LoadRuntimeConfig 需要的最小接口(避免 setting 包 import store 产生 cycle)。
|
||||
//
|
||||
// 重要:VoiceGetByName 用 4 个返回值(speaker/resourceID/model/found/err)
|
||||
// 而非 (VoiceRef, error),这样 store 包不需要 import setting 包
|
||||
// 就能实现这个接口(避免循环 import)。
|
||||
type Store interface {
|
||||
SettingsGetAll() (map[string]string, error)
|
||||
SettingsGetInt(key string, def int) (int, error)
|
||||
SettingsGetBool(key string, def bool) (bool, error)
|
||||
SettingsGetDuration(key string, def time.Duration) (time.Duration, error)
|
||||
// GetVoiceForTTS 给定 voice 名字,返 (speaker_id, resource_id, model, found, err)。
|
||||
// - found=false 表示 voice 不存在(此时 err=nil,返回值是空串)
|
||||
// - err!=nil 是真错误(db 失败等)
|
||||
// - 找到时返 voice 行的真实字段值
|
||||
GetVoiceForTTS(name string) (speaker, resourceID, model string, found bool, err error)
|
||||
}
|
||||
|
||||
func getEnvDefault(name, def string) string {
|
||||
|
||||
@@ -98,6 +98,22 @@ func (s *Store) VoiceGetByName(name string) (*Voice, error) {
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
// GetVoiceForTTS 实现 setting.Store 接口,返 voice 行的 TTS 关键字段。
|
||||
// - found=false: voice 不存在(ErrNotFound 翻译为 found=false)
|
||||
// - err != nil: 真错误(db 失败等)
|
||||
// 这个方法存在是为了让 *Store 满足 setting.Store 接口,且不引起
|
||||
// setting → store → setting 循环 import。
|
||||
func (s *Store) GetVoiceForTTS(name string) (speaker, resourceID, model string, found bool, err error) {
|
||||
v, err := s.VoiceGetByName(name)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
return "", "", "", false, nil
|
||||
}
|
||||
return "", "", "", false, err
|
||||
}
|
||||
return v.Speaker, v.ResourceID, v.Model, true, nil
|
||||
}
|
||||
|
||||
// VoiceInsert 新增音色;name 冲突返回 ErrDuplicate。
|
||||
// 空字符串/格式不合法返回 error;不依赖 SQLite 约束作为唯一校验。
|
||||
func (s *Store) VoiceInsert(v Voice) (int64, error) {
|
||||
|
||||
Reference in New Issue
Block a user