本文档面向需要做正式业务接入、平台封装和长期维护的团队,重点说明密钥管理、鉴权分层、签名授权、文件路径组织、返回结构约定和错误映射方式,便于前端、服务端、任务系统与内部后台围绕同一套接口模型稳定协作。
这份文档的重点不是让前端直接调用底层接口,而是帮助团队围绕 SU.RUN 的存储能力设计一层稳定的服务端接入层。通过服务端统一权限、路径、日志、错误结构和 requestId 约定,前端调用会更清晰,后续扩展账单、审计、队列任务和多组织能力时也更容易保持一致。
本文档适用于已经具备后端框架的接入团队,也适用于正在搭建第一版服务端接口的项目。字段结构、鉴权边界、错误模型和日志约定均可直接作为默认实施规范。
建议在业务服务端封装 `/api/storage/presign-upload`、`/api/storage/presign-download`、`/api/storage/object-metadata` 这类接口,让前端始终通过业务语义访问存储能力。
如果有转码、清洗、批处理、训练、索引等长任务,建议由任务系统持有平台接入参数,在服务端完成对象读写、回调与审计。
如平台内部还需要资产管理、素材中心、知识库后台或计费协同系统,建议统一复用一套服务端接入层,而不是每个系统各自接入。
API 接入通常围绕控制台中的接入参数、组织信息和账单主体展开。先确认这些基础信息,再继续设计服务端接口、请求结构和权限边界,会更适合正式项目长期维护。
最常用的是 S3 兼容文件接口,用于上传、下载、列目录、删除文件、分段上传和授权下载。这一层适合应用程序、媒体处理服务、训练脚本和自动化任务直接接入。
如需将平台能力集成到业务后台,例如自动生成上传授权、统一下发文件路径、拉取账单结果或完成业务审计,建议通过服务端接入组织级能力,再向前端暴露业务接口。
长期密钥建议仅保存在服务端。浏览器、小程序和移动端不直接保存访问密钥,而是通过业务服务端获取短期授权或授权链接。
对于多项目、多团队或品牌合作场景,建议先定义统一的桶命名、目录前缀、权限策略和回写路径,再开始批量接入。
在正式项目中,上传、下载、元数据读取和任务回写通常都会涉及权限、文件路径、错误结构和日志统一。先完成一层服务端封装,后续前端、App、任务系统、运营后台和管理系统会更容易共用同一套规则,避免每个入口各自处理一遍鉴权和路径逻辑。
输入建议包含业务对象类型、文件名、Content-Type、组织或项目上下文;输出建议包含 bucket、objectKey、uploadUrl、expiresIn、requestId。
输入建议包含对象标识、业务归属和下载人身份;输出建议包含 bucket、objectKey、downloadUrl、expiresIn、requestId,并在后端完成权限检查。
如果前端需要展示文件名、大小、更新时间、下载状态,建议由业务后端查询并整理成统一结构,而不是把底层字段直接透给前端。
仓库里已提供最小 POST 示例路由,演示如何在登录用户上下文下生成上传授权、收口 objectKey,并返回统一结果结构。
仓库里已提供最小 POST 示例路由,演示如何校验 objectKey 属于当前用户目录、确认对象存在,并返回统一 asset 结构。
仓库里已提供最小 POST 示例路由,演示如何在服务端完成权限收口后,再签发短时 downloadUrl。
当前仓库里的示例 route 会读取下面这组环境变量。可以把它们理解为文档示例与实际代码对齐时所需的基础运行配置,也可以作为你在本地或测试环境搭第一版接口时的参考命名方式。
SURUN_S3_ENDPOINT=https://your-s3-endpoint SURUN_S3_REGION=auto SURUN_ACCESS_KEY_ID=your-access-key SURUN_SECRET_ACCESS_KEY=your-secret-key SURUN_DEFAULT_BUCKET=media-assets
上传接口的目标不是简单返回一个 uploadUrl,而是把业务身份、对象路径、文件类型、有效期和 requestId 一次性收口。这样前端、移动端和任务系统都能复用同一套上传逻辑。
建议至少校验文件名、Content-Type、业务归属和文件路径生成规则,避免不同业务线把文件写进混乱目录。
POST /api/storage/presign-upload
{
"fileName": "cover.png",
"contentType": "image/png",
"resourceType": "project-cover",
"projectId": "proj_xxx"
}{
"success": true,
"bucket": "media-assets",
"objectKey": "projects/proj_xxx/covers/2026/06/cover.png",
"uploadUrl": "https://your-upload-url",
"expiresIn": 600,
"requestId": "req_xxx"
}下载接口的重点在于权限判断。真正应该由后端决定的是“谁可以下载哪个对象、下载多久、是否需要审计”,而不是只返回一个可以直接跳转的 URL。
建议后端先确认当前用户、组织、项目和文件归属,再决定是否签发短时下载授权,而不是只看 objectKey 是否存在。
POST /api/storage/presign-download
{
"objectKey": "projects/proj_xxx/covers/2026/06/cover.png",
"resourceType": "project-cover",
"projectId": "proj_xxx"
}{
"success": true,
"bucket": "media-assets",
"objectKey": "projects/proj_xxx/covers/2026/06/cover.png",
"downloadUrl": "https://your-download-url",
"expiresIn": 300,
"requestId": "req_xxx"
}元数据接口适合给前端或后台展示文件名、大小、更新时间、业务归属和下载状态。推荐由业务服务端做统一整理,而不是把底层字段直接透给页面。
建议只返回前端真正渲染需要的字段,例如文件名、大小、更新时间和可下载状态,不把内部映射字段直接透出。
{
"success": true,
"object": {
"bucket": "media-assets",
"objectKey": "projects/proj_xxx/covers/2026/06/cover.png",
"fileName": "cover.png",
"size": 248731,
"contentType": "image/png",
"updatedAt": "2026-06-10T08:00:00.000Z",
"downloadable": true
},
"requestId": "req_xxx"
}建议由业务服务端统一签发授权结果,再交给前端或任务系统使用。
export async function POST() {
const uploadUrl = await createUploadUrl();
return Response.json({
success: true,
bucket: 'media-assets',
objectKey: 'uploads/2026/06/avatar.png',
uploadUrl,
expiresIn: 600,
});
}建议由业务服务端统一签发授权结果,再交给前端或任务系统使用。
export async function GET() {
const downloadUrl = await createDownloadUrl();
return Response.json({
success: true,
bucket: 'media-assets',
objectKey: 'projects/2026/06/cover.png',
downloadUrl,
expiresIn: 300,
});
}请求先到业务后端,由后端识别当前用户、组织、项目或任务身份。上传下载是否允许,不在客户端决定。
后端根据业务语义生成 objectKey,并统一决定 bucket、有效期、Content-Type、requestId 和授权类型。
客户端拿到 uploadUrl 或 downloadUrl 之后,只负责执行对象读写。真正的业务完成状态,仍然由后端确认入库或回写。
建议业务接口返回统一结果结构,便于前端、任务队列和日志系统处理。
{
"success": true,
"bucket": "media-assets",
"objectKey": "projects/2026/06/cover.png",
"etag": "...",
"size": 248731,
"requestId": "req_xxx"
}建议业务接口返回统一结果结构,便于前端、任务队列和日志系统处理。
{
"success": true,
"bucket": "media-assets",
"objectKey": "projects/2026/06/cover.png",
"downloadUrl": "https://your-download-url",
"expiresIn": 600
}建议业务接口统一返回一致的错误结构,便于前端、后台和任务系统使用同一种方式处理失败。底层细节建议保留在日志中,不直接展示给终端用户。
{
"success": false,
"error": {
"code": "DOWNLOAD_LINK_EXPIRED",
"message": "下载地址已过期,请重新获取。",
"retryable": true
},
"requestId": "req_xxx"
}用于请求 JSON 结构错误、缺少必要字段或字段类型不匹配。
用于 fileName、contentType、resourceType 等字段校验失败。
用于后端权限判断未通过,或 objectKey 超出当前用户可访问范围。
用于 confirm-upload 回查对象失败,通常说明 PUT 尚未成功或 objectKey 填写错误。
用于示例环境变量未配置,或演示存储配置不可用。
前后端分离团队建议由前端仅调用业务 API,由后端负责校验用户身份、决定文件路径、生成上传下载授权,并记录 requestId 和审计日志。这样调用边界更清晰,后端职责也更稳定。
存在任务队列或 AI 作业的场景,建议把存储能力封装成统一的服务模块,例如 `storageService.createUploadUrl()`、`storageService.createDownloadUrl()`、`storageService.copyObject()`。这样无论是控制台、后台、脚本还是任务系统,都可以使用一致的调用方式,后续扩展账单、审计或容量策略时也更容易维护。
正式产品更稳定的方式是由业务服务端统一处理组织级能力,再向前端暴露业务接口。这样权限、审计、错误处理和字段口径都更可控。
可以,但授权地址必须由业务服务端签发。浏览器不能直接保存长期密钥,也不应自行决定关键文件路径。
最少建议返回 bucket、objectKey、downloadUrl、expiresIn 和 requestId,这样前端和日志系统都能保持一致口径。
文档中心帮助你完成接入,客户服务页帮助你了解采购、支持与服务信息。