Cloudflare Pages + 阿里云 OSS 上传器:一次从故障到稳定的复盘

一个带密码访问控制、支持大文件分片上传的双站点文件上传器实践记录。

目标

这个项目部署在 Cloudflare Pages 上。浏览器只和同源 Worker 通信,OSS 的长期 AccessKey 保留在 Cloudflare Secret 中,不会发送给浏览器。

主要能力:

  • 密码登录与安全会话 Cookie

  • 国内站点与海外站点切换(港澳台文件上传到海外站点)

  • OSS 分片上传、暂停与继续上传

  • 云端文件列表与一键复制链接

  • 黑黄主题、中文优先的界面

最终架构

浏览器
│ 同源 API + 会话 Cookie
Cloudflare Pages Advanced Mode / Worker
│ 在 Worker 内生成 OSS 签名
阿里云 OSS(国内 Bucket / 海外 Bucket)

这里最重要的边界是:浏览器绝不持有 OSS 长期密钥;只有 Worker 可以读取 Cloudflare 中的 OSS_ACCESS_KEY_IDOSS_ACCESS_KEY_SECRET

遇到的主要问题与解决方法

1. 泛化报错掩盖了真实原因

最初页面只提示“上传失败,请检查网络或 Worker 配置”。这类提示对使用者友好,却不利于排错。

改进方式:Worker 返回可理解的 OSS 错误,前端显示后端的具体信息,同时保留简洁的兜底提示。这样才能区分网络问题、鉴权问题和签名问题。

2. SignatureDoesNotMatch 不等于一定是密钥错误

OSS 返回 SignatureDoesNotMatch 的含义是:OSS 服务端计算出的签名,与请求中提供的签名不同。

应按下面顺序排查:

  1. 确认 Cloudflare Pages Production 环境中的 AccessKey ID 和 Secret 是同一对。

  2. 确认 Bucket 所在区域与 Endpoint 一致。

  3. 确认 HTTP 方法、DateContent-Type、查询参数与签名字符串完全一致。

  4. 确认不是误查了独立 Worker:Pages 项目和独立 Worker 是两个资源,变量不会自动共享。

本项目最终确认:文件列表能读取,说明密钥、区域和基础签名已正常;问题只发生在上传对象路径的签名。

3. OSS V1 的“签名路径”和“请求 URL 路径”不能混用

这是本次最关键的修复。

文件名含有中文、空格等字符时:

  • 实际发往 OSS 的 URL 路径必须百分号编码;

  • OSS V1 的 CanonicalizedResource 必须使用原始对象名进行签名。

如果将已经编码的路径直接参与 V1 签名,文件列表仍可能正常(列表没有文件对象路径),但初始化分片上传会持续报 SignatureDoesNotMatch

正确的处理应将两者分开:

const rawPath = `/${objectKey}`; // 用于 V1 签名
const requestPath = encodeOssPath(rawPath); // 仅用于实际 fetch URL

这个规则对中文文件名、带空格的文件名和特殊字符文件名都很重要。

4. 分片上传的签名查询参数要精确

OSS V1 并不是把所有 URL 查询参数都加入 CanonicalizedResource。

  • 列表请求中的 list-typeprefixmax-keys 只存在于请求 URL 中;

  • 分片上传中的 uploadsuploadIdpartNumber 属于需要签名的 OSS 子资源。

将普通列表参数错误地加入签名,或漏掉分片子资源,都会造成签名不匹配。

5. 外观更新也要保护核心逻辑

一次主题改造中,如果直接用另一份完整 HTML 替换页面,可能意外带回旧的登录或上传脚本。

更稳妥的做法:

  • 以已验证版本的登录、会话和上传脚本为基线;

  • 只替换 CSS、可见文案和非功能性 HTML;

  • 发布后检查浏览器控制台、登录状态、站点切换、文件列表和真实上传。

发布前检查清单

  • GitHub 主分支包含预期提交,Cloudflare Pages 自动部署完成。

  • Pages 的 Production 环境包含 SITE_PASSWORDOSS_ACCESS_KEY_IDOSS_ACCESS_KEY_SECRET

  • 两个 OSS Bucket 的区域、Endpoint 和自定义域名正确。

  • 登录、文件列表、国内/海外站点切换正常。

  • 使用含中文或空格文件名的小文件完成一次真实上传。

  • 文件库只显示最近 30 个,并能成功复制链接。

  • 不在代码库、截图、日志或公开文档中写入任何密码和 AccessKey Secret。

可复用的经验

  1. 先获得真实服务端错误,再开始定位。

  2. 对对象存储签名,逐字符核对“实际请求”和“参与签名的数据”。

  3. 外观改造与鉴权、上传改造分开提交、分开验证。

  4. 生产验证必须包含一次真实上传;本地签名单元测试不能完全代替线上验证。

  5. 凭据永远放在 Secret 中,公开分享只记录变量名和配置原则。

结语

稳定的上传器并不只取决于界面或一次成功的 API 调用。清楚的系统边界、可读的错误信息、对签名细节的严格处理,以及每次发布后的真实验证,才是让它长期可维护的关键。