为上传的媒体选择一个存储适配器。数据库备份包含媒体元数据而非存储的文件,因此请单独备份存储后端。
概览
| 存储 | 使用场景 | 签名上传 |
|---|---|---|
| R2 绑定 | 站点运行在 Cloudflare Workers 上 | 否 |
| S3 | Node.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" }),
}),
],
});
配置
| 选项 | 类型 | 描述 |
|---|---|---|
binding | string | wrangler.jsonc 中的 R2 绑定名称 |
publicUrl | string | 存储桶的可选公共 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(),
}),
],
});
配置
| 选项 | 类型 | 必需 | 描述 |
|---|---|---|---|
endpoint | string | 是 | S3 端点 URL |
bucket | string | 是 | 存储桶名称 |
accessKeyId | string | 否* | 访问密钥 |
secretAccessKey | string | 否* | 秘密密钥 |
region | string | 否 | 区域(默认:"auto") |
publicUrl | string | 否 | 可选的 CDN 或公共 URL |
* accessKeyId 和 secretAccessKey 必须同时提供或同时省略。
从环境变量解析 S3 配置
s3({...}) 中省略的任何字段在进程启动时从匹配的 S3_* 环境变量读取。这允许您一次构建容器镜像,在启动时注入凭证而无需重新构建。s3({...}) 中的显式值始终优先于环境变量。
| 环境变量 | 字段 | 备注 |
|---|---|---|
S3_ENDPOINT | endpoint | 必须是有效的 http/https URL |
S3_BUCKET | bucket | |
S3_ACCESS_KEY_ID | accessKeyId | |
S3_SECRET_ACCESS_KEY | secretAccessKey | |
S3_REGION | region | 默认为 "auto" |
S3_PUBLIC_URL | publicUrl | 可选的 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",
}),
}),
],
});
配置
| 选项 | 类型 | 描述 |
|---|---|---|
directory | string | 文件存储的目录路径 |
baseUrl | string | 提供文件的基础 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;
}
这种一致性允许在不更改应用程序代码的情况下切换存储后端。