Files
musicdl-catalog-sync-suite/Music_Server/docs/superpowers/specs/2026-04-19-music-cloud-public-music-service-design.md

20 KiB
Raw Permalink Blame History

Music_Cloud 公网音乐服务方案设计

日期:2026-04-19
状态:已确认设计,待实现计划
范围:Music_Cloud / catalogsync、独立部署的 Public Music ServiceMusicFree 插件接入、播放器后端 API

1. 背景

当前 Music_Cloud / catalogsync 已经承担了这些职责:

  • 从多个平台采集歌单池、榜单、歌曲元数据
  • 同步歌单歌曲关系、歌手关系、热度数据
  • 多源搜歌、解析、下载、去重
  • 维护本地文件、对象存储、file_locationssong_backend_presence
  • 提供运维后台,用于采集、同步、下载、上传、巡检

它本质上是内容底座和文件资产库,而不是面向最终听歌用户的播放器服务端。

接下来要补的是一套独立于 Music_Cloud 的公网服务,使其可以:

  • MusicFree 提供完整音源插件所需接口
  • 为未来自建网页播放器或 App 提供播放器后端接口
  • 部署在另一台公网服务器,不与 NAS 上的 Music_Cloud 混为一个服务

2. 目标

本方案的目标是:

  • 保持 Music_Cloud 只负责采集、同步、下载、上传、运维
  • 新建一个可独立部署的 Public Music Service
  • MusicFree 插件只做薄适配,不直连数据库,不承担复杂播放回落逻辑
  • 公网服务优先播放 Music_Cloud 已下载或已上传的文件
  • 当本地或对象存储无可播文件时,公网服务再回落到外部平台解析
  • 同时为未来网页播放器保留用户态 API 边界

非目标:

  • 本阶段不设计社交、评论、推荐算法、多人权限系统
  • 本阶段不把 catalogsync 运维后台改造成前台播放器
  • 本阶段不要求把现有 catalogsync.db 直接暴露到公网

3. 结论与总架构

采用三层结构:

  1. Music_Cloud
    • 部署位置:NAS / 内网
    • 角色:内容真相源、下载与存储资产管理、运维控制台
  2. Public Music Service
    • 部署位置:公网服务器
    • 角色:内容查询、播放解析、播放器后端、MusicFree 兼容接口
  3. Object Storage / CDN
    • 部署位置:云存储或兼容对象存储
    • 角色:公网分发音频文件与封面

核心原则:

  • Music_Cloud 与公网服务物理分开、职责分开
  • 公网服务默认读取本机只读目录镜像,不跨公网直接读 NAS 的 SQLite
  • 公网服务可选通过私有链路向 NAS 请求未上传资源,但该链路不对公网开放
  • MusicFree 和未来网页播放器只连接公网服务,不直连 NAS

4. 组件边界

4.1 Music_Cloud

保留现有职责:

  • 歌单采集:歌单广场、榜单、手工输入歌单
  • 歌单同步:歌单详情、歌曲列表、歌手派生
  • 下载:跨平台搜歌、候选优选、落盘、去重
  • 上传:对象存储补传、file_locations 更新、song_backend_presence 刷新
  • 运维:任务编排、日志、暂停恢复、巡检去重

新增一个导出职责:

  • 导出供公网服务使用的目录镜像快照

4.2 Public Music Service

这是本方案新增的主服务。对外暴露两个命名空间:

  • /mf/v1/*
    • 面向 MusicFree 插件
    • 返回结构尽量贴近 MusicFree 需要的字段
  • /player/v1/*
    • 面向自建网页播放器或 App
    • 提供用户态能力,例如收藏、历史、播放上下文

其内部拆为 6 个模块:

  • catalog_reader
    • 读取目录镜像库
  • playlist_service
    • 推荐歌单、榜单、歌单详情、歌曲分页
  • cover_service
    • 封面定位、代理、缓存
  • media_resolver
    • 选播放源、做优先级回落
  • stream_service
    • 签名流地址、Range、代理转发
  • player_service
    • 收藏、历史、最近播放、用户歌单等用户态能力

4.3 MusicFree 插件

插件是薄层,不负责:

  • 数据库存取
  • 文件路径选择
  • 多平台解析
  • 对象存储鉴权
  • NAS 私有回源

插件只负责:

  • 调用 /mf/v1/*
  • 把响应映射为 MusicFree 插件结构
  • sheetItem / musicItem 的稳定 ID 传回服务端

5. 数据同步方案

5.1 选择的方案

采用“快照镜像”而不是“公网服务直读 NAS 主库”。

理由:

  • 主库仍由 Music_Cloud 独占写入,风险最小
  • 公网服务读本机只读库,延迟稳定,运维简单
  • 即使 NAS 暂时不可达,公网服务也能继续提供上一次成功快照
  • 后续可以升级到增量同步,但第一版不需要先上复杂 CDC

5.2 同步链路

Music_Cloud 在这些时机触发目录快照导出:

  • collect 任务完成后
  • sync 任务完成后
  • download 任务完成后
  • upload 任务完成后
  • 定时兜底任务,例如每 30 分钟一次

导出产物:

  • 一个目录镜像数据库,例如 catalog_read.db
  • 一个清单文件,例如 manifest.json
  • 可选封面缓存目录

manifest.json 至少包含:

  • snapshot_id
  • generated_at
  • schema_version
  • playlist_count
  • track_count
  • file_count
  • cover_count

发布方式推荐:

  1. NAS 导出快照
  2. 上传到对象存储或通过 rsync/scp 推送到公网服务器
  3. 公网服务下载或接收最新成功快照
  4. 先写到临时路径
  5. 校验清单后原子替换线上读库

5.3 只读目录镜像库

第一版不要求完整复制 catalogsync.db 全表,只同步公网查询真正需要的字段。

建议镜像库包含这些读模型表:

  • catalog_playlists
    • playlist_id
    • platform
    • remote_playlist_id
    • source_kind
    • name
    • description
    • cover_url
    • play_count
    • song_count
    • synced_at
    • updated_at
  • catalog_tracks
    • song_id
    • platform
    • remote_song_id
    • name
    • singers
    • album
    • cover_url
    • duration_ms
    • metadata_json
  • catalog_playlist_tracks
    • playlist_id
    • song_id
    • position
  • catalog_track_files
    • song_id
    • quality_label
    • ext
    • file_size_bytes
    • backend_type
    • backend_name
    • locator
    • public_url
    • status
    • is_primary
  • catalog_track_presence
    • song_id
    • has_local
    • has_object_storage
    • has_private_origin
    • has_external_fallback
  • catalog_toplists
    • toplist_id
    • platform
    • name
    • description
    • cover_url
    • play_count
    • song_count
    • group_name

说明:

  • 这些表是导出后的读模型,不要求与 catalogsync 内部表名完全一致
  • 读模型允许从 playlists / songs / playlist_songs / file_locations / song_backend_presence 聚合而来
  • 公网服务不依赖 job_* 运维表

6. 存储与播放资源策略

6.1 播放源优先级

公网服务按以下顺序选择播放源:

  1. 对象存储 / CDN 上已有可播文件
  2. 公网服务本地缓存文件
  3. NAS 私有回源
  4. 外部平台解析回落

说明:

  • 第 1 优先级最适合公网分发
  • 第 2 优先级用于热点缓存或调试
  • 第 3 优先级只走内网或私有网络,不暴露给公网
  • 第 4 优先级用于“歌单来自平台 A,但平台 A 拿不到时到其他平台找同名候选”

6.2 NAS 私有回源

为避免“未上传但已下载的歌”在公网不可播,设计一个可选的私有回源能力:

  • Music_Cloud 暴露一个仅内网可访问的私有 origin 接口
  • 该接口只接受来自公网服务的签名请求
  • 该接口只负责:
    • 验签
    • 定位本地文件
    • 以只读方式返回流

不允许:

  • 对公网暴露 NAS 本地路径
  • 公网直接浏览 NAS 目录
  • MusicFree 或网页端直接访问 NAS

6.3 外部平台回落

Music_Cloud 中不存在可播放文件时,media_resolver 可回落到外部平台解析。

回落策略:

  • 按歌曲名、歌手名进行跨平台搜索
  • 候选优先匹配高可信条目
  • 在可信候选内优先更高音质和更大文件
  • 若音质接近,按配置的下载源顺序决定优先级

回落结果默认用于在线播放,不强制自动入库。

原因:

  • 播放链路要快
  • 不把播放器请求与下载入库任务耦合
  • 避免用户点击播放时触发重型后台任务

7. MusicFree 兼容接口设计

7.1 插件能力映射

根据 MusicFree 现有插件接口,插件需要实现这些方法:

  • getRecommendSheetTags
  • getRecommendSheetsByTag
  • getMusicSheetInfo
  • getTopLists
  • getTopListDetail
  • getMediaSource

映射如下:

  • getRecommendSheetTags()
    • GET /mf/v1/recommend/tags
  • getRecommendSheetsByTag(tag, page)
    • GET /mf/v1/recommend/sheets
  • getMusicSheetInfo(sheetItem, page)
    • GET /mf/v1/playlists/{id}
    • GET /mf/v1/playlists/{id}/tracks?page={page}
  • getTopLists()
    • GET /mf/v1/toplists
  • getTopListDetail(topListItem, page)
    • GET /mf/v1/toplists/{id}
    • GET /mf/v1/toplists/{id}/tracks?page={page}
  • getMediaSource(musicItem, quality)
    • POST /mf/v1/media/resolve

7.2 稳定 ID 规范

插件与服务端交互时使用稳定前缀 ID

  • 歌单:catalogsync:playlist:{playlist_id}
  • 榜单:catalogsync:toplist:{toplist_id}
  • 歌曲:catalogsync:song:{song_id}

这样做的目的是:

  • 避免与其他插件的裸 ID 冲突
  • 插件端可以稳定回传主键
  • 服务端未来调整数据库内部结构时,不破坏插件协议

7.3 返回对象

歌单基础对象:

{
  "id": "catalogsync:playlist:18165",
  "platform": "catalogsync",
  "title": "经典老歌:免费下载重温好旋律",
  "coverImg": "https://public-host/mf/v1/covers/playlists/18165",
  "description": "netease / 歌单广场",
  "worksNum": 126
}

歌曲基础对象:

{
  "id": "catalogsync:song:3476",
  "platform": "catalogsync",
  "title": "海屿你",
  "artist": "马也 / Crabbit",
  "album": "",
  "artwork": "https://public-host/mf/v1/covers/songs/3476",
  "duration": 0
}

7.4 /mf/v1/* 详细接口

GET /mf/v1/recommend/tags

用途:

  • 返回推荐歌单标签分组

响应示例:

{
  "pinned": [
    { "id": "all", "title": "全部" },
    { "id": "netease", "title": "网易云" },
    { "id": "qq", "title": "QQ音乐" },
    { "id": "kuwo", "title": "酷我" }
  ],
  "data": [
    {
      "title": "来源",
      "data": [
        { "id": "playlist_square", "title": "歌单广场" },
        { "id": "toplist", "title": "排行榜" }
      ]
    }
  ]
}

GET /mf/v1/recommend/sheets

查询参数:

  • tag
  • page
  • page_size
  • platform
  • sort

默认排序:

  • play_count_desc

默认过滤:

  • 仅返回 song_count > 0 的歌单

GET /mf/v1/playlists/{id}

返回歌单头信息:

  • id
  • title
  • coverImg
  • description
  • worksNum
  • playCount

GET /mf/v1/playlists/{id}/tracks

查询参数:

  • page
  • page_size

返回:

  • sheetItem
    • 仅第 1 页可返回
  • musicList
  • isEnd

GET /mf/v1/toplists

返回榜单分组数组,每组包含:

  • title
  • data

每个榜单对象字段与歌单对象兼容。

GET /mf/v1/toplists/{id}

返回榜单头信息。

GET /mf/v1/toplists/{id}/tracks

与歌单 tracks 分页结构一致。

GET /mf/v1/covers/playlists/{id}

返回歌单封面。

约束:

  • 不要求 Bearer Token
  • 可直接返回对象存储地址、静态文件或代理流
  • 应允许较长缓存时间

GET /mf/v1/covers/songs/{id}

返回歌曲封面,约束与歌单封面一致。

POST /mf/v1/media/resolve

请求体示例:

{
  "song_id": "catalogsync:song:3476",
  "quality": "super"
}

响应体示例:

{
  "song_id": "catalogsync:song:3476",
  "selected_source": {
    "kind": "object_storage",
    "backend": "main-s3",
    "quality": "super",
    "ext": "flac",
    "size_bytes": 42345678
  },
  "stream": {
    "url": "https://public-host/mf/v1/media/stream/eyJhbGciOi...",
    "headers": {},
    "expires_at": "2026-04-19T12:00:00Z",
    "range_supported": true
  }
}

8. 播放器后端接口设计

/player/v1/* 面向未来网页播放器或 App,不要求与 MusicFree 插件接口一致。

第一版范围只做单用户自用,不设计复杂账号体系。

8.1 用户能力范围

第一版支持:

  • 首页聚合
  • 推荐歌单列表
  • 榜单列表
  • 歌单详情
  • 歌曲播放解析
  • 收藏歌曲
  • 收藏歌单
  • 最近播放
  • 播放记录上报

第一版不支持:

  • 社交评论
  • 多用户协作歌单
  • 站内消息
  • 推荐算法
  • 付费体系

8.2 /player/v1/* 接口

GET /player/v1/home

返回首页聚合数据:

  • 热门歌单
  • 榜单分组
  • 最近播放
  • 收藏入口

GET /player/v1/playlists

查询参数:

  • scope
    • recommend
    • toplist
    • favorite
    • mine
  • page
  • page_size
  • sort

GET /player/v1/playlists/{id}

返回歌单头信息和统计信息。

GET /player/v1/playlists/{id}/tracks

返回歌曲分页列表。

POST /player/v1/tracks/{id}/play

返回播放器使用的播放地址,可内部复用 media_resolver

GET /player/v1/me/favorites/tracks

返回收藏歌曲列表。

PUT /player/v1/me/favorites/tracks/{id}

收藏歌曲。

DELETE /player/v1/me/favorites/tracks/{id}

取消收藏歌曲。

GET /player/v1/me/favorites/playlists

返回收藏歌单列表。

PUT /player/v1/me/favorites/playlists/{id}

收藏歌单。

DELETE /player/v1/me/favorites/playlists/{id}

取消收藏歌单。

GET /player/v1/me/history

返回最近播放记录。

POST /player/v1/me/history

写入播放记录,字段包括:

  • track_id
  • played_at
  • progress_seconds
  • source_kind

9. 鉴权与安全

9.1 API 鉴权

/mf/v1/*/player/v1/* 的 JSON 接口统一使用 Bearer Token。

第一版采用单用户 Token 模型:

  • 服务端配置一个或少量长期 Token
  • MusicFree 插件和网页播放器都用这个 Token

后续如需多用户,再升级为真正的登录体系。

9.2 封面访问

封面地址不适合依赖 Bearer Header,因为:

  • MusicFreecoverImg / artwork 仅提供 URL
  • 图片加载通常不会自动带业务鉴权头

因此第一版采用以下之一:

  • 封面接口直接公开只读访问
  • 或返回可长期缓存的签名图片 URL

推荐第一版直接公开封面接口,因为风险低、实现简单。

9.3 播放地址保护

播放地址不能直接暴露真实文件路径、对象存储原始 Key、NAS 本地路径。

规则:

  • resolve 接口只返回短时有效的流地址
  • 该地址带签名和过期时间
  • 服务端验签后再执行真正的文件流读取或转发

要求:

  • 支持 Range
  • 支持 HEAD 或等效元信息读取
  • 支持必要的透传 Header

10. 数据库与存储设计

10.1 继续复用的 Music_Cloud 数据

第一版继续把这些数据视为事实来源:

  • playlists
  • songs
  • playlist_songs
  • file_assets
  • file_locations
  • song_backend_presence

10.2 建议补强字段

为了让公网服务更顺滑,建议在导出链路或源库中确保这些字段可用:

  • playlists.cover_url
  • playlists.description
  • playlists.play_count
  • playlists.collected_song_count
  • songs.cover_url
  • songs.duration_ms

要求:

  • 能拿到就导出
  • 拿不到就允许为空
  • 不要求第一版先完成所有平台补全

10.3 Public Music Service 自身数据

公网服务不直接复用 catalogsync.db 作为用户库。

它需要至少两份本地数据:

  1. catalog_read.db
    • 只读目录镜像库
    • 来源是 Music_Cloud 导出
  2. player.db
    • 用户态数据库
    • 保存收藏、历史、用户歌单、播放上下文

第一版可使用 SQLite

  • 对单用户自用足够
  • 运维成本低
  • 未来若用户态写入量明显增加,可平滑迁移到 PostgreSQL

11. 部署设计

11.1 Music_Cloud 部署

保持在 NAS

  • NAS 统一根目录:/volume4/Music_Cloud
  • catalogsync 工作目录:/volume4/Music_Cloud/catalogsync
  • 数据库:/volume4/Music_Cloud/catalogsync/data/catalogsync.db
  • 本地曲库目录:/volume4/Music_Cloud/library
  • 歌单信息输出目录:/volume4/Music_Cloud/playlists
  • 运维后台:catalogsync serve

11.2 Public Music Service 部署

部署在公网服务器:

  • 服务进程:一个 Web 服务
  • 读库:catalog_read.db
  • 用户库:player.db
  • 缓存目录:封面缓存、热点流缓存
  • 反向代理:Nginx / Caddy

Public Music Service 先部署在 NAS 上联调 / 过渡运行:

  • 宿主机标准目录:/volume4/Music_Cloud/Music_Server
  • Docker 项目目录:/volume4/Music_Cloud/Music_Server/app
  • 运行时配置目录:/volume4/Music_Cloud/Music_Server/config
  • 运行时数据目录:/volume4/Music_Cloud/Music_Server/data
  • 运维脚本目录:/volume4/Music_Cloud/Music_Server/bin
  • 不再使用旧路径:/volume4/Music_Server 下的 app 目录

11.3 对象存储

推荐启用对象存储作为主公网分发层:

  • 音频文件 URL 优先走对象存储或 CDN
  • 封面文件优先走对象存储或静态缓存
  • 公网服务只负责解析、鉴权和必要的中转

12. 错误处理与降级

必须明确这些降级行为:

  • 最新快照导入失败
    • 继续使用上一次成功快照
  • 对象存储不可用
    • 回落到公网缓存、NAS 私有回源或外部平台
  • 外部平台解析失败
    • 返回“不可播放”,不触发自动下载
  • 封面缺失
    • 返回默认占位图
  • 歌单歌曲数为 0
    • 默认不在推荐列表返回

13. 测试策略

实现阶段至少覆盖这些测试:

  • 目录快照导出与导入测试
  • 目录镜像库查询测试
  • MusicFree 插件协议契约测试
  • 歌单详情分页测试
  • 榜单分页测试
  • media_resolver 优先级测试
  • Range 流播放测试
  • Bearer Token / 流签名鉴权测试
  • 收藏与历史 API 测试

手工验证至少包括:

  • MusicFree 中成功加载推荐歌单
  • 点进歌单后分页加载歌曲正常
  • 榜单加载正常
  • 已上传歌曲优先走对象存储
  • 未上传但 NAS 有本地文件时,可通过私有回源播放
  • 本地和私有回源都没有时,可回落到外部平台

14. 分阶段实施

Phase 1: 目录镜像与 MusicFree 浏览

交付内容:

  • Music_Cloud 导出目录镜像快照
  • 公网服务加载 catalog_read.db
  • /mf/v1/recommend/*
  • /mf/v1/playlists/*
  • /mf/v1/toplists/*
  • /mf/v1/covers/*

目标:

  • 先让 MusicFree 能像音乐平台一样浏览歌单和榜单

Phase 2: 播放解析

交付内容:

  • /mf/v1/media/resolve
  • stream_service
  • 对象存储优先
  • 可选 NAS 私有回源
  • 外部平台回落

目标:

  • MusicFree 可以实际播放

Phase 3: 播放器后端

交付内容:

  • /player/v1/home
  • /player/v1/playlists/*
  • /player/v1/me/favorites/*
  • /player/v1/me/history
  • player.db

目标:

  • 为未来网页播放器或 App 提供后端

Phase 4: 首个前端播放器

交付内容:

  • 自建网页播放器或客户端

说明:

  • 该阶段不在本 spec 的实现范围内
  • 本 spec 只负责把服务端接口边界设计清楚

15. 实施边界与仓库建议

建议保持仓库职责清晰,并以当前工作树为准:

  • D:\source\musicdl-catalog-sync-worktrees\catalog-sync
    • 继续承载 Music_Cloud / catalogsync
    • 负责目录镜像导出、私有回源签名能力、对象存储存在性同步
  • D:\source\musicdl-catalog-sync-worktrees\Music_Server
    • 承载 Public Music Service
    • 包含 /mf/v1/*/player/v1/*
    • 本 spec 与 implementation plans 也保存在这个仓库
  • D:\source\musicdl-catalog-sync-worktrees\Music_Server\integrations\musicfree-plugin
    • 承载 MusicFree 插件适配逻辑
    • 最终产出单文件插件或对应构建产物

第一阶段允许在 Music_Server 仓库内同时维护服务端代码和插件代码,但部署和运行时仍保持为两个独立产物。

16. 最终结论

最终架构定为:

  • Music_Cloud:内网内容底座
  • Public Music Service:公网内容与播放服务
  • MusicFree 插件:薄适配层
  • Player Backend:与公网服务同部署、面向未来播放器前端的用户态 API
  • Object Storage / CDN:首选公网分发层

核心决策定为:

  • 公网服务与 Music_Cloud 分开部署
  • 公网服务不直接读 NAS 主库,采用目录镜像快照
  • MusicFree 只连 /mf/v1/*
  • 未来网页播放器只连 /player/v1/*
  • 播放优先级为“对象存储 -> 公网缓存 -> NAS 私有回源 -> 外部平台回落”

这份 spec 作为后续 implementation plan 的唯一设计依据。