[ PROMPT_NODE_24252 ]
R2 常见陷阱
[ SKILL_DOCUMENTATION ]
# R2 常见陷阱与故障排除
## 列表截断 (List Truncation)
typescript
// ❌ 错误: 使用 include 时不要比较对象数量
while (listed.objects.length < options.limit) { ... }
// ✅ 正确: 始终使用 truncated 属性
while (listed.truncated) {
const next = await env.MY_BUCKET.list({ cursor: listed.cursor });
// ...
}
**原因:** 带有元数据的 `include` 可能会在每页返回较少的对象,以适应元数据大小。
## ETag 格式
typescript
// ❌ 错误: 在头部使用 etag (未加引号)
headers.set('etag', object.etag); // 缺少引号
// ✅ 正确: 使用 httpEtag (已加引号)
headers.set('etag', object.httpEtag);
## 校验和限制
每次 PUT 操作仅允许使用一种校验和算法:
typescript
// ❌ 错误: 多个校验和
await env.MY_BUCKET.put(key, data, { md5: hash1, sha256: hash2 }); // 报错
// ✅ 正确: 选择一个
await env.MY_BUCKET.put(key, data, { sha256: hash });
## 分段上传要求
- 所有分段必须大小统一(最后一段除外)
- 分段编号从 1 开始(不是 0)
- 未完成的上传会在 7 天后自动中止
- `resumeMultipartUpload` 不会验证 uploadId 是否存在
## 条件操作
typescript
// 前置条件失败返回不带 body 的对象
const object = await env.MY_BUCKET.get(key, {
onlyIf: { etagMatches: '"wrong"' }
});
// 检查 body,而不仅仅是 null
if (!object) return new Response('Not found', { status: 404 });
if (!object.body) return new Response(null, { status: 304 }); // 前置条件失败
## 键验证
typescript
// ❌ 危险: 路径遍历
const key = url.pathname.slice(1); // 可能是 ../../../etc/passwd
await env.MY_BUCKET.get(key);
// ✅ 安全: 验证键
if (!key || key.includes('..') || key.startsWith('/')) {
return new Response('Invalid key', { status: 400 });
}
## 存储类陷阱
- InfrequentAccess: 30 天最低计费(即使提前删除)
- 无法通过生命周期将 IA 转换为 Standard(请使用 S3 CopyObject)
- IA 读取会产生检索费用
## 流长度要求
typescript
// ❌ 错误: 未知长度的流会静默失败
const response = await fetch(url);
await env.MY_BUCKET.put(key, response.body); // 可能在无错误的情况下失败
// ✅ 正确: 缓冲或使用 Content-Length
const data = await response.arrayBuffer();
await env.MY_BUCKET.put(key, data);
// 或: 如果已知,传递 Content-Length
const object = await env.MY_BUCKET.put(key, request.body, {
httpMetadata: {
contentLength: parseInt(request.headers.get('content-length') |