Files
musicdl-catalog-sync-suite/Music_Server/docs/superpowers/specs/2026-04-23-music-server-multi-search-design.md
T

12 KiB
Raw Blame History

Music_Server 多类型搜索与歌手详情设计

日期:2026-04-23
状态:已确认设计,待实现计划
范围:catalog-syncMusic_ServerMusicFree

1. 背景

当前 Music_Server 面向 MusicFree 插件只支持单曲搜索,链路上存在三个直接影响使用的问题:

  1. 搜索页只能返回 单曲,无法返回 歌手歌单
  2. 即使补出歌手搜索结果,当前 MusicFree 的歌手详情页默认固定为 单曲 / 专辑 两个 tab,而本次目标是不再展示专辑,只展示该歌手的全部歌曲。
  3. 当前已有榜单详情能力,但如果把榜单混入“歌单搜索”结果,插件详情入口还不能自动识别并转发到榜单详情接口。

当前三端职责边界保持不变:

  • catalog-sync 负责采集、同步、下载和维护源库 catalogsync.db
  • Music_Server 负责把源库导出成只读快照,并对 MusicFree 插件暴露 /mf/v1/* 接口。
  • MusicFree 负责插件调用、结果展示和详情页交互,不直接访问源站接口或服务端数据库。

本轮需要解决的是:

  1. Music_Server 支持 单曲 / 歌手 / 歌单 三类搜索。
  2. 歌手 搜索结果按平台分别显示,不做跨平台合并。
  3. 点进歌手后只展示该歌手的全部可播歌曲,不再展示专辑。
  4. 歌单 搜索同时返回普通歌单和现有榜单,并且点进去后能正常打开对应详情。

2. 目标与非目标

2.1 目标

  • Music_Server 暴露 MusicFree 兼容的三类搜索接口。
  • Music_Server 导出稳定的歌手只读模型,避免运行时从 songs.singers 临时反推。
  • Music_Server 只返回至少包含 1 首可播歌曲的歌手和歌单结果。
  • Music_Server 歌手详情只返回该歌手的可播歌曲列表,并支持分页。
  • Music_Server 歌单搜索同时覆盖普通歌单和榜单。
  • Music_Server 插件同时支持 musicartistsheet 三类搜索。
  • MusicFreeMusic_Server 歌手详情仅展示单曲列表,不展示专辑 tab。

2.2 非目标

  • 不做跨平台歌手身份合并。
  • 不做歌手专辑详情能力。
  • 不改动其它插件的歌手详情行为。
  • 不新增新的播放解析策略。
  • 不改动 MusicFree 全局搜索架构,只做与 Music_Server 插件兼容所需的最小改动。

3. 方案选择

评估过三个方向:

  1. 运行时从 catalog_tracks.singers 现算歌手搜索和详情。
  2. 在导出阶段补歌手读模型,再由服务端和插件接入。
  3. 服务端只提供少量接口,更多聚合逻辑放到插件端处理。

本轮选择方案 2,原因如下:

  • catalog-sync 源库已经有 artistsartist_songs 表,可以稳定表达“平台内歌手”和“歌手作品关系”。
  • 歌手详情需要分页、排序和稳定 id,用导出读模型比字符串现算更可靠。
  • 榜单混入歌单搜索后,插件只需识别 id 类型并分发详情请求,不需要自己做复杂聚合。

4. 核心设计

4.1 读模型扩展

catalog_read.db 中新增两张表:

catalog_artists

  • artist_id integer primary key
  • artist_key text not null unique
  • platform text not null
  • remote_artist_id text
  • name text not null
  • normalized_name text not null
  • avatar_url text
  • description text
  • playable_song_count integer not null

用途:

  • 作为歌手搜索结果和歌手详情头部的主表。
  • artist_id 直接沿用源库 artists.id,避免重新映射。
  • artist_key 沿用源库 platform + remote_artist_id/normalized_name 的稳定语义。

catalog_artist_tracks

  • artist_id integer not null
  • song_id integer not null
  • position integer not null

用途:

  • 存储歌手与歌曲的展开关系。
  • 用于歌手详情分页和稳定排序。

排序约定:

  • position 由导出时确定,默认按歌曲名升序、song_id 升序生成稳定序号。

4.2 导出策略

Music_Server/scripts/export_catalog_read.py 在现有导出流程基础上新增歌手导出:

  1. 从源库 artists 读取歌手主体。
  2. 通过 artist_songs 关联到 songs
  3. 通过 file_assets + file_locations(status='active') 过滤出当前可播歌曲。
  4. 仅保留 playable_song_count > 0 的歌手进入 catalog_artists
  5. 将这些可播歌曲关系写入 catalog_artist_tracks

补充规则:

  • 优先使用源库 artists 关系,不从 songs.singers 反推歌手。
  • 如果歌手缺少头像或简介,允许写空。
  • 如果歌手没有可播歌曲,不进入只读库,这样搜索和详情天然只面向可用结果。

4.3 服务端接口

在现有 /mf/v1 之下新增四个接口:

GET /mf/v1/search/artists

参数:

  • q
  • page
  • page_size

返回 MusicFree 兼容形状:

  • isEnd
  • data

单个结果字段:

  • id: catalogsync:artist:{artist_id}
  • platform: 平台名,例如 netease / qq / kuwo
  • name
  • avatar
  • worksNum: 可播歌曲数
  • description
  • supportedArtistTabs: ["music"]

GET /mf/v1/artists/{artist_id}

返回歌手详情头部字段,至少包含:

  • id
  • platform
  • name
  • avatar
  • worksNum
  • description
  • supportedArtistTabs

GET /mf/v1/artists/{artist_id}/tracks

参数:

  • page
  • page_size

返回:

  • isEnd
  • musicList

歌曲仍复用当前单曲对象结构和播放链路。

GET /mf/v1/search/sheets

参数:

  • q
  • page
  • page_size

语义:

  • 同时搜索 catalog_playlistscatalog_toplists
  • 将两类结果统一映射成 MusicFree 的 sheet 形状

单个结果字段:

  • id
    • 普通歌单:catalogsync:playlist:{playlist_id}
    • 榜单:catalogsync:toplist:{toplist_id}
  • platform
  • title
  • coverImg
  • description
  • worksNum
  • playableSongCount
  • playCount

4.4 查询语义与排序

单曲搜索

保持现有语义:

  • 只返回有 active 文件位置的歌曲。
  • 排序为:
    1. 歌名精确匹配
    2. 歌名前缀匹配
    3. 歌名模糊匹配
    4. 歌手名模糊匹配
    5. lower(name) 升序
    6. song_id 升序

歌手搜索

只搜索 catalog_artists,并保持平台内独立:

  • 不做跨平台合并。
  • 只返回 playable_song_count > 0 的歌手。
  • 排序为:
    1. 歌手名精确匹配
    2. 歌手名前缀匹配
    3. 歌手名模糊匹配
    4. playable_song_count desc
    5. lower(name) asc
    6. artist_id asc

歌单搜索

同时搜索 catalog_playlists.namecatalog_toplists.name

  • 只返回 playable_song_count > 0 的结果。
  • 结果合并后按以下规则排序:
    1. 标题精确匹配
    2. 标题前缀匹配
    3. 标题模糊匹配
    4. play_count desc
    5. 类型稳定顺序:普通歌单优先于榜单
    6. 稳定 id 升序

4.5 插件适配

Music_Server 的两个插件资产都要同步修改:

  • src/music_server/plugin_assets/music_server.js
  • src/music_server/plugin_assets/music_server_lan.js

搜索能力

  • supportedSearchType 改为 ["music", "artist", "sheet"]
  • search(query, page, type) 改为按类型分发:
    • music -> /mf/v1/search/songs
    • artist -> /mf/v1/search/artists
    • sheet -> /mf/v1/search/sheets

歌手映射

新增 mapArtistItem(...),负责把服务端歌手结果映射到 MusicFree 形状:

  • id
  • name
  • avatar
  • worksNum
  • platform
  • description
  • supportedArtistTabs

歌手详情

新增 getArtistWorks(artistItem, page, type)

  • type === "music" 时,请求 /mf/v1/artists/{artist_id}/tracks
  • 其余类型返回空列表

歌单详情入口识别榜单

增强 getMusicSheetInfo(sheetItem, page)

  • 若 id 前缀是 catalogsync:playlist:,继续走 /mf/v1/playlists/*
  • 若 id 前缀是 catalogsync:toplist:,自动走 /mf/v1/toplists/*

这样榜单即使从“歌单搜索”结果页点入,也能正常打开详情。

4.6 MusicFree 最小兼容改动

当前 MusicFree 的歌手详情页固定渲染 musicalbum 两个 tab。
本轮只对歌手详情页做最小改动:

  1. 如果 artistItem.supportedArtistTabs 存在,则使用该数组作为当前歌手详情页 tab 集合。
  2. 否则继续回退到默认 ["music", "album"]
  3. 当 tab 只有一个时,不渲染 tab 栏,直接展示对应列表。

预期效果:

  • Music_Server 歌手详情页直接显示“全部歌曲”。
  • 其它插件继续维持原本的“单曲 / 专辑”体验,不受影响。

5. 数据流

目标数据流如下:

  1. catalog-sync 持续维护源库中的 artistsartist_songssongsplaylist_songs、文件位置等数据。
  2. Music_Server 执行导出脚本,把源库转换成包含歌手读模型的 catalog_read.db
  3. Music_Server 通过 CatalogReader 对三类搜索与详情提供只读查询。
  4. Music_Server 插件按 music / artist / sheet 三类请求分发到对应接口。
  5. MusicFree 搜索页展示三类结果。
  6. 用户点击歌手结果后,插件只拉取该歌手的歌曲列表;点击歌单结果时,普通歌单和榜单都能按各自详情接口打开。

6. 错误处理与兼容性

6.1 服务端

  • 空查询直接返回空列表。
  • 不存在的歌手、歌单、榜单返回 404
  • 非法 id 返回 400404,与现有公开 id 处理风格保持一致。
  • 歌手详情和歌曲列表都只面向只读库中已导出的歌手 id,不做运行时兜底推断。

6.2 插件

  • artist 类型的非 music 详情请求直接返回空结果,不抛异常。
  • 歌单详情里若识别到榜单 id,则自动转发到榜单详情,不暴露给上层页面额外判断。
  • 若服务端返回空列表,插件返回 isEnd: true 的空结果,保证 MusicFree 页面可正常结束加载。

6.3 客户端

  • 对不带 supportedArtistTabs 的旧插件保持原行为。
  • Music_Server 插件,只有一个 tab 时隐藏 tab 栏,不影响列表分页和批量操作。

7. 测试策略

7.1 Music_Server

需要覆盖以下测试层次:

  1. 导出脚本测试

    • 验证 artists / artist_songs 被正确导出到 catalog_artists / catalog_artist_tracks
    • 验证没有可播歌曲的歌手不会被导出
    • 验证歌单搜索联合结果可覆盖普通歌单和榜单
  2. CatalogReader 测试

    • search_artists(...)
    • get_artist(...)
    • list_artist_tracks(...)
    • search_sheets(...)
    • 排序、分页、空查询和边界条件
  3. 路由测试

    • /mf/v1/search/artists
    • /mf/v1/artists/{artist_id}
    • /mf/v1/artists/{artist_id}/tracks
    • /mf/v1/search/sheets
    • token 鉴权兼容现有行为

7.2 插件与客户端

至少覆盖以下验证:

  1. 插件搜索分发

    • music
    • artist
    • sheet
  2. 插件详情分发

    • 歌手详情只拉歌曲
    • 搜索结果中的榜单通过 getMusicSheetInfo(...) 正确落到榜单详情接口
  3. MusicFree 页面行为

    • Music_Server 歌手详情只显示歌曲列表
    • 其它插件歌手详情仍保留 单曲 / 专辑

8. 实施范围

预计涉及以下文件:

Music_Server

  • scripts/export_catalog_read.py
  • src/music_server/services/catalog_reader.py
  • src/music_server/routes/mf_catalog.py
  • src/music_server/plugin_assets/music_server.js
  • src/music_server/plugin_assets/music_server_lan.js
  • tests/test_export_catalog_read.py
  • tests/test_catalog_reader.py
  • tests/test_mf_catalog_routes.py
  • tests/test_plugin_routes.py(如需校验插件导出字段)

MusicFree

  • src/pages/artistDetail/components/body.tsx
  • 可能附带 src/pages/artistDetail/store/atoms.ts
  • 可能附带 src/pages/artistDetail/components/resultList.tsx

原则:

  • 只为支持单 tab 歌手详情做最小必要改动。
  • 不改动全局搜索框架和其它插件约定。

9. 验收标准

满足以下条件即视为完成:

  1. MusicFree 中 Music_Server 插件搜索页可切换并返回 单曲 / 歌手 / 歌单 三类结果。
  2. 歌手 结果按平台分别显示。
  3. 点进歌手后直接看到该歌手的全部可播歌曲,并可正常分页与播放。
  4. 歌手详情页不再展示专辑 tab。
  5. 歌单 搜索可同时返回普通歌单和榜单。
  6. 从搜索结果点进榜单时,详情页和歌曲播放都正常。
  7. 其它插件的歌手详情行为不发生变化。