选择媒体存储

本页内容

为上传的媒体选择一个存储适配器。数据库备份包含媒体元数据而非存储的文件,因此请单独备份存储后端。

概览

存储使用场景签名上传
R2 绑定站点运行在 Cloudflare Workers 上
S3Node.js 站点使用 AWS S3、R2 的 S3 API、MinIO 或兼容存储
本地Node.js 站点有一个可写的持久卷

Cloudflare R2 绑定

在 Cloudflare Workers 上使用 R2 绑定适配器。绑定在运行时提供访问,因此站点不需要 R2 访问密钥。

import emdash from "emdash/astro";
import { r2 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			storage: r2({ binding: "MEDIA" }),
		}),
	],
});

配置

选项类型描述
bindingstringwrangler.jsonc 中的 R2 绑定名称
publicUrlstring存储桶的可选公共 URL

设置

将 R2 绑定添加到 Wrangler 配置中:

wrangler.jsonc

{
  "r2_buckets": [
    {
      "binding": "MEDIA",
      "bucket_name": "emdash-media"
    }
  ]
}

wrangler.toml

[[r2_buckets]]
binding = "MEDIA"
bucket_name = "emdash-media"

公共访问

要从公共存储桶提供媒体,使用 Cloudflare API 连接自定义域名,然后将其来源设置为 publicUrl。Cloudflare 的 r2.dev 开发 URL 有速率限制,不适用于生产流量。

storage: r2({
	binding: "MEDIA",
	publicUrl: "https://media.example.com",
});

如果同一存储桶存储自动 JSON 备份,公共存储桶来源可能会暴露 backups/ 下的对象。请使用私有存储桶和 EmDash 的媒体路由,或将公共来源限制为媒体对象。参见备份

S3 兼容存储

S3 适配器在 Node.js 上与 Cloudflare R2 的 S3 API、AWS S3、MinIO 和兼容服务一起工作。

以下配置在 Node.js 进程启动时从 S3_* 变量解析端点、存储桶、凭证、区域和可选的公共 URL:

import emdash, { s3 } from "emdash/astro";

export default defineConfig({
	integrations: [
		emdash({
			storage: s3(),
		}),
	],
});

配置

选项类型必需描述
endpointstringS3 端点 URL
bucketstring存储桶名称
accessKeyIdstring否*访问密钥
secretAccessKeystring否*秘密密钥
regionstring区域(默认:"auto"
publicUrlstring可选的 CDN 或公共 URL

* accessKeyIdsecretAccessKey 必须同时提供或同时省略。

从环境变量解析 S3 配置

s3({...}) 中省略的任何字段在进程启动时从匹配的 S3_* 环境变量读取。这允许您一次构建容器镜像,在启动时注入凭证而无需重新构建。s3({...}) 中的显式值始终优先于环境变量。

环境变量字段备注
S3_ENDPOINTendpoint必须是有效的 http/https URL
S3_BUCKETbucket
S3_ACCESS_KEY_IDaccessKeyId
S3_SECRET_ACCESS_KEYsecretAccessKey
S3_REGIONregion默认为 "auto"
S3_PUBLIC_URLpublicUrl可选的 CDN 前缀

环境变量在进程启动时从 process.env 读取。这是仅限 Node 的功能。

调用不带参数的 s3()S3_* 环境变量读取每个字段:

import emdash, { s3 } from "emdash/astro";

export default defineConfig({
	integrations: [
		emdash({
			// 无参数的 s3():所有字段来自 S3_* 环境变量
			storage: s3(),

			// 或混合使用:覆盖一个字段,其余来自环境
			// storage: s3({ publicUrl: "https://cdn.example.com" }),
		}),
	],
});

通过 S3 API 使用 R2

当需要直接签名上传到 R2 时,在 Node.js 上使用 S3 适配器。使用 Cloudflare API 或 CLI 创建范围受限的 R2 API 凭证,然后设置以下运行时变量:

S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
S3_BUCKET=emdash-media
S3_ACCESS_KEY_ID=<r2-access-key-id>
S3_SECRET_ACCESS_KEY=<r2-secret-access-key>
S3_REGION=auto
S3_PUBLIC_URL=https://media.example.com

将真实值保存在 Node.js 托管平台的密钥管理器中。公共 URL 是可选的,不会替代用于上传的 S3 API 端点。

MinIO

将相同的运行时变量指向 MinIO。将 S3_ENDPOINT 设为 MinIO API 来源,S3_BUCKET 设为存储桶名称,两个凭证变量设为范围受限的 MinIO 访问密钥。仅当该来源公开提供存储桶对象时才设置 S3_PUBLIC_URL

本地文件系统

使用本地存储进行开发或用于具有持久磁盘的单个 Node.js 服务器。文件存储在该磁盘上的目录中。

import emdash, { local } from "emdash/astro";

export default defineConfig({
	integrations: [
		emdash({
			storage: local({
				directory: "./uploads",
				baseUrl: "/_emdash/api/media/file",
			}),
		}),
	],
});

配置

选项类型描述
directorystring文件存储的目录路径
baseUrlstring提供文件的基础 URL

baseUrl 应与 EmDash 的媒体文件端点 (/_emdash/api/media/file) 匹配,除非你配置了自定义静态文件服务器。

为不同环境使用不同的存储

以下配置在开发期间使用本地目录,在 Cloudflare 生产构建中使用 R2:

import emdash, { local } from "emdash/astro";
import { r2 } from "@emdash-cms/cloudflare";

const storage = import.meta.env.PROD
	? r2({ binding: "MEDIA" })
	: local({
			directory: "./uploads",
			baseUrl: "/_emdash/api/media/file",
		});

export default defineConfig({
	integrations: [emdash({ storage })],
});

签名上传

S3 适配器支持签名上传 URL,允许客户端直接上传到存储而不经过服务器。这提高了大文件的性能。

使用 S3 适配器时签名上传是自动的。管理界面在可用时使用它们。

支持签名上传的适配器:

  • S3(包括通过 S3 API 的 R2)

不支持签名上传的适配器:

  • R2 绑定(请改用带有 R2 凭证的 S3 适配器)
  • 本地

存储接口

所有存储适配器实现相同的接口:

interface Storage {
	upload(options: {
		key: string;
		body: Buffer | Uint8Array | ReadableStream;
		contentType: string;
	}): Promise<UploadResult>;

	download(key: string): Promise<DownloadResult>;
	delete(key: string): Promise<void>;
	exists(key: string): Promise<boolean>;
	list(options?: ListOptions): Promise<ListResult>;
	getSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl>;
	getPublicUrl(key: string): string;
}

这种一致性允许在不更改应用程序代码的情况下切换存储后端。