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 个, 全部通过)
This commit is contained in:
+28
-1
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
// schemaVersion 是当前 schema 版本号;每次结构性变更 +1。
|
||||
// migrate.go 负责在 Open 时按版本号增量应用。
|
||||
const schemaVersion = 1
|
||||
const schemaVersion = 2
|
||||
|
||||
// Store 是 SQLite 访问层的统一入口;所有 settings/voices 操作都通过它。
|
||||
type Store struct {
|
||||
@@ -149,5 +149,32 @@ func (s *Store) migrate() error {
|
||||
INSERT OR IGNORE INTO schema_version (version) VALUES (?)`, schemaVersion); err != nil {
|
||||
return fmt.Errorf("insert schema_version: %w", err)
|
||||
}
|
||||
|
||||
// channels 表(多渠道路由,阶段 1 引入)。
|
||||
// - name 唯一:管理侧标识,不允许重名
|
||||
// - credentials_json: 序列化后的 provider.Credentials(见 store/channels.go 注释)
|
||||
// - voices CSV: 限定该渠道支持的对外音色;空 = 不限
|
||||
// - priority/weight: 大者优先 + 同档加权随机(由 adapter/route 实现)
|
||||
// - status: 1=enabled 2=disabled(留扩展位,管理接口放阶段 2 上)
|
||||
// - auto_ban: new-api 风格连续失败自动禁用标记,阶段 1 仅存,未接自动逻辑
|
||||
if _, err := s.db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS channels (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
provider TEXT NOT NULL,
|
||||
credentials_json TEXT NOT NULL,
|
||||
voices TEXT NOT NULL DEFAULT '',
|
||||
priority INTEGER NOT NULL DEFAULT 0,
|
||||
weight INTEGER NOT NULL DEFAULT 1,
|
||||
status INTEGER NOT NULL DEFAULT 1,
|
||||
auto_ban INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)`); err != nil {
|
||||
return fmt.Errorf("create channels: %w", err)
|
||||
}
|
||||
if _, err := s.db.Exec(`CREATE INDEX IF NOT EXISTS idx_channels_status_priority ON channels(status, priority DESC)`); err != nil {
|
||||
return fmt.Errorf("create idx_channels_status_priority: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user