Skip to main content
前台右下角的全站音乐播放器 + 后台「音乐管理」页(R2 直传、文件名识别、同步删除)。
/api/music* 已补齐 Pages Functions:播放列表、预签名上传、注册、编辑与删除在 Workers 和 Pages 两种部署形态下都可用。

前台播放器

  • 悬浮按钮:右下角圆形音符按钮,平时缩进窗口外露出一点圆弧,悬停 / 点击即滑出
  • 弹出面板:点击按钮弹出卡片面板 —— 曲目信息 · 可拖动进度条 · 上一首 / 播放暂停 / 下一首 · 音量 · 播放列表(点击切换、当前曲目高亮 + 均衡动画);点面板外部或 ✕ 收起
  • 记忆:自动连播;记忆上次曲目与进度、音量持久化(localStorage),刷新后恢复但不自动出声
  • 自适应:无音乐或接口失败时完全隐藏;后台路由(/admin、/write、编辑页)自动收起
  • 按需加载:播放器的 CSS 与 JS 不在首屏关键路径上,滚动 / 空闲时才加载,不影响首屏

后台音乐管理

  • 上传:选择 / 拖拽音频文件,浏览器直传 R2(预签名 URL),显示上传进度;选择文件后按文件名自动识别**「歌曲名-歌手」**预填输入框
  • 列表:行内试听 / 重命名 / 删除(删除与 R2 对象同步);曲目多时列表卡片内滚动、表头吸顶,上传区不随列表滚走
  • 管理端列表请求带 ?_=<时间戳> 穿透 s-maxage 边缘缓存,增删即时可见

上传架构(R2 直传)

  • 音频文件不经过 Worker,规避请求体上限与带宽计费(R2 egress 免费)
  • 环境变量:R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY / R2_ENDPOINT + 下面两组桶变量
  • 降级:未配置 R2 凭据时,读取播放列表仍可用(D1),上传返回 503

目标桶选择(musicStorage())

音乐优先写回音乐桶,仅在音乐桶配置不完整时回退媒体桶(兼容只授予媒体桶权限的旧凭据):
这个优先级是 v2.7.1 的修复点:此前的实现会误把音乐写进媒体桶。现在只要音乐桶两个变量都在,就一定写音乐桶。删除时用 resolveR2Object() 同时识别媒体桶与音乐桶的公开域名,因此切换上传目标前后的历史曲目都能正确同步删除;外链(非 /music/ 前缀或域名不匹配)不会被当成桶内对象。

文件白名单与大小

oga(Ogg Audio 的另一种扩展名)在 v2.7.1 加入白名单;但接口的错误提示文案里尚未列出 oga——收到不支持格式时返回的仍是 不支持的音频格式(mp3 / m4a / ogg / wav / aac / opus / flac),而 oga 实际上可以正常上传。

预签名 PUT 签名细节

Content-Type 参与签名,浏览器 PUT 时必须原样发送响应里的 contentType;且 R2 桶 CORS 的 AllowedHeaders 必须包含 Content-Type,否则预检失败、上传表现为无原因的 xhr.onerror。
上传失败时接口会暴露真实失败阶段(预检失败 / 签名不匹配 / 网络中断等),便于定位是 CORS 还是签名问题;同时前端会调用删除接口清理可能产生的孤儿对象(幂等)。

API

获取播放列表(公开)

响应:
按 sort ASC, created_at ASC 排序。响应头 Cache-Control: s-maxage=60, max-age=30。
只读接口带 s-maxage=60 边缘缓存;管理后台以 ?_=<时间戳> 穿透缓存取最新列表。

签发预签名上传 URL(需登录)

请求体:
响应:
浏览器拿到后直接 PUT 上传(Content-Type 与响应一致);上传失败时调用删除接口清理孤儿对象(幂等)。

注册曲目(需登录)

请求体(title 与 url 必填):
响应(201):
id 由服务端生成(song- + 随机串);缺 title 或 url 返回 400「缺少 title / url」。注意响应字段名是 track(不是 music)。

编辑曲目(需登录)

请求体: { "title", "artist", "cover", "sort", "duration" },title 必填(否则 400「缺少 title」)。成功返回 { "ok": true },D1 写入失败返回 500「数据库更新失败,请稍后重试」。

删除曲目(需登录)

删除顺序是先 R2、后 D1:
  1. 用 resolveR2Object() 解析 url 所属桶与对象 key(同时识别音乐桶与媒体桶公开域名;外链返回 null,跳过 R2 删除)
  2. 命中则向对应桶发起服务端签名 DELETE(x-amz-content-sha256: UNSIGNED-PAYLOAD + x-amz-date,SignedHeaders host;x-amz-content-sha256;x-amz-date)
  3. R2 删除失败 → 返回 502「R2 对象删除失败,请稍后重试」,D1 行保留(数据一致优先,避免幽灵曲目)
  4. R2 成功(或对象本就不存在,S3 DELETE 幂等,404 视为成功)后再删 D1 行;D1 删除失败返回 500「数据库删除失败,请稍后重试」

环境变量(GitHub Secrets)

逐项获取方式(控制台路径、格式要求)见 部署密钥指南。

表结构

迁移 0015_hot_path_indexes.sql 为 music 表补了 idx_music_sort,让播放列表的 sort ASC, created_at ASC 排序走索引。