LogoLaunchSaaS

存储

使用拆分包架构配置文件存储

LaunchSaaS 通过拆分包设计提供文件存储支持。核心包 @launchsaas/storage 定义接口和 Storage 服务;每个提供商是独立的包,按需安装。

更新时间:2026-03-15

架构

@launchsaas/storage      ← 接口 + Storage 类(始终需要)
@launchsaas/storage-s3   ← S3 兼容(AWS S3、Cloudflare R2、MinIO 等)

你只需添加实际使用的提供商包。

LaunchSaaS 默认使用 @launchsaas/storage-s3。在你自己的应用中,按需安装该包;若不需要存储功能,跳过 Storage.init() 调用即可。

设置存储功能

1. 安装提供商包

# S3 兼容(AWS S3、Cloudflare R2、MinIO 等)
pnpm add @launchsaas/storage-s3

2. 添加环境变量

将提供商的 keys 导出添加到应用的 env.ts

// src/env.ts
import { keys as storageKeys } from "@launchsaas/storage-s3";

export const env = createEnv({
  extends: [
    // ... 其他 keys
    storageKeys,
  ],
  // ...
});

然后在 .env 中设置变量:

S3_REGION="auto"
S3_BUCKET="your-bucket-name"
S3_ACCESS_KEY_ID="your-access-key-id"
S3_SECRET_ACCESS_KEY="your-secret-access-key"
S3_API_ENDPOINT="https://<account-id>.r2.cloudflarestorage.com"
S3_PUBLIC_ENDPOINT="https://pub-<id>.r2.dev"

3. 在 instrumentation.ts 中初始化

取消注释 src/instrumentation.ts 中的存储代码块:

// src/instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    const { Storage } = await import("@launchsaas/storage");
    const { S3StorageProvider } = await import("@launchsaas/storage-s3");
    Storage.init(S3StorageProvider.create());
  }
}

环境变量

变量描述
S3_REGION区域(例如 us-east-1,R2 使用 auto
S3_BUCKET存储桶名称
S3_ACCESS_KEY_ID访问密钥 ID
S3_SECRET_ACCESS_KEY秘密访问密钥
S3_API_ENDPOINTS3 API 端点 URL
S3_PUBLIC_ENDPOINT访问上传文件的公共基础 URL

提供商配置

Cloudflare R2(推荐)

  1. 前往 Cloudflare Dashboard → R2
  2. 创建存储桶
  3. 创建具有 R2 权限的 API 令牌
  4. 添加上述环境变量

AWS S3

S3_REGION="us-east-1"
S3_BUCKET="your-bucket-name"
S3_ACCESS_KEY_ID="your-access-key-id"
S3_SECRET_ACCESS_KEY="your-secret-access-key"
S3_API_ENDPOINT="https://s3.us-east-1.amazonaws.com"
S3_PUBLIC_ENDPOINT="https://your-bucket-name.s3.us-east-1.amazonaws.com"

MinIO(自托管)

S3_REGION="us-east-1"
S3_BUCKET="your-bucket-name"
S3_ACCESS_KEY_ID="your-access-key"
S3_SECRET_ACCESS_KEY="your-secret-key"
S3_API_ENDPOINT="https://your-minio-server.com"
S3_PUBLIC_ENDPOINT="https://your-minio-server.com/your-bucket-name"

CORS 配置

对于浏览器上传,在存储桶上配置 CORS:

[
  {
    "AllowedOrigins": ["https://yourdomain.com"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
    "AllowedHeaders": ["*"],
    "MaxAgeSeconds": 3600
  }
]

自定义存储提供商

要使用内置 S3 提供商未覆盖的存储服务,可创建新的 storage-xxx 包:

1. 创建包

mkdir -p packages/storage-myservice/src

packages/storage-myservice/package.json

{
  "name": "@launchsaas/storage-myservice",
  "version": "0.1.0",
  "private": true,
  "main": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts"
  },
  "dependencies": {
    "@launchsaas/errors": "workspace:*",
    "@launchsaas/storage": "workspace:*",
    "@t3-oss/env-nextjs": "^0.13.10",
    "my-storage-sdk": "^1.0.0",
    "zod": "^4.0.0"
  }
}

packages/storage-myservice/src/provider.ts

import type {
  DeleteOptions,
  StorageProvider,
  UploadOptions,
} from "@launchsaas/storage";

export class MyServiceStorageProvider implements StorageProvider {
  readonly name = "myservice";

  static create(): MyServiceStorageProvider {
    return new MyServiceStorageProvider();
  }

  async upload(options: UploadOptions): Promise<{ url: string }> {
    // 实现上传
    return { url: "https://example.com/file" };
  }

  async delete(options: DeleteOptions): Promise<void> {
    // 实现删除
  }

  getPublicUrl(key: string): string {
    return `https://example.com/${key}`;
  }

  getKeyFromUrl(url: string): string {
    return url.replace("https://example.com/", "");
  }
}

packages/storage-myservice/src/index.ts

export { MyServiceStorageProvider } from "./provider";
export { keys } from "./keys";

2. 在应用中接入

pnpm add @launchsaas/storage-myservice

keys 添加到 env.ts,并在 instrumentation.ts 中初始化:

export async function register() {
  const { Storage } = await import("@launchsaas/storage");
  const { MyServiceStorageProvider } =
    await import("@launchsaas/storage-myservice");
  Storage.init(MyServiceStorageProvider.create());
}

禁用存储功能

若要完全禁用存储功能,只需在 instrumentation.ts 中不调用 Storage.init()Storage.enabled() 将返回 false,操作会快速失败并给出清晰的错误信息。

参考资料