Skip to content

数据模型设计

本文档基于以下源文件编写:

目录

  1. 概述
  2. ER 关系图
  3. Song 歌曲模型
  4. Playlist 歌单模型
  5. PlaylistSong 关联模型
  6. Config 配置模型
  7. AuthToken 认证令牌模型
  8. JSPlugin 插件模型
  9. 歌词模型
  10. 备份模型
  11. 请求/响应 DTO
  12. 模型关系类图

1. 概述

Songloft 的数据持久层基于 SQLite,采用 goose 迁移 + sqlc 固定 SQL + squirrel 动态 SQL 的分层访问栈。领域模型定义在 internal/models/ 包,与 sqlc 自动生成的行映射模型(internal/database/sqlc/models.go)分离,由 Repository 层负责两者之间的转换。

当前 schema 共 7 张表songsplaylistsplaylist_songsconfigsauth_tokensjs_pluginsplugin_storage,通过 25 个迁移文件从初始版本演进到当前状态。核心表的 updated_at 列均由 SQLite TRIGGER 自动维护。

章节来源


2. ER 关系图

图表来源


3. Song 歌曲模型

Song 是系统最核心的模型,包含 37 个持久化字段 + 3 个运行时计算字段,承载本地歌曲、网络歌曲和电台三种业务形态。类型由 CHECK(type IN ('local', 'remote', 'radio')) 约束。

常量必填字段
TypeLocallocalfile_path
TypeRemoteremoteurl
TypeRadioradiourl

3.1 字段分组

基础信息id(PK), type, title, artist, album, year(0007), genre(0007), track(0020), language(0022), style(0022), duration

文件与音频file_path, url, file_size, format, bit_rate, sample_rate, is_live, is_video(0024), file_modified_at(0019), cache_path(0014)

CUE 分轨cue_source_path(0018), cue_track_index(0018), cue_audio_path(0018)

封面cover_path(json:"-"), cover_url(序列化时重写)

歌词lyric(json:"-", LyricPayload JSON), lyric_source(json:"-"), lyric_remote_url, lyric_url(运行时)

插件与去重plugin_entry_path, source_data(opaque JSON), dedup_key, source_url(运行时)

识别fingerprint(0008, Chromaprint), fingerprint_duration(0008), isrc(0009)

时间戳added_at, updated_at(TRIGGER)

章节来源

3.2 自定义 MarshalJSON

Song 实现了 MarshalJSON(),在序列化时重写四个字段,使客户端无需关心内部存储细节:

字段重写逻辑示例
urlPlaybackURL() 统一播放端点/api/v1/songs/42/play
cover_urlCoverURLPath(),无封面为空/api/v1/songs/42/cover
lyric_urlLyricURLPath(),无歌词为空/api/v1/songs/42/lyric
source_urlremote/radio 填原始 URLhttps://stream.example.com/live.m3u8

通过 type songAlias Song 技巧避免递归。原始 URL 保留在数据库不暴露给客户端,所有播放统一通过 /api/v1/songs/{id}/play 分发。

章节来源

3.3 PlaybackURL 与 HLS 规则

  • 普通歌曲/api/v1/songs/{id}/play
  • HLS 电台(URL 后缀 .m3u8/.m3u):/api/v1/songs/{id}/play.m3u8

追加 .m3u8 后缀原因:ExoPlayer/AVPlayer 按 URL 后缀选择 MediaSource 类型,无后缀会落到 ProgressiveMediaSource 导致 HLS 直播流无法播放。IsHLSStream() 解析 URL 路径后缀判定,仅 TypeRadio 生效。

章节来源

3.4 CoverURLPath 与 LyricURLPath

两个方法遵循"有则返回端点、无则返回空"原则,避免客户端发起注定 404 的请求:

  • CoverURLPathCoverPathCoverURL 任一非空时返回 /api/v1/songs/{id}/cover
  • LyricURLPathLyric 非空,或 LyricSource=="url"LyricRemoteURL 非空时返回 /api/v1/songs/{id}/lyric

章节来源

3.5 DedupKey 去重机制

dedup_key 用于网络歌曲去重,典型形态 <platform>:<platform_id>。与 plugin_entry_path 组成条件唯一索引(partial unique index),仅 dedup_key != '' 的行生效:

sql
CREATE UNIQUE INDEX idx_songs_dedup_key_unique
    ON songs(plugin_entry_path, dedup_key) WHERE dedup_key != '';

章节来源


4. Playlist 歌单模型

字段Go 类型JSON说明
IDint64id自增主键
Typestringtypenormalradio(CHECK 约束)
Namestringname全局唯一(idx_playlists_name_unique
Descriptionstringdescription描述
CoverPathstring-(不暴露)封面本地路径
CoverURLstringcover_url序列化时重写为 /api/v1/playlists/{id}/cover
Labels[]stringlabelsJSON 数组(DB 默认 '[]'
SongCountintsong_count运行时计算(非持久化)

DB 层另有 position(排序用)和时间戳列。MarshalJSON() 同 Song 一样重写 cover_url

CanAddSong 校验normal 歌单仅接受 local/remoteradio 歌单仅接受 radio

Labelsbuilt_in 标记内置不可删除歌单(预置 id=1「收藏」、id=2「电台收藏」),auto_created 标记扫描自动创建的歌单。

章节来源


5. PlaylistSong 关联模型

实现歌单与歌曲的 M:N 多对多关联id(PK), playlist_id(FK), song_id(FK), position, added_at

关键约束:UNIQUE(playlist_id, song_id) 防重复添加;ON DELETE CASCADE 跟随歌单/歌曲删除;(playlist_id, position) 复合索引加速排序。

章节来源


6. Config 配置模型

Key-value 模式,key 全局唯一,value 为 JSON 字符串。预置配置项:

key说明迁移
music_path音乐目录与排除目录0001
scan_config自动扫描、间隔、格式0001
jwt_secretJWT 密钥(randomblob 随机生成)0001
source_validation / source_fallback / source_metrics音源验证/降级/指标0001
plugin_registries官方插件注册表 URL0006
scan_auto_create_playlists是否自动创建歌单0012

章节来源


7. AuthToken 认证令牌模型

JWT 双 Token 认证,auth_tokens 表记录令牌元数据与撤销审计

字段说明
token_id唯一标识(UNIQUE)
token_typeaccess(短期)或 refresh(长期),CHECK 约束
client_infoUser-Agent 追踪
expires_at过期时间
revoked_at撤销时间(nullable,sql.NullTime),判空即知是否已撤销
revoked_by / revoked_reason撤销审计(谁、为什么)

TokenInfoAuthToken 的客户端友好版本(去掉内部 ID),用于令牌列表 API。

章节来源


8. JSPlugin 插件模型

QuickJS 沙盒插件的元数据与运行状态。核心字段:

字段说明
entry_path路由前缀(UNIQUE),如 "myplugin"
main入口文件(默认 main.js
permissionsJSON 数组,如 ["net","storage","fs:music"]
public_paths无需 JWT 的路径前缀(0010 新增)
external_paths可访问的外部绝对路径(0013 新增)
icon插件图标(0011 新增)
statusactive / inactive(默认)/ error,CHECK 约束
zip_hash / entry_hashSHA256 哈希,用于文件指纹热更新检测
file_pathZIP 文件相对路径

运行时比对文件系统 hash 与数据库记录,不一致时自动触发重新加载。

章节来源


9. 歌词模型

9.1 LyricPayload

songs.lyric 列的结构化存储格式,同时也是 /api/v1/songs/{id}/lyric 的响应形态:

字段JSON说明
Lyriclyric主歌词(LRC 文本)
Tlyrictlyric翻译歌词
Rlyricrlyric罗马音歌词
Lxlyriclxlyric逐字歌词

关键方法:MarshalString() 空 payload 返回空字符串(非 "{}")保持 SQL 判空语义;UnmarshalLyric(raw) 兼容空字符串、合法 JSON、裸 LRC 文本三种历史形态;ApplyLyricToSong(s, text, source) 按 source 类型决定存到 Lyric 还是 LyricRemoteURL

9.2 LyricSource 枚举

说明
file同目录 .lrc 文件
embedded音频内嵌歌词
url运行时按需拉取(LyricRemoteURL 存地址)
cachedURL 拉取后缓存的文本
manual用户手动调整,扫描不覆盖(0005 迁移通过重建表扩展 CHECK)

章节来源


10. 备份模型

版本化快照设计(当前 BackupVersion = 1),将歌单与歌曲导出为自描述 JSON。

  • BackupData:顶层容器,含 versionexported_atplaylists[]
  • BackupPlaylist:歌单快照(name/type/description/labels/songs[])
  • BackupSong:Song 的精简版(16 个核心字段),不含 ID、时间戳、歌词、封面路径等可重建数据
  • ImportResult:导入统计 -- playlists_created/playlists_merged/songs_created/songs_matched/songs_skipped

章节来源


11. 请求/响应 DTO

领域模型与 HTTP 层通过 DTO 解耦。

认证LoginRequest(username/password) -> LoginResponse(access_token/refresh_token/expires_in/token_type);RefreshTokenRequest(refresh_token);RevokeTokenRequest(reason)

批量操作BatchDeleteSongsRequest(ids/delete_files) -> BatchDeleteSongsResponse(deleted);BatchDeletePlaylistsRequest(ids) -> BatchDeletePlaylistsResponse(deleted)

自动创建歌单AutoCreatePlaylistsRequest(include_subdirs) -> AutoCreatePlaylistsResponse(playlists[]/total);子结构 PlaylistInfo(playlist_id/name/song_count)

配置CreateConfigRequest(key/value);UpdateConfigRequest(value);ConfigFilter(keyword/limit/offset/order_by/order)

升级RemoteVersionInfo(version/git_commit/build_time/download_url_prefix/release_notes);UpgradeProgress(status/progress/current_step/error),状态流转 idle -> downloading -> testing -> replacing -> restarting(异常 failed/resetting

通用ErrorResponse({error, detail});SuccessResponse({message})

章节来源


12. 模型关系类图

图表来源