ADVANCED · 深度实战 · 零服务器
Workers R2 · D1 实战指南
三个 Cloudflare 核心产品,一个图片托管服务。用边缘函数处理请求、对象存储保存图片、SQLite 数据库记录元数据——全程零服务器,完全免费。
10万
Workers 请求/天
10GB
R2 免费存储
5GB
D1 免费存储
200+
全球节点
👤 用户浏览器
HTTP 请求
⚡ Worker
唯一入口
唯一入口
PUT file
🪣 R2
存图片文件
存图片文件
INSERT / SELECT
🗄️ D1
存元数据
存元数据
上传流程
POST /upload → Worker 收到文件 → put() 存入 R2 → INSERT 写入 D1 → 返回图片 URL
查询流程
GET /images → Worker 查询 D1 → SELECT 获取列表 → 返回含 R2 URL 的 JSON 数组
极速冷启动
基于 V8 隔离器,冷启动 <1ms,比传统 Serverless 快 100 倍
免费额度慷慨
每天 10 万次请求完全免费,CPU 时间 10ms/请求
全球边缘部署
一次 deploy,自动部署到全球 200+ PoP,就近响应用户
最简 Worker:Hello World
src/index.js
export default { async fetch(request, env) { const url = new URL(request.url); return new Response(`Hello from Workers! 路径: ${url.pathname}`, { headers: { 'Content-Type': 'text/plain; charset=utf-8' }, }); }, };
wrangler.toml 配置
wrangler.toml
name = "my-worker" main = "src/index.js" compatibility_date = "2024-01-01"
关键命令
BASH
# 初始化新项目(wrangler init 已废弃,使用此命令) npm create cloudflare@latest my-worker # 本地开发(热重载) wrangler dev # 部署到生产 wrangler deploy # 查看日志 wrangler tail
与 S3 的最大区别:R2 的出口流量完全免费。AWS S3 按流量收费,大量下载时费用会暴涨;R2 没有这个问题。
图片/文件存储
通过 Worker 上传,生成公开 URL 供前端展示
访问控制
默认私有,可通过 Worker 鉴权后再返回文件内容,也可开启 bucket 级公开访问
Worker 中操作 R2
JavaScript · R2 读写示例
// 上传文件(env.BUCKET 是 R2 bucket 的 binding) const key = `images/${Date.now()}.jpg`; await env.BUCKET.put(key, file.stream(), { httpMetadata: { contentType: file.type }, }); // 读取文件 const object = await env.BUCKET.get(key); if (!object) return new Response('Not Found', { status: 404 }); return new Response(object.body, { headers: { 'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream' }, }); // 删除文件 await env.BUCKET.delete(key);
wrangler.toml 绑定(追加到已有配置)
wrangler.toml · 片段
# 追加到 wrangler.toml [[r2_buckets]] binding = "BUCKET" # Worker 里用 env.BUCKET 访问 bucket_name = "my-images" # R2 bucket 的实际名称
关键命令
BASH
# 创建 bucket wrangler r2 bucket create my-images # 列出 bucket 中的文件 wrangler r2 object list my-images # 上传本地文件到 R2(调试用) wrangler r2 object put my-images/test.jpg --file ./test.jpg # 开启公开访问(Dashboard 操作): # R2 → Bucket → Settings → Public Access → Allow Access # 开启后获得 pub-xxx.r2.dev 域名
标准 SQL
完整 SQLite 语法,支持 CREATE / INSERT / SELECT / UPDATE / DELETE,熟悉 SQL 即可上手
与 Worker 原生集成
通过 binding 直接在 Worker 代码里查询,无需数据库连接字符串,无 TCP 连接开销
建表(schema.sql)
SQL · schema.sql
CREATE TABLE IF NOT EXISTS images ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL, -- R2 中的文件路径 url TEXT NOT NULL, -- 公开访问 URL filename TEXT, -- 原始文件名 created_at DATETIME DEFAULT CURRENT_TIMESTAMP );
Worker 中操作 D1
JavaScript · D1 读写示例
// 插入一条记录(env.DB 是 D1 的 binding) await env.DB .prepare('INSERT INTO images (key, url, filename) VALUES (?, ?, ?)') .bind(key, url, filename) .run(); // 查询最新 20 条 const { results } = await env.DB .prepare('SELECT * FROM images ORDER BY created_at DESC LIMIT 20') .all(); // 按 key 查单条 const row = await env.DB .prepare('SELECT * FROM images WHERE key = ?') .bind(key) .first();
wrangler.toml 绑定(追加到已有配置)
wrangler.toml · 片段
# 追加到 wrangler.toml [[d1_databases]] binding = "DB" # Worker 里用 env.DB 访问 database_name = "images-db" # D1 数据库名称 database_id = "your-id" # 创建后从控制台获取
关键命令
BASH
# 创建数据库(输出中包含 database_id) wrangler d1 create images-db # 执行 schema 建表 wrangler d1 execute images-db --file=./schema.sql # 本地调试时执行(不影响生产) wrangler d1 execute images-db --local --file=./schema.sql # 直接执行 SQL 语句 wrangler d1 execute images-db --command "SELECT * FROM images"
实现两个接口:
POST /upload 上传图片,GET /images 查询列表。第 1 步:建表
创建 schema.sql 文件并在 D1 中执行:
schema.sql
CREATE TABLE IF NOT EXISTS images ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL, url TEXT NOT NULL, filename TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );
第 2 步:Worker 主逻辑
创建 src/index.js:
src/index.js · 完整代码
export default { async fetch(request, env) { const url = new URL(request.url); if (request.method === 'POST' && url.pathname === '/upload') { return handleUpload(request, env); } if (request.method === 'GET' && url.pathname === '/images') { return handleList(env); } return new Response('Not Found', { status: 404 }); }, }; async function handleUpload(request, env) { // 1. 解析 multipart 表单,获取文件字段 const formData = await request.formData(); const file = formData.get('file'); if (!file) { return json({ error: '缺少 file 字段' }, 400); } // 2. 生成唯一 key,存入 R2 const ext = file.name.split('.').pop() || 'bin'; const key = `images/${Date.now()}-${Math.random().toString(36).slice(2)}.${ext}`; await env.BUCKET.put(key, file.stream(), { httpMetadata: { contentType: file.type }, }); // 3. 组装公开 URL(需在 Dashboard 开启 bucket 公开访问) const url = `https://pub-YOUR_BUCKET_ID.r2.dev/${key}`; // 4. 写入 D1 元数据 await env.DB .prepare('INSERT INTO images (key, url, filename) VALUES (?, ?, ?)') .bind(key, url, file.name) .run(); return json({ success: true, url, key }); } async function handleList(env) { const { results } = await env.DB .prepare('SELECT * FROM images ORDER BY created_at DESC LIMIT 20') .all(); return json(results); } // 辅助:返回 JSON 响应 function json(data, status = 200) { return new Response(JSON.stringify(data, null, 2), { status, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, }); }
第 3 步:完整 wrangler.toml
wrangler.toml · 完整配置
name = "image-host" main = "src/index.js" compatibility_date = "2024-01-01" [[r2_buckets]] binding = "BUCKET" bucket_name = "my-images" [[d1_databases]] binding = "DB" database_name = "images-db" database_id = "从 wrangler d1 create 输出中获取"
第 4 步:部署流程
1
创建 R2 Bucket 并开启公开访问
BASH
wrangler r2 bucket create my-images # 然后在 Dashboard: R2 → my-images → Settings → Public Access → Allow
2
创建 D1 数据库并建表
BASH
wrangler d1 create images-db # 复制输出的 database_id 填入 wrangler.toml wrangler d1 execute images-db --file=./schema.sql
3
部署 Worker
BASH
wrangler deploy # 成功后得到 https://image-host.your-account.workers.dev
4
测试接口
BASH · 测试
# 上传图片 curl -X POST https://image-host.your-account.workers.dev/upload \ -F "file=@./photo.jpg" # 查询列表 curl https://image-host.your-account.workers.dev/images
🔐
加 API Key 鉴权
在 Worker 里校验
Authorization: Bearer <key> header,Key 存在 Workers Secrets 里(wrangler secret put API_KEY),防止任何人都能上传。🗑️
加删除接口
新增
DELETE /images/:key 接口,同时调用 env.BUCKET.delete(key) 删除 R2 文件,以及 env.DB.prepare('DELETE FROM images WHERE key = ?') 清除记录。🌐
绑定自定义域名
在 Cloudflare Dashboard → Workers → 你的 Worker → Settings → Domains & Routes,添加自定义域名,让接口变成
https://api.yourdomain.com/upload。