Skip to content
Table of contents

Cloud Drive:把私人云盘放进 Cloudflare Pages

5 min read ··· views #cloudflare / #r2 / #云盘

项目地址:github.com/willimt/cloud-drive 没有公共演示站。这是给自己的文件用的入口,部署完成后需要密码登录。

我一直想要一个很朴素的云盘:能放文件、能找回来、偶尔给别人发一个链接,仅此而已。它不需要用户体系、后台数据库,也不想为了这点事再养一台服务器。

Cloud Drive 就是在这个前提下做的。静态页面放在 Cloudflare Pages,读写、登录和分享由 Pages Functions 接住,文件和少量状态都放进 R2。桶保持私有,浏览器不会直接碰到 R2;每次访问都先经过应用的鉴权接口。

文件列表、面包屑、拖拽上传区和右键操作菜单

上图是平时最常打开的页面。路径可以一直点回根目录,列表或网格的显示偏好会被记住;文件的更多操作都收在右侧菜单里,免得界面一开始就挤满按钮。

它适合做什么

这是单密码的私人云盘,适合自己用,或者和少量彼此信任的人共享。所有管理员共用 AUTH_PASSWORD,没有账号、角色、审计日志或多人协作那一套;要做团队网盘,还是应该选有完整权限模型的产品。

在这个边界内,它把日常要用的事情都补上了:

  • 新建文件夹、重命名、移动、回收站恢复,以及列表 / 网格视图和批量移动、删除;
  • 点击或拖拽上传。同名文件不会覆盖旧文件,会自动换成 文件名 (1).扩展名
  • 文件达到 32 MiB 时改用 R2 分片上传。上传进行中可以暂停、继续或取消;失败的分片最多会自动重试三次;
  • 全盘搜索至少输入两个字符。目录、搜索结果、分享记录和回收站都是按页加载;
  • 图片、视频、音频、文本和 PDF 可以直接预览,Office 文档则老老实实下载;
  • 文件或文件夹都能生成分享链接,可加密码、设有效期,也能在“我的分享”里逐个或一次性撤销。

文件夹的移动、回收和恢复会分批处理。文件很多时,页面会提示正在继续处理,而不是让一次请求硬扛到底。已经创建的分享在重命名或移动后会尝试同步路径;移入回收站的项目会让关联分享失效。

分享页长什么样

分享不是把桶公开出去。它只是应用生成的一条 /s/… 链接,访问者拿到的是经过分享校验后的内容。需要密码的链接会单独校验;过期和撤销的链接也不会再读取文件。

为单个文件创建分享链接,可选密码和有效期

“我的分享”会列出链接、保护状态和到期时间

没有密码的文件分享页,支持预览和下载

分享密码并不以明文保存,记录里只保留加盐后的派生值。管理员登录和带密码的分享都会做限流:同一来源在十分钟内连续失败五次,会被限制十五分钟。它是给私人项目的一层刹车,不是 WAF 或 Access 的替代品。

不需要服务器,仍然需要认真配置

本地开发只需要 Node.js 和 Wrangler。复制一份本地变量文件,填好强密码和另一段随机密钥,然后启动:

npm install
Copy-Item .dev.vars.example .dev.vars
npm run dev
powershell
AUTH_PASSWORD="换成强登录密码"
SESSION_SECRET="换成另一段随机长字符串"
dotenv

本地 R2 数据会落在 .wrangler/ 里,删掉这个目录就相当于清掉本地云盘。提交代码前跑一遍:

npm run check
npm test
powershell

部署时,创建一个不公开的 R2 桶,在 Pages 项目中把它绑定为 BUCKET,再把 AUTH_PASSWORDSESSION_SECRET 作为加密变量配置进去。项目中的 wrangler.jsonc 还声明了 IMAGES 绑定:它只用于网格里的图片缩略图,没开通图片转换或图片超过 20 MiB 时,云盘仍能使用,只是会显示占位图。完整的本地运行、Pages 配置和验收步骤放在项目的 README 里。

几个不该忽略的细节

  • 不要开启 R2 的公共访问,也不要把 R2 API 密钥写进前端;
  • AUTH_PASSWORDSESSION_SECRET 应该是不同的值。后者变更后,现有登录和分享会话都会失效;
  • 回收站只是一次软删除,不是备份。永久删除、误删桶或账号异常都救不回来,重要文件仍要另做备份;
  • R2 的存储和操作超过免费额度后会产生费用,分片上传会增加写操作。额度和价格以 Cloudflare R2 定价页 为准。

这个项目没有试图把网盘做得无所不能。它只是把文件、分享和恢复这几件事收在一个小小的界面里,省掉一台服务器和一套数据库,也把该留意的边界摆在台面上。