docs: 文档与实现对齐,并收录全量代码检查报告

- README:平台能力表按实际能力位修正(qs 歌单/专辑改 —、kw/kg/tx/wy 的
  parse 改 ✅、qt/lr 封面改 ✅),接口表补 album 分页位、parse/playlist
  支持平台、user-playlist 仅 tx/wy、DELETE 登录端点、qs 扫码方式;
  项目结构补 http/ 下 4 个文件与 routes-login 的 bili/lr
- docs/API.md:整篇改为指向 /openapi.json 的速查版,把唯一的接口事实
  来源收敛到 JSON Schema;平台数按实际 15 个更新
- CODE-AUDIT.md:新增,记录 2026-10-03 全量检查的 P0/P1/P2 与文档不一致项,
  顶部标注修复状态(已修 P0/P1,未动项明确列出)

为什么改写而不是逐条打补丁:文档与实现双向失配的地方太多,逐条修补会留下
自相矛盾的段落;改成「能力以 /sources 与 /openapi.json 为准,文档只做索引」
后,漂移的结构性来源就被消除了。
This commit is contained in:
snowy-yang committed 2026-10-03 00:27:30 +08:00
1 parent de2dd311ee
commit dd28ba157c
3 files changed
+487 -67

No files matched your search

+93 -57
View File
@@ -1,26 +1,47 @@
# MusicServer 聚合音乐 API
多平台音乐聚合接口:搜索、播放地址、歌词、封面、榜单、歌单。支持平台:**kw**(酷我)/ **kg**(酷狗)/ **tx**(QQ音乐)/ **wy**(网易云)。
多平台音乐聚合接口:搜索、播放地址、歌词、封面、榜单、歌单、专辑、歌手、链接解析、登录。
> 本文档为静态速查版。交互式文档以服务器实际输出为准:
共 15 个平台:`kw`(酷我)/ `kg`(酷狗)/ `tx`(QQ音乐)/ `wy`(网易云)/ `qs`(汽水)/ `migu`(咪咕)/
`qian`(千千)/ `joox` / `bili`(哔哩哔哩)/ `5sing` / `apple`(Apple Music,仅试听),
以及有声平台 `xm`(喜马拉雅)/ `me`(猫耳FM)/ `qt`(蜻蜓FM)/ `lr`(懒人听书)。
> **本文档是人工速查版,可能滞后于代码。**
> 接口与参数以服务器自动生成的规范为准(路由 schema 同源,永远与实现同步):
>
> - **Swagger UI**:`http://localhost:16688/docs`(可直接调试)
> - **ReDoc**:`http://localhost:16688/redoc`
> - **OpenAPI 3.1**:`http://localhost:16688/openapi.json`(可生成各语言客户端)
> - **Swagger UI**:`/docs`(可直接调试)
> - **ReDoc**:`/redoc`
> - **OpenAPI 3.1**:`/openapi.json`(可生成各语言客户端)
> - **平台能力位**:`GET /sources` —— 各接口具体支持哪些平台,一律以它的能力位为准,本文不再硬列
## 约定
- 默认端口 **16688**(可用 `PORT` 环境变量修改)。
- 仅支持 `GET` 方法;服务器支持 CORS(`Access-Control-Allow-Origin: *`)。
- 默认端口 **16688**(`PORT` 环境变量可改)。
- **全部接口为路径参数风格**,分页走 path 段且第 1 页可省略:`/search/kw/稻香`、`/search/kw/稻香/2`、
`/album/kg/123/2`。查询参数只用于修饰(`?quality=`、`?limit=`、`?singer=` 等)。
- 默认返回 `Access-Control-Allow-Origin: *`,可直接在浏览器/前端调用。
- **成功**:`200` + `{"code": 0, "data": ...}`
- **失败**:对应 HTTP 状态码 + `{"code": <状态码>, "msg": "...", "detail": ...}`
- `422` 参数校验失败(缺失、非法音质、页码越界等,FastAPI 风格)
- `404` 接口不存在 / 平台不支持该能力
- `405` 方法不允许(响应带 `Allow` 头)
- `502` 上游平台请求失败
- `500` 服务器内部错误
- 资源类接口统一为**路径参数风格**;`/search`、`/find` 搜索类接口使用 query 参数。
- 平台歌曲 id 形态:kw/tx/wy 用 `songmid`,kg 用 `hash`。
| 状态码 | 含义 |
| --- | --- |
| `422` | 参数校验失败(Fastify/Ajv,FastAPI 风格) |
| `404` | 接口不存在,或平台不支持该能力 |
| `405` | 方法不允许(响应带 `Allow` 头) |
| `502` | 上游平台请求失败 |
| `500` | 服务器内部错误 |
### 鉴权与限流
- 设置 `AUTH_KEY` 后**全部路径**都要求 `X-Server-Key: <AUTH_KEY>`(含文档页与 404,防止扫描探测接口存在性)。
- `/stream/*` 流代理例外:播放器无法携带自定义头,改用 `/url` 返回流地址时自动附带的 `?exp=&sig=` 短时签名(HMAC 派生,10 分钟有效)。
- `RATE_LIMIT` 为每 IP 每分钟允许的请求数(默认 120,设 0 关闭),超限返回 `429` + `Retry-After`。
### 歌曲 id 形态
各平台 id 约定(含 `kw` 的 songmid、`kg` 的 hash、`qs`/`xm`/`me` 的 id、`migu` 的三元组、
`qt`/`lr` 的复合 id、`bili` 的 `<bvid>|<cid>` 等)详见 README「歌曲 ID 约定」。
复合 id 含 `|` / `:` 时在 URL 中需编码(`|` → `%7C`)。
## 接口速查
@@ -29,70 +50,88 @@
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/health` | 健康检查(status / uptime / 平台数) |
| GET | `/sources` | 支持的平台及其音质、能力(search/url/lyric/pic/boards/hotSearch/songList/singer/album) |
| GET | `/sources` | 平台列表:音质与全部能力位(**判断某接口支持哪些平台的唯一依据**) |
| GET | `/` | 302 重定向到 `/docs` |
| GET | `/docs` | Swagger UI 文档页 |
| GET | `/redoc` | ReDoc 阅读版文档 |
| GET | `/openapi.json` | OpenAPI 3.1 规范文档 |
| GET | `/docs` · `/redoc` · `/openapi.json` | Swagger UI / ReDoc / OpenAPI 3.1 规范 |
### 搜索
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/search?name=稻香&singer=周杰伦&source=kw&page=1&limit=20` | 搜索歌曲;`source` 省略时聚合全部平台,失败的平台跳过 |
| GET | `/find?name=稻香&singer=周杰伦&albumName=魔杰座&interval=03:43&excludeSource=kw` | 跨平台精确匹配,按歌手/歌名/专辑/时长综合排序 |
| GET | `/search/:source/:keyword[/:page]` | 搜索歌曲;`source` 为平台 id 或 `all`(聚合,自带 `noAggregate` 的平台不参与)。`?singer=` 附加歌手、`?limit=` 每页条数 |
| GET | `/find/:source/:keyword` | 精确匹配搜索;`?singer=` `?albumName=` `?interval=` 辅助匹配,`source=all` 时 `?excludeSource=` 跳过平台 |
### 歌曲资源
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/url/:source/:id` | 播放地址;query 可带 `quality`(128k/320k/flac/flac24bit,默认 128k)与 `albumId`(kg 需要) |
| GET | `/lyric/:source/:id` | 歌词;可带 `name`/`interval`(部分平台搜索歌词时需要) |
| GET | `/pic/:source/:id` | 封面;可带 `albumId`(kg 需要) |
| GET | `/url/:source/:id` | 播放地址;`?quality=`(64k/128k/320k/flac/flac24bit,默认 128k)。取流链:外部音源(启用时)→ SDK 直连 → 酷我兜底(响应 `via` 标记实际命中通道) |
| GET | `/stream/:source/:id` | 加密流解密代理,`source` 用 path 段区分(`qs` 汽水解密流 / `migu` 咪咕 Z3D 加密流)。供 `/url` 返回的相对地址使用,不接受自行构造 |
| GET | `/lyric/:source/:id` | 歌词;可带 `?name=` `?interval=`(部分平台搜索歌词时需要) |
| GET | `/pic/:source/:id` | 封面;可带 `?albumId=`(kg 需要) |
### 榜单
### 榜单 / 歌单 / 热搜
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/boards/:source` | 榜单列表 |
| GET | `/boards/:source/:boardId` | 榜单详情;可带 `page`。`boardId` 兼容榜单列表的带前缀 `id`(`kw__93`)与裸 `bangid`(`93`) |
### 歌单 & 热搜
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/boards/:source/:boardId[/:page]` | 榜单详情(分页 path 段)。`boardId` 兼容带前缀 id(`kw__93`)与裸 `bangid`(`93`) |
| GET | `/hot-search/:source` | 热搜词 |
| GET | `/song-list/:source` | 歌单列表;可带 `page`/`sortId`/`tagId`。`sortId`/`tagId` 各平台词汇不同(kw `new`/`hot`、tx `5`/`2`、kw 标签为 `173-digest` 形态等),`sortId` 省略时用平台默认 |
| GET | `/song-list/:source[/:page]` | 歌单列表;`?sortId=` `?tagId=` 各平台词汇不同,省略时用平台默认 |
| GET | `/playlist/:source/:id[/:page]` | 歌单详情与曲目;`?limit=`。bili 支持合集/多分P |
| GET | `/playlist/:source/search/:keyword` | 歌单搜索(`?page=` `?limit=`) |
| GET | `/playlist/:source/categories` | 歌单分类列表 |
| GET | `/playlist/:source/categories/:categoryId` | 分类下的歌单(`?page=` `?limit=`) |
| GET | `/user-playlist/:source` | 个人歌单(需登录;仅 tx/wy 有完整实现,其余 404/502) |
### 歌手 & 专辑
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/singer/:source/:id` | 歌手信息(name/desc/avatar 与歌曲/专辑数量)。支持 kg/tx/wy |
| GET | `/singer/:source/:id/songs` | 歌手歌曲列表;可带 `page`/`limit`(默认 20)。支持 kg/tx/wy |
| GET | `/singer/:source/:id/albums` | 歌手专辑列表;可带 `page`/`limit`。支持 kg/tx/wy |
| GET | `/album/:source/:id` | 专辑详情与曲目列表;可带 `page`/`limit`(limit 仅 kg 生效,kw 单页固定 1000 条)。支持 kg/kw/tx;tx 的专辑名/作者由曲目数据合成(desc 为空) |
| GET | `/singer/:source/:id` | 歌手信息(name/desc/avatar 与歌曲/专辑数量) |
| GET | `/singer/:source/:id/songs[/:page]` | 歌手歌曲列表;`?limit=` |
| GET | `/singer/:source/:id/albums[/:page]` | 歌手专辑列表;`?limit=` |
| GET | `/album/:source/:id[/:page]` | 专辑详情与曲目;`?limit=` |
| GET | `/album/:source/search/:keyword` | 专辑搜索(`?page=` `?limit=`) |
歌手/专辑 id 来源:搜索结果条目包含 `albumId`;歌手 id 需从平台方获取(不在搜索结果中)。
### 链接解析
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| POST | `/parse/song/:source` | 解析歌曲网页链接 → 歌曲信息,body `{"link": "..."}` |
| POST | `/parse/album/:source` | 解析专辑链接 → 专辑信息与曲目 |
| POST | `/parse/playlist/:source` | 解析歌单链接 → 歌单信息与曲目 |
支持的平台见 `/sources` 的 `parse` 能力位;不支持的平台返回 404。
### 登录
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| POST | `/login/wy/qr` | 获取网易云扫码登录二维码(返回 `key`/`qrurl`/`qrimg`,`qrimg` 为 data URL 可直接展示) |
| POST | `/login/wy/qr/check` | 轮询扫码状态,body 传 `{ "key": "..." }`。返回 `code`:800 过期 / 801 等待 / 802 已扫码 / 803 登录成功(自动保存凭据) |
| POST | `/login/wy/cellphone` | 网易云手机号登录;body 传 `phone` + `password`(明文,服务端 MD5 后提交)或 `captcha`,可带 `countrycode`(默认 86) |
| DELETE | `/login/wy` | 退出网易云登录 |
| POST | `/login/kg` | 绑定酷狗 token;body 传 `{ "token": "...", "userid": ... }`(App 抓包获取),服务端向用户中心验证后存储 |
| DELETE | `/login/kg` | 退出酷狗登录 |
| POST | `/login/tx` | 绑定 QQ 音乐 cookie;body 传 `{ "cookie": "..." }`(需含 `uin` 与 `qm_keyst` 字段)。可选附 App 抓包的 `openId`/`accessToken`/`refreshToken`/`refreshKey` 供自动续期 |
| DELETE | `/login/tx` | 退出 QQ 音乐登录 |
| POST | `/login/refresh` | 手动触发 kg/tx 登录态续期(与自动任务同一逻辑;未登录平台跳过,单平台失败不影响其他) |
| GET | `/login/status` | 登录状态(wy 实时校验 / kg、tx 已存凭据,含 `vipType` 与最近更新时间) |
| POST | `/login/wy/qr` → `/login/wy/qr/check` | 网易云扫码(`check` body 传 `{"key": "..."}`) |
| POST | `/login/wy/cellphone` | 网易云手机号登录(`password` 明文或 `captcha`) |
| DELETE | `/login/wy` | 退出网易云 |
| POST | `/login/kg/qr` → `/login/kg/qr/check` | 酷狗扫码 |
| POST | `/login/kg/captcha` → `/login/kg/cellphone` | 酷狗短信验证码登录 |
| POST | `/login/kg/password` | 酷狗账密登录(命中风控返回挑战,配合 `/login/kg/verify` 重试) |
| POST | `/login/kg` | 绑定酷狗 token(App 抓包) |
| DELETE | `/login/kg` | 退出酷狗 |
| POST | `/login/tx/qr` → `/login/tx/qr/check` | QQ 音乐扫码 |
| POST | `/login/tx` | 绑定 QQ 音乐 cookie(可选 App 抓包字段供自动续期) |
| DELETE | `/login/tx` | 退出 QQ 音乐 |
| POST | `/login/qs/qr` → `/login/qs/qr/check` | 汽水音乐扫码(用**汽水音乐 App** 扫一扫) |
| DELETE | `/login/qs` | 退出汽水音乐 |
| POST | `/login/xm` · `/login/me` | 绑定喜马拉雅 cookie / 猫耳FM token |
| DELETE | `/login/xm` · `/login/me` | 退出对应平台 |
| POST | `/login/lr` | 懒人听书账号登录(`account` + `password`) |
| DELETE | `/login/lr` | 退出懒人听书 |
| POST | `/login/bili/qr` → `/login/bili/qr/check` | 哔哩哔哩扫码(解锁大会员音质) |
| DELETE | `/login/bili` | 退出哔哩哔哩 |
| POST | `/login/refresh` | 手动触发登录态续期(kg/tx) |
| GET | `/login/status` | 登录状态总览(各平台 `logged` 与账号资料) |
- 登录态持久化于 `data/auth.json`(不入库),登录后取流与平台请求自动携带凭据。
- **登录态自动续期**:服务器启动 10s 后自动对 kg/tx 执行一次续期,此后每 24h 一次(kg 走 `login_by_token` AES/RSA 协议换新 token;tx 走 `music.login.LoginServer`,仅浏览器 cookie 时可能被网关拒绝,需在 `/login/tx` 附 App 抓包的完整凭据才可靠),结果输出到控制台日志。
- VIP 歌曲完整播放地址暂不在支持范围;登录态目前用于提升请求稳定性与为后续能力预留。
- 凭据持久化于 `data/auth.json`(不入库,文件权限 0600),登录后取流与平台请求自动携带。
- 自动续期:启动 10s 后对 kg/tx 执行一次,此后每 24h 一次(音源模式下不参与取流,自动续期不启动)。
## 响应示例
@@ -103,15 +142,12 @@
// GET /lyric/kw/228908
{ "code": 0, "data": { "lyric": "[00:00.00]...", "tlyric": "", "yrc": "" } }
// 参数缺失
// 422
{ "code": 422, "msg": "querystring must have required property 'name'", "detail": [...] }
// 参数非法 → 422
{ "code": 422, "msg": "params/source must be equal to one of the allowed values", "detail": [...] }
// 平台不支持该能力
// 404
// 平台不支持该能力 → 404
{ "code": 404, "msg": "不支持的平台: xx" }
// 方法不允许
// 405 + Allow: GET
{ "code": 405, "msg": "方法 POST 不允许,请使用 GET" }
// 方法不允许 → 405 + Allow 头
{ "code": 405, "msg": "方法 POST 不允许,请使用 GET, HEAD" }
```