mcpField
C
cloudflare-r2

cloudflare-r2

github.com/bagelhole云与 DevOps1.2k339 次安装23 周前更新
代码仓库

关于 cloudflare-r2

用于管理 Cloudflare R2 对象存储的技能,面向存储文件、媒体、备份和模型产物的开发者。它介绍 S3 兼容访问,以及通过 Wrangler、Worker 绑定和公开存储桶进行操作。

主要功能

  1. 01使用 Wrangler 管理存储桶和对象
  2. 02通过 S3 API 访问 R2
  3. 03将存储桶绑定到 Workers
  4. 04配置公开访问和自定义域名
  5. 05设置生命周期与 CORS 规则

适用场景

  1. 01保存用户上传文件和媒体
  2. 02存储备份或静态资源
  3. 03通过 Workers 提供边缘文件访问
  4. 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 令牌
  1. 前往 R2 > Manage R2 API Tokens > Create API token。
  2. 选择权限:对象读取和写入,或仅对象读取。
  3. 尽可能限定到特定存储桶。
  4. 保存 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 即可直接提供文件。

  1. 前往 R2 > 存储桶 > Settings > Public access。
  2. 启用公开访问并设置自定义域名(例如,assets.example.com)。
  3. 可通过 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 绑定返回 undefinedwrangler.toml 中的绑定名称不匹配确认 binding 名称与 Env 接口属性一致
公开访问返回 403未启用公开访问在存储桶 Settings > Public access 中启用

相关技能

评论

支持 Markdown