概述
后台「媒体库」用于管理博客图片。上传采用 浏览器直传 R2(与音乐同款 S3 预签名模式),图片文件不经过 Worker,D1media 表只存元数据。
- 新上传:
POST /api/media/upload-url签发预签名 PUT URL → 浏览器直接 PUT 到 R2 →POST /api/media登记元数据 - 图片本体一律存 R2 的媒体专用桶
R2_MEDIA_BUCKET(与音乐桶相互独立),D1media表只存元数据(url为 R2 公开地址) - 降级:未配置好 R2 媒体桶时,
upload-url返回503,只读列表仍可用 - 所有媒体接口(含列表)都需要会话 token
签发预签名上传 URL(需登录)
响应:
签名细节(浏览器直传必须对齐)
约束与错误:
503 的判定要同时具备五项:R2_MEDIA_BUCKET、R2_MEDIA_PUBLIC_BASE、R2_ACCESS_KEY_ID、R2_SECRET_ACCESS_KEY、R2_ENDPOINT。只配桶不配公开域名也会降级为 503——否则能签发上传 URL 却拼不出 publicUrl,上传完只会在桶里留下无法回收的孤儿对象。uploadUrl 后直接 PUT 上传(Content-Type 与响应一致),完成后再登记元数据。
登记媒体(需登录)
url 协议白名单:仅允许 http(s)://——R2 公开地址或外部图床链接;data: / javascript: / vbscript: 等协议一律 400 拒绝(图片本体一律走 R2 直传,不再接受 base64 内嵌;历史遗留的 base64 记录已由迁移 0014 清除)。
响应(201):
id 由服务端生成(m- + 12 字节随机 hex),name 截断到 200 字符、type 截断到 64 字符。
获取媒体列表(需登录)
{ "ok": true, "media": [ ... ] },按 created_at DESC, id DESC 排序,响应头 Cache-Control: no-store(增删即时可见)。
删除媒体(需登录)
- 查该行
url,用extractMediaR2Key解析对象 key——同时校验路径前缀/media/与 origin 必须等于R2_MEDIA_PUBLIC_BASE,因此形如https://evil.example/media/x的外链不会被当成桶内对象 - 命中则向媒体桶发起服务端签名 DELETE(签名头
host;x-amz-content-sha256;x-amz-date+UNSIGNED-PAYLOAD) - R2 删除失败不阻塞(
try/catch吞掉),继续删除 D1 元数据行——避免留下「前端删不掉、库里还有」的幽灵记录;外部图床链接不触发 R2 删除 - 删除成功后按
created_at DESC, id DESC的列表立即少掉该项
环境变量(GitHub Secrets)
只有一组 R2 凭据,该令牌必须对你启用的媒体桶 / 音乐桶 / 备份桶都有读写权限。变量的逐项获取方式见 部署密钥指南。
表结构
迁移
0015_hot_path_indexes.sql 为 media 表补了 idx_media_created_id,让上面的列表排序走索引。