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