R2 对象存储
Cloudflare R2 是一个对象存储服务,兼容 AWS S3 API,最大的特点是零出口流量费用。它非常适合存储图片、视频、备份文件等大量数据。
R2 简介
什么是 R2?
R2 是 Cloudflare 提供的对象存储服务,让你能够存储大量非结构化数据(图片、视频、文档等)。与传统对象存储不同,R2 不收取出口流量费用。
R2 与 S3 对比
| 特性 | Cloudflare R2 | AWS S3 |
|---|---|---|
| 存储费用 | $0.015/GB/月 | $0.023/GB/月 |
| 出口流量 | 免费 | $0.09/GB |
| API | S3 兼容 | 原生 S3 |
| CDN 集成 | 内置 | 需额外配置 CloudFront |
免费额度
| 资源 | 免费额度 |
|---|---|
| 存储空间 | 10GB |
| A 类操作(写入) | 100 万次/月 |
| B 类操作(读取) | 1000 万次/月 |
R2 的优势
零出口费用:这是 R2 最大的优势。传统云存储按流量收费,R2 完全免费。
S3 兼容:使用现有的 S3 SDK 和工具,无需修改代码。
全球加速:通过 Cloudflare CDN 加速访问。
与 Workers 集成:可以在边缘直接操作 R2。
快速开始
创建存储桶
- 登录 Cloudflare 控制台
- 进入 "R2 Object Storage"
- 点击 "Create bucket"
- 输入存储桶名称
- 选择位置提示(可选)
- 点击 "Create bucket"
存储桶命名规则
- 长度:3-63 个字符
- 只能包含小写字母、数字和连字符
- 必须以字母或数字开头和结尾
- 不能使用 IP 地址格式
公开访问
默认情况下,存储桶是私有的。要公开访问:
- 进入存储桶设置
- 点击 "Settings" 标签
- 在 "Public access" 部分,点击 "Allow Access"
- 配置自定义域名或使用 R2 公开 URL
配置自定义域名
步骤一:添加域名
- 进入存储桶的 "Settings"
- 在 "Custom domains" 部分,点击 "Connect Domain"
- 输入域名(如
cdn.example.com) - 如果域名托管在 Cloudflare,会自动配置 DNS
步骤二:验证
配置完成后,可以通过自定义域名访问文件:
https://cdn.example.com/image.jpg
API 访问
创建 API 令牌
- 进入 "R2 Object Storage"
- 点击 "Manage R2 API Tokens"
- 点击 "Create API token"
- 设置权限和存储桶访问范围
- 保存 Access Key ID 和 Secret Access Key
使用 AWS SDK
R2 兼容 S3 API,可以使用 AWS SDK:
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
const client = new S3Client({
region: 'auto',
endpoint: 'https://<account-id>.r2.cloudflarestorage.com',
credentials: {
accessKeyId: 'your-access-key',
secretAccessKey: 'your-secret-key',
},
});
// 上传文件
await client.send(new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'image.jpg',
Body: fileBuffer,
ContentType: 'image/jpeg',
}));
// 下载文件
const response = await client.send(new GetObjectCommand({
Bucket: 'my-bucket',
Key: 'image.jpg',
}));
使用 Python boto3
import boto3
s3 = boto3.client(
's3',
endpoint_url='https://<account-id>.r2.cloudflarestorage.com',
aws_access_key_id='your-access-key',
aws_secret_access_key='your-secret-key',
region_name='auto'
)
# 上传文件
s3.upload_file('local-file.jpg', 'my-bucket', 'remote-file.jpg')
# 下载文件
s3.download_file('my-bucket', 'remote-file.jpg', 'local-file.jpg')
在 Workers 中使用 R2
绑定 R2 存储桶
在 wrangler.toml 中配置:
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"
上传文件
export default {
async fetch(request, env, ctx) {
const formData = await request.formData();
const file = formData.get('file');
await env.MY_BUCKET.put(file.name, file.stream(), {
httpMetadata: {
contentType: file.type,
},
});
return Response.json({ success: true, key: file.name });
},
};
下载文件
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
const object = await env.MY_BUCKET.get(key);
if (!object) {
return new Response('Not Found', { status: 404 });
}
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata.contentType,
'Cache-Control': 'public, max-age=31536000',
},
});
},
};
列出文件
export default {
async fetch(request, env, ctx) {
const listed = await env.MY_BUCKET.list();
return Response.json({
objects: listed.objects.map(obj => ({
key: obj.key,
size: obj.size,
uploaded: obj.uploaded,
})),
});
},
};
删除文件
await env.MY_BUCKET.delete('file.jpg');
图床实战
创建图床 Worker
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
if (request.method === 'POST' && url.pathname === '/upload') {
const formData = await request.formData();
const file = formData.get('file');
if (!file || !file.type.startsWith('image/')) {
return new Response('Invalid file', { status: 400 });
}
const key = `images/${Date.now()}-${file.name}`;
await env.MY_BUCKET.put(key, file.stream(), {
httpMetadata: { contentType: file.type },
});
return Response.json({
url: `https://cdn.example.com/${key}`,
key,
});
}
if (request.method === 'GET') {
const key = url.pathname.slice(1);
const object = await env.MY_BUCKET.get(key);
if (!object) {
return new Response('Not Found', { status: 404 });
}
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata.contentType,
'Cache-Control': 'public, max-age=31536000',
},
});
}
return new Response('Method Not Allowed', { status: 405 });
},
};
CORS 配置
设置 CORS 规则
在存储桶设置中配置 CORS:
[
{
"AllowedOrigins": ["https://example.com"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
生命周期规则
可以设置自动删除或转移旧文件的规则:
- 进入存储桶设置
- 点击 "Object lifecycle rules"
- 创建规则设置过期时间
常见问题
如何迁移现有数据?
使用 rclone 或 AWS CLI 同步数据:
# 配置 rclone
rclone config
# 同步数据
rclone sync s3:source-bucket r2:target-bucket
如何防止盗链?
配置 CORS 规则,只允许特定域名访问,或使用 Workers 检查 Referer。
文件大小限制?
单文件最大 5TB,但上传超过 5GB 需要使用分片上传。
参考链接
下一步
完成 R2 学习后,接下来学习 D1 数据库,了解如何使用边缘数据库。