https://github.com/tao-t356/private-notes
第一部分:为什么要做这个项目
现在市面上的笔记软件其实非常多。
比如 Notion、飞书、语雀、Obsidian、Apple Notes、Google Keep 等等。
这些工具都很好,但它们往往有几个问题。
第一个问题是,数据不完全在自己手里。
很多在线笔记工具,数据都存在平台服务器上。
当然大平台一般也很安全,但是对于一些非常私人的内容,比如:
- 日记
- 想法
- 密码提示
- 私人计划
- 临时灵感
- 账号记录
- 读书笔记
- 工作草稿
有些人就是不想放到第三方平台里。
第二个问题是,功能太重。
很多笔记软件越来越复杂。
一打开就是数据库、模板、团队空间、协作、AI、看板、表格、日历……
但有时候我们只是想要一个很简单的东西:
打开,输入,保存,搜索,结束。
第三个问题是,手机端体验不一定轻量。
有些软件 App 很大,有些网页版在手机上不太舒服。
而我想要的是一个手机浏览器能打开,也可以添加到主屏幕的小应用。
第四个问题是,自己部署的门槛太高。
很多开源笔记项目需要:
- 自己买 VPS
- 安装 Docker
- 配置数据库
- 配置反向代理
- 配置 HTTPS
- 维护服务
对普通用户来说太麻烦了。
所以这个项目的目标就是:
用尽可能轻的方式,做一个可以部署在 Cloudflare 上的私人加密笔记。
第二部分:这个项目是什么
这个项目叫 Private Notes。
它的核心架构很简单:
浏览器 ↓ Cloudflare Workers ↓ Cloudflare D1
前端页面直接由 Worker 返回。
后端 API 也在同一个 Worker 里。
数据存储用的是 Cloudflare D1。
它没有复杂的前后端分离,也没有额外服务器。
只要 Cloudflare 能跑 Workers,就能跑这个项目。
这个项目最重要的特点是:
1. 端到端加密
笔记内容不是直接明文存进数据库的。
用户输入密码之后,浏览器会用这个密码派生出本地加密密钥。
然后笔记标题和正文会在浏览器端加密,再发送到服务器保存。
也就是说,Cloudflare D1 里保存的是密文。
服务器只负责保存和读取密文,不负责解密内容。
当然,这也带来一个重要限制:
如果你忘记了密码,旧笔记无法恢复。
所以这个密码一定要记住。
2. 多密码,多数据空间
这个项目还有一个我觉得挺有意思的功能:
它支持多密码进入不同的数据空间。
比如你配置:
APP_PASSWORD=facker668 APP_PASSWORDS=guest=guest123,work=work888
那么:
- 输入 facker668,进入默认空间
- 输入 guest123,进入 guest 空间
- 输入 work888,进入 work 空间
不同空间的数据是隔离的。
这有什么用呢?
比如你可以这样用:
- 一个密码放自己的私人笔记
- 一个密码放工作草稿
- 一个密码给临时访客用
- 一个密码做演示数据
别人即使知道其中一个密码,也只能看到那个密码对应的数据空间。
3. 手机端体验优化
这个项目一开始其实手机端体验不太好。
尤其是登录和刷新页面的时候,会出现一种“页面跳来跳去”的感觉。
后来我把它改成了固定 App Shell 加锁屏 Overlay 的方式。
简单来说,现在页面刷新时不会先显示登录页再跳到应用页。
而是页面结构先稳定显示,然后上面盖一个解锁层。
这样手机端体验会自然很多。
4. 没有危险的一键清空按钮
之前项目里曾经有一个“忘记密码,清空旧笔记”的按钮。
但后来我觉得这个功能非常危险。
因为这是私人笔记,如果别人误点,或者某种情况下被触发,那数据就没了。
所以现在已经移除了:
- 前端清空按钮
- 后端清空接口
也就是说,网页上没有一键清空旧资料的危险入口。
第三部分:实际演示
接下来我们看一下实际效果。
这里我已经部署好了一个演示站点。
打开页面之后,可以看到一个非常简单的登录界面。
上面写着 Private Notes,然后是密码输入框。
我输入密码,比如:
facker668
然后点击进入。
进入之后就可以看到笔记列表。
页面顶部有:
- 搜索框
- 搜索按钮
- 新建笔记按钮
- 退出登录按钮
下面是所有笔记。
我们点击“新建笔记”。
这里可以输入标题,比如:
今天的想法
正文可以写:
今天测试了一下这个私人笔记系统。 它部署在 Cloudflare Workers 上, 数据存在 D1 里面, 内容会在浏览器端加密。
然后点击保存。
保存之后,笔记会出现在列表里。
这里可以看到:
- 更新时间
- 字数
- 标题
- 正文
- 复制全文按钮
- 编辑按钮
- 删除按钮
如果正文很长,默认会折叠,可以点击展开全文。
现在我们再测试一下搜索。
搜索关键词,比如:
Cloudflare
它会在本地搜索标题和正文。
这里要注意,因为内容是加密的,服务端没法直接搜索明文。
所以搜索是在浏览器本地解密之后完成的。
第四部分:多密码空间演示
接下来演示一下多密码空间。
假设我有两个密码:
facker668 guest123
我先用 facker668 登录,创建一条笔记:
这是默认空间里的笔记
然后退出登录。
再用 guest123 登录。
你会发现这里看不到刚才那条笔记。
因为现在进入的是 guest 空间。
我在 guest 空间创建一条:
这是 guest 空间里的笔记
然后再退出,重新用 facker668 登录。
又只能看到默认空间的内容。
这个功能对我来说很有用。
比如我录视频的时候,可以用一个演示密码。
真实私人数据放在另外一个密码里。
这样就算录屏时不小心展示了,也不会把真实数据露出来。
第五部分:技术实现讲解
下面简单讲一下技术实现。
这个项目主要由几个部分组成:
src/ index.ts Worker 后端和 API homeHtml.ts 前端页面模板 auth.ts 登录、session、多密码、限流逻辑 migrations/ D1 数据库迁移文件 wrangler.jsonc Cloudflare Workers 配置
1. Cloudflare Workers
Cloudflare Workers 负责处理所有请求。
比如:
- / 返回前端页面
- /api/login 登录
- /api/session 检查登录状态
- /api/notes 获取和创建笔记
- /api/notes/:id 编辑和删除笔记
- /api/crypto-config 获取加密配置
整个项目不需要 Node.js 服务器常驻运行。
Workers 是按请求运行的。
2. Cloudflare D1
D1 是 Cloudflare 提供的 SQLite 数据库。
这个项目用 D1 存:
- 笔记密文
- vault 信息
- 加密 salt
- 登录失败限流记录
笔记表里有一个很重要的字段:
vault_id
不同密码会对应不同的 vault_id。
查询笔记时会加上这个条件:
WHERE vault_id = ?
所以不同密码空间的数据不会混在一起。
3. 前端加密
前端加密用的是浏览器的 Web Crypto API。
大致流程是:
用户输入密码 ↓ PBKDF2 派生密钥 ↓ AES-GCM 加密标题和正文 ↓ 密文发送到服务端 ↓ 存进 D1
解密的时候反过来:
从 D1 读取密文 ↓ 浏览器用本地密钥解密 ↓ 显示明文
服务端不会拿到解密后的内容。
4. 登录 Session
服务端登录这块也做了一些改进。
不是简单地把密码存在 Cookie 里。
而是使用 HttpOnly Cookie 保存签名 session。
session 里会包含:
- 版本号
- 签发时间
- 过期时间
- vaultId
然后用 COOKIE_SECRET 做 HMAC 签名。
这样前端 JavaScript 不能直接读取 session cookie,安全性会好一些。
5. 登录失败限流
项目里还加了登录失败限流。
规则是:
- 同一客户端连续失败 5 次
- 15 分钟窗口
- 触发后锁定 15 分钟
- 返回 429
- 带 Retry-After 响应头
这可以防止别人暴力猜密码。
限流记录也存在 D1 里。
第六部分:如何部署
下面讲一下怎么部署。
项目地址在 GitHub 上:
最简单的方式是直接点 README 里的:
Deploy to Cloudflare
Cloudflare 会帮你创建 Worker 和 D1。
部署时你需要配置几个变量:
APP_PASSWORD
默认数据空间的密码。
比如:
facker668
不过我建议你正式使用时改成自己的强密码。
APP_PASSWORDS
可选。
如果你想要多个数据空间,就配置这个。
格式是:
vault_id=password,vault_id2=password2
比如:
guest=guest123,work=work888
COOKIE_SECRET
这个是用来签名 session cookie 的。
建议设置成一个长随机字符串,比如 32 位以上。
如果你手动部署,大概步骤是:
第一步,安装依赖:
npm install
第二步,登录 Cloudflare:
npx wrangler login
第三步,创建 D1 数据库:
npx wrangler d1 create private-notes-db
然后把返回的 database_id 填进 wrangler.jsonc。
第四步,执行迁移:
npx wrangler d1 migrations apply DB --remote
第五步,设置 secrets:
npx wrangler secret put APP_PASSWORD npx wrangler secret put APP_PASSWORDS npx wrangler secret put COOKIE_SECRET
其中 APP_PASSWORDS 是可选的。
第六步,部署:
npm run deploy
部署完成后,你会得到一个 workers.dev 地址。
打开之后,就可以使用自己的私人笔记了。
第七部分:适合谁使用
这个项目适合几类人。
第一类,是想要一个轻量私人笔记的人。
不需要复杂功能,只想快速记录和搜索。
第二类,是想学习 Cloudflare Workers 和 D1 的开发者。
这个项目很适合作为入门案例,因为它包含:
- Worker API
- D1 数据库
- migration
- secrets
- session cookie
- 前端加密
- PWA
- GitHub 一键部署
第三类,是想拥有自己数据的人。
不想依赖大型笔记平台,但又不想维护服务器。
第四类,是内容创作者。
比如做教程、做演示,可以用多密码空间做演示数据,不影响真实数据。
第八部分:当前限制
当然,这个项目不是完美的。
目前它还有一些限制。
第一,不支持图片和附件上传。
之前我尝试过加图片功能,用 Cloudflare R2 保存加密图片。
但是 R2 可能涉及绑卡或者计费门槛,所以最后我把图片功能去掉了。
这个项目目前还是文本优先。
第二,如果忘记密码,旧笔记无法恢复。
这是端到端加密带来的必然结果。
安全和可恢复性之间,需要做取舍。
第三,它更适合单人使用,不适合团队协作。
第四,搜索是在本地完成的。
如果笔记非常非常多,可能需要后续优化。
第五,目前 UI 还是比较轻量的,不是完整商业产品级别。
但对于一个私人轻量笔记来说,我觉得已经够用了。
第九部分:后续可以做什么
后面如果继续优化,我觉得可以加几个方向。
第一个是导出备份。
比如一键导出加密 JSON。
这样即使 D1 出问题,也能自己保存一份。
第二个是导入备份。
配合导出功能,可以迁移数据。
第三个是改密码功能。
但这个比较麻烦,因为要把所有旧笔记解密后重新加密。
第四个是更好的移动端编辑体验。
比如编辑页面改成全屏抽屉,更像原生 App。
第五个是更好的数据管理。
比如回收站、归档、标签、置顶等等。