配置管理
本文档基于以下源文件编写:
- internal/services/config_service.go -- ConfigService 核心实现
- internal/database/config_repository.go -- 配置仓储层
- internal/handlers/config.go -- 通用 KV 配置端点
- internal/handlers/scan.go -- 扫描相关 settings 端点
- internal/handlers/hls.go -- HLS 代理 settings 端点
- internal/handlers/log.go -- 日志等级 settings 端点
- internal/handlers/tab_config_setting.go -- 底栏 Tab 配置端点
- internal/handlers/jsplugin_registry.go -- 插件注册表与 HTTP 代理 settings 端点
- internal/handlers/cache.go -- 缓存管理模块聚合端点
- internal/app/routers.go -- 路由注册与配置变更回调
- internal/models/models.go -- Config 数据模型
目录
- 数据模型与存储
- ConfigService 设计
- 三种配置接口体系
- 业务端点清单(/settings/*)
- 配置变更回调机制
- Tab 配置详解
- 模块聚合端点示例:/cache-manage/config
- 接口选型决策树
1. 数据模型与存储
章节来源:internal/models/models.go、internal/database/config_repository.go
所有配置持久化在 SQLite 的 configs 表中,每行一个键值对:
type Config struct {
ID int64 `json:"id"`
Key string `json:"key"` // 唯一键,如 "music_path"、"hls_proxy_enabled"
Value string `json:"value"` // 字符串值,复杂结构以 JSON 序列化存储
UpdatedAt time.Time `json:"updated_at"`
}仓储层 ConfigRepository 遵循项目的 sqlc + squirrel 混合策略:固定 SQL(Get/Set/Delete)走 sqlc,动态过滤的 List/Count 走 squirrel(支持 key LIKE 关键词搜索 + 白名单排序 + 分页)。Set 实现为 UPSERT on key,未命中的 Get/Delete 返回 ErrNotFound。
2. ConfigService 设计
章节来源:internal/services/config_service.go
ConfigService 是配置的唯一业务入口,所有 handler 通过它读写配置。核心设计要点:
2.1 内存缓存
使用 sync.Map 作为并发安全的内存缓存(key 为配置键,value 为原始字符串值):
- 读路径:先查缓存,命中直接返回;未命中则查数据库,写入缓存后返回
- 写路径:先写数据库,成功后更新缓存(
Set)或清除缓存(CreateConfig/UpdateConfig/DeleteConfig) ClearCache()重置整个缓存,ClearCacheKey(key)清除单个 key
2.2 类型化辅助方法
ConfigService 提供一组类型化的读写方法,将 configs 表的 string value 透明转换为业务所需类型:
| 方法 | 读/写 | 说明 |
|---|---|---|
GetString(key, default) | 读 | 返回原始字符串,缺失时返回默认值 |
GetInt(key, default) | 读 | strconv.Atoi 转换,失败返回默认值 |
GetBool(key, default) | 读 | strconv.ParseBool 转换,失败返回默认值 |
GetJSON(key, target) | 读 | json.Unmarshal 到任意结构体,缓存无效时自动清除 |
Set(key, value) | 写 | 写入字符串值并更新缓存 |
SetJSON(key, value) | 写 | json.Marshal 后调用 Set |
所有 Get* 方法在数据库查询失败时静默降级(slog.Warn 后返回默认值),不中断业务流程。GetJSON 是例外:它返回 error,由调用方决定降级策略。
面向通用 KV 端点另有完整 CRUD 方法(GetConfig/CreateConfig/UpdateConfig/DeleteConfig/ListConfigs/CountConfigs),直接透传到仓储层,写操作后清除对应 key 的缓存。
3. 三种配置接口体系
章节来源:internal/app/routers.go、AGENTS.md 配置接口规范章节
项目中存在三种配置接口,各有定位:
┌─────────────────────────────────────────────────────────────────┐
│ 配置接口体系 │
├─────────────────┬──────────────────┬────────────────────────────┤
│ /settings/* │ /module/config │ /configs/{key} │
│ 业务端点 │ 模块聚合端点 │ 通用 KV │
├─────────────────┼──────────────────┼────────────────────────────┤
│ 强类型 JSON │ 强类型 JSON │ 裸 key/value 字符串 │
│ 自带默认值 │ 自带默认值 │ key 不存在 → 404 │
│ 内联副作用 │ 内联副作用 │ 无副作用(除回调) │
│ 前端业务功能 │ 模块内部配置 │ admin 编辑器专用 │
└─────────────────┴──────────────────┴────────────────────────────┘/settings/<name>(孤立业务端点):强类型 JSON,handler 内自带默认值与副作用。路径为 kebab-case,归属到对应业务模块的 handler/module/config(模块聚合端点):配置与同模块动作端点强相关时使用(已有实例:/cache-manage/config)/configs/{key}(通用 KV):admin 配置编辑器(config_manager.dart)的后端入口,提供完整 CRUD。PUT 时 key 不存在返回 404,无强类型校验,副作用仅通过onConfigChanged回调触发。新增业务功能不应直调此接口
4. 业务端点清单(/settings/*)
章节来源:internal/app/routers.go(路由注册)、各 handler 文件
所有业务端点均为 GET + PUT 双方法,GET 返回当前值(含默认值),PUT 写入并可能触发副作用。
4.1 /settings/music-path
图表来源:internal/handlers/scan.go MusicPathSetting 结构体
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
path | string | "music" | 音乐根目录路径 |
exclude_dirs | string[] | ["@eaDir", "tmp"] | 按目录名排除(递归匹配) |
exclude_paths | string[] | [] | 按完整路径排除 |
- config key:
music_path(JSON 值) - handler:
ScanHandler - 副作用:PUT 后异步调用
onMusicPathChanged回调,重建 Scanner 并清理排除目录中的歌曲 - 验证:
path不能为空
4.2 /settings/hls-proxy
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | bool | false | HLS 电台反代开关 |
- config key:
hls_proxy_enabled(字符串"true"/"false") - handler:
HLSHandler - 业务语义:
false时电台 m3u8 直接 302 给 player;true时服务端拉取并改写 m3u8、代理所有切片
4.3 /settings/auto-scan
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | bool | false | 自动扫描开关 |
interval_seconds | int | 3600 | 扫描间隔(秒),范围 [60, 86400] |
- config key:
auto_scan(JSON 值) - handler:
ScanHandler - 副作用:PUT 后异步调用
onAutoScanChanged回调,通过autoScanner.ApplyConfig重启调度器 - 验证:
interval_seconds必须在 60 到 86400 之间
4.4 /settings/scan-title-source
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title_source | string | "tag" | 扫描标题来源,tag 或 filename |
- config key:
scan_title_source(字符串值) - handler:
ScanHandler - 副作用:PUT 后触发
onMusicPathChanged(需以「重新导入」模式扫描才能生效) - 验证:值必须为
"tag"或"filename"
4.5 /settings/scan-auto-create-playlists
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | bool | true | 扫描后是否自动创建歌单 |
- config key:
scan_auto_create_playlists(字符串"true"/"false") - handler:
ScanHandler
4.6 /settings/scan-playlist-mode
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
mode | string | "directory" | 目录歌单归并模式,取值 directory / top_level / bubble_up |
三种模式语义(与 PlaylistRepository.AutoCreate 一致):
directory:每个歌曲所在文件夹各自生成一个独立歌单top_level:只按顶层(一级)目录归并,同一顶层目录下的所有歌曲进同一个歌单bubble_up:歌曲加入自己所在目录歌单的同时,向上冒泡加入其所有父目录对应的歌单config key:
scan_playlist_mode(字符串值)handler:
ScanHandler验证:
mode必须为directory/top_level/bubble_up之一,否则返回 400
该模式仅在 §4.5
scan-auto-create-playlists开启(默认true)时生效;两者配合决定"是否创建目录歌单"与"如何归并目录"。
4.7 /settings/log-level
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
level | string | "info" | 日志等级:debug/info/warn/error |
- config key:
log_level(字符串值) - handler:
LogHandler(独立 handler,持有*slog.LevelVar) - 副作用:PUT 时通过
levelVar.Set(lvl)即时切换运行时日志等级,无需重启 - 验证:
ParseLogLevel映射表校验,非法等级返回 400
4.8 /settings/plugin-registries
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
registries | RegistryConfig[] | 官方插件源 | 插件订阅源列表 |
每个 RegistryConfig 包含 url(string)、name(string)、enabled(bool)。
- config key:
plugin_registries(JSON 值) - handler:
JSPluginHandler - 默认值:未配置时返回包含 Songloft 官方插件源的单元素列表
4.9 /settings/http-proxy
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
proxy | string | "" | HTTP 代理地址,如 http://192.168.1.1:7890 |
- config key:
http_proxy(JSON 值) - handler:
JSPluginHandler - 副作用:PUT 时调用
httputil.SetGlobalProxy(proxy)即时切换全局 HTTP 代理 - 验证:
SetGlobalProxy内部校验代理地址格式
4.10 /settings/tab-config
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
show_library | bool | true | 是否显示歌曲库 Tab |
show_playlists | bool | true | 是否显示歌单 Tab |
plugin_tabs | pluginTabEntry[] | [] | 插件 Tab 列表 |
- config key:
tab_config(JSON 值) - handler:
ConfigHandler - 详细规则见下方 Tab 配置详解
5. 配置变更回调机制
章节来源:internal/handlers/config.go(SetOnConfigChanged)、internal/app/routers.go(回调注册)
通用 KV 端点 PUT /configs/{key} 更新配置后会触发 onConfigChanged 回调。这是保持通用端点与业务端点副作用对齐的关键机制。
5.1 回调注册
// routers.go 中的注册逻辑
configHandler.SetOnConfigChanged(func(key string) {
switch key {
case "music_path":
musicPathChanged() // 重建 Scanner + 清理排除歌曲
case "auto_scan":
cfg := a.autoScanner.GetConfig()
a.autoScanner.ApplyConfig(cfg) // 重启自动扫描调度
}
})5.2 双入口一致性
回调通过 go 异步执行,不阻塞 PUT 响应。业务端点和通用端点写同一 config key 时,必须触发相同的副作用。例如 PUT /settings/music-path 在 handler 内直接调用 onMusicPathChanged(),而 PUT /configs/music_path 通过 configHandler.onConfigChanged("music_path") 间接触发同一函数。
6. Tab 配置详解
章节来源:internal/handlers/tab_config_setting.go
底部导航栏配置控制前端 App 底部 Tab 的组成,最多 5 个 Tab。
6.1 Tab 结构
┌───────────────────────────────────────────────────┐
│ 底部导航栏(最多 5 个 Tab) │
├──────────┬──────────┬──────────┬──────────┬────────┤
│ 首页 │ 歌曲库 │ 歌单 │ 插件X │ 设置 │
│ (固定) │ (可选) │ (可选) │ (可选) │(固定) │
└──────────┴──────────┴──────────┴──────────┴────────┘
固定项 可选项(最多 3 个) 固定项- 固定项:首页(Home)和设置(Settings)始终显示,不在配置中
- 可选项:歌曲库(
show_library)、歌单(show_playlists)和插件 Tab(plugin_tabs),三者总数不超过 3 个
6.2 插件 Tab 条目
type pluginTabEntry struct {
PluginID int `json:"plugin_id"` // 插件 ID
EntryPath string `json:"entry_path"` // 插件入口路径(不可为空,不可重复)
Name string `json:"name"` // 显示名称(不可为空)
}6.3 校验规则
- 可选 Tab 总数(
show_library启用算 1 +show_playlists启用算 1 +plugin_tabs长度)不超过 3 - 每个插件 Tab 的
entry_path不能为空 - 每个插件 Tab 的
name不能为空 - 插件 Tab 之间的
entry_path不能重复
6.4 默认配置
未配置时返回默认值:show_library=true、show_playlists=true、plugin_tabs=[],即 4 个 Tab(首页、歌曲库、歌单、设置)。
7. 模块聚合端点示例:/cache-manage/config
章节来源:internal/handlers/cache.go
缓存管理模块将配置端点与动作端点聚合在同一前缀 /api/v1/cache-manage/ 下,是模块聚合模式的典型实例:
| 端点 | 方法 | 说明 |
|---|---|---|
/cache-manage/stats | GET | 获取缓存统计(总大小、文件数、最大限制) |
/cache-manage/clean | POST | 清理全部缓存 |
/cache-manage/config | GET | 获取缓存配置(max_size、cache_dir) |
/cache-manage/config | PUT | 更新缓存配置 |
/cache-manage/validate-dir | POST | 验证目录可用性(自动创建 + 可写性检查 + 磁盘空间) |
配置端点与 stats/clean 共用同一个 CacheService,属于强关联场景,因此保留在模块前缀下而不拆到 /settings/。
PUT 更新缓存配置时的校验逻辑:
max_size不能为负数(0 表示不限制)cache_dir为空字符串时恢复默认目录cache_dir非空时必须为绝对路径,且通过可写性验证
8. 接口选型决策树
图表来源:综合 AGENTS.md 配置接口规范与各 handler 实现
为新增配置选择接口风格时,参考以下决策流程:
新增一个配置项
│
├── 该配置属于某个业务模块,且该模块已有动作端点?
│ │
│ ├── 是 → 模块已有 /module/config 端点?
│ │ │
│ │ ├── 是 → 扩展现有 config 结构体
│ │ └── 否 → 评估是否值得新建聚合端点
│ │ 否则走 /settings/<name>
│ │
│ └── 否 → /settings/<name>
│
└── 仅供 admin 调试用?
│
├── 是 → 直接写 configs 表,通过通用 KV 端点访问
└── 否 → /settings/<name>核心原则:业务端点是用户可见入口的唯一来源,通用 KV 退化为 admin 后门。两条入口写同一 key 时,副作用必须通过 SetOnConfigChanged 回调保持一致。
