Solara 2.1.6 API 使用详解
Solara 2.1.6 对外的 HTTP 接口全解:Cookie 认证原理与 48 小时有效期、/api/storage 键值读写与字符串化陷阱、/proxy 四种 types(搜索/歌词/封面/直链)与 5 分钟缓存及 Range 音频流转发、/palette 取色接口与 1 小时缓存,附 CORS、状态码速查与代理返回登录页等真实排障实录。
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 部署指南》
二、前置条件
- 服务已按部署篇拉起,
GET /login可返回登录页; - 已拿到访问口令(本例
q1w2e3r4); - 客户端能保存 Cookie(curl 用
-c写、-b读,或直接-b 'auth=<base64>'); - 若从其他主机调用,把下文所有
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 转发上游):
| 路径 | 方法 | 用途 | 需要认证 |
|---|---|---|---|
/login | GET | 返回登录页 HTML | 否 |
/api/login | POST | 用口令换 auth Cookie | 否 |
/api/storage | GET / POST / DELETE | 键值存储(收藏、播放状态漫游) | 是 |
/proxy | GET / OPTIONS | 音乐聚合 API 代理 + 音频流转发 | 是 |
/palette | GET / OPTIONS | 封面主色/渐变分析 | 是 |
/ | GET | 播放器主页 HTML | 是 |
| 其余静态路径 | GET | css/js/图片等静态文件 | 按扩展名放行 |
约定要点:
- 协议:HTTP/1.1,无版本前缀(不是
/api/v1/...),接口随 Solara 版本变化,升级前应回归本文清单。 - 数据格式:除音频流与静态文件外均为
application/json;/proxy的上游返回可能是 JSON 数组或对象,也可能被上游直接透传为纯文本错误。 - 限流:服务端没有内置限流或配额;压力全部传导给上游免费 API,实际可用性受上游波动影响。
- 缓存:
/proxy5 分钟、/palette1 小时,均可在响应头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 解析而崩溃。
4.2 兑换 Cookie
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
错误口令与缺字段返回同一结果,不区分原因,避免探测。
4.4 带 Cookie 的完整调用范式
# 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_store | favoriteSongs、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 |
name | search | 关键词,需 URL 编码 |
count | search | 每页条数,前端默认 20 |
pages | search | 页码,从 1 开始 |
id | lyric / pic / url | 歌曲 id、歌词 id 或封面 id(搜索结果分别给出) |
size | pic | 封面边长,常用 300 |
br | url | 码率,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 s | MISS |
| 5 分钟内同参数 | 0.0017 s | HIT |
加 nocache=true | 0.009 s | MISS |
缓存命中时上游完全不发请求,这是 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 | 结果 | 耗时 |
|---|---|---|
netease | 200,正常返回数组 | 2.26 s(首次) |
kuwo | 400,{"detail":"Value of \source` is not supported."}` | 34.02 s |
tencent | 400,同上 | 3.32 s |
qq | 400,同上 | 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 |
| 204 | OPTIONS 预检成功 | 空 |
| 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'),再发实际请求;或者由同源反向代理转发,避免跨域预检。
9.6 Cookie 突然全部失效
口令变更会让所有旧 Cookie 立即失效(服务端无会话表,只比对 Base64 值);48 小时到期也会失效。重新 POST /api/login 即可,无需重启服务。
十、参考链接
- 站内:《Solara 2.1.6 介绍与选型指南》、《Solara 2.1.6 Docker Compose 部署指南》、《Solara 2.1.6 配置参数详解》
- 上游仓库:
https://github.com/akudamatata/Solara - 上游聚合 API:
https://music-api.gdstudio.xyz/api.php(GD音乐台) - 接口实现参考:仓库
server/index.js、server/routes/{login,auth,storage,proxy,palette}.js - 版本与接口行为查询日期:2026-10-25