Folia QQ音乐音源接入教程:MQTT长连线扫码背后的完整原理
【免费下载链接】folia-major专注于绚丽的歌词动画效果的本地音乐/navidrome/第三方多平台在线音乐播放器项目地址: https://gitcode.com/GitHub_Trending/fo/folia-major
Folia 是一款专注全屏歌词动画的在线音乐播放器,内置 QQ音乐、网易云、酷狗与 Navidrome 音源。本文带你快速接入 Folia QQ 音乐音源,并讲清楚一个有趣的问题:为什么 QQ 扫码登录必须靠一条 MQTT 长连线才能"守"住二维码?🎧
接入 QQ 音乐后能获得什么
连接 QQ 音乐账号后,Folia 不只是能搜索播放——它会同步你的账号数据:
| 能力 | 说明 |
|---|---|
| 在线搜索与播放 | 搜索歌曲、歌手、专辑,自动加载封面与歌词 |
| 账号歌单 | 拉取你的 QQ 音乐歌单并直接播放 |
| 我喜欢 / 收藏专辑 | 同步"我喜欢"列表与收藏专辑 |
| 全屏歌词动画 | 播放时驱动 Folia 的沉浸式歌词渲染 |
前端登录、轮询二维码、发现可用通道的完整链路收拢在 Omni 在线音乐层,入口是 src/services/onlineMusic/omni.ts,QQ 音源的适配器在 src/services/onlineMusic/qqProvider.ts。
快速开始:3 个配置项搞定接入
桌面版用户:直接打开就能用。Electron 主进程会在本地拉起 QQ 音乐 API 服务(见 electron/qqApiStartup.cjs),登录态存在系统安全存储里,无需任何配置。
Web 版用户(Vercel / Cloudflare)只需要两个变量:
VITE_QQ_API_BASE=/api/qq QQ_SESSION_SECRET=<一段至少 32 字节的随机密钥>VITE_QQ_API_BASE指向仓库内置的 serverless 入口(worker/qq.ts / api-ts/qq.ts)QQ_SESSION_SECRET用于加密封装登录态,千万不要加VITE_前缀——加了就会被编进前端资源
Docker 用户则完全免配置,deploy/docker/compose.yaml 已内置qq-api服务,docker compose up -d即可,详见 deploy/docker/qq-api/README.md。
两种扫码方式:为什么有的平台只支持微信
Folia 提供微信扫码与QQ 扫码两种方式,不同部署形态的支持范围并不一样:
| 部署方式 | 微信扫码 | QQ 扫码 |
|---|---|---|
| Electron 桌面版 / Docker / 裸 Node | ✅ | ✅ |
| Cloudflare + Durable Object | ✅ | ✅ |
| Vercel / Cloudflare(默认) | ✅ | ❌ |
⚠️ Vercel 缺少 QQ 扫码不是配置错误,而是平台能力限制:QQ 扫码需要在二维码有效期内持续保持一条 MQTT WebSocket 长连线,而 Vercel 没有可跨请求持有这条连接的运行时原语(见 docs/qq-music-deployment.md)。
打开登录弹窗时,前端会先请求/login/channels做能力发现,后端如实声明当前运行时支持哪些通道,界面自动显示对应选项——不需要改任何前端代码。
核心原理:QQ 扫码为什么非 MQTT 长连线不可
这是全文最关键的部分。QQ 扫码走的是MQTT over WebSocket协议,登录流程大致是:
- 后端向上游发起设备注册与二维码创建,拿到
qrcodeID和二维码图 - 后端用
qrcodeID建立 MQTT 连接并订阅该二维码的事件 - 你用 QQ 扫码后,上游通过这条连接推送
scanned(已扫码)、cookies(换取凭证的令牌)等事件 - 前端每隔约 2 秒轮询一次事件,拿到
cookies后完成凭证交换
难点在于这条连接"守"不住就全盘皆输:
- CONNECT 请求的是 clean session,订阅是 unicast——断连期间上游既不保留也不补送
- 如果两次轮询之间连接断了,重新连上时
scanned、cookies事件已经错过,而cookies里装的正是换凭证的令牌,漏掉就是死局
这就是源码注释里那句"必须有个能跨调用活着的东西握住这条连接"的由来(worker/qqQrChannel.ts)。
Cloudflare 的解法:用 Durable Object "托管"这条长连线
在 Serverless 平台上,一次 HTTP 请求结束连接就没了,而 Cloudflare 的Durable Object恰好能跨调用存活。Folia 用它实现了QqQrChannel类(worker/qqQrChannel.ts),设计上有几个值得学习的细节:
① 订阅必须先于二维码显示open要等到 MQTT 订阅确认(SUBACK)才返回。否则"显示后、订阅前"这一小段时间里被扫,事件不会补送(worker/qqQrChannel.ts)。
② 三重保险保证连接一定关得掉出向 WebSocket 期间 Durable Object 不能休眠,按在线时间计费。所以:open幂等(同一二维码只开一条 socket)、前端关闭弹窗时显式调用/close释放、再用alarm兜底——即使前两个都失效,二维码到期时连接也会被强制释放(worker/qqQrChannel.ts)。
③ Durable Object 里不存任何凭证它只搬运登录阶段的暂态:qrcodeID、二维码图、已收到的 MQTT 事件。真正的凭证交换留在请求侧完成,登录态封在加密 token 里。
想要启用这条通道,只需在wrangler.jsonc中加上QQ_QR_CHANNEL绑定(绑定名和类名必须精确匹配),再重新部署,/login/channels就会从["wechat"]变成["qq","wechat"]。
登录态与设备身份:两个容易被忽视的细节
Sealed Token(加密封装令牌):Serverless 形态下服务端不保存任何凭证,登录态由QQ_SESSION_SECRET加密后封进 token,由客户端携带。这意味着密钥丢失或轮换时所有用户需要重新扫码——Folia 提供了QQ_SESSION_SECRET_PREVIOUS变量做旧令牌过渡验证,避免"全员掉线"。
稳定的设备身份:QQ 扫码协议要求 QIMEI 引导和后续调用跑在同一个装置身份上,所以设备标识需要跨进程重启保留(Docker 里挂在qq-api-state具名卷上)。该文件只存设备识别值,不含任何账号凭证。
一句话排错表
| 现象 | 优先检查 |
|---|---|
页面显示Login Error | /api/qq/login/channels是否返回 JSON 而不是 HTML 页面 |
返回501 | QQ_SESSION_SECRET未设置或设置后未重新部署 |
| Vercel 只有微信扫码 | 正常行为,非故障 |
| Cloudflare 只有微信 | 检查QQ_QR_CHANNEL绑定与 migration |
修改VITE_QQ_API_BASE后仍请求旧地址 | 该变量构建时写入,必须重新构建部署 |
扫码后上游返回20279 | 先在 QQ 音乐账号中清理旧登录设备,再重新扫码 |
写在最后
Folia 接入 QQ 音乐音源,桌面端开箱即用;Web 端则是一次很好的"用 Serverless 承载长连线"的工程案例:MQTT 的 clean session + unicast 语义决定了长连线一刻不能松,而 Durable Object 恰好是 Cloudflare 上唯一能替你在两次请求之间"握着"这条线的原语。理解了open 幂等 + close 显式释放 + alarm 兜底这套组合拳,你基本也就读懂了这类扫码登录的实现精髓。🎯
完整的环境变量说明与部署步骤,建议对照 docs/qq-music-deployment.md 和 Omni 层架构说明 src/services/onlineMusic/README.md 一起阅读。
【免费下载链接】folia-major专注于绚丽的歌词动画效果的本地音乐/navidrome/第三方多平台在线音乐播放器项目地址: https://gitcode.com/GitHub_Trending/fo/folia-major
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考