InsForge 存储 API 实战指南:Bucket 对象存储的上传、下载、鉴权与数据库集成完整解析
【免费下载链接】InsForgeThe all-in-one, open-source backend platform for agentic coding. InsForge gives your coding agent database, auth, storage, compute, hosting, and AI gateway to ship full-stack apps end-to-end.项目地址: https://gitcode.com/GitHub_Trending/in/InsForge
本文基于仓库中 .archive/docs/deprecated/insforge-storage-api.md 整理扩展,并对照当前仓库中 backend/src/api/routes/storage/index.routes.ts、backend/src/services/storage/storage.service.ts 等源码逐一核对,为你呈现一份可复制、可运行的 InsForge 对象存储(Storage)API 使用手册。读完本文,你将掌握:如何通过 MCP 工具与 REST 接口管理 Bucket、如何以 multipart/form-data 上传对象并正确使用返回的绝对 URL、如何利用公开/私有 Bucket 实现免鉴权下载与受控访问、如何把文件与元数据分离存储到数据库,以及如何读懂统一错误格式并规避常见踩坑点。
InsForge 将存储抽象为“Bucket(桶)+ Object(对象)”两层模型:Bucket 是命名空间与访问控制单元(公开/私有),Object 是实际的文件二进制与元数据。所有对象操作走 REST API,Bucket 管理可借助 MCP 工具,二者共享同一套鉴权体系。下面按“API 概览 → Bucket 管理 → 对象操作 → 数据库集成 → 错误处理”的顺序完整展开。
1. API 概览:Base URL、鉴权模型与核心约定
1.1 Base URL 与开发环境
存储 API 的默认 Base URL 为:
http://localhost:7130所有对象接口统一挂在/api/storage前缀下,核心路径模式为:
/api/storage/buckets/:bucketName/objects/:objectKey1.2 鉴权规则一览
不同操作对鉴权的要求不同,整理如下(与 路由源码 中verifyAdmin、verifyUser、conditionalDownloadAuth三个中间件的使用位置一一对应):
| 操作 | 鉴权要求 | 说明 |
|---|---|---|
| 上传(PUT/POST) | Authorization: Bearer <token> | 需要登录用户或 API Key,走verifyUser |
| 删除对象(DELETE) | Authorization: Bearer <token> | 需要登录用户,走verifyUser |
| 下载对象(GET) | 公开 Bucket 免鉴权;私有 Bucket 需 token | 由conditionalDownloadAuth先查询 Bucket 可见性再决定是否跳过鉴权 |
| 列举/管理 Bucket | 需要管理员鉴权 | 创建、列举、删除 Bucket 均走verifyAdmin |
| 管理存储配置、S3 访问密钥 | 需要管理员鉴权 | GET/PUT /api/storage/config、/api/storage/s3/*均走verifyAdmin |
在源码层面,API Key 调用者与普通用户走的是不同的数据库连接路径:普通 JWT 用户通过withUserContext在storage.objects的行级安全策略(RLS)约束下读写,而 API Key 是机器凭据、不具备用户身份,使用后端连接池(绕过端用户 RLS)执行操作。这一点从 storage.service.ts 中大量if (hasApiKey || ctx?.role === 'project_admin') ... runWithRootAccess(...)分支可以得到印证。文档中提示“API keys are for MCP testing(API Key 用于 MCP 测试)”,生产环境推荐使用会话 token。
1.3 🔴 关键约定:响应中的 URL 是绝对 URL,直接使用即可
这是最容易踩坑的一点:存储 API 返回的url字段是完整可用的绝对 URL,形如:
http://localhost:7130/api/storage/buckets/avatars/objects/user123.jpg- 不要自行拼接 host,也不要二次加工该 URL;
- 该 URL 可直接用于
<img src>、<video src>或fetch()请求; - 无论公开还是私有 Bucket,只要你有权访问,URL 都能直接使用。
需要补充的是,当前源码在构造对象 URL 时还会追加一个?v=<版本戳>查询参数用于 CDN 缓存失效(cache-busting):每次上传都会基于对象 etag(本地存储退化为uploaded_at毫秒时间戳)生成新的版本戳,保证覆盖写之后拿到的是新 URL 而非陈旧缓存。参见 storage.service.ts 中的buildObjectUrl实现。
2. Bucket 管理:MCP 工具与 REST 管理端点
Bucket 是整个存储体系的根级容器。原文档推荐使用 MCP 工具完成 Bucket 管理,同时 REST 也提供了等价的管理端点(需管理员鉴权)。
2.1 通过 MCP 工具管理 Bucket
| MCP 工具 | 功能 | 关键参数 |
|---|---|---|
create-bucket | 创建 Bucket | bucketName、isPublic(默认true) |
list-buckets | 列出所有 Bucket | — |
delete-bucket | 删除 Bucket | bucketName |
isPublic默认值为true,这一点与 REST 创建接口的参数校验一致:在 packages/shared-schemas/src/storage-api.schema.ts 中,createBucketRequestSchema定义为isPublic: z.boolean().default(true)。
2.2 通过 REST 端点管理 Bucket(管理员)
| 方法 | 路径 | 说明 |
|---|---|---|
POST | /api/storage/buckets | 创建 Bucket,请求体{ "bucketName": "avatars", "isPublic": true },成功返回 201 |
GET | /api/storage/buckets | 列出所有 Bucket(名称、可见性、创建时间) |
PATCH | /api/storage/buckets/:bucketName | 更新 Bucket 可见性,请求体{ "isPublic": true } |
DELETE | /api/storage/buckets/:bucketName | 删除整个 Bucket |
创建 Bucket 的 curl 示例:
curl -X POST http://localhost:7130/api/storage/buckets \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"bucketName": "avatars", "isPublic": true}'成功响应(201):
{ "message": "Bucket created successfully", "bucketName": "avatars", "isPublic": true, "nextActions": "This is a PUBLIC bucket - objects can be accessed without authentication. You can use /api/storage/buckets/:bucketName/objects/:objectKey to upload an object to the bucket, and /api/storage/buckets/:bucketName/objects to list the objects in the bucket." }更新可见性(PATCH)示例:
# Mac/Linux curl -X PATCH http://localhost:7130/api/storage/buckets/avatars \ -H 'Authorization: Bearer <token>' \ -H 'Content-Type: application/json' \ -d '{"isPublic": true}' # Windows PowerShell(使用 curl.exe,嵌套 JSON 需要不同的引号转义) curl.exe -X PATCH http://localhost:7130/api/storage/buckets/avatars \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{\"isPublic\": true}'响应:
{ "message": "Bucket visibility updated", "bucket": "avatars", "isPublic": true, "nextActions": "Bucket is now PUBLIC - objects can be accessed without authentication." }2.3 Bucket 命名规则与底层创建流程
- Bucket 名必须是合法标识符。当前源码(storage.service.ts 的
validateBucketName)强制要求匹配正则^[a-zA-Z0-9_-]+$,即只允许字母、数字、连字符和下划线,否则返回 400(STORAGE_INVALID_PARAMETER); - 原文档强调“不能以下划线开头”,这属于推荐约定,当前代码层面的硬性约束是上述字符集校验;
- 重复创建同名 Bucket 会返回 409(
STORAGE_ALREADY_EXISTS)。
创建流程的底层实现是先调用存储提供者(provider)创建实际存储空间,成功后再向storage.buckets表写入一行记录——先写后端、后写数据库的顺序避免了“数据库有记录但存储后端无实际目录”的孤儿记录导致永久 409 的问题。本地文件系统实现见 backend/src/providers/storage/local.provider.ts,S3 实现见 backend/src/providers/storage/s3.provider.ts,两者都实现了 backend/src/providers/storage/base.provider.ts 定义的统一StorageProvider接口。
3. 对象上传:PUT 指定 Key 与 POST 自动生成 Key
对象上传统一使用multipart/form-data,表单字段名为file。
3.1 PUT:指定 Key 上传(创建或覆盖)
PUT /api/storage/buckets/:bucketName/objects/:objectKeyconst formData = new FormData(); formData.append('file', fileObject);curl 示例:
# Windows PowerShell: 使用 curl.exe curl -X PUT http://localhost:7130/api/storage/buckets/avatars/objects/user123.jpg \ -H "Authorization: Bearer YOUR_SESSION_TOKEN" \ -F "file=@/path/to/image.jpg"成功响应:
{ "bucket": "avatars", "key": "user123.jpg", "size": 15234, "mimeType": "image/jpeg", "uploadedAt": "2025-07-18T04:32:13.801Z", "url": "http://localhost:7130/api/storage/buckets/avatars/objects/user123.jpg?v=abc123..." }注意两点实现细节:
- 当前路由把对象 Key 用通配符
objects/*捕获(index.routes.ts),因此 Key 中可以包含/,天然支持“伪目录”结构的对象键,例如users/user123/avatar.jpg; - PUT 语义为“创建或替换”:对已存在的 Key 再次 PUT 会原地覆盖,且覆盖时不会改变对象的归属者(
uploaded_by字段在冲突更新分支中被刻意保留),相关逻辑见 storage.service.ts 的putObject方法。
3.2 POST:服务端自动生成唯一 Key
POST /api/storage/buckets/:bucketName/objects# Windows PowerShell: 使用 curl.exe curl -X POST http://localhost:7130/api/storage/buckets/posts/objects \ -H "Authorization: Bearer YOUR_SESSION_TOKEN" \ -F "file=@/path/to/image.jpg"成功响应(201):
{ "bucket": "avatars", "key": "image-1737546841234-a3f2b1.jpg", "size": 15234, "mimeType": "image/jpeg", "uploadedAt": "2025-07-18T04:32:13.801Z", "url": "http://localhost:7130/api/storage/buckets/avatars/objects/image-1737546841234-a3f2b1.jpg" }自动生成的 Key 格式为{净化后的文件名}-{毫秒时间戳}-{6位随机串}{原扩展名},例如image-1737546841234-a3f2b1.jpg,由 storage.service.ts 的generateObjectKey生成:文件名中非[a-zA-Z0-9-_]字符会被替换为连字符并截断到 32 字符,随机串由Math.random().toString(36)派生,时间戳保证同一毫秒内的并发上传也不会冲突。
3.3 上传的 MIME 安全与大小限制
上传链路上内置了两道防护(见 index.routes.ts):
- MIME 魔数检测:
enforceSafeMimeType会读取文件内存缓冲,通过魔数(magic bytes)真实探测文件类型,覆盖客户端上报的 mimetype;可执行类型(HTML、SVG、JS 等)会被归一化为application/octet-stream,避免存储被用于托管恶意脚本(工具实现见 backend/src/utils/mime-guard.ts); - 文件大小上限:默认最大 50 MB,可通过存储配置调整(见下文第 8 节),超限返回 413。
4. 对象下载与公开/私有访问控制
4.1 下载对象
GET /api/storage/buckets/:bucketName/objects/:objectKey该接口返回对象的原始字节内容(带正确的 Content-Type 头),而不是 JSON 包装。核心访问规则:
- 公开 Bucket:无需任何鉴权即可下载;
- 私有 Bucket:需要携带
Authorization: Bearer <token>。
底层实现中,下载路由挂载了conditionalDownloadAuth中间件:先查询storage.buckets中该 Bucket 的public字段,公开则直接放行,否则回落到verifyUser。因此你可以在一个私有 Bucket 和一个公开 Bucket 之间通过 PATCH 动态切换可见性,下载行为即时生效。
此外,当前源码为下载提供了更精细的策略:
- 本地存储提供者(Local):直接以 API 自身 URL 返回文件字节(缓冲读取);
- S3 提供者:私有 Bucket 走presigned URL 重定向(默认有效期 1 小时,调用方可传
expiresIn自定义,服务端钳制在 1 秒~7 天之间;公开 Bucket 不设置过期);同时支持代理流式下载(proxy mode),通过Range请求头支持 206/416 分段响应,媒体文件的拖拽播放也能正常工作——这部分在 index.routes.ts 的streamS3ObjectDownload中实现; - 对于被判定为不安全的 MIME 类型,响应会强制附加
Content-Disposition: attachment(强制下载而非内联渲染)并设置X-Content-Type-Options: nosniff。
4.2 列举对象
GET /api/storage/buckets/:bucketName/objects查询参数:
| 参数 | 说明 | 默认值 |
|---|---|---|
prefix | 按 Key 前缀过滤 | 无 |
limit | 单页最大条数 | 100(当前源码钳制在 1~1000) |
offset | 分页偏移量 | 0 |
search | 按文件名(Key)模糊搜索 | 无 |
curl 示例:
# Windows PowerShell: 使用 curl.exe curl -X GET "http://localhost:7130/api/storage/buckets/avatars/objects?limit=10&prefix=users/" \ -H "Authorization: Bearer <token>"原文档记载的响应示例(包含分页头X-Total-Count、X-Page、X-Page-Size):
{ "bucketName": "avatars", "prefix": null, "objects": [ { "bucket": "avatars", "key": "user123.jpg", "size": 15234, "mimeType": "image/jpeg", "uploadedAt": "2025-07-18T04:32:13.801Z", "url": "http://localhost:7130/api/storage/buckets/avatars/objects/user123.jpg" } ], "pagination": { "limit": 100, "offset": 0, "total": 1 }, "nextActions": "You can use PUT /api/storage/buckets/:bucketName/objects/:objectKey to upload with a specific key, or POST /api/storage/buckets/:bucketName/objects to upload with auto-generated key, and GET /api/storage/buckets/:bucketName/objects/:objectKey to download an object." }需要说明:对照当前路由实现(index.routes.ts),现在的响应体已调整为{ data, pagination, nextActions }结构——data承载对象数组、pagination内含offset/limit/total,并经过listObjectsResponseSchema校验(见 storage-api.schema.ts)。无论哪种形态,pagination.total都是过滤后的总数,可作为前端分页依据;对象数组中的url均为可直接使用的绝对 URL。
4.3 删除对象
删除单个对象:
DELETE /api/storage/buckets/:bucketName/objects/:objectKey# Windows PowerShell: 使用 curl.exe curl -X DELETE http://localhost:7130/api/storage/buckets/avatars/objects/user123.jpg \ -H "Authorization: Bearer <token>"响应:
{ "message": "Object deleted successfully" }批量删除对象(当前源码新增能力):
DELETE /api/storage/buckets/:bucketName/objects请求体{ "keys": ["a.txt", "b.txt"] }(最多 1000 个 Key),返回逐 Key 的状态结果:
{ "results": [ { "key": "a.txt", "status": "deleted" }, { "key": "b.txt", "status": "notFound" } ] }删除的底层顺序是“先删数据库行、后删存储后端”:数据库删除成功但存储删除失败时,会记录警告日志并返回failed状态,便于调用方感知并重试,避免产生孤儿数据。
5. 与数据库集成:文件与元数据分离
InsForge 存储与数据库是两个独立模块,推荐的集成模式是:对象二进制进存储,对象元数据进数据库。数据库表中用json列存放对象元数据即可。
5.1 Option 1:PUT 指定 Key + 存储元数据
// Step 1: 以已知 Key 上传对象 const formData = new FormData(); formData.append('file', file); const upload = await fetch('/api/storage/buckets/images/objects/avatar.jpg', { method: 'PUT', headers: { 'Authorization': `Bearer ${token}` }, body: formData }); // Step 2: 将元数据写入数据库 const records = [{ userId: 'user123', image: await upload.json() // 存储对象元数据 }]; await fetch('/api/database/profiles', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(records) });5.2 Option 2:POST 自动生成 Key + 存储元数据
// Step 1: 以服务端生成的唯一 Key 上传 const formData = new FormData(); formData.append('file', file); const upload = await fetch('/api/storage/buckets/images/objects', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData }); const fileData = await upload.json(); // Step 2: 把含自动生成 Key 的元数据写入数据库 const records = [{ userId: 'user123', image: fileData // 包含自动生成的 key }]; await fetch('/api/database/profiles', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(records) });两种方式的取舍:需要稳定、可预测的对象地址(如固定头像路径,覆盖即可更新)选 PUT;不在乎 Key、希望保证唯一性(如用户发帖图片)选 POST。数据库记录中保存响应返回的key和url字段,读取时即可直接拼装<img src>。
从数据流上看,上传响应中的size、mimeType、uploadedAt与数据库storage.objects表中的元数据行一一对应(服务端通过INSERT ... ON CONFLICT DO UPDATE在写入存储后同步元数据行),这正是“数据库只存元数据”落地的证据。
6. 错误响应格式与常见错误码
所有存储接口的错误响应统一采用以下格式:
{ "error": "ERROR_CODE", "message": "Human-readable error message", "statusCode": 400, "nextActions": "Suggested action to resolve the error" }例如 Bucket 不存在:
{ "error": "BUCKET_NOT_FOUND", "message": "Bucket 'nonexistent' does not exist", "statusCode": 404, "nextActions": "Create the bucket first" }结合路由源码,实际可观测到的常见错误码与触发条件如下:
| error 代码 | HTTP 状态码 | 典型触发场景 |
|---|---|---|
STORAGE_ALREADY_EXISTS | 409 | 重复创建同名 Bucket,或对象 Key 与既有记录冲突(数据库唯一约束 23505) |
STORAGE_NOT_FOUND | 404 | Bucket / 对象不存在 |
STORAGE_INVALID_PARAMETER | 400 | Bucket 名或 Key 非法、请求体不符合 schema、expiresIn非数字等 |
STORAGE_PERMISSION_DENIED | 403 | RLS 拒绝写入(数据库权限错误 42501),或缺少用户上下文 |
编写客户端时,建议始终以error代码(而非 message 文本)做分支判断,并善用nextActions字段向用户给出可执行的下一步指引。
7. 重要规则与最佳实践清单
最后,汇总原文档的“Important Rules”并结合源码补充实践建议:
对象操作规范
- 上传一律使用 multipart/form-data,字段名固定为
file; - 数据库只存元数据,不存二进制;元数据建议用
json列类型; - 响应中的
url是绝对 URL,直接用于前端展示与 fetch,切勿二次拼接 host。
- 上传一律使用 multipart/form-data,字段名固定为
Bucket 命名
- 只允许字母、数字、连字符、下划线(源码正则
^[a-zA-Z0-9_-]+$); - 遵循文档约定:不以
_开头,保持命名整洁; - 命名一经创建即被复用,创建同名 Bucket 会得到 409。
- 只允许字母、数字、连字符、下划线(源码正则
鉴权与工具分工
- Bucket 管理推荐使用 MCP 工具(
create-bucket/list-buckets/delete-bucket),或使用等价的管理员 REST 端点; - 对象操作统一走 REST API;
- 所有写操作都需要鉴权;公开 Bucket 的下载免鉴权;
- API Key 适用于 MCP 测试与机器场景,生产环境优先使用会话 token。
- Bucket 管理推荐使用 MCP 工具(
安全与扩展能力(源码补充)
- 上传内置 MIME 魔数校验,可执行类型自动降级为
application/octet-stream,下载端对不安全 MIME 强制attachment; - 存储配置接口
GET/PUT /api/storage/config(管理员)可动态调整全局最大文件大小(maxFileSizeMb,允许 1~200 MB,默认 50 MB,参见 storage-config.service.ts); - 如需超大文件直传 S3,可使用当前源码提供的上传/下载策略接口(
POST /api/storage/buckets/:bucketName/upload-strategy、GET /api/storage/buckets/:bucketName/download-strategy/objects/*、POST .../confirm-upload),获取 presigned URL 后在客户端直传/直下,绕过网关代理; - 面向 S3 生态的
S3_BUCKET部署还提供/storage/v1/s3网关与访问密钥管理接口(GET /api/storage/s3/config、POST/DELETE /api/storage/s3/access-keys),可对接 AWS SDK 与各类 S3 工具。
- 上传内置 MIME 魔数校验,可执行类型自动降级为
掌握以上内容后,你就可以在 InsForge 上完成“上传文件 → 落库元数据 → 公开/私有访问控制 → 前端直接引用绝对 URL”的完整文件管理闭环。如果想进一步深入,推荐阅读存储服务的完整实现 backend/src/services/storage/storage.service.ts、提供者抽象 backend/src/providers/storage/base.provider.ts,以及单元测试(如 storage-routes.test.ts、localstorageprovider.test.ts)来验证各接口行为。
【免费下载链接】InsForgeThe all-in-one, open-source backend platform for agentic coding. InsForge gives your coding agent database, auth, storage, compute, hosting, and AI gateway to ship full-stack apps end-to-end.项目地址: https://gitcode.com/GitHub_Trending/in/InsForge
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考