fix(setting): runtime config 全局变量加 sync.RWMutex 保护,避免半写状态

TTSOptions / TTSTimeout / TTSConfigErr / Auth.APIKeys / CORS 在运行期会被
LoadRuntimeConfig(PUT /api/settings 触发)整体替换,struct 整体赋值不是原子的,
若 TTS 请求正并发读,可能拿到半写状态。单用户场景概率低,属正确性隐患。

改动:
- setting/config.go: 把可变运行时配置迁到包内私有变量,新增 ttsMu
  (sync.RWMutex) 统一保护,提供 Get*/Set* 访问器
  - GetTTSOptions/SetTTSOptions: 值类型快照,读端无锁开销
  - GetTTSTimeout/SetTTSTimeout
  - GetTTSConfigErr/SetTTSConfigErr
  - GetAuthAPIKeys/SetAuthAPIKeys: 拷贝进出,防止外部持有底层 slice
  - GetCORSAllowAll/GetCORSOrigins/SetCORS: AllowAll 和 Origins 拆开,
    避免热路径 CORSConfig 整体读时锁粒度过粗
- 删除导出可变全局 var TTSOptions/TTSTimeout/TTSConfigErr/Auth/CORS,
  编译期强制所有读写走 Get*/Set*
- Server.Port/TrustedProxyHops/SetupToken 仅启动期写,运行期无并发修改,
  保持原状不加锁
- LoadRuntimeConfig/InitAuthConfig/InitCORSConfig 全切到 Set*
- CheckEnvironmentVariables/LogStartupSummary 一次性拿快照,
  缩短锁占用窗口,避免多次 log.Printf 之间数据被替换

调用点:
- controller/tts.go: 热路径 opts := setting.GetTTSOptions(),
  context 超时走 GetTTSTimeout(),/health 块用 GetTTSConfigErr
- controller/settings.go: 写改 SetAuthAPIKeys/SetCORS
- controller/setup.go: 写改 SetAuthAPIKeys
- middleware/auth.go: 鉴权改 GetAuthAPIKeys()(在 RLock 下拿拷贝)
- middleware/admin_auth.go: 同上
- middleware/cors.go: matchOrigin 改 GetCORSAllowAll/GetCORSOrigins

读多写少,RWMutex 读路径不互斥,不会拖慢热路径。
This commit is contained in:
sun
2026-09-21 10:50:03 +08:00
parent d6aafa41ff
commit 19d83f7a3b
8 changed files with 221 additions and 104 deletions
+2 -2
View File
@@ -15,7 +15,7 @@ import (
// - Authorization 头 Bearer token 在列表中 → 放行
// - 其它 → 401 + JSON {error: 'unauthorized', code: 'admin_auth_failed'}
//
// 设计: 与现有 /v1/audio/speech 用的 setting.Auth 共享同一份 keys,
// 设计: 与现有 /v1/audio/speech 用的鉴权 key 列表(setting.GetAuthAPIKeys)共享同一份 keys,
// 用户只用管一个 env 变量(OPENAI_TTS_API_KEY)。
func RequireAdmin(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -25,7 +25,7 @@ func RequireAdmin(next http.Handler) http.Handler {
return
}
keys := setting.Auth.APIKeys
keys := setting.GetAuthAPIKeys()
if len(keys) == 0 {
// 没配 admin key,等同无鉴权
next.ServeHTTP(w, r)