EmDash 将上传的媒体(图片、文档、视频)存储在可配置的存储后端中。根据您的部署平台和需求进行选择。
概述
| 存储 | 最适合 | 特性 |
|---|---|---|
| R2 Binding | Cloudflare Workers | 零配置,快速 |
| S3 | 任何平台 | 签名上传,CDN 支持 |
| Local | 开发环境 | 简单的文件系统存储 |
Cloudflare R2 (Binding)
在部署到 Cloudflare Workers 时使用 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" 公共访问
对于公共媒体 URL,在您的 R2 存储桶上启用公共访问:
- 转到 Cloudflare 控制面板 > R2 > 您的存储桶
- 在设置中启用公共访问
- 将公共 URL 添加到您的配置中:
storage: r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev",
});
S3 兼容存储
S3 适配器适用于 Cloudflare R2(通过 S3 API)、MinIO 和其他 S3 兼容服务。
以下配置将 EmDash 指向 S3 兼容存储桶:
import emdash, { s3 } from "emdash/astro";
export default defineConfig({
integrations: [
emdash({
storage: s3({
endpoint: process.env.S3_ENDPOINT,
bucket: process.env.S3_BUCKET,
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
region: "auto", // Optional, defaults to "auto"
publicUrl: process.env.S3_PUBLIC_URL, // Optional CDN URL
}),
}),
],
});
配置
| 选项 | 类型 | 必需 | 描述 |
|---|---|---|---|
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
将 S3 凭证与 R2 一起使用以获得签名上传 URL 等功能:
storage: s3({
endpoint: "https://<account-id>.r2.cloudflarestorage.com",
bucket: "emdash-media",
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
publicUrl: "https://pub-xxxx.r2.dev",
});
在 Cloudflare 控制面板的 R2 > Manage R2 API Tokens 下生成 R2 API 凭证。
MinIO
将 S3 适配器指向带有访问凭证的 MinIO 端点:
storage: s3({
endpoint: "https://minio.example.com",
bucket: "emdash-media",
accessKeyId: process.env.MINIO_ACCESS_KEY,
secretAccessKey: process.env.MINIO_SECRET_KEY,
publicUrl: "https://minio.example.com/emdash-media",
});
本地文件系统
使用本地存储进行开发。文件存储在磁盘上的目录中。
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)匹配。
基于环境的配置
根据环境切换存储后端:
import emdash, { s3, 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 binding(改用带有 R2 凭证的 S3 适配器)
- Local
Storage 接口
所有存储适配器实现相同的接口:
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;
}
这种一致性允许在不更改应用程序代码的情况下切换存储后端。