写这篇文章,是因为我自己刚用 Cloudflare 把开源项目 JSimages 跑通上线了。过程中踩了一堆坑,也做了不少定制,干脆整理成一篇「教程 + 成品展示」,供想自建图床的朋友参考。
一、为什么不直接用图床?
写博客、做笔记、发社群,图片托管永远是个绕不开的问题。第三方图床要么限速限容,要么哪天就跑路了;大厂的对象存储又要绑卡、还要操心账单。
Cloudflare 给出的方案很香:
- Workers:Serverless 跑后端逻辑,免费额度每天 10 万次请求;
- R2:对象存储,免费 10GB,且 出口流量免费(不像 S3 那样被流量计费);
- D1:边缘 SQLite,存图片元数据够用。
三者组合,个人图床几乎可以做到「零成本、不绑卡焦虑」。
二、成品先睹为快
我已经上线了一个,地址在这里 👉 https://tc.861409665.xyz/
它目前具备这些能力:
- 🖼️ 游客可直接上传 / 访问图片(也可关掉登录后使用);
- 🔐 带管理后台
/admin,登录后可查看、删除图片; - 📦 单张上限 10MB,支持常见图片格式;
- 💡 超出 9GB 存储配额后自动拒绝新上传,不再继续吃额度;
- 🛑 紧急情况可一行配置
DISABLED=true全站停用,杜绝误扣费。
页面底部放了我的个人博客和个人主页:
- 个人博客:https://chun7.top/
- 个人主页:https://zy.chun7.top/
三、技术选型与架构
浏览器 ──► Cloudflare Worker(_worker.js)
├── D1(media 表:url + size)
└── R2(图片二进制存储)请求进来后,Worker 负责鉴权、写库、读写 R2,并顺手用 caches.default 做一层边缘缓存,加速图片回源。
四、开始部署
1. 准备材料
- 开源仓库:https://github.com/0-RTT/JSimages (根目录就是
_worker.js,是个 Worker 项目,不是 Pages) - Cloudflare 账号,并创建一个 API Token,权限勾选:
Workers 脚本:编辑、D1:编辑、R2:编辑 - 在 Cloudflare 控制台 开启 R2(首次需要绑卡验证,但免费额度内不扣费)
- 一个域名(我用的
861409665.xyz,最终绑到子域tc.861409665.xyz)
2. 写 wrangler.toml
JSimages 仓库本身没有 wrangler.toml,需要自己建。核心配置如下:
name = "jsimages"
main = "_worker.js"
compatibility_date = "2024-09-23"
[[d1_databases]]
binding = "DATABASE"
database_name = "jsimages"
database_id = "91dd6235-d95b-4b2a-8423-e2beaf7f64ab"
[[r2_buckets]]
binding = "R2_BUCKET"
bucket_name = "jsimages"
[vars]
DOMAIN = "tc.861409665.xyz"
USERNAME = "admin"
ADMIN_PATH = "admin"
ENABLE_AUTH = "false"
MAX_SIZE_MB = "10"
QUOTA_STORAGE_MB = "9000"
APP_VERSION = "v3"
# PASSWORD 用 secret 存放,不要写进文件⚠️ 密码类敏感信息请用 wrangler secret put PASSWORD 注入,别明文落在配置文件里。3. 创建云端资源
# 建 R2 桶
wrangler r2 bucket create jsimages
# 建 D1 数据库
wrangler d1 create jimages
# 建表(注意加了 size 列,用于配额统计)
wrangler d1 execute jsimages --remote --command \
"CREATE TABLE IF NOT EXISTS media (url TEXT PRIMARY KEY, size INTEGER DEFAULT 0);"4. 部署
wrangler deploy部署成功后你会拿到一个 *.workers.dev 默认域名(我的是 https://jsimages.sjzy.workers.dev)。
5. 绑定自定义域名
JSimages 用 [[domains]] 字段的方式在某些 wrangler 版本不被识别,稳妥做法是走 Zone 的 Workers Routes,或直接在 DNS 里加一条:
tc.861409665.xyz A 192.0.2.1 代理(Proxied)加上之后,访问子域就会落到这个 Worker。
五、我的定制改动
开源版界面比较素,我按自己的需求改了 _worker.js:
- 标题:
JSimages→时间煮雨の图床(title、描述、关键词、div.title全改); - 页脚:原本的「开源声明」换成个人博客 / 个人主页链接;
- 游客访问:
ENABLE_AUTH=false,无需登录即可上传; - 免责声明:底部加红色提示,严禁上传违法、色情、暴力、侵权等内容;
- 防超额:上传/访问时比对
QUOTA_STORAGE_MB(我设 9000),超了就拒;再加DISABLED开关一键全站 403; - 缓存根治:首页缓存键带上
cv=APP_VERSION,每次改版升一版(现在是v3),旧缓存自然失效。
关键代码片段(缓存键带版本号):
const cacheKey = new Request(
request.url + (request.url.includes('?') ? '&' : '?') + 'cv=' + config.appVersion,
request
);六、踩坑记录(都是真金白银的时间)
| 坑 | 现象 | 解决办法 |
|---|---|---|
| R2 未开启 | 部署后 API 报 10042 | 控制台手动 Enable R2 |
| wrangler 中文路径找不到入口 | 部署报找不到 _worker.js | 把项目拷到纯 ASCII 路径(如 C:/jsimages_deploy)再 deploy |
| secret put 顺序限制 | 4.x 报 latest version isn't currently deployed | 先 deploy,再 secret put,或改为可选跳过 |
[[domains]] 不识别 | 部署报 Unexpected fields | 改用 Zone Workers Routes / DNS 记录 |
| DNS 需手动加 | Token 无 DNS:Edit 权限,403 | 控制台手动加 A 记录并代理 |
| 改版不生效 | 刷新/Purge 都没用,还是旧界面 | caches.default 程序化缓存顶住默认 URL,靠 APP_VERSION 缓存键根治 |
最阴的一坑是缓存:Cloudflare 的 caches.default 是 Worker 自己写的程序化缓存,和 CDN 的 Purge 不是一回事,后台清缓存清不掉它。改版后必须让缓存键变化,否则用户永远看到旧页面。七、安全提醒(很重要)
- 部署用的 API Token 用完建议吊销重建,别长期留着;
- Cloudflare 账号密码定期更换;
- 敏感配置走
secret,别进版本库; - 对外公开上传的图床,务必加免责声明 + 内容审核,避免被滥用。
八、结语
整套下来,一个属于自己的、免费、可控、还能随时停用的图床就跑起来了。源码开放、部署简单,适合想摆脱第三方图床的朋友。
想看效果直接访问:https://tc.861409665.xyz/
如果你也搭了,欢迎在评论区贴出你的地址,一起交流踩坑经验 🚀
本文由 时间煮雨の图床 实战整理。更多内容见个人博客 https://chun7.top/ 与个人主页 https://zy.chun7.top/。

评论 (0)