[ PROMPT_NODE_24254 ]
R2 设计模式
[ SKILL_DOCUMENTATION ]
# R2 模式与最佳实践
## 流式传输大文件
typescript
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 });
## 条件 GET (304 Not Modified)
typescript
const ifNoneMatch = request.headers.get('if-none-match');
const object = await env.MY_BUCKET.get(key, {
onlyIf: { etagDoesNotMatch: ifNoneMatch?.replace(/"/g, '') || '' }
});
if (!object) return new Response('Not found', { status: 404 });
if (!object.body) return new Response(null, { status: 304, headers: { 'etag': object.httpEtag } });
return new Response(object.body, { headers: { 'etag': object.httpEtag } });
## 带验证的上传
typescript
const key = url.pathname.slice(1);
if (!key || key.includes('..')) return new Response('Invalid key', { status: 400 });
const object = await env.MY_BUCKET.put(key, request.body, {
httpMetadata: { contentType: request.headers.get('content-type') || 'application/octet-stream' },
customMetadata: { uploadedAt: new Date().toISOString(), ip: request.headers.get('cf-connecting-ip') || 'unknown' }
});
return Response.json({ key: object.key, size: object.size, etag: object.httpEtag });
## 带进度的分片上传
typescript
const PART_SIZE = 5 * 1024 * 1024; // 5MB
const partCount = Math.ceil(file.size / PART_SIZE);
const multipart = await env.MY_BUCKET.createMultipartUpload(key, { httpMetadata: { contentType: file.type } });
const uploadedParts: R2UploadedPart[] = [];
try {
for (let i = 0; i 0) {
await env.MY_BUCKET.delete(listed.objects.map(o => o.key));
}
truncated = listed.truncated;
cursor = listed.cursor;
}
}
## 校验和验证与存储转换
t