从零搭建 AI 图床:Claude Code + Cloudflare R2 + OpenClaw 全流程保姆级指南
一个人、一句话、一个下午,用 Vibe Coding 搭出带图片编辑、文件夹管理、AI Agent 接入的完整图床系统。
目录
- 这篇文章讲什么
- 最终成果预览
- 你需要准备什么
- 第一步:创建 Cloudflare R2 存储桶
- 第二步:用 Claude Code Vibe 出 Worker 后端
- 第三步:Vibe 出 HTML 前端管理器
- 第四步:部署上线
- 第五步:接入自定义域名(可选)
- 第六步:让 AI Agent 管理你的图床
- 踩过的坑与解决方案
- 完整文件清单
- 写在最后
这篇文章讲什么

我想要一个自己的图床——能拖拽上传、能裁剪压缩、能管理文件夹、能让 AI 帮我自动传图。
市面上的图床要么付费、要么有审核、要么不支持 API。所以我决定自己搭一个。
整个过程我几乎没手写代码,全程 Vibe Coding——用自然语言告诉 Claude Code 我要什么,它帮我写。从后端 API 到前端 UI 到 AI Agent 接入,一个下午全搞定。
技术栈:
| 组件 | 技术 | 作用 |
|---|---|---|
| 对象存储 | Cloudflare R2 | 存图片,免费额度很大 |
| 后端 API | Cloudflare Worker | 处理上传/删除/重命名 |
| 前端 UI | 单文件 HTML | 管理界面,Lightroom 风格暗色主题 |
| AI 接入 | Python / Node.js SDK | 让 AI Agent 程序化管理图床 |
| AI 工作流 | OpenClaw | AI Agent 平台,通过 Skill 调用图床 |
最终成果预览





你会得到一个这样的系统:
- 单文件 HTML 管理器(~1870 行):暗色主题、拖拽上传、图片网格、文件夹树、灯箱预览、图片编辑(裁剪/压缩/格式转换)、批量操作、一键复制链接
- Cloudflare Worker API(~190 行):处理上传(FormData + JSON 两种模式)、列表(支持文件夹浏览)、删除、重命名/移动、防盗链
- Python & Node.js SDK:让 AI Agent 或脚本通过代码管理图床
- OpenClaw Skill:让 AI 用自然语言管理你的图床(”帮我把这张图上传到 blog 文件夹”)
你需要准备什么
| 工具 | 用途 | 获取方式 |
|---|---|---|
| Cloudflare 账号 | R2 存储 + Worker | dash.cloudflare.com 免费注册 |
| Node.js 18+ | 运行 Wrangler CLI | nodejs.org |
| Claude Code | Vibe Coding 主力 | npm install -g @anthropic-ai/claude-code |
| OpenClaw(可选) | AI Agent 平台 | openclaw.ai |
| 一个域名(可选) | 自定义图片链接 | 任意域名注册商 |
第一步:创建 Cloudflare R2 存储桶
1.1 开通 R2
登录 Cloudflare Dashboard → 左侧菜单找到 R2 对象存储 → 创建存储桶。
存储桶名称: my-image-bucket # 随便起,记住就行
位置提示: 亚太地区 # 选离你近的
免费额度:R2 每月免费 10GB 存储 + 1000 万次读取 + 100 万次写入,个人图床完全够用。
1.2 初始化 Worker 项目
打开终端,用 Wrangler 创建项目:
# 安装 Wrangler(Cloudflare 的 CLI 工具)
npm install -g wrangler
# 登录 Cloudflare
wrangler login
# 创建项目
mkdir my-picbed && cd my-picbed
npm init -y
1.3 配置 wrangler.toml
创建 wrangler.toml:
name = "my-picbed"
main = "src/index.js"
compatibility_date = "2024-12-01"
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-image-bucket" # 改成你的存储桶名
1.4 设置认证令牌
# 生成一个随机令牌(或者用你自己想的密码)
wrangler secret put AUTH_TOKEN
# 输入你的令牌,比如: MySecretToken123
这个令牌用于保护上传/删除等写操作,防止别人乱传图片到你的桶里。
第二步:用 Claude Code Vibe 出 Worker 后端
这是 Vibe Coding 的精华部分。打开 Claude Code:
cd my-picbed
claude
2.1 第一版:基础 CRUD
给 Claude Code 的第一个 Prompt:
帮我写一个 Cloudflare Worker,作为图床的后端 API。
功能:
1. POST / — 上传图片(FormData 格式,字段 file + path)
2. GET /{key} — 获取图片,返回图片内容
3. GET /list — 列出所有图片(需要认证)
4. DELETE /{key} — 删除图片(需要认证)
认证方式:请求头 X-Auth-Token 与环境变量 AUTH_TOKEN 比较。
CORS:允许所有来源。
R2 Bucket 绑定名为 BUCKET。
文件放在 src/index.js。
Claude Code 会直接生成 src/index.js,一个完整可用的 Worker。
2.2 迭代加功能
Vibe Coding 的核心是对话式迭代。基础版跑通后,我继续跟 Claude Code 对话,逐步加功能:
加重命名:
给 Worker 加一个 PUT /rename 接口,接收 JSON { oldKey, newKey },
实现方式是:读取旧文件 → 写入新 key → 删除旧文件。
加防盗链:
给 GET 图片接口加防盗链检查。
检查 Referer 头,只允许以下域名访问:
- example.com(和所有子域名)
- localhost
- 127.0.0.1
如果 Referer 不在白名单里,返回 403。
没有 Referer 的请求(直接访问)放行。
加 AI Agent 接口:
POST 接口除了支持 FormData,还要支持 JSON 模式:
1. { url: "https://...", folder: "blog", filename: "photo.jpg" } — 从 URL 抓图上传
2. { base64: "...", contentType: "image/png", folder: "blog" } — base64 上传
这样 AI Agent 就能通过 JSON API 上传图片了。
加文件夹浏览:
GET /list 接口加参数支持:
- ?prefix=blog/&delimiter=/ — 列出 blog/ 文件夹下的文件和子文件夹
- 无参数时列出所有文件(兼容旧行为)
返回格式:{ files: [...], folders: [...], prefix: "blog/" }
每个 Prompt 就是一个需求,Claude Code 直接改代码。整个 Worker 从 50 行迭代到 190 行,全程没手写一行。
2.3 最终 Worker 架构
POST / → 上传(FormData 前端用 / JSON AI Agent 用)
GET /list → 列出文件(支持文件夹浏览)
GET /{key} → 获取图片(带防盗链检查)
PUT /rename → 重命名/移动文件
DELETE /{key} → 删除文件
OPTIONS * → CORS 预检
核心代码结构(简化版):
export default {
async fetch(request, env) {
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, X-Auth-Token",
};
// CORS 预检
if (request.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
const url = new URL(request.url);
const key = decodeURIComponent(url.pathname.slice(1));
// 认证检查(写操作)
const checkAuth = () => {
const token = request.headers.get("X-Auth-Token");
return token === env.AUTH_TOKEN;
};
// GET /list — 文件列表
if (request.method === "GET" && key === "list") {
if (!checkAuth()) return new Response("Unauthorized", { status: 401 });
// ... 列出文件,支持 prefix 和 delimiter 参数
}
// GET /{key} — 获取图片(带防盗链)
if (request.method === "GET" && key) {
// 检查 Referer 是否在白名单
const referer = request.headers.get("Referer");
if (referer) {
const allowedHosts = ["example.com", "localhost", "127.0.0.1"];
const refHost = new URL(referer).hostname;
if (!allowedHosts.some(h => refHost === h || refHost.endsWith("." + h))) {
return new Response("Forbidden", { status: 403 });
}
}
// ... 返回图片内容
}
// POST / — 上传(支持 FormData 和 JSON 两种模式)
if (request.method === "POST") {
if (!checkAuth()) return new Response("Unauthorized", { status: 401 });
const ct = request.headers.get("Content-Type") || "";
if (ct.includes("application/json")) {
const body = await request.json();
if (body.url) { /* 从 URL 抓图上传 */ }
if (body.base64) { /* base64 解码上传 */ }
} else {
// FormData 模式(前端用)
}
}
// PUT /rename — 重命名
// DELETE /{key} — 删除
}
};
第三步:Vibe 出 HTML 前端管理器
这是最大的一个 Prompt,我把所有想要的功能一次性描述清楚:
帮我做一个单文件 HTML 的图床管理工具 PicManager.html,要求:
【上传功能】
- 拖拽上传 + 点击选择文件
- 上传到当前浏览的文件夹
- 上传进度条
【文件管理】
- 图片网格展示,可调节大小
- 单选/多选/全选
- 重命名、移动、删除
- 批量操作(批量删除、批量复制链接)
- 搜索过滤
- 按时间/大小/名称排序
【文件夹管理】
- 左侧文件夹树
- 面包屑导航
- 创建/删除文件夹
- 文件夹间移动文件
【图片预览】
- 点击放大(灯箱模式)
- 左右翻页
【图片编辑】
- 裁剪(自由/1:1/4:3/16:9)
- 压缩(质量滑块)
- 调整尺寸
- 格式转换(JPG/PNG/WebP)
- 保存为新文件或覆盖原文件
【链接复制】
- 一键复制:原始 URL、Markdown、HTML、BBCode
【UI 设计】
- Lightroom 风格暗色主题
- 响应式布局
【配置】
- Worker URL、Auth Token、自定义域名存在 localStorage
- 首次使用弹出设置窗口
图片裁剪用 Cropper.js(CDN 引入)。
其他零依赖,纯原生 JS。
Claude Code 一次性生成了 1870 行的完整单文件应用。拖拽上传、灯箱预览、图片编辑、文件夹树——全部可用。
迭代修 Bug
生成的代码不可能一次完美,但 Vibe Coding 的好处是修 Bug 也用自然语言:
图片编辑器加载图片失败了,控制台报 CORS 错误。
能不能改成先通过 Worker API fetch 图片,
然后转成 blob URL 再给编辑器用?
这样就绕过了 canvas 的跨域安全策略。
Claude Code 理解了问题本质,把 <img crossOrigin="anonymous" src="..."> 改成了 blob URL 方案:
// 修复前:直接加载外部 URL,canvas 会报 CORS
img.crossOrigin = 'anonymous';
img.src = imageUrl;
// 修复后:通过 Worker 中转,完全绕过 CORS
const res = await fetch(workerUrl + '/' + encodeURIComponent(key));
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob);
img.src = blobUrl;
这就是 Vibe Coding 解 Bug 的体验——你描述现象和期望,AI 找到根因并修复。
第四步:部署上线
4.1 部署 Worker
wrangler deploy
部署成功后会得到一个 URL,类似:
https://my-picbed.your-account.workers.dev
4.2 使用前端
直接在浏览器打开 PicManager.html(双击就行,不需要服务器)。
首次使用会弹出设置窗口,填入:
– Worker URL: https://my-picbed.your-account.workers.dev
– Auth Token: 你之前设置的令牌
– 自定义域名: (可选,后面讲)
点保存,就能开始用了。
第五步:接入自定义域名(可选)
默认的 workers.dev URL 太长,可以绑定自己的域名。
5.1 在 Cloudflare 添加域名
确保你的域名在 Cloudflare DNS 管理下(nameserver 指向 Cloudflare)。
5.2 添加自定义域
Cloudflare Dashboard → Workers → 你的 Worker → 设置 → 域和路由 → 添加 → 自定义域。
输入你想用的子域名,比如 img.example.com。
Cloudflare 会自动配置 DNS 记录和 SSL 证书。等几分钟生效后,你的图片就可以通过类似 https://img.example.com/blog/photo.jpg 这样的短链接访问了。
5.3 更新前端配置
在 PicManager 设置里填入自定义域名,这样复制的链接会自动使用短域名。
第六步:让 AI Agent 管理你的图床
这是整个项目最有趣的部分——让 AI 通过自然语言管理你的图床。
6.1 Python SDK
先给 AI Agent 准备一个 SDK(还是让 Claude Code 写的):
帮我写一个 Python SDK picmanager.py,封装图床 API。
功能:upload_from_url, upload_file, upload_base64, list_all,
list_folder, search, rename, move, delete, url, markdown。
依赖 requests 库。
使用示例:
from picmanager import PicManager
pm = PicManager(
worker_url="https://my-picbed.your-account.workers.dev",
auth_token="your-token",
custom_domain="https://img.example.com"
)
# 从 URL 抓图上传
result = pm.upload_from_url("https://example.com/photo.jpg", folder="blog")
print(result["url"]) # https://img.example.com/blog/photo.jpg
# 上传本地文件
result = pm.upload_file("/path/to/image.png", folder="avatars")
# 获取 Markdown 引用
md = pm.markdown("blog/photo.jpg", alt="博客配图")
# 
# 列出所有图片
images = pm.list_all()
# 搜索
results = pm.search("photo")
# 获取统计
info = pm.info()
# {"total_files": 42, "total_size_human": "128.3 MB", ...}
6.2 Node.js SDK
同样让 Claude Code 写一个 Node.js 版本,零依赖(使用 Node 18+ 内置 fetch):
import { PicManager } from './picmanager.mjs';
const pm = new PicManager({
workerUrl: 'https://my-picbed.your-account.workers.dev',
authToken: 'your-token',
customDomain: 'https://img.example.com'
});
const result = await pm.uploadFromUrl('https://example.com/photo.jpg', { folder: 'blog' });
console.log(result.url);
6.3 OpenClaw 接入
OpenClaw 是一个 AI Agent 平台,支持本地运行和云端部署。它可以通过 Skill 扩展能力。
什么是 OpenClaw Skill?
Skill 是一个文件夹,里面有一个 SKILL.md 文件,用 Markdown 告诉 AI Agent “你能做什么、怎么做”。Agent 读到这个文件,就学会了一项新技能。
创建 PicManager Skill
~/.openclaw/workspace/skills/picmanager/
├── SKILL.md # 技能描述(Agent 的说明书)
├── cli.mjs # CLI 命令行工具
├── picmanager.mjs # Node.js SDK
└── .env # 配置(Worker URL + Token)
SKILL.md(核心文件,告诉 Agent 怎么用图床):
---
name: picmanager
description: 管理图床——上传、列出、搜索、重命名、移动、删除图片,获取图片链接。
---
# PicManager — 图床管理技能
你可以通过此技能管理用户的图床。
## 使用方法
通过 system.run 调用 CLI 工具:
\```bash
(cd ~/.openclaw/workspace/skills/picmanager && set -a && source .env && set +a && node cli.mjs <command>)
\```
### 可用命令
- upload-url <url> [--folder <f>] — 从 URL 抓图上传
- upload-file <path> [--folder <f>] — 上传本地文件
- list — 列出所有图片
- list-folder [path] — 列出文件夹内容
- search <keyword> — 搜索图片
- rename <old> <new> — 重命名
- move <key> <folder> — 移动文件
- delete <key> — 删除文件
- url <key> — 获取访问链接
- markdown <key> [alt] — 获取 Markdown 引用
- info — 获取统计信息
cli.mjs(Agent 实际调用的命令行工具):
#!/usr/bin/env node
import { PicManager } from './picmanager.mjs';
const [,, cmd, ...args] = process.argv;
function getPM() {
const workerUrl = process.env.PICMANAGER_WORKER_URL;
const authToken = process.env.PICMANAGER_AUTH_TOKEN;
if (!workerUrl || !authToken) {
console.error(JSON.stringify({ error: '请设置环境变量' }));
process.exit(1);
}
return new PicManager({
workerUrl,
authToken,
customDomain: process.env.PICMANAGER_CUSTOM_DOMAIN || ''
});
}
// ... switch(cmd) 分发到 SDK 各方法
.env(敏感配置,不进版本控制):
PICMANAGER_WORKER_URL=https://my-picbed.your-account.workers.dev
PICMANAGER_AUTH_TOKEN=your-token-here
PICMANAGER_CUSTOM_DOMAIN=https://img.example.com
OpenClaw 本地端 vs 云端
| 对比项 | 本地端 (Desktop) | 云端 (Server) |
|---|---|---|
| 运行环境 | 你的电脑 | 远程服务器 |
| 安装方式 | 下载桌面应用或 npm install |
Docker 部署或进程管理 |
| Skill 目录 | ~/.openclaw/workspace/skills/ |
同路径(服务器上) |
| 特有能力 | 浏览器控制、屏幕截图、本地文件访问 | 7×24 在线、定时任务、Webhook 触发 |
| 图床场景 | 手动对话:”帮我上传这张图” | 自动化:”监控文件夹,新图自动上传” |
本地端典型用法:
你在写博客时,对 OpenClaw 说:
“帮我把桌面上的 screenshot.png 上传到 blog-2024 文件夹,然后给我 Markdown 链接”
Agent 执行:
node cli.mjs upload-file ~/Desktop/screenshot.png --folder blog-2024
node cli.mjs markdown blog-2024/screenshot.png "截图"
返回:

云端典型用法:
在服务器上配置 OpenClaw,结合定时任务或 Webhook:
- 定时清理 30 天前的临时图片
- 接收 Webhook 后自动从 URL 抓图并上传
- 批量处理:把某个文件夹的图片全部移动到归档目录
- 与其他 Skill 联动:写完文章后自动提取图片 URL 并上传到自己的图床
# 示例:定时清理脚本(可作为 cron 或 OpenClaw 定时任务)
node cli.mjs list | node -e "
const imgs = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
const cutoff = Date.now() - 30*24*60*60*1000;
const old = imgs.filter(i => new Date(i.uploaded) < cutoff && i.key.startsWith('temp/'));
old.forEach(i => console.log(i.key));
"
# 然后对每个 key 执行 delete
踩过的坑与解决方案
坑 1:图片编辑器 CORS 报错
现象:在 HTML 前端的图片编辑器里,裁剪后导出 canvas 报 “Tainted canvases may not be exported”。
原因:<img> 标签设置 crossOrigin="anonymous" 加载外部图片时,如果服务器的 CORS 头不完美,canvas 会被标记为”受污染”,禁止 toBlob()/toDataURL()。
解决:不用 <img> 直接加载外部 URL,改成先通过 Worker API 把图片 fetch 下来,转成 blob URL:
const res = await fetch(workerUrl + '/' + encodeURIComponent(key));
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob);
img.src = blobUrl; // blob: 协议不受 CORS 限制
坑 2:R2 没有”文件夹”概念
现象:想实现文件夹管理,但 R2(和所有对象存储)没有真正的文件夹。
解决:用 key 中的 / 模拟文件夹结构。blog/photo.jpg 就是”blog 文件夹下的 photo.jpg”。
R2 的 list() 方法支持 prefix 和 delimiter 参数:
// 列出 blog/ 下的内容
const list = await env.BUCKET.list({ prefix: "blog/", delimiter: "/" });
// list.objects → 该文件夹下的文件
// list.delimitedPrefixes → 子文件夹列表
创建空文件夹:上传一个 0 字节的 .folder 占位文件。
坑 3:防盗链要考虑子域名
现象:白名单写了 example.com,但 blog.example.com 被拦截了。
解决:检查时同时匹配精确域名和子域名:
allowedHosts.some(h => refHost === h || refHost.endsWith("." + h))
坑 4:单文件 HTML 的配置管理
现象:不想把 Token 硬编码在 HTML 里,但又不想引入构建工具。
解决:用 localStorage 存配置,首次打开弹出设置窗口。配置存在用户浏览器本地,HTML 文件本身不含任何敏感信息,可以放心分享。
完整文件清单
my-picbed/
├── src/
│ └── index.js # Cloudflare Worker(后端 API,~190 行)
├── wrangler.toml # Wrangler 部署配置
├── PicManager.html # 前端管理器(单文件,~1870 行)
├── picmanager.py # Python SDK(依赖 requests)
├── picmanager.mjs # Node.js SDK(零依赖)
└── package.json
# OpenClaw Skill(如果用 OpenClaw)
~/.openclaw/workspace/skills/picmanager/
├── SKILL.md # 技能描述
├── cli.mjs # CLI 命令行工具
├── picmanager.mjs # Node.js SDK(同上)
└── .env # 配置(敏感信息)
写在最后
Vibe Coding 的体验
整个项目从零到完成,我做的事情是:
- 描述需求(用自然语言)
- 测试效果(在浏览器里点点看)
- 反馈问题(”这里有 Bug”、”加个功能”)
没有查文档、没有写模板代码、没有调 CSS 像素。Claude Code 处理了所有技术细节——从 Cloudflare Worker 的 API 设计到 Cropper.js 的集成到 R2 文件夹模拟的实现。
这不是说”AI 取代了编程”,而是编程的接口变了。以前的接口是键盘和 IDE,现在多了一个自然语言的接口。你需要知道自己要什么(产品思维),但不需要知道每一行怎么写。
费用
| 项目 | 费用 |
|---|---|
| Cloudflare R2 | 免费(10GB 以内) |
| Cloudflare Worker | 免费(10 万次/天) |
| 域名 | 看你选什么,十几块到几百块/年 |
| Claude Code | Anthropic API 费用,这个项目大概花了几美元 |
| OpenClaw | 免费版可用 |
总计:几美元 + 域名费(可选),得到一个完全自主可控的图床系统。
扩展方向
- 图片 CDN 优化:Cloudflare 自带全球 CDN,R2 的读取本身就很快
- 水印功能:在 Worker 里用 WebAssembly 加水印
- 图片转换:上传时自动转 WebP 节省空间
- 多用户:给不同用户分配不同的文件夹和令牌
- 接入更多 AI:除了 OpenClaw,任何能发 HTTP 请求的 AI Agent 都能用 JSON API
本文是一次真实的 Vibe Coding 实践记录。所有代码均由 Claude Code 生成,作者负责需求描述和方向把控。
了解 ~/jintaoblog/朝夕见闻志⚡️ 的更多信息
订阅后即可通过电子邮件收到最新文章。
