Cloudflare R2 图片上传部署教程
本教程用于维护 MarkdownCanDo 的匿名临时图片上传功能。架构是“私有 R2 桶 + 同域 Worker 代理 + Durable Object 原子配额”,不要把 R2 桶本身公开。
为什么采用这些限额
截至 2026 年 8 月 29 日,R2 Standard 每月免费包含 10 GB-month 存储、100 万次 Class A 操作和 1000 万次 Class B 操作,互联网出口免费。Cloudflare 网站的 Pro 套餐与 R2 是两套计费:Pro 不会增加 R2 的免费额度,R2 按账号汇总用量并独立结算。超出免费量后,Standard 存储为 $0.015/GB-month,Class A 为 $4.50/百万次,Class B 为 $0.36/百万次。
R2 的 GB-month 不是月底对象大小,而是账期内“每日峰值存储量”的平均值。当前账号其他 R2 桶合计约 123 MB,因此本站仍按免费层做保守预算。当前默认值如下:
| 范围 | 限额 |
|---|---|
| 单张图片 | 5 MiB |
| 单访客 / UTC 日 | 10 张或 20 MiB |
| 全站 / UTC 日 | 300 张或 1 GiB |
| 图片可用时间 | 上传后 24 小时 |
| 3 日滚动台账预算 | 4 GiB |
即使每天用满,考虑 24 小时可用期、生命周期执行时点和到期后最多约 24 小时的底层清理延迟,保守按三个 UTC 日批次共存计算,物理峰值约为 1 GiB × 3 = 3 GiB。配额台账也独立保留最近 3 个 UTC 日期,4 GiB 是本站上传量的预算护栏;它不是对整个 R2 账号的实时容量测量,仍需结合 R2 指标和账单提醒。每月最多约 9000 次上传,只占 Class A 免费量的约 0.9%。个人限额仍为 20 MiB/10 张,避免单个来源耗尽全站额度。
参考 Cloudflare 官方的 R2 定价、用量计费说明、对象生命周期和 R2 指标。
1. 首次创建私有桶
确认 Wrangler 已登录正确账号:
pnpm exec wrangler whoami首次部署时创建 Standard 桶:
pnpm exec wrangler r2 bucket create markdown-can-do-uploads --location apac --storage-class Standard本站中文内容与亚太访问为主,且同账号已有媒体桶位于 APAC,所以当前桶使用 APAC location hint。派生站点若没有明确的主要区域,应省略 --location apac,让 R2 使用官方推荐的 Automatic placement。
在 Cloudflare Dashboard 的 R2 object storage → markdown-can-do-uploads → Settings 中确认:
r2.devPublic Development URL 为关闭状态;- 没有给桶绑定公开 Custom Domain;
- 默认存储类型为 Standard。
也可以用 CLI 复核公开入口:
pnpm exec wrangler r2 bucket dev-url get markdown-can-do-uploads
pnpm exec wrangler r2 bucket domain list markdown-can-do-uploads第一条应显示 public access disabled,第二条应显示没有自定义域名。桶不需要配置 CORS,因为浏览器只访问同域 Worker,不直接访问 R2。
桶默认是私有的。不要为了“方便访问”打开公开入口,否则会绕过 Worker 的防盗链检查。
2. 设置 1 天生命周期
给 uploads/ 前缀添加自动删除规则:
pnpm exec wrangler r2 bucket lifecycle add markdown-can-do-uploads expire-temporary-uploads uploads/ --expire-days 1如果是把已有的 30 天规则改为 1 天,先删除同名旧规则再重建:
pnpm exec wrangler r2 bucket lifecycle remove markdown-can-do-uploads --name expire-temporary-uploads
pnpm exec wrangler r2 bucket lifecycle add markdown-can-do-uploads expire-temporary-uploads uploads/ --expire-days 1 --force核对规则:
pnpm exec wrangler r2 bucket lifecycle list markdown-can-do-uploadsWorker 会在上传满 24 小时后立即停止读取;Cloudflare 通常会在对象到期后的 24 小时内完成底层删除。不要改用 Infrequent Access:它没有 Standard 免费额度,并且有 30 天最低存储计费期,不适合一天临时图片。
3. 理解 Worker 配置
wrangler.jsonc 已包含:
IMAGE_UPLOADS私有 R2 binding;IMAGE_QUOTASQLite Durable Object;/api/images上传和/uploads/*读取的 Worker-first 路由;- 可调整的大小、张数、滚动容量和保留期变量;
- Worker 日志与少量追踪采样。
修改 binding 后重新生成类型:
pnpm exec wrangler types worker-configuration.d.ts上传服务使用 HMAC 生成每日访客配额标识。正式部署前创建一个至少 32 字符的高熵 secret,并确认它已经存在:
pnpm exec wrangler secret put IMAGE_QUOTA_SECRET
pnpm exec wrangler secret list不要把生产 secret 写进 wrangler.jsonc 或提交到 Git。配额 Durable Object 会每天通过 alarm 主动清理超过 3 个 UTC 日期的标识,不依赖下一次上传触发清理。
配额由 Durable Object 原子预留。R2 写入失败时会释放预留,因此并发上传不会轻易穿透全站额度,也不会因普通写入失败永久扣掉当天额度。
4. 本地验证
安装依赖、构建并启动本地 Worker:
pnpm install
cp .dev.vars.example .dev.vars
pnpm build
pnpm exec wrangler dev.dev.vars 已被 Git 忽略;本地占位 secret 仅用于开发,不要复用到生产。
浏览器中打开本地站点,从源码工具栏或所见即所得工具栏选择图片。源码模式也支持粘贴和拖放。
也可以用现有站点图标验证上传。Origin 必须与请求地址完全一致:
curl -i http://localhost:8787/api/images \
-X POST \
-H "Origin: http://localhost:8787" \
-H "Content-Type: image/png" \
--data-binary @public/logo-mini.png把响应中的 /uploads/... 地址填入下列命令。本站来源应返回 200:
curl -I "http://localhost:8787/uploads/<day>/<id>.png" \
-H "Referer: http://localhost:8787/playground/" \
-H "Sec-Fetch-Site: same-origin"外站来源和空 Referer 应返回 403:
curl -I "http://localhost:8787/uploads/<day>/<id>.png" \
-H "Referer: https://example.com/" \
-H "Sec-Fetch-Site: cross-site"响应还应包含 Cross-Origin-Resource-Policy: same-origin,且不能包含 Access-Control-Allow-Origin: *。
5. 测试与部署
pnpm test
pnpm typecheck
pnpm cloudflare:deploy:dry
pnpm cloudflare:deploy首次正式部署会按 exports 配置创建 SQLite Durable Object namespace。部署完成后再做一次真实上传和同源/跨站读取验证。
6. 监控和调整
查看桶对象数与当前大小:
pnpm exec wrangler r2 bucket info markdown-can-do-uploads同时在 Dashboard 查看 R2 Storage、Class A、Class B 和 Worker 请求量。若要修改限额,编辑 wrangler.jsonc 的 IMAGE_* 变量,并同时更新三语图片教程和上传界面文案。
不要仅提高每日额度而忽略保留期和生命周期执行延迟。新的理论峰值应满足:
全站每日字节数 × 保守共存批次数 < 滚动台账预算 < 可用 R2 存储预算当前参数为 1 GiB × 3 < 4 GiB < 10 GB-month。此外,网站 Pro 不会提升 Workers 请求额度;若没有单独开通 Workers Paid,Workers Free 仍为每天 10 万次动态请求。/api/images 与 /uploads/* 都会运行 Worker,缓存命中仍计请求数,因此应分别监控 Workers、Durable Objects 和 R2 用量。详见 Workers 定价。
防盗链的边界
Worker 会在读取缓存和 R2 之前检查 Referer 与 Sec-Fetch-Site,并返回 Cross-Origin-Resource-Policy: same-origin。缓存有效期会被限制在上传后的 24 小时内;到期后即使 R2 生命周期尚未完成物理删除也不再提供读取。这可以阻止正常浏览器把本站地址直接嵌入其他网页,但不能阻止他人下载图片后重新托管,也不能抵御能伪造请求头的非浏览器客户端。需要更强保护时应引入登录态或短时签名 URL;这会与可复制的静态 Markdown 地址产生取舍。