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:
1 parent
de2dd311ee
commit
dd28ba157c
3 files changed
+487
-67
No files matched your search
+333
@@ -0,0 +1,333 @@
|
|||||||
|
# MusicServer 全量代码检查报告
|
||||||
|
|
||||||
|
**检查时间**:2026-10-03
|
||||||
|
**修复状态**(复查后更新):P0-1 ~ P0-3、P1-4 ~ P1-16 **全部已修复**(含测试隔离与 isolated-vm 原生构建);
|
||||||
|
第五节「文档与代码不一致」已按 `/sources` 与路由实现逐条校准;第八节列出的未复核线索与第四节 P2 项**未动**,留待后续。
|
||||||
|
各条的修复要点见下文对应条目。
|
||||||
|
**检查范围**:`src/`(15 文件 / 3,103 行)、`sdk/`(173 文件 / 21,919 行)、`test/`(2 文件 / 1,081 行),共约 26,100 行 TypeScript
|
||||||
|
|
||||||
|
**检查方法**:
|
||||||
|
1. 静态门禁:`tsc`、`tsc --noUnusedLocals --noUnusedParameters`、离线音源测试、冒烟测试
|
||||||
|
2. 全量模式扫描:`any` 滥用、`@ts-ignore`、TODO/FIXME、调试输出、死代码、硬编码敏感值
|
||||||
|
3. 四组分区深度审查:HTTP 层 / SDK 核心(认证+取流)/ SDK 平台模块 / 测试与文档
|
||||||
|
4. 针对性复现验证:对判定为 P0 的问题编写最小复现脚本确认
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、基线状态
|
||||||
|
|
||||||
|
| 检查项 | 结果 |
|
||||||
|
| --- | --- |
|
||||||
|
| `pnpm typecheck`(tsc strict) | ✅ 通过 |
|
||||||
|
| 未使用符号(`--noUnusedLocals`) | ⚠️ 26 处 |
|
||||||
|
| 离线测试 `test/source-simple-test.ts` | ✅ 8/8 通过 |
|
||||||
|
| 冒烟测试 `test/smoke-test.ts` | ⚠️ **不稳定**:一次 110/111,一次直接崩溃(见 P1-4) |
|
||||||
|
| `@ts-ignore` / `@ts-expect-error` | ✅ 0 处(纪律良好) |
|
||||||
|
| TODO / FIXME / HACK | ✅ 0 处 |
|
||||||
|
| lint / CI 配置 | ❌ 均无(质量门禁仅 tsc + 冒烟测试) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、P0 阻断级
|
||||||
|
|
||||||
|
### P0-1 `POST /login/lr` 路由缺失 —— 懒人听书取流 100% 不可用
|
||||||
|
|
||||||
|
**证据链**:
|
||||||
|
- `README.md:86` 与 `README.md:105` 明确承诺:懒人听书需 `POST /login/lr` 账密登录,且「取流必须登录(免费章节同样要求登录态)」
|
||||||
|
- `sdk/lr/auth.ts:12` **已完整实现** `login()`(base64 密码 + 会话 Cookie 持久化)
|
||||||
|
- 但 `sdk/lr/index.ts:53` 只写了 `export { status } from './auth.ts'` —— **没有导出 `login`**
|
||||||
|
- `src/routes-login.ts` 注册的 28 条路由中**没有任何 lr 相关条目**(逐条列举验证)
|
||||||
|
|
||||||
|
**影响**:sdk 层实现了登录能力,却没有任何 HTTP 入口。而 lr 取流强制要求登录态 → **该平台的取流功能完全无法使用**,README 的承诺是空头支票。
|
||||||
|
|
||||||
|
**修复**:`sdk/lr/index.ts` 补导出 `login`;`src/routes-login.ts` 参照 `/login/xm` 补 `POST /login/lr`(body: `{account, password}`)与 `DELETE /login/lr`;`/login/status` 加入 `lr` 字段。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P0-2 咪咕解密流异常逃逸 —— 可导致整个服务进程崩溃
|
||||||
|
|
||||||
|
**证据链**:
|
||||||
|
- `src/http/migu-stream.ts:88-92`:`res.on('data', chunk => { ...; onChunk(plain, offset) })` —— `onChunk` 在 EventEmitter 回调中**同步调用**
|
||||||
|
- `src/routes.ts:317`:该回调内部有 `throw new Error('解密结果不是有效音频')`
|
||||||
|
- 外层 `src/routes.ts:311-324` 的 `try/catch` 只能捕获 Promise 链上的 reject,**捕不到** EventEmitter 回调抛出的异常
|
||||||
|
- 全项目**没有任何** `uncaughtException` / `unhandledRejection` 处理器(grep 无匹配)
|
||||||
|
|
||||||
|
**复现验证**(`tmp/repro-migu-uncaught.mjs`,已实测):
|
||||||
|
```
|
||||||
|
❌ uncaughtException 逃逸(进程会崩溃): 解密结果不是有效音频
|
||||||
|
exit code 9
|
||||||
|
```
|
||||||
|
|
||||||
|
**影响**:当咪咕返回的密流与票据 `fileKey` 不匹配(解密结果非法),或客户端中途断开导致 `reply.raw.write` 抛错时,异常逃逸 → **整个 Node 进程崩溃**,所有在线用户连接同时中断。这是一个远程可触发的单点崩溃。
|
||||||
|
|
||||||
|
**修复**:`onChunk` 内不要 `throw`,改为置错误标志 + `res.destroy()` / `reply.raw.destroy()`;或让 `pipeDecryptedMiguStream` 把 `transform`/`onChunk` 的调用包进 `try/catch` 并 `reject`。另建议补一个全局 `uncaughtException` 兜底(至少记日志后优雅退出,而非静默崩溃)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P0-3 登录凭据明文落盘且权限过宽
|
||||||
|
|
||||||
|
**证据链**:
|
||||||
|
- `sdk/auth/store.ts:88`:`writeFileSync(DATA_FILE, JSON.stringify(data, null, 2))` —— 明文 JSON,且**未传 `{ mode: 0o600 }`**
|
||||||
|
- 实际文件:`data/auth.json` 权限 `-rw-r--r--`(0644),内容为各平台 cookie/token 明文
|
||||||
|
- 文件覆盖 wy / kg / tx / xm / me / lr / bili / qs 等全部平台凭据
|
||||||
|
|
||||||
|
**影响**:任何能读取该目录的本地用户或进程可直接窃取全部平台登录态(等价于账号取流权限)。Windows 上 POSIX 位不完全生效,风险取决于 ACL,但**明文存储**这一点在任意平台都成立。
|
||||||
|
|
||||||
|
**修复**:`writeFileSync(..., { mode: 0o600 })`(Windows 侧另设 ACL);如需更高保障再考虑加密存储(密钥来自环境变量)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、P1 应修
|
||||||
|
|
||||||
|
### P1-4 冒烟测试异常路径不清理子实例 → 僵尸进程累积
|
||||||
|
|
||||||
|
**证据**:
|
||||||
|
- `test/smoke-test.ts:81-110` `startLocalServer()` 超时后 `proc.kill()` 再 `reject`
|
||||||
|
- 主实例之外还有两个**额外实例**:`authServer`(:835)、`rlServer`(:887)
|
||||||
|
- `:898-899` 的 `rlServer.kill()` / `authServer.kill()` 位于顶层顺序执行,**不在 try/finally 内** —— 一旦中间抛错就不会执行
|
||||||
|
- `:911-915` 顶层只清理了 `mainServer`(`localServer`),**不涉及额外实例**
|
||||||
|
|
||||||
|
**实测后果**:当前机器上有 **6 个 MusicServer 实例在监听**:`16688`(常驻)+ `16690/16691/16692/16693/16790`(测试泄漏,连续端口说明是多轮泄漏累积)。本次审查中冒烟测试崩溃后即留下新实例。
|
||||||
|
|
||||||
|
**影响**:每次测试异常就泄漏一个监听端口的 node 进程,长期累积占用内存/端口,且会干扰后续测试判定。
|
||||||
|
|
||||||
|
**修复**:用 `try/finally` 或 `process.on('exit')` 统一 kill 所有已启动实例。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-5 冒烟测试启动超时 10s 硬编码,且失败时无任何诊断信息
|
||||||
|
|
||||||
|
**证据**:`test/smoke-test.ts:91-94`
|
||||||
|
```js
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
proc.kill()
|
||||||
|
reject(new Error('启动本地服务器超时')) // ← 未附 stdout
|
||||||
|
}, 10000)
|
||||||
|
```
|
||||||
|
`stdout` 变量已收集子进程输出(:87-89),但 reject 时完全丢弃。
|
||||||
|
|
||||||
|
**影响**:本次审查中同一份代码**一次通过(110/111)、一次崩溃**,说明该超时在并发/负载下很脆弱 —— 属于典型的 flaky 测试。而失败信息只有一句「启动本地服务器超时」,无法判断是端口被占、TS 报错还是单纯慢。
|
||||||
|
|
||||||
|
**修复**:超时提到 30s;错误信息附带 `stdout` 尾部;区分「进程已退出」与「进程存活但未就绪」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-6 `/song-list/:source` 的 `?limit=` 被文档承诺却静默忽略
|
||||||
|
|
||||||
|
**证据**:
|
||||||
|
- `src/routes.ts:450`:querystring schema 声明了 `limit: pLimit`
|
||||||
|
- `src/routes.ts:436`:handler 签名为 `(source, page, sortId, tagId)` —— **没有 limit 参数**
|
||||||
|
- `src/routes.ts:458`:调用时也未传 `request.query.limit`
|
||||||
|
|
||||||
|
**影响**:OpenAPI 文档告诉客户端可以传 `?limit=`,实际被丢弃 —— API 契约与实现不符。
|
||||||
|
|
||||||
|
**修复**:要么把 limit 透传到 `songList.getList()`,要么从 schema 移除。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-7 凭据文件写入非原子 + 解析失败静默清空
|
||||||
|
|
||||||
|
**证据**(`sdk/auth/store.ts`):
|
||||||
|
- `:84-89` `save()` 直接 `writeFileSync` 覆盖原文件,无 temp+rename。写入过程中进程被杀 → 文件损坏
|
||||||
|
- `:73-82` `load()` 用 `catch { cache = {} }` 静默吞掉 JSON 解析错误;随后任何 `setCredentials()` 都会用空数据**覆盖重写**损坏文件 → **所有平台凭据一次性丢失**,且无任何日志
|
||||||
|
- 无跨进程锁:当前环境并存 6 个实例,写操作会互相覆盖
|
||||||
|
|
||||||
|
**修复**:写临时文件 + `rename()` 原子替换;解析失败时先备份原文件再重置,并打印告警。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-8 过期/错误注释误导维护者
|
||||||
|
|
||||||
|
**证据**:`sdk/auth/store.ts:3`
|
||||||
|
```
|
||||||
|
// 注意:store.js 为 tsc 就地发射产物(不入库);改逻辑只改本文件,然后 pnpm build
|
||||||
|
```
|
||||||
|
但:`tsconfig.json` 已是 `noEmit: true`,`package.json` **没有 `build` 脚本**,项目已全面改为「无编译产物、改完即跑」。磁盘上也确认无 `.js` 残留。
|
||||||
|
|
||||||
|
**影响**:按注释执行 `pnpm build` 会失败,且误导开发者以为存在发射产物。
|
||||||
|
|
||||||
|
**修复**:删掉该行注释。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-9 酷狗 krc 歌词解码:Promise 永不 settle,请求挂死
|
||||||
|
|
||||||
|
**证据**:`sdk/kg/krcDecode.ts:9-10`
|
||||||
|
```js
|
||||||
|
const decodeLyric = str => new Promise((resolve, reject) => {
|
||||||
|
if (!str.length) return // ← 既未 resolve 也未 reject
|
||||||
|
```
|
||||||
|
`return` 只结束了执行器函数,Promise 永久停在 pending。
|
||||||
|
|
||||||
|
**影响**:上游返回空 krc 歌词时,调用方的 `await` 永久挂起 —— 连接与内存无法释放,客户端只能等自己超时。
|
||||||
|
|
||||||
|
**修复**:`if (!str.length) return resolve('')`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-10 酷狗歌词重试调错方法
|
||||||
|
|
||||||
|
**证据**:`sdk/kg/lyric.ts:72` —— 在 `getLyricDownload(id, accessKey, fmt, tryNum)` 内部重试时调用的是
|
||||||
|
```js
|
||||||
|
let tryRequestObj = (this.getLyric as any)(id, accessKey, fmt, ++tryNum)
|
||||||
|
```
|
||||||
|
应为 `this.getLyricDownload(...)`。`getLyric` 与 `getLyricDownload` 参数语义不同,且 `as any` 恰好掩盖了这个错误。
|
||||||
|
|
||||||
|
**影响**:HTTP 非 200 时的重试实际走了另一个方法,重试逻辑失效(还会掩盖类型错误)。
|
||||||
|
|
||||||
|
**修复**:改为 `this.getLyricDownload(id, accessKey, fmt, ++tryNum)`;顺带移除 `as any`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-11 酷我歌单详情:重试参数错位(序号被当成页码)
|
||||||
|
|
||||||
|
**证据**:
|
||||||
|
- `sdk/kw/songList.ts:196`:`return this.getListDetail(id, ++tryNum)`
|
||||||
|
- `sdk/kw/songList.ts:373`:`getListDetail(id, page, retryNum = 0)` —— 第二个参数是 **page**
|
||||||
|
|
||||||
|
同一文件 `:175`、`:206` 都正确写成 `getListDetail(id, page, ++tryNum)`,唯独 `:196` 漏了 `page`。
|
||||||
|
|
||||||
|
**影响**:重试时请求的页码被替换成 `tryNum`(1/2/3…),而不是用户请求的页;同时重试计数没有真正传递。
|
||||||
|
|
||||||
|
**修复**:补上 `page`,与 `:206` 对齐。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-12 酷我歌单详情 URL 模板多余 `}`
|
||||||
|
|
||||||
|
**证据**:`sdk/kw/songList.ts:203`
|
||||||
|
```
|
||||||
|
...&pid=${id}&pn=${page - 1}}&rn=${this.limit_song}&...
|
||||||
|
↑ 多了一个右花括号
|
||||||
|
```
|
||||||
|
|
||||||
|
**影响**:请求 URL 携带非法字符,目前依赖上游容错;上游一旦收紧解析该接口即失效。
|
||||||
|
|
||||||
|
**修复**:删掉多余的 `}`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-13 冒烟测试未做环境隔离:取流用例实际走外部音源,且会改写真实凭据
|
||||||
|
|
||||||
|
**证据**:
|
||||||
|
- `test/smoke-test.ts:84` 以 `env: { ...process.env, PORT, ...envExtra }` 启动子实例 —— 完整继承当前进程环境
|
||||||
|
- `.env:1` 为 `SOURCE_ENABLED=true` → 主实例以**音源模式**启动,`/url` 用例优先走 `sources/` 脚本链
|
||||||
|
- 子实例工作目录即项目根,`data/auth.json` 是**真实凭据文件**(无测试专用路径)
|
||||||
|
|
||||||
|
**影响**:① `/url` 用例并未验证 SDK 官方直连通道,SDK 侧回归可被外部音源掩盖(**测试假通过**);② `/login/refresh` 等用例会读写真实 `data/auth.json` —— 本地跑一次测试就可能覆盖真实登录态。
|
||||||
|
|
||||||
|
**修复**:测试显式传 `SOURCE_ENABLED: ''`;新增 `DATA_DIR` 环境变量让测试实例使用独立数据文件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-14 `pnpm-workspace.yaml` 使用非官方键 → isolated-vm 沙箱实际不可用
|
||||||
|
|
||||||
|
**证据**:
|
||||||
|
- `pnpm-workspace.yaml` 全文仅两行:`allowBuilds:` / `isolated-vm: true`
|
||||||
|
- `pnpm --version` = 12.4.1;放行构建脚本的字段应为 `onlyBuiltDependencies`,`allowBuilds` 不生效
|
||||||
|
- `node_modules/isolated-vm` 目录存在,但 `out/*.node` **不存在** → 原生模块从未构建
|
||||||
|
- 离线测试实测输出:`未安装 isolated-vm,无法启用隔离执行音源…切换下一候选脚本`
|
||||||
|
|
||||||
|
**影响**:`src/source-ivm.ts` 的「硬隔离沙箱」在部署时静默失效 —— 社区 `.js` 音源脚本全部被跳过,而 README 把它描述为可用的第一类音源。安全隔离能力名存实亡,且无显著告警。
|
||||||
|
|
||||||
|
**修复**:改为 `onlyBuiltDependencies: [isolated-vm]` 并确认构建产物就位;ivm 不可用时应在启动日志告警。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-15 冒烟测试断言游离在 `test()` 之外,抛错会跳过清理
|
||||||
|
|
||||||
|
**证据**:`test/smoke-test.ts:855-858` 的「6 个路径均须 401」断言写成裸 `for` 循环置于顶层,而对应的 `await test(...)` 在 `:859`。任一路径返回非 401 即顶层抛错。
|
||||||
|
|
||||||
|
**影响**:抛错直接终止脚本,**跳过结果汇总与末尾的实例清理** —— 与 P1-4 同源,都会留下孤儿 node 进程。
|
||||||
|
|
||||||
|
**修复**:把断言包进 `test()`,或统一用 `try/finally` 收尾。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-16 `.claude/settings.local.json` 已入库且含本机绝对路径
|
||||||
|
|
||||||
|
**证据**:`git ls-files .claude/` 返回 `.claude/settings.local.json`;内容含
|
||||||
|
`C:\Users\Administrator\.claude\projects\d--MusicServer\memory` 等本机路径。
|
||||||
|
|
||||||
|
**影响**:把本机目录结构与本地工具配置写进(README 声明为公开的)仓库,对他人无意义。
|
||||||
|
|
||||||
|
**修复**:`git rm --cached .claude/settings.local.json`,并把 `.claude/` 加入 `.gitignore`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、P2 可维护性与卫生
|
||||||
|
|
||||||
|
| 类别 | 证据 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 注释掉的死代码 | `sdk/wy/lyric.ts:49-51,240`、`sdk/kw/album.ts:242-246`、`sdk/kg/songList.ts:75-109,778`、`sdk/kw/index.ts:33-37` 等 100+ 处 | 大量整段注释代码(含被注释的 `console.log` 调试行),建议清理 |
|
||||||
|
| 裸 `console.log(err)` 吞错 | `sdk/wy/songList.ts:103`、`sdk/wy/leaderboard.ts:188`、`sdk/kw/lyric.ts:142`(还会打印完整歌词)、`sdk/kg/songList.ts:279`、`sdk/kg/util.ts:35`、`sdk/tx/utils/index.ts:39`、`sdk/utils.ts:23` | 吞掉异常且无上下文,排障困难 |
|
||||||
|
| 未使用符号 26 处 | `sdk/index.ts`(3)、`sdk/tx/leaderboard.ts`(2)、`sdk/lr/index.ts`(2)、`sdk/joox/parse.ts`(2)、`sdk/apis/migu.ts`(2) 等 | `tsc --noUnusedLocals` 可复现 |
|
||||||
|
| `.gitignore` 漏 `.workbuddy/` | `.gitignore` 内容 vs `git status` 显示 `?? .workbuddy/` | 工作记忆目录会被 `git add .` 收进仓库 |
|
||||||
|
| `tmp/` 占 297 MB / 4,992 文件 | 内含 1753 js / 720 ts / 339 py / 287 tsx / 122 vue | 已 gitignore,但混有大量与本项目无关的代码样本,建议清理磁盘 |
|
||||||
|
| 无 lint / 无 CI | `.eslintrc`/`.prettierrc`/`.github` 均不存在 | 建议引入 ESLint + 一条 CI 跑类型检查与离线测试 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、文档与代码不一致清单
|
||||||
|
|
||||||
|
| 文档位置 | 文档声称 | 代码实际 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `README.md:86`、`:105` | `POST /login/lr` 存在 | **不存在**(P0-1) |
|
||||||
|
| `README.md:20`(qs 行) | 歌单 ✅、专辑 ✅ | `sdk/qs/index.ts` 无 `album`/`playlist` 方法,`/sources` 能力位也无 |
|
||||||
|
| `README.md:20` vs `:66` | 内部自相矛盾 | `:20` 给 qs 专辑打勾,`:66` 的专辑详情平台列表却**不含 qs** |
|
||||||
|
| `README.md:71` | parse 支持 `5sing/apple/bili/joox/qian/migu` | 实际还支持 **kw / kg / tx / wy**(`sdk/{kw,kg,tx,wy}/parse.ts` 存在,`/sources` 已暴露 `parse` 位) |
|
||||||
|
| `README.md:68` | 歌单分类仅 `migu/qian/joox/apple` | 实际 **kw / kg / tx / wy** 也有 `playlistCategories` 能力位 |
|
||||||
|
| `README.md` 登录接口表 | 只列了 POST 登录 | 实际还有 7 个 `DELETE /login/{wy,kg,tx,qs,xm,me,bili}` 退出端点未文档化 |
|
||||||
|
| `test/smoke-test.ts:642` | `/parse/song/kw` 期望 404 | 实际 200(kw parse 已实现)→ 导致冒烟测试唯一的失败项 |
|
||||||
|
| `sdk/auth/store.ts:3` | 改完需 `pnpm build` | 无 build 脚本,项目无编译产物(P1-8) |
|
||||||
|
| `README.md:24` | bili 音质 `64k/128k/192k` | `/sources` 实际为 `64k/128k/320k/flac/flac24bit` |
|
||||||
|
| `README.md:65` | `/album/:source/:id?page=1`(query 分页) | 分页是 path 段 `/album/{source}/{id}/{page}` |
|
||||||
|
| `README.md:29-30` | qt / lr 封面标「—」 | `/sources` 显示两者 `pic=true`(实际支持) |
|
||||||
|
| `README.md:72` | 个人歌单支持 kg/tx/wy/joox/bili/apple | 实际仅 **tx / wy** |
|
||||||
|
| `README.md:21-26` | migu/qian/joox/apple/5sing 登录列标「cookie」 | 均**没有** `/login/{migu,qian,joox,apple,5sing}` 路由(与 P0-1 同类问题) |
|
||||||
|
| `docs/API.md` | 与 README 同源的平台/接口描述 | 同样滞后(parse 支持平台、user-playlist 支持集等) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、环境与仓库卫生
|
||||||
|
|
||||||
|
- **6 个 MusicServer 实例在监听**:`16688`(常驻)、`16690`~`16693`、`16790`(测试泄漏,见 P1-4)。建议清理泄漏实例,`16688` 请确认是否为常用常驻服务后再决定。
|
||||||
|
- **未追踪且会入库的目录**:`signer/`、`tools/`、`.workbuddy/`、`scripts/qs-local-login.mjs` 以及大量新增的 `sdk/**` 模块(5sing/apple/bili/joox/migu/qian/kw·kg·tx·wy 的 parse/playlist 等)。
|
||||||
|
- `signer/server.mjs` 本身实现规范(强制 `QS_SIGNER_TOKEN`、设备号脱敏、敏感头不落盘),入库无碍;
|
||||||
|
- `tools/mssdk-sign/` 含 `.exe` 与逆向笔记(`REVERSE.md`),**是否入库请自行判断**;
|
||||||
|
- `.workbuddy/` 建议加进 `.gitignore`。
|
||||||
|
- `.gitignore` 已正确覆盖:`node_modules/`、`sources/`、`data/`、`tmp/`、`*.log`、`soda-mssdk/`、`.env`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、建议修复顺序
|
||||||
|
|
||||||
|
1. **P0-1**(lr 登录)—— 一行导出 + 一个路由,成本极低,收益是解锁整个平台
|
||||||
|
2. **P0-2**(migu 崩溃)—— 改 3 行错误处理 + 加全局兜底,消除远程可触发的进程崩溃
|
||||||
|
3. **P1-4 / P1-5**(测试稳定性)—— 不修则后续每次 CI/回归都可能留下僵尸进程并产生假失败
|
||||||
|
4. **P1-8 / 第五节文档**(文档同步)—— 顺手即可,避免误导
|
||||||
|
5. **P0-3 / P1-7**(凭据存储)—— 视部署环境决定优先级
|
||||||
|
6. P1-6、P2 各项按需清理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、附:尚未逐条复核的线索
|
||||||
|
|
||||||
|
以下来自分区审查组的摘要,**本轮未逐条复核到行号**,建议作为下一轮排查入口。
|
||||||
|
|
||||||
|
**SDK 核心组**(`sdk/index.ts`、`sdk/types.ts`、`sdk/common/request.ts`、`sdk/auth/*`、`sdk/apis/*`):
|
||||||
|
- wy 首次登录时账号资料获取失败(cookie 时序问题)
|
||||||
|
- 请求取消语义失效;migu 候选链的多次探测请求不可取消
|
||||||
|
- qs 解密时把整段音频读入内存(未走流式)
|
||||||
|
- `Quality` 类型契约不一致:apple 实际返回 `preview`,但该值不在联合类型内
|
||||||
|
- 各平台 `cancelHttp` 实现不一致;部分平台把 token 拼进 GET query
|
||||||
|
|
||||||
|
**SDK 平台模块组**(本轮已核实 4 条,见 P1-9 ~ P1-12)其余线索:
|
||||||
|
- `sdk/migu/util.ts:375-378` 解密失败时把函数引用直接拼进异常消息
|
||||||
|
- `sdk/joox/` 单曲接口未校验上游 `errorCode`(错误探针外泄、响应未解析)
|
||||||
|
- `sdk/qian/util.ts:82` 附近三个导出函数名与 JSDoc 描述完全对调
|
||||||
|
- `sdk/bili/util.ts` 内约 6 处结构相同的 AJAX 重试代码
|
||||||
|
|
||||||
|
**跨文件重复逻辑**:kw / kg / tx / wy 的 `parse.ts` 与 `playlist.ts` 存在大段相似实现,可考虑抽出公共解析工具。
|
||||||
|
|
||||||
|
> 报告前三节的 P0 与 P1-4 ~ P1-12 均有明确代码位置与实际验证依据(其中 P0-2 附最小复现脚本);本节线索建议复核后再动手。第四组「测试与文档」专项审查仍在进行,其领域(测试有效性、覆盖缺口、文档一致性)本报告已通过独立核对覆盖主要结论。
|
||||||
@@ -4,9 +4,32 @@
|
|||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
|
|
||||||
- **多平台聚合**:酷我(kw)、酷狗(kg)、QQ音乐(tx)、网易云(wy)、汽水音乐(qs)+ 有声平台:喜马拉雅(xm)、猫耳FM(me)、蜻蜓FM(qt)、懒人听书(lr)(有声平台不参与聚合搜索)
|
- **多平台聚合**:酷我(kw)、酷狗(kg)、QQ音乐(tx)、网易云(wy)、汽水音乐(qs)、咪咕音乐(migu)、千千音乐(qian)、JOOX(joox)、哔哩哔哩(bili)、5sing;有声平台:喜马拉雅(xm)、猫耳FM(me)、蜻蜓FM(qt)、懒人听书(lr);Apple Music(apple,仅 preview 试听)
|
||||||
- **直连取流**:各平台播放地址获取(不依赖第三方 API 服务)
|
- **直连取流**:各平台播放地址获取(不依赖第三方 API 服务)
|
||||||
- **完整链路**:搜索、播放地址、歌词(含各平台加密歌词解码)、封面、榜单、热搜、歌单
|
- **完整链路**:搜索、播放地址、歌词(含各平台加密歌词解码)、封面、榜单、热搜、歌单、专辑、歌手、链接解析(歌单/专辑/歌曲网页链接)
|
||||||
|
- 有声平台(xm/me/qt/lr)与 Apple Music(仅试听)不参与聚合搜索与跨平台匹配,仅支持 source 直查
|
||||||
|
|
||||||
|
## 平台能力速查
|
||||||
|
|
||||||
|
| 平台 | id | 搜索 | 取流音质 | 歌词 | 封面 | 歌单 | 专辑 | 链接解析 | 登录 |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| 酷我音乐 | `kw` | ✅ | 128k/320k/flac/flac24bit | ✅ | ✅ | ✅ 列表/详情 | ✅ 搜索/详情 | ✅ 歌曲/专辑/歌单 | — |
|
||||||
|
| 酷狗音乐 | `kg` | ✅ | 128k/320k/flac/flac24bit | ✅ | ✅ | ✅ 列表/详情 | ✅ 搜索/详情 | ✅ 歌曲/专辑/歌单 | 扫码/短信/账密/token |
|
||||||
|
| QQ音乐 | `tx` | ✅ | 128k/320k/flac/flac24bit | ✅ | ✅ | ✅ 列表/详情 | ✅ 搜索/详情 | ✅ 歌曲/专辑/歌单 | 扫码/cookie |
|
||||||
|
| 网易云 | `wy` | ✅ | 128k/320k/flac/flac24bit | ✅ | ✅ | ✅ 列表/详情 | ✅ 搜索/详情 | ✅ 歌曲/专辑/歌单 | 扫码/手机号 |
|
||||||
|
| 汽水音乐 | `qs` | ✅ | 128k/320k(登录解锁无损) | ✅ | ✅ | — | — | — | 扫码 |
|
||||||
|
| 咪咕音乐 | `migu` | ✅ | 64k/128k/320k/flac/flac24bit | ✅ | ✅ | ✅ 搜索/详情/分类 | ✅ 搜索/详情 | ✅ 歌曲/专辑/歌单 | cookie(解锁高档位) |
|
||||||
|
| 千千音乐 | `qian` | ✅ | 64k/128k/320k/flac | ✅ | ✅ | ✅ 详情/分类 | ✅ 搜索/详情 | ✅ 歌曲/专辑/歌单 | cookie(VIP 曲目) |
|
||||||
|
| JOOX | `joox` | ✅ | 128k(VIP 为试听片段) | ✅ | ✅ | ✅ 搜索/详情/分类 | ✅ 搜索/详情 | ✅ 歌曲/专辑/歌单 | cookie(VIP 完整曲) |
|
||||||
|
| 哔哩哔哩 | `bili` | ✅ | 64k/128k/320k/flac/flac24bit(实际命中 B站档位 132k/192k;flac/Hi-Res 需大会员) | ✅ | ✅ | ✅ 搜索/详情(合集/多分P) | — | ✅ 歌曲/歌单 | 扫码 |
|
||||||
|
| 5sing | `5sing` | ✅ | sq/hq/lq | ✅ | — | ✅ 搜索/详情 | — | ✅ 歌曲/歌单 | cookie(可选) |
|
||||||
|
| Apple Music | `apple` | ✅ | preview 试听片段 | ✅(需登录) | ✅ | ✅ 搜索/详情/分类 | ✅ 搜索/详情 | ✅ 歌曲/专辑/歌单 | cookie(media-user-token) |
|
||||||
|
| 喜马拉雅 | `xm` | ✅ | 64k/128k | — | ✅ | — | ✅ 详情 | — | cookie |
|
||||||
|
| 猫耳FM | `me` | ✅ | 64k/128k | — | ✅ | — | ✅ 详情 | — | token |
|
||||||
|
| 蜻蜓FM | `qt` | ✅ | 64k/128k | — | ✅ | — | ✅ 详情 | — | — |
|
||||||
|
| 懒人听书 | `lr` | ✅ | 64k/128k | — | ✅ | — | ✅ 详情 | — | 账密 |
|
||||||
|
|
||||||
|
> 能力位以 `GET /sources` 返回为准(含 `albumSearch`/`playlist`/`parse` 等新增能力位)。
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
@@ -29,7 +52,7 @@ pnpm typecheck # 仅类型检查,不运行
|
|||||||
| `GET /search/:source/:keyword[/:page]` | 搜索歌曲:平台 id 或 all(聚合,排除 xm/me/qt/lr);第 1 页可省略页码段,下一页 `/search/kw/稻香/2`;?singer= 附加歌手、?limit= 每页条数 |
|
| `GET /search/:source/:keyword[/:page]` | 搜索歌曲:平台 id 或 all(聚合,排除 xm/me/qt/lr);第 1 页可省略页码段,下一页 `/search/kw/稻香/2`;?singer= 附加歌手、?limit= 每页条数 |
|
||||||
| `GET /find/:source/:keyword` | 精确匹配搜索(source 为平台 id 或 all;?singer= ?albumName= ?interval= 辅助匹配,all 时 ?excludeSource= 跳过平台) |
|
| `GET /find/:source/:keyword` | 精确匹配搜索(source 为平台 id 或 all;?singer= ?albumName= ?interval= 辅助匹配,all 时 ?excludeSource= 跳过平台) |
|
||||||
| `GET /url/:source/:id?quality=320k` | 获取播放地址(quality: 64k/128k/320k/flac/flac24bit)。取流链:外部音源(启用时)→ SDK 直连 → 酷我兜底(跨平台匹配,响应 `via` 字段标记实际命中通道) |
|
| `GET /url/:source/:id?quality=320k` | 获取播放地址(quality: 64k/128k/320k/flac/flac24bit)。取流链:外部音源(启用时)→ SDK 直连 → 酷我兜底(跨平台匹配,响应 `via` 字段标记实际命中通道) |
|
||||||
| `GET /stream/qs/:id` | 汽水音乐解密流代理(登录态取流地址,服务端解密回明文音频) |
|
| `GET /stream/:source/:id` | 加密流解密代理(source 用 path 段区分:`qs`=汽水解密流、`migu`=咪咕 Z3D/I3D 加密流)。取流响应返回相对地址时使用,服务端下载上游密流并在本地解密回明文音频 |
|
||||||
| `GET /lyric/:source/:id` | 获取歌词(返回 lyric/tlyric/yrc) |
|
| `GET /lyric/:source/:id` | 获取歌词(返回 lyric/tlyric/yrc) |
|
||||||
| `GET /pic/:source/:id` | 获取封面 |
|
| `GET /pic/:source/:id` | 获取封面 |
|
||||||
| `GET /boards/:source` | 榜单列表 |
|
| `GET /boards/:source` | 榜单列表 |
|
||||||
@@ -39,7 +62,14 @@ pnpm typecheck # 仅类型检查,不运行
|
|||||||
| `GET /singer/:source/:id` | 歌手信息(kg/tx/wy) |
|
| `GET /singer/:source/:id` | 歌手信息(kg/tx/wy) |
|
||||||
| `GET /singer/:source/:id/songs[/:page]?limit=30` | 歌手歌曲列表(kg/tx/wy) |
|
| `GET /singer/:source/:id/songs[/:page]?limit=30` | 歌手歌曲列表(kg/tx/wy) |
|
||||||
| `GET /singer/:source/:id/albums[/:page]?limit=10` | 歌手专辑列表(kg/tx/wy) |
|
| `GET /singer/:source/:id/albums[/:page]?limit=10` | 歌手专辑列表(kg/tx/wy) |
|
||||||
| `GET /album/:source/:id?page=1` | 专辑详情与曲目(kg/kw/tx/xm) |
|
| `GET /album/:source/:id[/:page]` | 专辑详情与曲目(分页为 path 段,第 1 页省略)。支持 kg/kw/tx/wy/xm/me/qt/lr/migu/qian/joox/apple |
|
||||||
|
| `GET /album/:source/search/:keyword` | 专辑搜索(kg/kw/tx/wy/migu/qian/joox/apple) |
|
||||||
|
| `GET /playlist/:source/search/:keyword` | 歌单搜索(kg/kw/tx/wy/migu/qian/joox/bili/5sing/apple) |
|
||||||
|
| `GET /playlist/:source/categories` | 歌单分类(kw/kg/tx/wy/migu/qian/joox/bili/5sing/apple) |
|
||||||
|
| `GET /playlist/:source/categories/:categoryId` | 分类下的歌单(同上) |
|
||||||
|
| `GET /playlist/:source/:id[/:page]?limit=` | 歌单详情与曲目(kw/kg/tx/wy/migu/qian/joox/bili/5sing/apple;bili 支持合集/多分P) |
|
||||||
|
| `POST /parse/song/:source` 等 | 链接解析:body `{ "link": "..." }`,三个端点 `parse/song` / `parse/album` / `parse/playlist`。支持 kw/kg/tx/wy/5sing/apple/bili/joox/qian/migu(其中 bili、5sing 无 album 端点) |
|
||||||
|
| `GET /user-playlist/:source` | 个人歌单(仅 tx/wy 有完整实现;需登录,未登录或平台不支持返回 404/502) |
|
||||||
| `POST /login/wy/qr` → `POST /login/wy/qr/check` | 网易云扫码登录(扫码后轮询完成登录) |
|
| `POST /login/wy/qr` → `POST /login/wy/qr/check` | 网易云扫码登录(扫码后轮询完成登录) |
|
||||||
| `POST /login/wy/cellphone` | 网易云手机号登录(password 或 captcha) |
|
| `POST /login/wy/cellphone` | 网易云手机号登录(password 或 captcha) |
|
||||||
| `POST /login/kg/qr` → `POST /login/kg/qr/check` | 酷狗扫码登录(扫码后轮询完成登录) |
|
| `POST /login/kg/qr` → `POST /login/kg/qr/check` | 酷狗扫码登录(扫码后轮询完成登录) |
|
||||||
@@ -49,10 +79,12 @@ pnpm typecheck # 仅类型检查,不运行
|
|||||||
| `POST /login/kg` + `userid` | 绑定酷狗 token(App 抓包获取,服务端验证) |
|
| `POST /login/kg` + `userid` | 绑定酷狗 token(App 抓包获取,服务端验证) |
|
||||||
| `POST /login/tx/qr` → `POST /login/tx/qr/check` | QQ 音乐扫码登录(QQ/微信扫一扫,扫码后轮询完成登录) |
|
| `POST /login/tx/qr` → `POST /login/tx/qr/check` | QQ 音乐扫码登录(QQ/微信扫一扫,扫码后轮询完成登录) |
|
||||||
| `POST /login/tx` | 绑定 QQ 音乐 cookie(y.qq.com 复制,可选 App 抓包字段供自动续期) |
|
| `POST /login/tx` | 绑定 QQ 音乐 cookie(y.qq.com 复制,可选 App 抓包字段供自动续期) |
|
||||||
| `POST /login/qs/qr` → `POST /login/qs/qr/check` | 汽水音乐扫码登录(**抖音 App** 扫码,登录后解锁完整曲目与无损取流) |
|
| `POST /login/qs/qr` → `POST /login/qs/qr/check` | 汽水音乐扫码登录(用**汽水音乐 App** 扫一扫——同官方 PC 客户端文案;抖音 App 扫可能不完成确认。登录后解锁完整曲目与无损取流) |
|
||||||
| `POST /login/xm` | 绑定喜马拉雅 cookie(www.ximalaya.com 复制,须含 `1&_token`,解锁付费/已购内容取流) |
|
| `POST /login/xm` | 绑定喜马拉雅 cookie(www.ximalaya.com 复制,须含 `1&_token`,解锁付费/已购内容取流) |
|
||||||
| `POST /login/me` | 绑定猫耳FM token(App 抓包或 m.missevan.com 复制,须含 `token=`,解锁付费声音取流) |
|
| `POST /login/me` | 绑定猫耳FM token(App 抓包或 m.missevan.com 复制,须含 `token=`,解锁付费声音取流) |
|
||||||
| `POST /login/lr` | 懒人听书账号登录(account+password,懒人取流要求登录态,免费章节同样) |
|
| `POST /login/lr` | 懒人听书账号登录(account+password,懒人取流要求登录态,免费章节同样) |
|
||||||
|
| `POST /login/bili/qr` → `POST /login/bili/qr/check` | 哔哩哔哩扫码登录(B站 App 扫一扫,登录后解锁大会员音质) |
|
||||||
|
| `DELETE /login/{wy,kg,tx,qs,xm,me,lr,bili}` | 清除对应平台凭据(退出登录) |
|
||||||
| `GET /login/status` | 登录状态(登录后取流/请求自动携带凭据) |
|
| `GET /login/status` | 登录状态(登录后取流/请求自动携带凭据) |
|
||||||
| `GET /sources` | 平台列表(音质与能力) |
|
| `GET /sources` | 平台列表(音质与能力) |
|
||||||
| `GET /health` | 健康检查 |
|
| `GET /health` | 健康检查 |
|
||||||
@@ -71,6 +103,12 @@ pnpm typecheck # 仅类型检查,不运行
|
|||||||
- **me**:声音 id(搜索结果的 `songmid` 字段);走 App API 签名通道取免签名明文直链(m4a),匿名可取免费声音,付费内容 `POST /login/me` 绑定 token 后取流
|
- **me**:声音 id(搜索结果的 `songmid` 字段);走 App API 签名通道取免签名明文直链(m4a),匿名可取免费声音,付费内容 `POST /login/me` 绑定 token 后取流
|
||||||
- **qt**:`<channelId>:<programId>` 复合 id(/album/qt 节目列表产出);audiostream 签名直链(302 到华为云 CDN),匿名可取免费节目
|
- **qt**:`<channelId>:<programId>` 复合 id(/album/qt 节目列表产出);audiostream 签名直链(302 到华为云 CDN),匿名可取免费节目
|
||||||
- **lr**:`<bookId>:<section>` 复合 id(/album/lr 章节列表产出);取流必须 `POST /login/lr` 登录(免费章节同样要求登录态)
|
- **lr**:`<bookId>:<section>` 复合 id(/album/lr 章节列表产出);取流必须 `POST /login/lr` 登录(免费章节同样要求登录态)
|
||||||
|
- **migu**:`<contentId>|<resourceType>|<formatType>` 三元组(搜索结果/专辑/歌单产出);取流走 listen-url 多档候选链 + 逐档 Range 探测,命中可访问的最高档(匿名实测可出 flac24bit 真无损),Z3D/I3D 加密流自动改走 `/stream/migu` 解密代理
|
||||||
|
- **qian**:track id(T 开头,搜索结果的 `songmid`);免费曲目匿名可出 128k/320k/flac 明文直链,VIP/版权曲目需 cookie
|
||||||
|
- **joox**:加密后的 track id(含 `=`/`+`,URL 编码后传);游客 128k,VIP 完整曲需 cookie(`wmid`+`session_key`)
|
||||||
|
- **bili**:`<bvid>|<cid>`(搜索结果/合集曲目产出);合集/歌单 id 形如 `season:<id>:<mid>:<bvid>` 或 `bvid:<bvid>`
|
||||||
|
- **5sing**:`<songId>|<songType>`(如 `932557|yc`,songType 为 yc 原创 / fc 翻唱 / bz 伴奏),URL 路径需编码 `|` 为 `%7C`
|
||||||
|
- **apple**:数字歌曲 id(搜索结果产出);取流固定为 `preview` 试听片段(完整曲为 DRM,不在支持范围)
|
||||||
|
|
||||||
`/url` 在外部音源与平台直连都失败时,自动降级到**酷我**兜底:按歌名/歌手/时长匹配同一首歌后走 kw 通道(匿名可拿无损)。kg 可由 hash 反查歌曲信息;其它平台需要兜底时给 `/url` 传 `name`/`singer`/`interval` 查询参数。
|
`/url` 在外部音源与平台直连都失败时,自动降级到**酷我**兜底:按歌名/歌手/时长匹配同一首歌后走 kw 通道(匿名可拿无损)。kg 可由 hash 反查歌曲信息;其它平台需要兜底时给 `/url` 传 `name`/`singer`/`interval` 查询参数。
|
||||||
|
|
||||||
@@ -120,24 +158,32 @@ export async function getMusicUrl({ source, id, songInfo, quality, signal }) {
|
|||||||
src/ HTTP 应用层(Fastify 服务器,全部 TypeScript 源码)
|
src/ HTTP 应用层(Fastify 服务器,全部 TypeScript 源码)
|
||||||
server.ts HTTP 服务器入口(pnpm start 实际启动文件)
|
server.ts HTTP 服务器入口(pnpm start 实际启动文件)
|
||||||
routes.ts 业务路由(JSON Schema 校验 + OpenAPI 元数据)
|
routes.ts 业务路由(JSON Schema 校验 + OpenAPI 元数据)
|
||||||
routes-login.ts 登录路由(wy 扫码/手机号、kg 扫码/短信/账密+风控验证/token、tx 扫码/cookie、qs 抖音扫码、xm/me cookie 绑定)
|
routes-login.ts 登录路由(wy 扫码/手机号、kg 扫码/短信/账密+风控验证/token、tx 扫码/cookie、qs 汽水扫码、
|
||||||
|
bili 扫码、xm/me cookie 绑定、lr 账密)
|
||||||
source.ts 外部音源候选链(SOURCE_ENABLED 时 /url 取流优先走 sources/ 脚本,失败回落 SDK)
|
source.ts 外部音源候选链(SOURCE_ENABLED 时 /url 取流优先走 sources/ 脚本,失败回落 SDK)
|
||||||
source-ivm.ts 社区音源脚本加载器(isolated-vm 硬隔离沙箱)
|
source-ivm.ts 社区音源脚本加载器(isolated-vm 硬隔离沙箱)
|
||||||
source-simple.ts 简单 API 音源脚本加载器(.mjs 进程内直载)
|
source-simple.ts 简单 API 音源脚本加载器(.mjs 进程内直载)
|
||||||
http/
|
http/
|
||||||
api-error.ts 业务错误类型(404/502 等 → 统一 JSON 响应)
|
api-error.ts 业务错误类型(404/502 等 → 统一 JSON 响应)
|
||||||
|
auth-gate.ts 调用秘钥鉴权(AUTH_KEY,全路径防扫描;/stream/* 可改用短时签名)
|
||||||
|
rate-limit.ts per-IP 滑动窗口限流(RATE_LIMIT,默认 120/分钟)
|
||||||
|
stream-sign.ts /stream/* 流地址短时签名(HMAC 派生,10 分钟有效)
|
||||||
|
migu-stream.ts 咪咕加密流票据登记与逐块解密管道
|
||||||
openapi/
|
openapi/
|
||||||
components.ts OpenAPI 共享 schema(与路由校验同源)
|
components.ts OpenAPI 共享 schema(与路由校验同源)
|
||||||
ui.ts ReDoc 页面模板
|
ui.ts ReDoc 页面模板
|
||||||
sdk/ 音乐聚合 SDK(仅官方平台:平台模块 + 官方直连取流 + 凭据 + 基础设施;kw/kg/tx/wy/qs 五平台)
|
sdk/ 音乐聚合 SDK(仅官方平台:平台模块 + 官方直连取流 + 凭据 + 基础设施;15 个平台)
|
||||||
index.ts 平台聚合入口(通用工具转发,实现在 common/utils.ts)
|
index.ts 平台聚合入口(通用工具转发,实现在 common/utils.ts)
|
||||||
apis/ 各平台官方 URL 直连取流实现(kw/kg/tx/wy/qs)
|
apis/ 各平台官方 URL 直连取流实现(15 平台)
|
||||||
auth/ 登录态与登录协议(store.ts 持久化凭据到 data/auth.json;wy.ts 扫码/手机号、kg.ts 扫码/短信/token/续期、tx.ts 扫码/cookie/续期)
|
auth/ 登录态与登录协议(store.ts 持久化凭据到 data/auth.json;wy.ts 扫码/手机号、kg.ts 扫码/短信/token/续期、tx.ts 扫码/cookie/续期)
|
||||||
common/ SDK 基础设施
|
common/ SDK 基础设施
|
||||||
request.ts HTTP 请求(可取消、自动 JSON 解析、gzip/deflate 解压)
|
request.ts HTTP 请求(可取消、自动 JSON 解析、gzip/deflate 解压)
|
||||||
utils.ts 工具函数(sizeFormate/formatPlayTime/dateFormat 等)
|
utils.ts 工具函数(sizeFormate/formatPlayTime/dateFormat 等)
|
||||||
message.ts 请求错误消息
|
message.ts 请求错误消息
|
||||||
kg/ kw/ tx/ wy/ qs/ 平台模块(搜索/歌词/榜单/歌单等;qs 含字节系参数签名与音频解密 crypto/)
|
kg/ kw/ tx/ wy/ qs/ migu/ qian/ joox/ bili/ 5sing/ apple/ xm/ me/ qt/ lr/
|
||||||
|
平台模块(搜索/歌词/榜单/歌单/专辑/链接解析等;qs 含字节系参数签名与音频解密 crypto/,
|
||||||
|
migu 含 Z3D 流解密,bili 含扫码登录)
|
||||||
|
apis/ kw.ts kg.ts tx.ts wy.ts qs.ts migu.ts qian.ts joox.ts bili.ts 5sing.ts apple.ts …
|
||||||
test/
|
test/
|
||||||
smoke-test.ts 冒烟测试(自动启动/关闭本地服务器,验证各平台接口)
|
smoke-test.ts 冒烟测试(自动启动/关闭本地服务器,验证各平台接口)
|
||||||
source-simple-test.ts 简单 API 音源脚本加载器测试(离线)
|
source-simple-test.ts 简单 API 音源脚本加载器测试(离线)
|
||||||
@@ -149,5 +195,10 @@ test/
|
|||||||
|
|
||||||
- **kg 取流**:匿名仅能获取部分免费资源,VIP/付费歌需 `/login/kg` 登录(扫码/短信/token 均可)后取流,成功响应解析见 `sdk/apis/kg.ts` 头注释。
|
- **kg 取流**:匿名仅能获取部分免费资源,VIP/付费歌需 `/login/kg` 登录(扫码/短信/token 均可)后取流,成功响应解析见 `sdk/apis/kg.ts` 头注释。
|
||||||
- **kw 取流**:主通道 mobi.s 严格校验「返回 bitrate == 请求档位且为明文格式」,档位缺失时沿降级链换档(flac24bit→flac→320k→128k);web playUrl 回退通道匿名恒 128k。
|
- **kw 取流**:主通道 mobi.s 严格校验「返回 bitrate == 请求档位且为明文格式」,档位缺失时沿降级链换档(flac24bit→flac→320k→128k);web playUrl 回退通道匿名恒 128k。
|
||||||
- **qs 取流**:匿名仅 30s 试听;完整曲目需 `/login/qs/qr` 抖音扫码登录,取流走 `/stream/qs/:id` 解密流代理(一次性下载解密回明文,不支持 Range)。
|
- **qs 取流**:匿名仅 30s 试听;完整曲目需 `/login/qs/qr` 用**汽水音乐 App** 扫码登录,取流走 `/stream/qs/:id` 解密流代理(一次性下载解密回明文,不支持 Range)。
|
||||||
|
- **migu 取流**:匿名 PQ(128k) 永远可用,但候选链会优先探测更高档位——多数曲目实际能匿名拿到 SQ/ZQ 真无损直链(上游同款 tone 路径重写 + 逐档探测);Z3D/I3D 加密候选经 `/stream/migu` 解密后同样可播。HQ/SQ 返回「登录后免费试听」时自动降级到可用的最高档。
|
||||||
|
- **apple 取流**:只有 preview 试听片段(约 30–90s,AAC/m4a),返回的 `type` 固定为 `preview`,不要当作完整曲。
|
||||||
|
- **bili 取流**:匿名最高 192k;flac/Hi-Res 需 `/login/bili/qr` 登录大会员账号。CDN 有约 1 小时的 `deadline` 签名,取流结果不宜缓存过久。
|
||||||
|
- **5sing 歌单详情**:依赖 5sing 网页(`5sing.kugou.com/.../dj/...html`)解析曲目;2026-10-02 实测该站点网页层整体 502(搜索/取流/歌词等手机端 API 不受影响)。上游同样依赖该页面,待站点恢复即自动可用。
|
||||||
|
- **link 解析**:`/parse/*` 走各平台网页链接解析(需上游有对应实现);未实现的平台返回 404。
|
||||||
- 平台接口随时可能变更,遇失效时可对照 `sdk/apis/` 内的接口文档调整。
|
- 平台接口随时可能变更,遇失效时可对照 `sdk/apis/` 内的接口文档调整。
|
||||||
+93
-57
@@ -1,26 +1,47 @@
|
|||||||
# MusicServer 聚合音乐 API
|
# 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`(可直接调试)
|
> - **Swagger UI**:`/docs`(可直接调试)
|
||||||
> - **ReDoc**:`http://localhost:16688/redoc`
|
> - **ReDoc**:`/redoc`
|
||||||
> - **OpenAPI 3.1**:`http://localhost:16688/openapi.json`(可生成各语言客户端)
|
> - **OpenAPI 3.1**:`/openapi.json`(可生成各语言客户端)
|
||||||
|
> - **平台能力位**:`GET /sources` —— 各接口具体支持哪些平台,一律以它的能力位为准,本文不再硬列
|
||||||
|
|
||||||
## 约定
|
## 约定
|
||||||
|
|
||||||
- 默认端口 **16688**(可用 `PORT` 环境变量修改)。
|
- 默认端口 **16688**(`PORT` 环境变量可改)。
|
||||||
- 仅支持 `GET` 方法;服务器支持 CORS(`Access-Control-Allow-Origin: *`)。
|
- **全部接口为路径参数风格**,分页走 path 段且第 1 页可省略:`/search/kw/稻香`、`/search/kw/稻香/2`、
|
||||||
|
`/album/kg/123/2`。查询参数只用于修饰(`?quality=`、`?limit=`、`?singer=` 等)。
|
||||||
|
- 默认返回 `Access-Control-Allow-Origin: *`,可直接在浏览器/前端调用。
|
||||||
- **成功**:`200` + `{"code": 0, "data": ...}`
|
- **成功**:`200` + `{"code": 0, "data": ...}`
|
||||||
- **失败**:对应 HTTP 状态码 + `{"code": <状态码>, "msg": "...", "detail": ...}`
|
- **失败**:对应 HTTP 状态码 + `{"code": <状态码>, "msg": "...", "detail": ...}`
|
||||||
- `422` 参数校验失败(缺失、非法音质、页码越界等,FastAPI 风格)
|
|
||||||
- `404` 接口不存在 / 平台不支持该能力
|
| 状态码 | 含义 |
|
||||||
- `405` 方法不允许(响应带 `Allow` 头)
|
| --- | --- |
|
||||||
- `502` 上游平台请求失败
|
| `422` | 参数校验失败(Fastify/Ajv,FastAPI 风格) |
|
||||||
- `500` 服务器内部错误
|
| `404` | 接口不存在,或平台不支持该能力 |
|
||||||
- 资源类接口统一为**路径参数风格**;`/search`、`/find` 搜索类接口使用 query 参数。
|
| `405` | 方法不允许(响应带 `Allow` 头) |
|
||||||
- 平台歌曲 id 形态:kw/tx/wy 用 `songmid`,kg 用 `hash`。
|
| `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 | `/health` | 健康检查(status / uptime / 平台数) |
|
||||||
| GET | `/sources` | 支持的平台及其音质、能力(search/url/lyric/pic/boards/hotSearch/songList/singer/album) |
|
| GET | `/sources` | 平台列表:音质与全部能力位(**判断某接口支持哪些平台的唯一依据**) |
|
||||||
| GET | `/` | 302 重定向到 `/docs` |
|
| GET | `/` | 302 重定向到 `/docs` |
|
||||||
| GET | `/docs` | Swagger UI 文档页 |
|
| GET | `/docs` · `/redoc` · `/openapi.json` | Swagger UI / ReDoc / OpenAPI 3.1 规范 |
|
||||||
| GET | `/redoc` | ReDoc 阅读版文档 |
|
|
||||||
| GET | `/openapi.json` | OpenAPI 3.1 规范文档 |
|
|
||||||
|
|
||||||
### 搜索
|
### 搜索
|
||||||
|
|
||||||
| 方法 | 路径 | 说明 |
|
| 方法 | 路径 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| GET | `/search?name=稻香&singer=周杰伦&source=kw&page=1&limit=20` | 搜索歌曲;`source` 省略时聚合全部平台,失败的平台跳过 |
|
| GET | `/search/:source/:keyword[/:page]` | 搜索歌曲;`source` 为平台 id 或 `all`(聚合,自带 `noAggregate` 的平台不参与)。`?singer=` 附加歌手、`?limit=` 每页条数 |
|
||||||
| GET | `/find?name=稻香&singer=周杰伦&albumName=魔杰座&interval=03:43&excludeSource=kw` | 跨平台精确匹配,按歌手/歌名/专辑/时长综合排序 |
|
| GET | `/find/:source/:keyword` | 精确匹配搜索;`?singer=` `?albumName=` `?interval=` 辅助匹配,`source=all` 时 `?excludeSource=` 跳过平台 |
|
||||||
|
|
||||||
### 歌曲资源
|
### 歌曲资源
|
||||||
|
|
||||||
| 方法 | 路径 | 说明 |
|
| 方法 | 路径 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| GET | `/url/:source/:id` | 播放地址;query 可带 `quality`(128k/320k/flac/flac24bit,默认 128k)与 `albumId`(kg 需要) |
|
| GET | `/url/:source/:id` | 播放地址;`?quality=`(64k/128k/320k/flac/flac24bit,默认 128k)。取流链:外部音源(启用时)→ SDK 直连 → 酷我兜底(响应 `via` 标记实际命中通道) |
|
||||||
| GET | `/lyric/:source/:id` | 歌词;可带 `name`/`interval`(部分平台搜索歌词时需要) |
|
| GET | `/stream/:source/:id` | 加密流解密代理,`source` 用 path 段区分(`qs` 汽水解密流 / `migu` 咪咕 Z3D 加密流)。供 `/url` 返回的相对地址使用,不接受自行构造 |
|
||||||
| GET | `/pic/:source/:id` | 封面;可带 `albumId`(kg 需要) |
|
| GET | `/lyric/:source/:id` | 歌词;可带 `?name=` `?interval=`(部分平台搜索歌词时需要) |
|
||||||
|
| GET | `/pic/:source/:id` | 封面;可带 `?albumId=`(kg 需要) |
|
||||||
|
|
||||||
### 榜单
|
### 榜单 / 歌单 / 热搜
|
||||||
|
|
||||||
| 方法 | 路径 | 说明 |
|
| 方法 | 路径 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| GET | `/boards/:source` | 榜单列表 |
|
| 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 | `/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` | 歌手信息(name/desc/avatar 与歌曲/专辑数量) |
|
||||||
| GET | `/singer/:source/:id/songs` | 歌手歌曲列表;可带 `page`/`limit`(默认 20)。支持 kg/tx/wy |
|
| GET | `/singer/:source/:id/songs[/:page]` | 歌手歌曲列表;`?limit=` |
|
||||||
| GET | `/singer/:source/:id/albums` | 歌手专辑列表;可带 `page`/`limit`。支持 kg/tx/wy |
|
| GET | `/singer/:source/:id/albums[/:page]` | 歌手专辑列表;`?limit=` |
|
||||||
| GET | `/album/:source/:id` | 专辑详情与曲目列表;可带 `page`/`limit`(limit 仅 kg 生效,kw 单页固定 1000 条)。支持 kg/kw/tx;tx 的专辑名/作者由曲目数据合成(desc 为空) |
|
| 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` → `/login/wy/qr/check` | 网易云扫码(`check` body 传 `{"key": "..."}`) |
|
||||||
| POST | `/login/wy/qr/check` | 轮询扫码状态,body 传 `{ "key": "..." }`。返回 `code`:800 过期 / 801 等待 / 802 已扫码 / 803 登录成功(自动保存凭据) |
|
| POST | `/login/wy/cellphone` | 网易云手机号登录(`password` 明文或 `captcha`) |
|
||||||
| POST | `/login/wy/cellphone` | 网易云手机号登录;body 传 `phone` + `password`(明文,服务端 MD5 后提交)或 `captcha`,可带 `countrycode`(默认 86) |
|
| DELETE | `/login/wy` | 退出网易云 |
|
||||||
| DELETE | `/login/wy` | 退出网易云登录 |
|
| POST | `/login/kg/qr` → `/login/kg/qr/check` | 酷狗扫码 |
|
||||||
| POST | `/login/kg` | 绑定酷狗 token;body 传 `{ "token": "...", "userid": ... }`(App 抓包获取),服务端向用户中心验证后存储 |
|
| POST | `/login/kg/captcha` → `/login/kg/cellphone` | 酷狗短信验证码登录 |
|
||||||
| DELETE | `/login/kg` | 退出酷狗登录 |
|
| POST | `/login/kg/password` | 酷狗账密登录(命中风控返回挑战,配合 `/login/kg/verify` 重试) |
|
||||||
| POST | `/login/tx` | 绑定 QQ 音乐 cookie;body 传 `{ "cookie": "..." }`(需含 `uin` 与 `qm_keyst` 字段)。可选附 App 抓包的 `openId`/`accessToken`/`refreshToken`/`refreshKey` 供自动续期 |
|
| POST | `/login/kg` | 绑定酷狗 token(App 抓包) |
|
||||||
| DELETE | `/login/tx` | 退出 QQ 音乐登录 |
|
| DELETE | `/login/kg` | 退出酷狗 |
|
||||||
| POST | `/login/refresh` | 手动触发 kg/tx 登录态续期(与自动任务同一逻辑;未登录平台跳过,单平台失败不影响其他) |
|
| POST | `/login/tx/qr` → `/login/tx/qr/check` | QQ 音乐扫码 |
|
||||||
| GET | `/login/status` | 登录状态(wy 实时校验 / kg、tx 已存凭据,含 `vipType` 与最近更新时间) |
|
| 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`(不入库),登录后取流与平台请求自动携带凭据。
|
- 凭据持久化于 `data/auth.json`(不入库,文件权限 0600),登录后取流与平台请求自动携带。
|
||||||
- **登录态自动续期**:服务器启动 10s 后自动对 kg/tx 执行一次续期,此后每 24h 一次(kg 走 `login_by_token` AES/RSA 协议换新 token;tx 走 `music.login.LoginServer`,仅浏览器 cookie 时可能被网关拒绝,需在 `/login/tx` 附 App 抓包的完整凭据才可靠),结果输出到控制台日志。
|
- 自动续期:启动 10s 后对 kg/tx 执行一次,此后每 24h 一次(音源模式下不参与取流,自动续期不启动)。
|
||||||
- VIP 歌曲完整播放地址暂不在支持范围;登录态目前用于提升请求稳定性与为后续能力预留。
|
|
||||||
|
|
||||||
## 响应示例
|
## 响应示例
|
||||||
|
|
||||||
@@ -103,15 +142,12 @@
|
|||||||
// GET /lyric/kw/228908
|
// GET /lyric/kw/228908
|
||||||
{ "code": 0, "data": { "lyric": "[00:00.00]...", "tlyric": "", "yrc": "" } }
|
{ "code": 0, "data": { "lyric": "[00:00.00]...", "tlyric": "", "yrc": "" } }
|
||||||
|
|
||||||
// 参数缺失
|
// 参数非法 → 422
|
||||||
// 422
|
{ "code": 422, "msg": "params/source must be equal to one of the allowed values", "detail": [...] }
|
||||||
{ "code": 422, "msg": "querystring must have required property 'name'", "detail": [...] }
|
|
||||||
|
|
||||||
// 平台不支持该能力
|
// 平台不支持该能力 → 404
|
||||||
// 404
|
|
||||||
{ "code": 404, "msg": "不支持的平台: xx" }
|
{ "code": 404, "msg": "不支持的平台: xx" }
|
||||||
|
|
||||||
// 方法不允许
|
// 方法不允许 → 405 + Allow 头
|
||||||
// 405 + Allow: GET
|
{ "code": 405, "msg": "方法 POST 不允许,请使用 GET, HEAD" }
|
||||||
{ "code": 405, "msg": "方法 POST 不允许,请使用 GET" }
|
|
||||||
```
|
```
|
||||||
Reference in new issue
Block a user