BunshipBunship
集成能力

S3 协议存储

Bunship 存储落地指南:变量怎么填、R2 怎么配、如何按用户切 provider、上线前怎么验收。

对象存储配置不对,会直接影响上传、CMS 封面、AI 结果文件访问。

这页只讲可执行步骤,目标是最快稳定上线。

推荐方案(优先)

除非你团队已经深度使用 AWS,否则优先上 Cloudflare R2

  • 接口兼容 S3,接入快
  • 常见 CDN 架构下流量成本更可控
  • 和 Bunship 当前上传链路直接匹配

Bunship 最少需要的变量

默认只走环境变量时,只要这 6 个变量正确,上传链路就能跑通:

  • S3_ENDPOINT
  • S3_REGION
  • S3_ACCESS_KEY
  • S3_SECRET_KEY
  • S3_BUCKET
  • PUBLIC_S3_URL_BASE

请使用 PUBLIC_S3_URL_BASE 作为对外访问前缀。旧变量 NEXT_PUBLIC_S3_URL_BASE 仍兼容;不要写成 S3_URL_BASE

这些是运行时变量,不是 Docker build 变量。Web/API 容器需要一份;如果 AI 任务跑在 Trigger.dev 云端,Trigger.dev 项目的环境变量里也需要 S3_*PUBLIC_S3_URL_BASE,否则任务能上传但返回 URL 会错。

完整环境变量说明,包括 provider 兜底变量,请看环境变量配置

快速接入(Cloudflare R2)

1. 创建 Bucket

例如:bunship-prod

2. 创建 API Token

给该 bucket 开通读写权限,拿到 Access Key / Secret Key。

3. 准备 Endpoint 和公网访问域名

  • Endpoint 格式:https://<ACCOUNT_ID>.r2.cloudflarestorage.com
  • 公网访问前缀:
    • 你自己的 CDN/自定义域名,或
    • R2 的公开域名(已开启时)

4. 填写 Web/API 运行时环境变量

S3_ENDPOINT="https://<ACCOUNT_ID>.r2.cloudflarestorage.com"
S3_REGION="auto"
S3_ACCESS_KEY="<R2_ACCESS_KEY_ID>"
S3_SECRET_KEY="<R2_SECRET_ACCESS_KEY>"
S3_BUCKET="bunship-prod"
PUBLIC_S3_URL_BASE="https://cdn.你的域名"

如果使用 Trigger.dev 执行 AI 任务,也把同一组 S3_*PUBLIC_S3_URL_BASE 配到 Trigger.dev 项目的 prod Environment Variables,或通过 .github/workflows/deploy-trigger.yml + apps/ship-api/trigger.config.ts 同步。

5. 重启 web 与 api 服务

修改变量后必须重启,让上传客户端重新加载配置。

通过 settings 配置上传 Provider

Bunship 目前支持的是服务端配置层能力:上传 API 会读取 settings 表里的 UPLOAD_PROVIDER_OVERRIDES[userId]UPLOAD_PROVIDER_DEFAULT,再为这次请求选择 Better Upload provider。

这适合管理员按租户分 bucket、按地区落盘,或从一个 S3 兼容 provider 平滑迁移到另一个 provider。当前仓库还没有提供用户自己在界面里切换 provider 的自助 UI。

当前支持的上传 provider:

  • cloudflare
  • aws
  • backblaze
  • tigris
  • digitalocean
  • minio
  • wasabi
  • custom

Provider 解析顺序:

  1. 用户级覆盖:UPLOAD_PROVIDER_OVERRIDES[userId]
  2. 系统默认:UPLOAD_PROVIDER_DEFAULT
  3. 环境变量兜底:BETTER_UPLOAD_PROVIDER
  4. 最终兜底:cloudflare

基于 settings 的 provider 路由当前作用于 Better Upload 服务端路由(/s3/upload/admin/s3/upload)。已有的预签名 STS 辅助接口和 AI 结果存储仍依赖共享的 S3_*PUBLIC_S3_URL_BASE 环境变量。

默认 provider

UPLOAD_PROVIDER_DEFAULT 可以直接写 provider 字符串:

"cloudflare"

也可以写对象,带上指定 bucket 和 client 配置:

{
  "provider": "aws",
  "bucketName": "prod-assets",
  "client": {
    "accessKeyId": "...",
    "secretAccessKey": "...",
    "region": "us-east-1"
  }
}

用户级覆盖

UPLOAD_PROVIDER_OVERRIDES 是按用户 ID 索引的映射:

{
  "user_123": "cloudflare",
  "user_456": {
    "provider": "aws",
    "bucketName": "team-a-assets"
  },
  "user_789": {
    "provider": "custom",
    "bucketName": "oss-bucket",
    "client": {
      "endpoint": "https://oss-cn-hangzhou.aliyuncs.com",
      "accessKeyId": "...",
      "secretAccessKey": "...",
      "region": "cn-hangzhou",
      "forcePathStyle": false
    }
  }
}

client 会传给 Better Upload 对应 provider 的工厂函数,并覆盖环境变量兜底值。空字符串和 null 会被忽略,所以用户覆盖只写 providerbucketName 也可以,凭证继续走 env。

Bucket 解析顺序:

  1. 用户/默认 settings 对象里的 bucketName
  2. BETTER_UPLOAD_<PROVIDER>_BUCKET
  3. BETTER_UPLOAD_BUCKET
  4. S3_BUCKET

生产验收(必须通过)

  1. 用户侧上传一张图片。
  2. 后台 CMS 上传/生成一张封面。
  3. 触发一次 AI 生成并打开返回文件 URL。

这 3 步都通过,存储链路基本就稳定了。

常见问题

  1. 403 / 签名错误: endpoint、region、密钥不是同一账号体系。
  2. 上传成功但打不开: PUBLIC_S3_URL_BASE 不对,或 Bucket/CDN 没放通。
  3. 本地正常,线上异常: web 和 api 的环境变量不一致。
  4. 成本上涨过快: 缓存策略不完整 + 大文件过多。

什么时候改用 AWS S3

如果你已经有:

  • 成熟 AWS 网络和 IAM 策略
  • 强制 AWS 合规要求
  • 统一 AWS 成本和日志体系

那就直接用 S3,不必绕一层。

Next Steps