关于 cloudflare-r2
用于管理 Cloudflare R2 对象存储的技能,面向存储文件、媒体、备份和模型产物的开发者。它介绍 S3 兼容访问,以及通过 Wrangler、Worker 绑定和公开存储桶进行操作。
主要功能
- 01使用 Wrangler 管理存储桶和对象
- 02通过 S3 API 访问 R2
- 03将存储桶绑定到 Workers
- 04配置公开访问和自定义域名
- 05设置生命周期与 CORS 规则
适用场景
- 01保存用户上传文件和媒体
- 02存储备份或静态资源
- 03通过 Workers 提供边缘文件访问
- 04保存机器学习模型产物和训练数据
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ bagelhole/ devops-security-agent-skills --skill cloudflare-r2安装后在 Claude Code 中输入/cloudflare-r2确认已加载。
详细介绍
Cloudflare R2
基于 Cloudflare 全球网络构建的兼容 S3 的对象存储,不收取出口流量费用。
适用场景
- 存储用户上传内容、媒体文件、备份或静态资源。
- 替代 AWS S3,为读取密集型工作负载消除出口流量费用。
- 通过 Workers 或公开存储桶访问,在边缘提供文件服务。
- 构建多云存储,避免供应商锁定(兼容 S3 API)。
- 存储机器学习模型制品、训练数据或推理结果。
前提条件
- 已启用 R2 的 Cloudflare 账户(控制台 > R2 > 订阅)。
- 已安装 Wrangler CLI v3+:
npm install -g wrangler。 - 已通过
wrangler login或CLOUDFLARE_API_TOKEN完成身份验证。 - 如需访问 S3 API:在 R2 > Manage R2 API Tokens 下创建 R2 API 令牌。
使用 Wrangler 管理存储桶
创建和列出存储桶
# Create a new bucket
npx wrangler r2 bucket create app-assets
# Create a bucket in a specific region (hint for data locality)
npx wrangler r2 bucket create eu-uploads --location=eu
# List all buckets
npx wrangler r2 bucket list
# Delete an empty bucket
npx wrangler r2 bucket delete old-bucket
对象操作
# Upload a single file
npx wrangler r2 object put app-assets/images/logo.png --file=./logo.png
# Upload with content type
npx wrangler r2 object put app-assets/data/report.json \
--file=./report.json \
--content-type="application/json"
# Download an object
npx wrangler r2 object get app-assets/images/logo.png --file=./downloaded-logo.png
# Delete an object
npx wrangler r2 object delete app-assets/images/old-logo.png
# Get object metadata
npx wrangler r2 object head app-assets/images/logo.png
兼容 S3 的 API 访问
R2 支持 S3 API,因此现有工具(AWS CLI、boto3、s3cmd)开箱即用。
生成 R2 API 令牌
- 前往 R2 > Manage R2 API Tokens > Create API token。
- 选择权限:对象读取和写入,或仅对象读取。
- 尽可能限定到特定存储桶。
- 保存 Access Key ID 和 Secret Access Key。
配置 AWS CLI
# Configure a named profile for R2
aws configure --profile r2
# Access Key ID: <your-r2-access-key>
# Secret Access Key: <your-r2-secret-key>
# Region: auto
# Output: json
# Use the R2 endpoint
export R2_ENDPOINT="https://<ACCOUNT_ID>.r2.cloudflarestorage.com"
# List buckets
aws s3 ls --endpoint-url=$R2_ENDPOINT --profile=r2
# Sync a directory
aws s3 sync ./dist s3://app-assets/static/ \
--endpoint-url=$R2_ENDPOINT \
--profile=r2
# Copy a file
aws s3 cp ./backup.tar.gz s3://app-assets/backups/backup-$(date +%Y%m%d).tar.gz \
--endpoint-url=$R2_ENDPOINT \
--profile=r2
# List objects with prefix
aws s3 ls s3://app-assets/images/ \
--endpoint-url=$R2_ENDPOINT \
--profile=r2
# Remove objects by prefix
aws s3 rm s3://app-assets/tmp/ --recursive \
--endpoint-url=$R2_ENDPOINT \
--profile=r2
Python boto3 客户端
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
aws_access_key_id="<R2_ACCESS_KEY>",
aws_secret_access_key="<R2_SECRET_KEY>",
region_name="auto",
)
# Upload file
s3.upload_file("./report.pdf", "app-assets", "reports/report.pdf")
# Generate presigned URL (valid for 1 hour)
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "app-assets", "Key": "reports/report.pdf"},
ExpiresIn=3600,
)
print(url)
# List objects
response = s3.list_objects_v2(Bucket="app-assets", Prefix="images/", MaxKeys=100)
for obj in response.get("Contents", []):
print(f"{obj['Key']} - {obj['Size']} bytes")
Worker 绑定
将 R2 存储桶绑定到 Workers 或 Pages Functions,以便无需 API 令牌即可在服务器端访问。
Wrangler 配置
# wrangler.toml
name = "asset-worker"
main = "src/index.ts"
compatibility_date = "2024-09-01"
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "app-assets"
[[r2_buckets]]
binding = "UPLOADS"
bucket_name = "user-uploads"
使用 R2 执行操作的 Worker
// src/index.ts
interface Env {
ASSETS: R2Bucket;
UPLOADS: R2Bucket;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// GET — serve file from R2
if (request.method === "GET") {
const key = url.pathname.slice(1); // strip leading /
const object = await env.ASSETS.get(key);
if (!object) {
return new Response("Not Found", { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
headers.set("cache-control", "public, max-age=86400");
return new Response(object.body, { headers });
}
// PUT — upload file to R2
if (request.method === "PUT") {
const key = url.pathname.slice(1);
const contentType = request.headers.get("content-type") || "application/octet-stream";
await env.UPLOADS.put(key, request.body, {
httpMetadata: { contentType },
customMetadata: { uploadedAt: new Date().toISOString() },
});
return new Response(JSON.stringify({ key, status: "uploaded" }), {
headers: { "Content-Type": "application/json" },
});
}
// DELETE — remove file
if (request.method === "DELETE") {
const key = url.pathname.slice(1);
await env.UPLOADS.delete(key);
return new Response(null, { status: 204 });
}
return new Response("Method Not Allowed", { status: 405 });
},
};
在 Worker 中生成预签名 URL
// Generate time-limited signed URLs using Workers
import { AwsClient } from "aws4fetch";
interface Env {
R2_ACCESS_KEY: string;
R2_SECRET_KEY: string;
R2_ACCOUNT_ID: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const aws = new AwsClient({
accessKeyId: env.R2_ACCESS_KEY,
secretAccessKey: env.R2_SECRET_KEY,
});
const url = new URL(request.url);
const key = url.searchParams.get("key");
if (!key) return new Response("Missing key", { status: 400 });
const r2Url = `https://${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/app-assets/${key}`;
const signed = await aws.sign(new Request(r2Url), {
aws: { signQuery: true },
});
return Response.json({ url: signed.url });
},
};
公开存储桶访问
启用公开访问,无需 Worker 即可直接提供文件。
- 前往 R2 > 存储桶 > Settings > Public access。
- 启用公开访问并设置自定义域名(例如,
assets.example.com)。 - 可通过
https://assets.example.com/<key>访问对象。
# Or enable via the r2.dev subdomain (for testing)
# Bucket Settings > R2.dev subdomain > Allow Access
# URL: https://pub-<hash>.r2.dev/<key>
生命周期规则
配置对象自动过期或转换。
# Set lifecycle rules via the Cloudflare dashboard:
# R2 > bucket > Settings > Object lifecycle rules
# Or via API
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/r2/buckets/app-assets/lifecycle" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"rules": [
{
"id": "expire-tmp-files",
"enabled": true,
"conditions": { "prefix": "tmp/" },
"actions": { "deleteObject": { "daysAfterCreationDate": 7 } }
},
{
"id": "expire-old-logs",
"enabled": true,
"conditions": { "prefix": "logs/" },
"actions": { "deleteObject": { "daysAfterCreationDate": 90 } }
}
]
}'
CORS 配置
# Set CORS policy for browser-based uploads
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/r2/buckets/app-assets/cors" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"corsRules": [
{
"allowedOrigins": ["https://example.com"],
"allowedMethods": ["GET", "PUT", "HEAD"],
"allowedHeaders": ["Content-Type", "Authorization"],
"maxAgeSeconds": 3600
}
]
}'
故障排除
| 症状 | 原因 | 修复方法 |
|---|---|---|
通过 S3 API 出现 NoSuchBucket 错误 | 端点或存储桶名称错误 | 确认端点为 https://<ACCOUNT_ID>.r2.cloudflarestorage.com |
SignatureDoesNotMatch | 密钥错误或端点不匹配 | 重新生成 R2 API 令牌;确保区域为 auto |
| 上传成功,但 GET 返回 404 | 键路径不匹配(开头有斜杠) | R2 键不应以 / 开头 |
| 大文件上传速度慢 | 使用了单流上传 | 使用分段上传;通过 wrangler 设置 --expected-size |
| 浏览器中出现 CORS 错误 | 存储桶缺少 CORS 配置 | 为来源域名添加 CORS 规则 |
Worker 绑定返回 undefined | wrangler.toml 中的绑定名称不匹配 | 确认 binding 名称与 Env 接口属性一致 |
| 公开访问返回 403 | 未启用公开访问 | 在存储桶 Settings > Public access 中启用 |
相关技能
- cloudflare-workers - 生成签名 URL 和在边缘提供文件服务
- cloudflare-pages - 使用 R2 绑定的 Pages Functions
- cdn-setup - 用于资源交付的 CDN 配置
评论
支持 Markdown