Solara 2.1.6 API 使用详解

Solara 2.1.6 对外的 HTTP 接口全解:Cookie 认证原理与 48 小时有效期、/api/storage 键值读写与字符串化陷阱、/proxy 四种 types(搜索/歌词/封面/直链)与 5 分钟缓存及 Range 音频流转发、/palette 取色接口与 1 小时缓存,附 CORS、状态码速查与代理返回登录页等真实排障实录。

SolaraSolara音乐流媒体自托管2026/10/25

Solara 2.1.6 API 使用详解

目标:把 Solara 2.1.6 对外暴露的 HTTP 接口逐条讲清——认证怎么换 Cookie、键值存储怎么读写、音乐代理的四种 types 怎么调、封面调色板返回什么、缓存与 CORS 的边界在哪。文中每条请求与响应均为 2026-10-25 在 Ubuntu 22.04 + Docker Engine 29.9.2 环境下的真实回显。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • Docker Engine:29.9.2,Docker Compose 插件 v5.6.1
  • Solara 版本:2.1.6(官方最新稳定 tag,版本查询日期 2026-10-25)
  • 服务地址:http://127.0.0.1:8136(宿主端口 → Express 前台 8787)
  • 客户端工具:curl 8.5.0、Python 3.12(仅用于格式化 JSON 与解码 Cookie)
  • 演示访问口令:q1w2e3r4
  • 上游聚合 API:https://music-api.gdstudio.xyz/api.php(GD音乐台,项目默认源)
  • 认证形态:单一 Cookie,无 Token、无 OAuth、无 API Key
  • 部署基线见 《Solara 2.1.6 Docker Compose 部署指南》

二、前置条件

  1. 服务已按部署篇拉起,GET /login 可返回登录页;
  2. 已拿到访问口令(本例 q1w2e3r4);
  3. 客户端能保存 Cookie(curl 用 -c 写、-b 读,或直接 -b 'auth=<base64>');
  4. 若从其他主机调用,把下文所有 127.0.0.1:8136 换成实际地址与端口。

先确认服务在监听:

curl -s -o /dev/null -w "GET /login -> %{http_code} bytes=%{size_download}\n" http://127.0.0.1:8136/login

实测输出:

GET /login -> 200 bytes=31502

三、API 总览

Solara 的接口面很窄,一共 5 组路由,全部由 Express 前台直接处理(音乐数据再经内部 Wrangler 转发上游):

路径方法用途需要认证
/loginGET返回登录页 HTML否
/api/loginPOST用口令换 auth Cookie否
/api/storageGET / POST / DELETE键值存储(收藏、播放状态漫游)是
/proxyGET / OPTIONS音乐聚合 API 代理 + 音频流转发是
/paletteGET / OPTIONS封面主色/渐变分析是
/GET播放器主页 HTML是
其余静态路径GETcss/js/图片等静态文件按扩展名放行

约定要点:

  • 协议:HTTP/1.1,无版本前缀(不是 /api/v1/...),接口随 Solara 版本变化,升级前应回归本文清单。
  • 数据格式:除音频流与静态文件外均为 application/json;/proxy 的上游返回可能是 JSON 数组或对象,也可能被上游直接透传为纯文本错误。
  • 限流:服务端没有内置限流或配额;压力全部传导给上游免费 API,实际可用性受上游波动影响。
  • 缓存:/proxy 5 分钟、/palette 1 小时,均可在响应头 X-Cache-Status 观察 HIT/MISS。
  • CORS:/proxy 与 /palette 返回 Access-Control-Allow-Origin: *,并 Expose 了 X-Cache-Status;/api/storage 允许 GET,POST,DELETE,OPTIONS。

💡 技术拓展:Solara 同时支持 Cloudflare Pages 与 Docker 两种形态,两套实现的路由路径完全一致(Pages Functions 的目录结构映射成同名 URL)。这意味着本文的 curl 示例在 Cloudflare 部署上同样适用,差异只在存储后端(D1 云数据库 vs 本地 SQLite)与代理转发方式。迁移时接口层无需改代码,改的是环境变量与数据落点。

四、认证与首次调用

4.1 未认证时的行为

未携带有效 Cookie 时,页面请求与接口请求的响应不同:

curl -s -o /dev/null -w "GET / -> %{http_code} redirect=%{redirect_url}\n" http://127.0.0.1:8136/
curl -s -w "\nHTTP %{http_code}\n" http://127.0.0.1:8136/api/storage
curl -s -w "\nHTTP %{http_code}\n" "http://127.0.0.1:8136/palette?image=https://example.com/a.jpg"

实测输出:

GET / -> 302 redirect=http://127.0.0.1:8136/login
{"error":"Unauthorized","message":"Login required"}
HTTP 401
{"error":"Unauthorized","message":"Login required"}
HTTP 401

规则很明确:页面 302 跳登录页,接口 401 返回 JSON。后者是 2.1.6 的行为,避免前端把登录页 HTML 当 JSON 解析而崩溃。

curl -s -D - -o /dev/null -X POST http://127.0.0.1:8136/api/login \
  -H 'Content-Type: application/json' \
  -d '{"password":"q1w2e3r4"}'

实测输出:

HTTP/1.1 200 OK
X-Powered-By: Express
Set-Cookie: auth=cTF3MmUzcjQ=; Max-Age=172800; Path=/; SameSite=Lax; HttpOnly
Content-Type: application/json; charset=utf-8
Content-Length: 16
ETag: W/"10-oV4hJxRVSENxc/wX8+mA4/Pe4tA"
Date: Sun, 25 Oct 2026 04:18:03 GMT
Connection: keep-alive
Keep-Alive: timeout=5

Cookie 值是口令的 Base64:

python3 -c "import base64;print(base64.b64decode('cTF3MmUzcjQ=').decode())"

实测输出:

q1w2e3r4

要点:

  • 有效期 Max-Age=172800,即 48 小时;到期需重新登录。
  • HttpOnly + SameSite=Lax,浏览器脚本拿不到,降低 XSS 盗用风险。
  • 通过 HTTPS 访问时会追加 Secure 属性;纯 HTTP 访问不追加。
  • 服务端不做会话表,只比对 Cookie auth == base64(PASSWORD),因此口令一改,所有旧 Cookie 立即失效。

4.3 口令错误与缺字段

curl -s -X POST http://127.0.0.1:8136/api/login \
  -H 'Content-Type: application/json' -d '{"password":"wrong-pass-2026"}' \
  -w "\nHTTP %{http_code}\n"
curl -s -X POST http://127.0.0.1:8136/api/login \
  -H 'Content-Type: application/json' -d '{}' \
  -w "\nHTTP %{http_code}\n"

实测输出:

{"success":false}
HTTP 401
{"success":false}
HTTP 401

错误口令与缺字段返回同一结果,不区分原因,避免探测。

# 1) 登录并写入 Cookie 罐
curl -s -c cookies.txt -X POST http://127.0.0.1:8136/api/login \
  -H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}'

# 2) 后续请求统一 -b cookies.txt
curl -s -b cookies.txt http://127.0.0.1:8136/api/storage?status=1

实测输出:

{"success":true}
{"d1Available":true}

⚠️ 注意:如果服务端没有配置 PASSWORD(未设置该环境变量),认证中间件直接放行,/api/login 也恒返回 {"success":true}。生产环境务必确认已配置口令。

五、存储 API:/api/storage

这是 Solara 唯一的持久化接口,收藏列表与播放进度都存这里。Docker 形态下落 SQLite(/data/solara.db,WAL 模式),Cloudflare 形态下落 D1,表结构与键语义一致。

5.1 可用性检查

curl -s -b cookies.txt "http://127.0.0.1:8136/api/storage?status=1" -w "\nHTTP %{http_code}\n"

实测输出:

{"d1Available":true}
HTTP 200

status 参数只要出现(哪怕 ?status=0)就返回可用性,不判断值。该接口用于前端判断是否启用服务端漫游。

5.2 批量写入

curl -s -b cookies.txt -X POST http://127.0.0.1:8136/api/storage \
  -H 'Content-Type: application/json' \
  -d '{"data":{"d5-probe":"2026-10-25","d5-json":{"a":1}}}' \
  -w "\nHTTP %{http_code}\n"

实测输出:

{"d1Available":true,"updated":2}
HTTP 200

请求体必须是 {"data": {键: 值}} 形式,data 为对象;updated 返回提交的键数量。写入是 UPSERT(存在则覆盖),没有新增/更新之分。

值类型陷阱:值最终按字符串入库。传对象不会报错,但会被 String(value) 转成 "[object Object]",读回即失真:

curl -s -b cookies.txt "http://127.0.0.1:8136/api/storage" -w "\nHTTP %{http_code}\n"

实测输出(节选,favorites:songs 为前端已有数据):

{"d1Available":true,"data":{"favorites:songs":"[{\"id\":\"5257138\",\"name\":\"屋顶\"}]","d5-probe":"2026-10-25","d5-json":"[object Object]"}}
HTTP 200

正确做法是写入前自行 JSON.stringify,读回后 JSON.parse。前端对 favorites:songs 就是这么处理的——值是字符串化的 JSON 数组。

空对象写入不报错:

{"d1Available":true,"updated":0}
HTTP 200

data 不是对象(例如数组)时拒绝:

{"error":"Invalid payload"}
HTTP 400

5.3 按键读取

curl -s -b cookies.txt "http://127.0.0.1:8136/api/storage?keys=d5-probe,d5-missing" -w "\nHTTP %{http_code}\n"

实测输出:

{"d1Available":true,"data":{"d5-probe":"2026-10-25","d5-missing":null}}
HTTP 200

keys 是英文逗号分隔的键名列表,前后空格会被裁剪、空段被丢弃。不存在的键返回 null 而不是省略,客户端可据此区分「没有这个键」与「键存在但值为空串」。

5.4 全量读取

不传 keys 即返回全部:

curl -s -b cookies.txt "http://127.0.0.1:8136/api/storage" -w "\nHTTP %{http_code}\n"

实测输出:

{"d1Available":true,"data":{"favorites:songs":"[{\"id\":\"5257138\",\"name\":\"屋顶\"}]","d5-probe":"2026-10-25","d5-json":"[object Object]"}}
HTTP 200

没有分页、没有筛选、没有排序,全量返回。键数量随使用增长,但单机场景规模很小(收藏与播放状态各几百字节),不构成压力。

5.5 批量删除

curl -s -b cookies.txt -X DELETE http://127.0.0.1:8136/api/storage \
  -H 'Content-Type: application/json' \
  -d '{"keys":["d5-probe","d5-json"]}' -w "\nHTTP %{http_code}\n"

实测输出:

{"d1Available":true,"deleted":2}
HTTP 200

⚠️ deleted 是请求中的键数量,不是实际删除行数。删除两个根本不存在的键,同样返回 2:

curl -s -b cookies.txt -X DELETE http://127.0.0.1:8136/api/storage \
  -H 'Content-Type: application/json' \
  -d '{"keys":["no-such-a","no-such-b"]}' -w "\nHTTP %{http_code}\n"

实测输出:

{"d1Available":true,"deleted":2}
HTTP 200

因此不能用 deleted 判断删除是否命中,需要再 GET ?keys=... 复核。空数组删除返回 0:

{"d1Available":true,"deleted":0}
HTTP 200

5.6 键名与落表规则

服务端按固定白名单把键分到两张表:

表键名
favorites_storefavoriteSongs、currentFavoriteIndex、favoritePlayMode、favoritePlaybackTime
playback_store其余所有键(含前端实际使用的 favorites:songs、playback:*)

分表对调用方透明,但直接查库时要知道目标表。注意 favorites:songs 不在白名单里,实际落 playback_store——按白名单字面推断容易查错表。

六、代理 API:/proxy

/proxy 是 Solara 的取数中枢:搜索、歌词、封面、播放直链都走它,上游统一是 API_BASE_URL 指向的聚合服务。它有两种模式:

  • 不带 target:把查询参数转发给上游音乐 API,带 5 分钟本地缓存;
  • 带 target:把目标 URL 当音频流转发,仅允许 *.kuwo.cn 域名,并透传 Range。

6.1 参数总览

参数适用 types说明
types全部search / lyric / pic / url
source全部音源标识,前端提供 netease、kuwo、joox
namesearch关键词,需 URL 编码
countsearch每页条数,前端默认 20
pagessearch页码,从 1 开始
idlyric / pic / url歌曲 id、歌词 id 或封面 id(搜索结果分别给出)
sizepic封面边长,常用 300
brurl码率,128/320/999(999 取最高,常为 FLAC)
nocache全部true 跳过读缓存,本次也不写入
target音频流酷我音频直链,走独立转发分支

6.2 搜索

curl -s -b cookies.txt -D - -o /tmp/d1.json \
  "http://127.0.0.1:8136/proxy?types=search&source=netease&name=%E5%91%A8%E6%9D%B0%E4%BC%A6&count=5&pages=1"

实测响应头(节选)与正文:

HTTP/1.1 200 OK
X-Cache-Status: HIT
Access-Control-Expose-Headers: X-Cache-Status
Content-Type: application/json; charset=utf-8

[{"id":"509781655","name":"想你就写信 (Live)","artist":["周杰伦","李硕","张鑫"],"album":"...","pic_id":"109951163038292176","url_id":"509781655","lyric_id":"509781655","source":"netease","from":"music.gdstudio.xyz"}, ...]

返回是数组,每个元素含 id、name、artist(数组)、album、pic_id、url_id、lyric_id、source、from。后续三种 types 分别用 id/lyric_id/pic_id。

首次请求(缓存未命中)耗时明显,二次同参数命中缓存:

场景耗时(实测)X-Cache-Status
首次搜索2.26 sMISS
5 分钟内同参数0.0017 sHIT
加 nocache=true0.009 sMISS

缓存命中时上游完全不发请求,这是 Solara 体验流畅的关键。

6.3 歌词

curl -s -b cookies.txt --max-time 90 \
  "http://127.0.0.1:8136/proxy?types=lyric&id=509781655&source=netease" -w "\nHTTP %{http_code}\n"

实测输出(节选):

{"lyric":"[00:00.00] 作词 : 方文山\n[00:01.00] 作曲 : 周杰伦\n[00:26.17]达布:看你在摇椅上织围巾\n..."}
HTTP 200

返回对象只有一个 lyric 字段,是 LRC 文本,\n 为行分隔、[mm:ss.xx] 为时间轴。前端直接按 LRC 解析,无需再转格式。

6.4 封面

curl -s -b cookies.txt --max-time 90 \
  "http://127.0.0.1:8136/proxy?types=pic&id=109951163038292176&source=netease&size=300" \
  -w "\nHTTP %{http_code}\n"

实测输出:

{"url":"https://p2.music.126.net/yD9vbpuILH-tqNRIaP640g==/109951163038292176.jpg?param=300y300","from":"music.gdstudio.xyz"}
HTTP 200

只返回封面 URL,不返回图片二进制;客户端拿到 URL 后自行加载(同源策略允许跨域图片直接展示)。

6.5 播放直链

curl -s -b cookies.txt --max-time 130 \
  "http://127.0.0.1:8136/proxy?types=url&id=509781655&source=netease&br=128" \
  -w "\nHTTP %{http_code}\n"

实测输出:

{"url":"https://m801.music.126.net/20261025134414/011bf89f5d4a83c884fad7fcbee79767/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/17432853135/a2d8/c265/26ad/fd9ff88a1226ec6b79a77ffca292d465.mp3","br":128,"size":3820077,"from":"music.gdstudio.xyz"}
HTTP 200

br=999 会请求最高音质,实测返回 FLAC,响应里的 br 是实际码率而非请求值:

{"url":"https://m701.music.126.net/.../ea81703417fffba23f78ade85f925a12.flac","br":918,"size":27401646,"from":"music.gdstudio.xyz"}
HTTP 200

无效 id 不报错,返回空直链:

{"url":"","br":0,"size":0,"from":"music.gdstudio.xyz"}
HTTP 200

⚠️ 这个空结果会被缓存 5 分钟:缓存逻辑只排除「搜索空数组」和含错误标记的响应,空直链不在此列。短时间内重试同一个无效 id 会持续拿到空结果,排障时要加 nocache=true。

6.6 音频流 Range 播放

直链拿到后,客户端直接请求音乐 CDN;若需经 Solara 转发(酷我音源),用 target 参数:

curl -s -o /dev/null -H "Range: bytes=0-65535" \
  -w "HTTP %{http_code} type=%{content_type} bytes=%{size_download} time=%{time_total}s\n" \
  "http://127.0.0.1:8136/proxy?target=http://<酷我音频直链>"

对直链本身的 Range 实测(不经代理):

HTTP 206 type=audio/mpeg; charset=UTF-8 bytes=65536 time=0.147003s
HTTP 206 type=audio/mpeg; charset=UTF-8 bytes=65536 time=0.135246s

服务端对音频流只做三件事:透传 Range、透传安全响应头(content-type、content-range、content-length、etag、last-modified 等)、在客户端断开时 AbortController 立即中止上游连接。因此切歌不会留下悬挂的上游连接。

target 分支有域名白名单:只接受 kuwo.cn 及其子域,其他主机一律 400 Invalid target;协议只允许 http/https,且强制降为 http。这是防止代理被当成开放 SSRF 通道的边界。

6.7 缓存与错误

缓存规则(源码与实测一致):

  • 默认 TTL 300 秒;缓存键剔除 s(随机签名)与 nocache,因此同一逻辑请求即使签名变化也能命中;
  • nocache=true 时既跳过读缓存,也不写入;
  • 上游非 200、响应含 "error" 或 "status":0 时不缓存;
  • types=search 且结果为空数组([])时不缓存——避免把「上游临时无结果」固化 5 分钟;
  • 命中时 Cache-Control: public, max-age=300,未缓存时 no-store。

缺 types 报错:

curl -s -b cookies.txt --max-time 60 "http://127.0.0.1:8136/proxy?source=netease" -w "\nHTTP %{http_code}\n"

实测输出:

Missing types
HTTP 400

注意返回的是纯文本而非 JSON,客户端做统一 JSON 解析时要容错。

6.8 音源可用性实测

同一关键词分别请求三个前端可选音源:

source结果耗时
netease200,正常返回数组2.26 s(首次)
kuwo400,{"detail":"Value of \source` is not supported."}`34.02 s
tencent400,同上3.32 s
qq400,同上2.50 s
joox无响应,curl 超时(70 s)70 s

结论:当前上游只稳定支持 netease;kuwo 虽在前端下拉里,但上游已不再受理该 source;joox 直接超时。前端保留这些选项只是历史兼容,实际可用性取决于上游当天状态。

💡 技术拓展:为什么上游会返回「source 不支持」而不是 404?因为聚合 API 用一个统一的 api.php 入口,source 是内部路由键,键不存在时走参数校验分支,返回 400 + 业务错误体。这类「200 外壳 + detail 错误体」的接口,客户端不能只看 HTTP 状态码,还要检查响应内容里的 detail/error 字段——Solara 的缓存层正是因此把含 "error" 的响应排除在缓存之外。

七、调色板 API:/palette

播放器会根据封面主色动态换主题,这个接口就是取色服务:输入一张图片 URL,输出一组可直接用的 CSS 颜色与渐变。

7.1 请求

curl -s -b cookies.txt -D - \
  "http://127.0.0.1:8136/palette?image=https%3A%2F%2Fp2.music.126.net%2FyD9vbpuILH-tqNRIaP640g%3D%3D%2F109951163038292176.jpg%3Fparam%3D300y300"

参数名 image 或 url 都接受(取先出现的那个),值必须是完整 URL,需 URL 编码。

实测响应头与正文:

HTTP/1.1 200 OK
X-Cache-Status: MISS
Content-Type: application/json; charset=utf-8

{"source":"https://p2.music.126.net/yD9vbpuILH-tqNRIaP640g==/109951163038292176.jpg?param=300y300","baseColor":"#ba7f5d","averageColor":"#563026","accentColor":"#ba7f5d","contrastColor":"#f8fafc","gradients":{"light":{"colors":["#c0b5a5","#c4bfb0","#c9c3ba"],"gradient":"linear-gradient(140deg, #c0b5a5 0%, #c4bfb0 45%, #c9c3ba 100%)"},"dark":{"colors":["#38302a","#2b2723","#201d1c"],"gradient":"linear-gradient(135deg, #38302a 0%, #2b2723 55%, #201d1c 100%)"}},"tokens":{"light":{"primaryColor":"#856451","primaryColorDark":"#60493c"},"dark":{"primaryColor":"#836756","primaryColorDark":"#5d493e"}}}

7.2 响应字段

字段含义
source输入图片的 URL 回显
baseColor / accentColor主色调(当前算法两者相同)
averageColor全图平均色
contrastColor与主色对比度足够的文字色(深色底给浅字)
gradients.light / .dark亮/暗主题各一组三色渐变与可直接使用的 linear-gradient() 字符串
tokens.light / .dark主题变量,含 primaryColor 与 primaryColorDark

服务端做的是「取色 + 生成主题令牌」,不做 CSS 注入;前端拿到 tokens 后写入 CSS 自定义属性。

7.3 缓存

同一图片二次请求命中缓存,耗时从 0.107 s 降到 0.0012 s:

HTTP 200 time=0.107305s   X-Cache-Status: MISS
HTTP 200 time=0.001214s   X-Cache-Status: HIT

TTL 为 3600 秒(1 小时),键为 palette:v3:<图片URL>,比 /proxy 的 5 分钟长得多——封面颜色不会变,缓存久一点没有副作用。

7.4 错误响应

curl -s -b cookies.txt "http://127.0.0.1:8136/palette" -w "\nHTTP %{http_code}\n"
curl -s -b cookies.txt "http://127.0.0.1:8136/palette?image=not-a-url" -w "\nHTTP %{http_code}\n"

实测输出:

{"error":"Missing image parameter"}
HTTP 400
{"error":"Invalid image URL"}
HTTP 400

其他错误:上游图片拉取失败返回 502 {"error":"Failed to fetch image"};上游返回非 2xx 时透传上游状态码并附 {"error":"Upstream request failed with status <code>"};单次拉图超时 15 秒。

八、通用约定与边界

8.1 CORS 预检

/proxy 与 /palette 的 OPTIONS 需要认证,带 Cookie 时返回:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,HEAD,OPTIONS
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 86400

不带 Cookie 时直接 401 JSON。也就是说,跨域调用方必须先完成登录拿到 Cookie,再做预检;浏览器场景下同源访问不受影响。

8.2 状态码速查

状态码典型场景响应体
200正常读取/写入/代理成功JSON
204OPTIONS 预检成功空
206音频 Range 请求音频二进制
302未认证访问页面空,Location: /login
400参数缺失/非法、types 缺失、上游 source 不支持JSON 或纯文本
401未认证访问接口、口令错误{"error":"Unauthorized"} 或 {"success":false}
404路径不存在纯文本 Not Found
502上游代理/图片拉取失败纯文本或 JSON error

8.3 鉴权边界的一个细节

认证中间件把「带公开后缀的路径」整体放行,后缀白名单包含 .json、.txt、.css、.js、图片与字体扩展名。这些扩展名的请求不做 Cookie 校验:

curl -s -o /dev/null -w "GET /favicon.png -> %{http_code} bytes=%{size_download}\n" http://127.0.0.1:8136/favicon.png
curl -s -o /dev/null -w "GET /data.json -> %{http_code}\n" http://127.0.0.1:8136/data.json

实测输出:

GET /favicon.png -> 200 bytes=1387735
GET /data.json -> 404

/data.json 返回 404 是因为文件不存在;只要静态目录里存在同名 .json 文件,它就会被公开读取。因此不要把敏感配置以 .json/.txt 形式放进站点根目录,接口数据一律走 /api/storage。

九、常见问题(排障实录)

9.1 代理返回登录页 HTML、状态码却是 200

现象:/proxy?types=search... 返回 17 KB 的登录页 HTML,HTTP 200,前端 JSON 解析报错。

定位:查看服务日志,出现连续的内部重定向:

[wrangler:inf] GET /proxy 302 Found (3ms)
[wrangler:inf] GET /login 200 OK (9ms)

原因:Express 把请求转给内部 Wrangler 时,需要携带 Cookie: auth=base64(PASSWORD);某些旧版入口实现没带该请求头,Wrangler 侧认证失败跳登录页,登录页 HTML 又被当作上游数据原样返回。

处理:升级到 2.1.6(该版本代理层已补内部 Cookie),确认运行版本与编排文件一致后重启。实测 2.1.6 下同一请求返回正常 JSON 数组。

教训:编排文件里的标签必须与实际运行标签一致。本次排障中发现编排写着 2.1.6、实际运行 2.1.5,两者代理行为不同,先用 docker inspect 核对再查代码。

9.2 搜索一直 MISS、命中不了缓存

先确认参数完全相同(含 count、pages);nocache=true 会强制不缓存。若参数一致仍 MISS,检查响应体是否含 "error" 或 "status":0——这两类响应按设计不入缓存。空数组搜索结果同样不缓存。

9.3 无效歌曲 id 反复返回空直链

types=url 的空结果 {"url":""} 会被缓存 5 分钟。排障期间在 URL 上加 &nocache=true 再试,或换一个有效 id 验证链路本身是否正常。

9.4 歌词延迟高、偶发超时

歌词与直链都由上游实时生成,实测歌词 3.26 s、直链 0.83 s,冷门歌曲可能更慢。客户端应设置 10 秒以上超时并做降级(无歌词时只显示播放器)。joox 音源实测 70 秒无响应,不要把它作为默认源。

9.5 跨域调用预检 401

OPTIONS 也在认证中间件之后。跨域前端需先 POST /api/login 拿到 Cookie(credentials: 'include'),再发实际请求;或者由同源反向代理转发,避免跨域预检。

口令变更会让所有旧 Cookie 立即失效(服务端无会话表,只比对 Base64 值);48 小时到期也会失效。重新 POST /api/login 即可,无需重启服务。

十、参考链接