对象存储
配置 R2 binding,理解上传限制、文件归属、下载响应和 API Key 调用。
模块与数据位置
packages/storage/src/index.ts 负责文件校验和 R2 操作,apps/web/src/server/files.ts 连接 HTTP、会话、配额与数据库。R2_BUCKET binding 保存正文,D1 保存原始文件名、MIME、大小、归属和对象键。默认文件是私有资源,不通过公开 bucket URL 直接分享。
本地启动使用 Wrangler 本地 R2,不需要先购买或创建远程 bucket。生产 bucket 由部署目标的资源规划绑定。修改名称前请阅读 部署指南,防止既有对象留在旧 bucket。
上传限制
| 项目 | 当前默认 |
|---|---|
| 单文件大小 | 大于 0,最多 1,048,576 字节(1 MiB) |
| 文本类型 | text/plain、text/markdown |
| 图片类型 | image/png、image/jpeg、image/webp |
| 文件数量 | 每用户 100 个,由 product.quotas.storedFiles 配置 |
文件数量是产品配置;单文件限制与允许 MIME 在 storage 包校验。调整时同步服务端、上传控件提示及测试,不能只放宽 HTML accept。
从工作台验证
- 打开
/app/files,上传一个小型 .txt 或图片。 - 查看文件名、大小和状态,刷新后仍能找到同一记录。
- 下载文件,确认字节内容正确。
- 删除后刷新列表,确认记录消失且不能再次下载。
对象键使用用户 ID 与随机 UUID,不直接使用上传文件名。下载以附件形式返回,使用私有且不缓存的响应策略,不直接让浏览器执行上传内容。
在自己的前端调用
const form = new FormData();
form.set('file', selectedFile);
const response = await fetch('/api/files', { method: 'POST', body: form });
if (!response.ok) throw new Error('Upload failed');不要手工设置 multipart Content-Type,让浏览器生成 boundary。读取列表使用 GET /api/files;单文件下载和删除按当前 files.ts 中的 ID 路径调用,不向服务端提交任意 R2 key。
API Key 与归属检查
设置页创建的文件 API Key 通过 x-api-key 头提交,作用范围仅为文件接口。它仍受用户邮箱验证、封禁状态、配额和归属限制。不同用户即使知道文件 ID 也不能下载或删除。
存储上传成功但元数据写入失败时,现有实现清理刚上传的 R2 对象;删除先操作 R2 再删除元数据。扩展多步骤处理时保留失败补偿,不把半完成记录显示成成功。
与 AI 摘要的关系
摘要只接受自己上传的纯文本或 Markdown,不支持直接把图片当文本处理。当前摘要输入最多读取前 16,000 个字符,上传允许 1 MiB 不代表模型会处理整个文件。详见 AI 与任务。
验证与排障
- Unsupported file type or size:检查实际 MIME、零字节和大小,而不只看扩展名。
- 配额不足:检查当前用户文件数及 storedFiles,不改客户端计数绕过服务端。
- 列表有记录但正文不存在:核对目标 bucket 与部署资源名,并检查中断的上传/删除。
- 401/403:检查会话、Key 状态、邮箱验证和文件归属。
运行 Workers/API 测试并增加两个账号的交叉访问检查;真实生产验收使用自己部署的 bucket,不操作公共演示的其他用户文件。