cloudflare-r2
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCloudflare R2
Cloudflare R2
R2 is S3-compatible object storage with zero egress fees. Access via Workers Binding API or S3 API.
R2 是兼容S3的对象存储服务,无出口流量费用。可通过Workers Binding API或S3 API访问。
Quick Start
快速开始
bash
npx wrangler r2 bucket create my-bucketjsonc
// wrangler.jsonc
{
"r2_buckets": [{ "binding": "MY_BUCKET", "bucket_name": "my-bucket" }]
}typescript
export interface Env {
MY_BUCKET: R2Bucket;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const key = new URL(request.url).pathname.slice(1);
if (request.method === "PUT") {
await env.MY_BUCKET.put(key, request.body);
return new Response("Uploaded", { status: 201 });
}
if (request.method === "GET") {
const object = await env.MY_BUCKET.get(key);
if (!object) return new Response("Not Found", { status: 404 });
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
return new Response(object.body, { headers });
}
return new Response("Method Not Allowed", { status: 405 });
},
};bash
npx wrangler r2 bucket create my-bucketjsonc
// wrangler.jsonc
{
"r2_buckets": [{ "binding": "MY_BUCKET", "bucket_name": "my-bucket" }]
}typescript
export interface Env {
MY_BUCKET: R2Bucket;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const key = new URL(request.url).pathname.slice(1);
if (request.method === "PUT") {
await env.MY_BUCKET.put(key, request.body);
return new Response("上传完成", { status: 201 });
}
if (request.method === "GET") {
const object = await env.MY_BUCKET.get(key);
if (!object) return new Response("未找到文件", { status: 404 });
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
return new Response(object.body, { headers });
}
return new Response("不允许的请求方法", { status: 405 });
},
};Binding Configuration
绑定配置
jsonc
{
"r2_buckets": [
{
"binding": "MY_BUCKET",
"bucket_name": "my-bucket",
"preview_bucket_name": "my-bucket-preview", // Optional: for local dev
"jurisdiction": "eu" // Optional: GDPR
}
]
}See binding.md for details.
jsonc
{
"r2_buckets": [
{
"binding": "MY_BUCKET",
"bucket_name": "my-bucket",
"preview_bucket_name": "my-bucket-preview", // 可选:用于本地开发
"jurisdiction": "eu" // 可选:符合GDPR要求
}
]
}详情请参阅 binding.md。
Workers Binding API (R2Bucket)
Workers Binding API (R2Bucket)
| Method | Description |
|---|---|
| Get metadata without body |
| Get object with body |
| Store object |
| Delete up to 1000 keys |
| List objects with pagination |
| Start multipart upload |
| Resume multipart upload |
| 方法 | 描述 |
|---|---|
| 获取元数据(不含文件内容) |
| 获取对象(包含文件内容) |
| 存储对象 |
| 删除最多1000个对象 |
| 分页列出对象 |
| 启动分段上传 |
| 恢复分段上传 |
get with range
按范围获取对象
typescript
const partial = await env.MY_BUCKET.get("large.bin", {
range: { offset: 0, length: 1024 },
});typescript
const partial = await env.MY_BUCKET.get("large.bin", {
range: { offset: 0, length: 1024 },
});put with metadata
带元数据存储对象
typescript
await env.MY_BUCKET.put("file.txt", "Hello!", {
httpMetadata: { contentType: "text/plain" },
customMetadata: { author: "Alice" },
storageClass: "InfrequentAccess", // Optional
});typescript
await env.MY_BUCKET.put("file.txt", "Hello!", {
httpMetadata: { contentType: "text/plain" },
customMetadata: { author: "Alice" },
storageClass: "InfrequentAccess", // 可选
});list with pagination
分页列出对象
typescript
const listed = await env.MY_BUCKET.list({ prefix: "images/", limit: 100 });
for (const obj of listed.objects) console.log(obj.key, obj.size);
if (listed.truncated) {
/* use listed.cursor for next page */
}See api.md for complete reference.
typescript
const listed = await env.MY_BUCKET.list({ prefix: "images/", limit: 100 });
for (const obj of listed.objects) console.log(obj.key, obj.size);
if (listed.truncated) {
/* 使用listed.cursor获取下一页 */
}完整参考请参阅 api.md。
R2Object / R2ObjectBody
R2Object / R2ObjectBody
typescript
interface R2Object {
key: string;
version: string;
size: number;
etag: string;
httpEtag: string;
uploaded: Date;
httpMetadata: R2HTTPMetadata;
customMetadata: Record<string, string>;
storageClass: "Standard" | "InfrequentAccess";
writeHttpMetadata(headers: Headers): void;
}
interface R2ObjectBody extends R2Object {
body: ReadableStream;
arrayBuffer(): Promise<ArrayBuffer>;
text(): Promise<string>;
json<T>(): Promise<T>;
blob(): Promise<Blob>;
}typescript
interface R2Object {
key: string;
version: string;
size: number;
etag: string;
httpEtag: string;
uploaded: Date;
httpMetadata: R2HTTPMetadata;
customMetadata: Record<string, string>;
storageClass: "Standard" | "InfrequentAccess";
writeHttpMetadata(headers: Headers): void;
}
interface R2ObjectBody extends R2Object {
body: ReadableStream;
arrayBuffer(): Promise<ArrayBuffer>;
text(): Promise<string>;
json<T>(): Promise<T>;
blob(): Promise<Blob>;
}S3 API Compatibility
S3 API 兼容性
Endpoint:
https://<ACCOUNT_ID>.r2.cloudflarestorage.comtypescript
import { S3Client } from "@aws-sdk/client-s3";
const S3 = new S3Client({
region: "auto",
endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: { accessKeyId: ACCESS_KEY_ID, secretAccessKey: SECRET_ACCESS_KEY },
});Region: Always (or alias).
"auto""us-east-1"端点:
https://<ACCOUNT_ID>.r2.cloudflarestorage.comtypescript
import { S3Client } from "@aws-sdk/client-s3";
const S3 = new S3Client({
region: "auto",
endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: { accessKeyId: ACCESS_KEY_ID, secretAccessKey: SECRET_ACCESS_KEY },
});区域:始终为(或别名)。
"auto""us-east-1"Presigned URLs
预签名URL
typescript
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const downloadUrl = await getSignedUrl(S3, new GetObjectCommand({ Bucket: "my-bucket", Key: "file.pdf" }), { expiresIn: 3600 });
const uploadUrl = await getSignedUrl(S3, new PutObjectCommand({ Bucket: "my-bucket", Key: "file.pdf", ContentType: "application/pdf" }), { expiresIn: 3600 });Important: Presigned URLs work only with S3 endpoint (not custom domains). Configure CORS for browser use. See presigned-urls.md.
typescript
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const downloadUrl = await getSignedUrl(S3, new GetObjectCommand({ Bucket: "my-bucket", Key: "file.pdf" }), { expiresIn: 3600 });
const uploadUrl = await getSignedUrl(S3, new PutObjectCommand({ Bucket: "my-bucket", Key: "file.pdf", ContentType: "application/pdf" }), { expiresIn: 3600 });重要提示:预签名URL仅适用于S3端点(不适用于自定义域名)。若要在浏览器中使用,请配置CORS。详情请参阅 presigned-urls.md。
Multipart Uploads
分段上传
For objects > 100 MB.
typescript
const mpu = await env.MY_BUCKET.createMultipartUpload("large.zip");
const part1 = await mpu.uploadPart(1, chunk1); // Min 5 MiB
const part2 = await mpu.uploadPart(2, chunk2);
await mpu.complete([part1, part2]);
// or: await mpu.abort();Resume:
env.MY_BUCKET.resumeMultipartUpload(key, uploadId)Limits: 5 MiB–5 GiB per part, 10,000 parts max, ~5 TiB max object. See multipart.md.
适用于大于100MB的对象。
typescript
const mpu = await env.MY_BUCKET.createMultipartUpload("large.zip");
const part1 = await mpu.uploadPart(1, chunk1); // 最小5 MiB
const part2 = await mpu.uploadPart(2, chunk2);
await mpu.complete([part1, part2]);
// 或:await mpu.abort();恢复上传:
env.MY_BUCKET.resumeMultipartUpload(key, uploadId)限制:每个分段大小为5 MiB–5 GiB,最多支持10,000个分段,单个对象最大约5 TiB。详情请参阅 multipart.md。
CORS Configuration
CORS 配置
json
[
{
"AllowedOrigins": ["https://example.com"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["Content-Type"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]bash
npx wrangler r2 bucket cors set my-bucket --file cors.jsonjson
[
{
"AllowedOrigins": ["https://example.com"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["Content-Type"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]bash
npx wrangler r2 bucket cors set my-bucket --file cors.jsonLifecycle Policies
生命周期策略
bash
undefinedbash
undefinedDelete after 90 days
90天后删除对象
npx wrangler r2 bucket lifecycle add my-bucket --id "cleanup" --expire-days 90
npx wrangler r2 bucket lifecycle add my-bucket --id "cleanup" --expire-days 90
Transition to Infrequent Access
30天后转换为低频访问存储类别
npx wrangler r2 bucket lifecycle add my-bucket --id "archive" --transition-days 30 --transition-class STANDARD_IA
npx wrangler r2 bucket lifecycle add my-bucket --id "archive" --transition-days 30 --transition-class STANDARD_IA
With prefix
针对特定前缀
npx wrangler r2 bucket lifecycle add my-bucket --id "logs" --prefix "logs/" --expire-days 7
npx wrangler r2 bucket lifecycle add my-bucket --id "logs" --prefix "logs/" --expire-days 7
Abort incomplete multipart
中止未完成的分段上传
npx wrangler r2 bucket lifecycle add my-bucket --id "mpu" --abort-incomplete-days 1
See [lifecycle.md](references/lifecycle.md) for S3 API examples.
---npx wrangler r2 bucket lifecycle add my-bucket --id "mpu" --abort-incomplete-days 1
S3 API示例请参阅 [lifecycle.md](references/lifecycle.md)。
---Storage Classes
存储类别
| Class | Storage | Notes |
|---|---|---|
| Standard | $0.015/GB | Default |
| InfrequentAccess | $0.01/GB | +$0.01/GB retrieval, 30-day min |
typescript
await env.MY_BUCKET.put("archive.zip", data, { storageClass: "InfrequentAccess" });| 类别 | 存储费用 | 说明 |
|---|---|---|
| Standard | $0.015/GB | 默认类别 |
| InfrequentAccess | $0.01/GB | 额外收取$0.01/GB的取回费用,最短存储期限30天 |
typescript
await env.MY_BUCKET.put("archive.zip", data, { storageClass: "InfrequentAccess" });Event Notifications
事件通知
Push to Cloudflare Queues on object changes.
bash
npx wrangler queues create r2-events
npx wrangler r2 bucket notification create my-bucket --event-type object-create --queue r2-eventsEvents: ,
object-createobject-deletetypescript
export default {
async queue(batch: MessageBatch<R2EventMessage>, env: Env) {
for (const msg of batch.messages) {
console.log(`${msg.body.action}: ${msg.body.object.key}`);
msg.ack();
}
},
};当对象发生变化时,推送事件至Cloudflare Queues。
bash
npx wrangler queues create r2-events
npx wrangler r2 bucket notification create my-bucket --event-type object-create --queue r2-events支持的事件类型:、
object-createobject-deletetypescript
export default {
async queue(batch: MessageBatch<R2EventMessage>, env: Env) {
for (const msg of batch.messages) {
console.log(`${msg.body.action}: ${msg.body.object.key}`);
msg.ack();
}
},
};Public Buckets
公共存储桶
Custom domain (recommended)
自定义域名(推荐)
Dashboard → Bucket → Settings → Custom Domains → Add. Enables Cache, WAF, Access.
控制台 → 存储桶 → 设置 → 自定义域名 → 添加。可启用缓存、WAF、访问控制。
r2.dev (dev only)
r2.dev(仅用于开发)
Dashboard → Bucket → Settings → Public Development URL → Enable.
Warning: r2.dev is rate-limited. Use custom domain for production.
控制台 → 存储桶 → 设置 → 公共开发URL → 启用。
警告:r2.dev有请求速率限制。生产环境请使用自定义域名。
Data Migration
数据迁移
Super Slurper (bulk)
Super Slurper(批量迁移)
Dashboard → R2 → Data Migration. Copies from S3/GCS/compatible storage. Objects > 1 TB skipped.
控制台 → R2 → 数据迁移。从S3/GCS或兼容存储复制对象。大于1TB的对象将被跳过。
Sippy (incremental)
Sippy(增量迁移)
bash
npx wrangler r2 bucket sippy enable my-bucket --provider s3 --bucket source --access-key-id <KEY> --secret-access-key <SECRET>Copies on-demand as objects are requested.
bash
npx wrangler r2 bucket sippy enable my-bucket --provider s3 --bucket source --access-key-id <KEY> --secret-access-key <SECRET>当对象被请求时,按需复制。
Wrangler Commands
Wrangler 命令
bash
undefinedbash
undefinedBucket
存储桶操作
wrangler r2 bucket create|delete|list|info <name>
wrangler r2 bucket create|delete|list|info <name>
Object
对象操作
wrangler r2 object put|get|delete <bucket>/<key> [--file <path>]
wrangler r2 object put|get|delete <bucket>/<key> [--file <path>]
CORS
CORS配置
wrangler r2 bucket cors set|get|delete <bucket>
wrangler r2 bucket cors set|get|delete <bucket>
Lifecycle
生命周期策略
wrangler r2 bucket lifecycle list|add|remove <bucket>
wrangler r2 bucket lifecycle list|add|remove <bucket>
Notifications
事件通知
wrangler r2 bucket notification list|create|delete <bucket>
wrangler r2 bucket notification list|create|delete <bucket>
Sippy
Sippy迁移
wrangler r2 bucket sippy enable|disable|get <bucket>
---wrangler r2 bucket sippy enable|disable|get <bucket>
---Limits
限制
| Parameter | Limit |
|---|---|
| Buckets per account | 1,000,000 |
| Object size | 5 TiB |
| Single upload | 5 GiB |
| Multipart parts | 10,000 |
| Key length | 1,024 bytes |
| Metadata size | 8,192 bytes |
| Delete batch | 1,000 keys |
| Lifecycle rules | 1,000/bucket |
| Notification rules | 100/bucket |
| 参数 | 限制值 |
|---|---|
| 每个账户的存储桶数量 | 1,000,000 |
| 对象大小 | 5 TiB |
| 单次上传大小 | 5 GiB |
| 分段上传的分段数 | 10,000 |
| 对象键长度 | 1,024 字节 |
| 元数据大小 | 8,192 字节 |
| 批量删除的对象数 | 1,000 个 |
| 每个存储桶的生命周期规则数 | 1,000条/bucket |
| 每个存储桶的通知规则数 | 100条/bucket |
Pricing
定价
| Metric | Standard | Infrequent Access |
|---|---|---|
| Storage | $0.015/GB | $0.01/GB |
| Class A | $4.50/M | $9.00/M |
| Class B | $0.36/M | $0.90/M |
| Retrieval | Free | $0.01/GB |
| Egress | Free | Free |
Free tier: 10 GB storage, 1M Class A, 10M Class B.
Class A: PUT, COPY, LIST, CreateMultipartUpload
Class B: GET, HEAD
Free: DELETE, AbortMultipartUpload
See pricing.md for optimization tips.
| 指标 | Standard | Infrequent Access |
|---|---|---|
| 存储费用 | $0.015/GB | $0.01/GB |
| A类操作 | $4.50/百万次 | $9.00/百万次 |
| B类操作 | $0.36/百万次 | $0.90/百万次 |
| 取回费用 | 免费 | $0.01/GB |
| 出口流量费用 | 免费 | 免费 |
免费额度:10 GB存储容量,100万次A类操作,1000万次B类操作。
A类操作:PUT、COPY、LIST、CreateMultipartUpload
B类操作:GET、HEAD
免费操作:DELETE、AbortMultipartUpload
成本优化技巧请参阅 pricing.md。
Conditional Operations
条件操作
typescript
const object = await env.MY_BUCKET.get("file.txt", {
onlyIf: { etagMatches: expectedEtag },
// or: etagDoesNotMatch, uploadedBefore, uploadedAfter
});
// Or from HTTP headers
const object = await env.MY_BUCKET.get("file.txt", { onlyIf: request.headers });typescript
const object = await env.MY_BUCKET.get("file.txt", {
onlyIf: { etagMatches: expectedEtag },
// 或:etagDoesNotMatch, uploadedBefore, uploadedAfter
});
// 或从HTTP头获取条件
const object = await env.MY_BUCKET.get("file.txt", { onlyIf: request.headers });Server-Side Encryption (SSE-C)
服务器端加密(SSE-C)
typescript
await env.MY_BUCKET.put("secret.txt", data, { ssecKey: encryptionKey });
const object = await env.MY_BUCKET.get("secret.txt", { ssecKey: encryptionKey });Lost key = lost data.
typescript
await env.MY_BUCKET.put("secret.txt", data, { ssecKey: encryptionKey });
const object = await env.MY_BUCKET.get("secret.txt", { ssecKey: encryptionKey });密钥丢失将导致数据无法恢复。
Prohibitions
注意事项
- ❌ Do not use r2.dev for production
- ❌ Do not store encryption keys in code
- ❌ Do not skip CORS for browser access
- ❌ Do not ignore multipart limits (5 MiB min)
- ❌ Do not use presigned URLs with custom domains
- ❌ 请勿在生产环境中使用r2.dev
- ❌ 请勿在代码中存储加密密钥
- ❌ 浏览器访问时请勿跳过CORS配置
- ❌ 请勿忽略分段上传的限制(最小5 MiB)
- ❌ 请勿在自定义域名中使用预签名URL
References
参考文档
- binding.md — Binding configuration
- api.md — Workers API reference
- presigned-urls.md — Browser integration
- multipart.md — Large file uploads
- lifecycle.md — Object expiration
- pricing.md — Cost optimization
- binding.md — 绑定配置
- api.md — Workers API参考
- presigned-urls.md — 浏览器集成
- multipart.md — 大文件上传
- lifecycle.md — 对象过期策略
- pricing.md — 成本优化
Cross-References
交叉参考
- cloudflare-workers — Worker development
- cloudflare-pages — Pages with R2
- cloudflare-queues — Event consumers
- cloudflare-d1 — SQL database
- cloudflare-workers — Worker开发
- cloudflare-pages — 结合R2的Pages服务
- cloudflare-queues — 事件消费者
- cloudflare-d1 — SQL数据库