Cloudflare 临时邮箱 S3 附件存储配置指南:R2 对象存储接入与附件管理实战
【免费下载链接】cloudflare_temp_emailCloudFlare free temp domain email 免费收发 临时域名邮箱 支持附件 IMAP SMTP TelegramBot项目地址: https://gitcode.com/GitHub_Trending/cl/cloudflare_temp_email
本指南以 cloudflare_temp_email 项目的 S3 附件功能为核心,讲解如何在 Cloudflare R2(或其他兼容 S3 协议的对象存储)上保存与下载邮件附件,涵盖 bucket 与凭证创建、Worker Secrets 配置、签名 URL 上传下载流程,以及前端交互与后端 API 的完整实现链路。读完本文,你将能独立为本项目启用附件持久化存储,并理解其底层签名 URL 机制与密钥管理方式。
说明:以上截图来自仓库文档 vitepress-docs/docs/zh/guide/feature/s3-attachment.md,展示在邮件详情中查看附件并将附件保存到 S3 的界面操作。
一、功能定位:为什么需要 S3 附件
在临时邮箱场景中,邮件正文通常存储在 Cloudflare D1 数据库里,而邮件附件的体积往往远超文本内容。若将所有附件一并写入 D1,会带来两个问题:
- 数据库体积膨胀:大量 Base64 编码的附件会迅速消耗 D1 的存储配额,且 D1 按读取行数与存储量计费,成本随之上升;
- 数据库读写效率下降:列表查询、全文检索等操作会因行内的大字段而变慢。
因此项目将附件"外置"到对象存储:正文留在 D1,附件按需上传到 S3 兼容存储(官方文档直接以 Cloudflare R2 为例,也兼容其他 S3 服务)。附件与邮件分离后,邮件列表页无需加载附件二进制数据,只有用户主动保存或下载时才通过签名 URL 与对象存储交互。
[!NOTE] 此功能是可选项。若你的使用场景不需要保存附件,可以直接跳过本章节,系统其余功能不受影响。
二、前置条件与配置项总览
启用 S3 附件功能前,需要准备:
- 一个 Cloudflare 账号,并在 Cloudflare 控制台创建R2 bucket(也可以使用其他兼容 S3 协议的对象存储服务,如遇兼容性问题可在项目仓库提 issue);
- 参考 Cloudflare 官方文档为 bucket 配置CORS 策略,允许来自你部署域名的跨域请求(前端需要直接向对象存储发起 PUT 请求上传附件);
- 参考 Cloudflare R2 的S3 Token文档创建访问令牌,获得以下三个凭证:
ENDPOINT(S3 兼容端点地址)Access Key IDSecret Access Key
这些凭证会被配置为 Cloudflare Worker 的 Secrets。从源码看,Worker 侧共使用四个与 S3 相关的环境变量,定义于 worker/src/types.d.ts:
| 环境变量 | 类型 | 作用 |
|---|---|---|
S3_ENDPOINT | string \| undefined | S3 兼容服务的端点地址(R2 的 S3 API 端点) |
S3_ACCESS_KEY_ID | string \| undefined | R2 S3 Token 的 Access Key ID |
S3_SECRET_ACCESS_KEY | string \| undefined | R2 S3 Token 的 Secret Access Key |
S3_BUCKET | string \| undefined | 对象存储 bucket 名称 |
S3_URL_EXPIRES | number \| undefined | 可选,签名 URL 有效期(秒),默认 360 秒 |
其中前四个变量是启用功能的必要条件。源码 worker/src/mails_api/s3_attachment.ts 中通过isS3Enabled判断四个变量是否全部非空,只有全部配置后才认为 S3 附件功能可用:
export const isS3Enabled = (c: Context<HonoCustomType>) => { return !(!c.env.S3_ENDPOINT || !c.env.S3_ACCESS_KEY_ID || !c.env.S3_SECRET_ACCESS_KEY || !c.env.S3_BUCKET); }S3_URL_EXPIRES是可选配置,控制签名 URL 的有效期,源码中取值为c.env.S3_URL_EXPIRES || 360,即未配置时默认 360 秒。
三、配置步骤:创建 R2 Bucket 与写入 Worker Secrets
3.1 创建 bucket 并准备 CORS 与凭证
- 登录 Cloudflare 控制台,进入R2 → Buckets,创建一个 bucket(例如命名为
temp-mail-attachments),记下 bucket 名称; - 在该 bucket 的Settings → CORS中按 Cloudflare 官方文档添加 CORS 策略,允许你的前端域名通过浏览器跨域上传/下载附件(对应前端的
PUT与GET请求); - 进入R2 → Manage API Tokens → Create API Token,按 R2 S3 Token 的方式生成凭证,页面会返回
ENDPOINT、Access Key ID与Secret Access Key三项信息。
3.2 写入 Worker Secrets
在项目根目录进入worker目录,逐条执行wrangler secret put命令将凭证写入 Worker 的 Secrets:
cd worker pnpm wrangler secret put S3_ENDPOINT pnpm wrangler secret put S3_ACCESS_KEY_ID pnpm wrangler secret put S3_SECRET_ACCESS_KEY # 请注意这里的 bucket 是你的 bucket 名称 pnpm wrangler secret put S3_BUCKET每条命令执行后,终端会提示输入对应的值(粘贴凭证后回车即可)。wrangler secret put会将值加密存储,不会明文写入代码或配置文件。
[!NOTE] 除了命令行方式,也可以直接在 Cloudflare Worker 的 UI 界面中,通过Settings → Variables and Secrets添加同名 Secrets。
3.3 验证功能是否生效
配置完成后,S3 功能是否开启会通过 Worker 的两个接口暴露给前端:
- worker/src/commom_api.ts 中的公共设置接口返回
isS3Enabled布尔值; - worker/src/admin_api/worker_config.ts 中的 Worker 配置接口同样返回
S3_ENABLED字段。
两者均直接复用isS3Enabled(c)的判定结果。前端拿到该值后,会决定是否展示"S3 附件"标签页以及邮件列表中的"保存到S3"按钮(见 frontend/src/views/Index.vue 中showSaveS3="openSettings.isS3Enabled"与v-if="openSettings.isS3Enabled"的写法)。
四、使用方式:保存与下载附件
配置完成后,前端界面出现两个入口:
- 保存附件:打开任意邮件的附件列表,点击"保存到S3"按钮,将当前附件上传至对象存储(见 vitepress-docs/docs/zh/guide/feature/s3-attachment.md 中的界面截图);
- 下载附件:进入"S3附件"标签页,查看已保存的附件列表,点击"下载"即可取回文件。
说明:上图为 S3 附件管理页的界面截图(来源同仓库文档),表格中展示附件的 key(形如
37/one-api.db)与下载操作按钮。
4.1 前端"保存到S3"完整流程
以 frontend/src/views/Index.vue 中的saveToS3为例,保存流程分两步:
- 向后端请求签名 PUT URL:
const { url } = await api.fetch(`/api/attachment/put_url`, { method: 'POST', body: JSON.stringify({ key: `${mail_id}/${filename}` }) });注意这里前端把附件 key 组织为
mail_id/文件名的形式; - 拿到 URL 后,通过表单数据直接向该 URL 发起
PUT请求上传:const formData = new FormData(); formData.append(filename, blob); await fetch(url, { method: 'PUT', body: formData }); message.success(t('saveToS3Success'));上传成功后提示"保存到s3成功"。
4.2 前端"S3附件"列表与下载
S3 附件列表页 对应后端list与get_url两个接口:
- 进入页面时调用
GET /api/attachment/list拉取当前地址下的附件 key 列表; - 点击"下载"时调用
POST /api/attachment/get_url换取签名 GET URL,再跳转下载。
删除附件则调用POST /api/attachment/delete。
五、后端实现原理:签名 URL 与按地址隔离
所有附件 API 都集中在 worker/src/mails_api/s3_attachment.ts,并统一注册在 worker/src/mails_api/index.ts:
// attachment (S3) api.get('/api/attachment/list', s3_attachment.list) api.post('/api/attachment/delete', s3_attachment.deleteKey) api.post('/api/attachment/put_url', s3_attachment.getSignedPutUrl) api.post('/api/attachment/get_url', s3_attachment.getSignedGetUrl)5.1 客户端初始化与密钥隔离
getS3Client使用@aws-sdk/client-s3构造 S3 客户端,region固定为"auto"(R2 的默认区域约定),端点与凭证来自上述环境变量:
return new S3Client({ region: "auto", endpoint: c.env.S3_ENDPOINT, credentials: { accessKeyId: c.env.S3_ACCESS_KEY_ID, secretAccessKey: c.env.S3_SECRET_ACCESS_KEY, }, });值得注意的一个安全设计:所有对象的 key 都以当前登录邮箱地址作为前缀。例如列表接口只列出Prefix: ${address}/下的对象,删除、签名下载、签名上传也都拼接${address}/${key}。这意味着即使多个临时地址共用同一个 bucket,附件也按地址相互隔离,一个地址的用户无法列举或操作其他地址的附件。当前地址来自请求中的 JWT payload(c.get("jwtPayload")),因此该接口必须经过登录鉴权。
5.2 签名 URL:附件不经过 Worker 转发
四个接口中,上传与下载都采用AWS S3 预签名 URL(presigned URL)模式:
getSignedPutUrl:用PutObjectCommand生成 PUT 签名 URL,前端直接向对象存储上传;getSignedGetUrl:用GetObjectCommand生成 GET 签名 URL,前端直接下载。
签名 URL 的有效期由S3_URL_EXPIRES控制(默认 360 秒),使用@aws-sdk/s3-request-presigner的getSignedUrl生成。这种设计的核心收益是:附件二进制数据不经过 Worker 中转,Worker 只负责鉴权与签发有时效的 URL,既减轻了 Worker 的 CPU 与流量开销,也避免 D1 存储被附件撑大。
5.3 列表与删除
list:调用ListObjectsV2Command,Prefix限定为address/,返回结果中剔除地址前缀后输出{ key }数组;deleteKey:调用DeleteObjectCommand删除指定 key 的对象,返回{ success: true }。
六、附件裁剪策略:与 S3 的配合
为了控制邮件正文与附件的整体体积,Worker 还提供附件裁剪逻辑,位于 worker/src/email/check_attachment.ts:
- 开启
REMOVE_ALL_ATTACHMENT时,收信后直接移除全部附件; - 开启
REMOVE_EXCEED_SIZE_ATTACHMENT且邮件大小 ≥ 2MB 时,移除超过阈值的附件。
被移除的附件在重新生成的 MIME 邮件中被清空(attachments: []),从而避免超大邮件写入 D1。结合 S3 附件功能,合理的策略是:正文与常规附件入库,超大或需要留存的附件保存到 R2,兼顾查询性能与存储成本。需要说明的是,这两项裁剪策略与 S3 保存是相互独立的开关,是否同时启用由你的业务诉求决定。
七、注意事项
- CORS 必配:由于上传是前端浏览器直接向 R2 发起
PUT请求,bucket 的 CORS 策略必须允许你的站点域名(含请求方法PUT/GET与所需请求头),否则浏览器会拦截跨域请求; - Secrets 不落盘:
S3_ACCESS_KEY_ID与S3_SECRET_ACCESS_KEY属于敏感凭证,请通过wrangler secret put或 Worker UI 的 Secrets 管理,不要硬编码到wrangler.toml或提交进仓库; - 签名 URL 有时效:上传/下载 URL 默认 360 秒内有效,过期后需重新向后端申请;
- bucket 名称即配置值:
S3_BUCKET的值就是你在 R2 中创建的 bucket 名称,注意大小写与名称完全一致; - 其他 S3 服务:官方文档说明可以接入其他兼容 S3 协议的服务,但 R2 之外的兼容性问题请通过 issue 反馈,项目按 R2 的 S3 API 行为为主进行开发与测试。
至此,你已完整掌握该项目的 S3 附件配置、界面操作与底层实现。相关源码入口可继续查阅 worker/src/mails_api/s3_attachment.ts、worker/src/mails_api/index.ts 与 frontend/src/views/Index.vue。
【免费下载链接】cloudflare_temp_emailCloudFlare free temp domain email 免费收发 临时域名邮箱 支持附件 IMAP SMTP TelegramBot项目地址: https://gitcode.com/GitHub_Trending/cl/cloudflare_temp_email
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考