Memos on EdgeOne 部署教程:零数据库,纯 Blob 存储


Memos 是一个轻量的自托管笔记应用,数据全部留在自己手里。问题是,传统玩法都要先有一台服务器:装 Docker、跑 SQLite、配反向代理。
如果你手上只有腾讯云 EdgeOne 账号,这篇文章就是为你准备的。memos-on-edgeone 把 Memos 迁移到了 EdgeOne Makers:不建库、不初始化、不运维,关联 GitHub 仓库推送即部署,所有数据落在 EdgeOne Blob 里。
项目简介
| 层级 | 技术 |
|---|---|
| 运行时 | EdgeOne Makers Cloud Functions(Node.js 22.11.0) |
| 后端框架 | Hono |
| 数据存储 | EdgeOne Blob(默认)/ S3 兼容存储(可选) |
| 前端 | React + Vite + TailwindCSS |
| 认证 | JWT(HS256)+ bcrypt |
它做的事情可以概括成一句话:把这个应用需要的所有东西都变成 Blob 里的几个 key。
- 用户、备忘录、附件、实例设置,全部存储在 Blob
- 数据库是纯 JSON 文档,命名空间首次访问时自动创建,不需要跑迁移脚本
- 也可以整体切换到任意 S3 兼容存储(MinIO、AWS S3、Cloudflare R2、阿里云 OSS、腾讯云 COS、七牛 Kodo、又拍 USS、华为云OBS)
- AI 语音转写走任意 OpenAI 兼容接口,自行填 endpoint 和 API Key
数据是怎么存的
理解这一点,你就明白它为什么能”零初始化”了。
Blob 里的 key 布局非常直白:
| Key | 内容 |
|---|---|
db/user.json、db/memo.json、db/attachment.json … | 十二张表,每张表是一个 { rows: [...], nextId } 的 JSON 文档 |
instance/settings/GENERAL、instance/settings/STORAGE、instance/settings/AI | 实例设置(站点名、注册开关、存储与 AI 配置) |
attachments/<uid>/<filename> | 附件原始文件 |
后端没有换掉 Memos 的 SQL,而是实现了一个覆盖实际查询范围的 SQL 子集引擎(SELECT/INSERT/UPDATE/DELETE、WHERE、ORDER BY、LIMIT/OFFSET、COUNT/SUM、ON CONFLICT 等),把每条语句翻译成对内存里的行数组做过滤与写回。数据量小的时候,这套方案的读取速度和写放大都可以接受——因为个人笔记通常也就几百上千条。
为什么不用 EdgeOne KV 存储用户和配置数据
这是很多人看到”数据存 Blob”时会问的问题:EdgeOne 不是自带 KV 吗?答案很直接——国内版 EdgeOne 的 KV 存储需要提交申请、人工审核后才能使用,因此这个项目把用户、备忘录与配置数据统一放进了 Blob。理由不止一条:
1. KV 需要申请开通,Blob 不用
KV 存储的开通流程是:控制台顶部导航栏 → KV Storage → Apply now,然后填写申请理由、提交申请表、等待工作人员审核。也就是说,想用 KV 的部署者要先卡在一个人工审批环节,审核结果和时长不可控。
而 Blob 是零配置的:代码里第一次 getStore("memos") 调用时,平台会自动为当前项目创建同名命名空间,后续调用直接复用。既不用申请,也不用在控制台点”创建命名空间”再”绑定变量名”。
对于一个”点几下就能用起来”的部署教程,把申请审核写进前置条件是体验最差的一环。
2. KV 当前仅在 Edge Functions 运行时可用
官方存储对比文档里写得很清楚:KV 的运行时环境是 Edge Functions only,而 Blob 支持 Edge Functions / Cloud Functions。
memos-on-edgeone 的后端是 Node.js Cloud Functions(依赖 @edgeone/pages-blob 这个 Node.js SDK,edgeone.json 里也配置了 cloudFunctions.maxDuration 与 externalNodeModules)。即使申请到了 KV 命名空间,也无法在这个运行时里直接访问——最后还是要绕回 Blob。
3. 一致性模型:用户和配置需要读后立即一致
| 能力 | KV 存储 | Blob 存储 |
|---|---|---|
| 一致性 | 最终一致性,60s 内全球同步 | 默认最终一致,单次读可切换强一致 |
| 运行时 | 仅 Edge Functions | Edge Functions / Cloud Functions |
| 启用方式 | 控制台创建命名空间 → 绑定项目 | getStore('name') 直接可用 |
| 免费版存储空间 | 1 GB | 1 GB |
| 单值大小 | 25 MB | 25 MB |
KV 只有最终一致性,没有”读最新值”的选项。用户表、实例设置这类权威数据必须读到刚写入的内容——比如注册后立刻登录、刚改完设置就刷新页面——不能容忍最长 60 秒的延迟。
Blob 则可以在每次读取时指定 { consistency: "strong" } 绕过边缘缓存直读主存储。本项目正是这么做的:所有对 JSON 表的读取都强制强一致(worker/src/storage/index.ts 中的 EdgeOneBlobProvider 每次 get 都带 consistency: 'strong'),保证读后写成立。
容量并不是原因——两者免费版都是 1 GB、单值 25 MB。真正的差别是开通门槛、运行时可用性和一致性语义。
顺带一提:以后想换回 KV 也不难
Blob 里是纯粹的 key → JSON 文档,db/user.json 这类 key 天然可以一对一映射成 KV 的 key。也就是说,如果哪天 KV 申请下来了,把 worker/src/db/blob-database.ts 里的存储实现换成 KV 后端即可,业务层 SQL 和表结构都不用动。这也是这个项目坚持把存储收敛到一层抽象接口的原因。
部署方式一:控制台关联 Git 仓库(推荐)
这是最省事的路径,不需要装任何 CLI,也不需要本地构建。
1. Fork 仓库
把 memos-on-edgeone Fork 到你自己的 GitHub 账号。
2. 从 Git 仓库导入项目
- 登录 EdgeOne 国内版控制台 | EdgeOne 国际版控制台
- 进入 边缘应用 → Pages(Makers)→ 创建项目
- 选择 从 Git 仓库导入
- 授权 GitHub,选择刚才 Fork 的仓库
- 构建配置会自动读取仓库根目录的
edgeone.json,一般不需要手改
edgeone.json 里的关键配置如下:
{ "buildCommand": "npm run build:web", "installCommand": "npm install && cd web && npm install", "outputDirectory": "./web/dist", "nodeVersion": "22.11.0", "cloudFunctions": { "maxDuration": 120, "externalNodeModules": ["@aws-sdk/client-s3", "@aws-sdk/s3-request-presigner"] }, "rewrites": [{ "source": "/*", "destination": "/index.html" }]}注意 rewrites:前端是 SPA,所有未知路径都要回退到 index.html,/u/<username> 这类页面靠它才能正常打开。
3. 配置环境变量
在 项目设置 → 环境变量 中添加:
JWT_SECRET=<你的密钥>INSTANCE_NAME=memos-on-edgeoneAPP_VERSION=1.0.0BLOB_STORE_NAME=memosJWT_SECRET 是唯一必填项,用来签发登录态。生成一个足够随机的值:
# macOS / Linuxopenssl rand -base64 32
# Windows PowerShell[Convert]::ToBase64String([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32))不要用示例值或短字符串。JWT_SECRET 泄露等于任何人都能伪造登录令牌。
4. 访问并注册管理员
部署成功后(通常 2~5 分钟),打开 EdgeOne 分配的默认域名。
第一个完成注册的用户会自动成为管理员(ADMIN),之后的注册才是普通用户。所以部署完成后请立刻完成管理员注册,再到「设置 → 实例设置」里打开「禁止用户注册」,避免被别人抢先注册成管理员。
访问 https://你的域名/api/v1/health,返回 {"status":"ok"} 说明 Cloud Functions 已经正常工作;如果返回 HTML 页面或 404,说明 Functions 还没部署成功或路由没命中。
5. 绑定自定义域名(可选)
在 项目 → 域名管理 → 添加自定义域名 中填入你的域名,按提示在 DNS 服务商处添加对应的 CNAME 记录,证书可在控制台申请免费证书或使用托管证书。
部署方式二:本地开发 + 推送自动部署
想在本地改代码再部署,按这套流程走。
1. 安装 EdgeOne CLI 并登录
npm install -g edgeoneedgeone -v # 确认安装成功edgeone login # 浏览器弹窗授权;命令行会询问站点,选 China 或 Globaledgeone whoami # 查看当前账号2. 克隆并安装依赖
git clone https://github.com/你的用户名/memos-on-edgeone.gitcd memos-on-edgeonenpm installcd web && npm install && cd ..根目录和 web 是两个独立的 npm 工程,EdgeOne 的安装命令也正是分两步安装的。
3. 配置本地环境变量
cp .dev.vars.example .dev.vars # Windows: copy .dev.vars.example .dev.vars编辑 .dev.vars,至少填入 JWT_SECRET:
BLOB_STORE_NAME=memosJWT_SECRET=your-super-secret-jwt-key-hereINSTANCE_NAME=memos-on-edgeoneAPP_VERSION=1.0.0.dev.vars 已在 .gitignore 中,不要把它提交到仓库。
4. 启动本地开发服务
需要两个终端:
# 终端 1:后端 Cloud Functionsnpm run dev
# 终端 2:前端 Vite 开发服务器npm run dev:webedgeone makers dev 默认在 8088 端口同时提供前端页面与 Functions 服务。如果想把控制台已配置的环境变量同步到本地,可以执行 edgeone makers link 关联项目,或者用 edgeone makers env pull 拉取环境变量。
5. 推送触发自动部署
git push origin mainEdgeOne 检测到推送后会自动安装依赖、执行 npm run build:web、把前端部署到 CDN、把 Functions 部署到边缘节点,并更新域名指向。
部署方式三:CLI 直接上传
不想每次都推 Git,也可以直接从本地上传构建产物:
edgeone makers deploy # 生产环境edgeone makers deploy -e preview # 预览环境CI 里也可以用 API Token 免交互部署:
edgeone makers deploy -n my-memos -t $EDGEONE_API_TOKEN部署后的常用配置
这些都在前端的「设置」里完成,不需要重新部署。
附件存储
默认附件也存在 Blob 里(key 为 attachments/<uid>/<filename>)。需要注意 Cloud Functions 对请求体超过 6 MB 会直接拒绝,所以大附件走的是预签名 URL 直传:函数只签发一个短期令牌,浏览器把文件字节直接 PUT 到存储,再回调 POST /api/v1/attachments/complete 落库,绕开了函数体积限制。
想把附件搬到自己的 S3 兼容存储,可以在 设置 → 存储 里选择 S3 并填写 endpoint、region、bucket、Access Key / Secret Key。切换后新旧附件都能正常读取:每条附件记录都会写入 storage_type 标记(BLOB 或 S3),读写时据此解析到对应后端。
如果连数据库也要一起搬到 S3,那就在环境变量里设置 STORAGE_TYPE=s3 加上一组 S3_* 变量,此时 Blob 不再承担任何数据:
STORAGE_TYPE=s3S3_ENDPOINT=https://s3.your-provider.comS3_REGION=autoS3_BUCKET=your-bucket-nameS3_ACCESS_KEY_ID=your-access-keyS3_SECRET_ACCESS_KEY=your-secret-keyS3_FORCE_PATH_STYLE=true # MinIO 等需要路径风格的服务保持 trueAI 语音转写
在 设置 → AI 里新增一个 OpenAI 兼容的自定义 API(endpoint + API Key),指定为转写模型(如 whisper-1),录音文件就能直接转成文字写进备忘录。API Key 存在 Blob 的 instance/settings/AI 里,接口返回时会做脱敏。
关闭注册
设置 → 实例设置 中打开「禁止用户注册」和「禁止密码登录」(如需配合 OAuth 登录)。
环境变量速查
| 变量 | 说明 | 必填 | 默认值 |
|---|---|---|---|
JWT_SECRET | JWT 签名密钥 | 是 | - |
BLOB_STORE_NAME | Blob 命名空间名 | 否 | memos |
STORAGE_TYPE | blob | s3 | 否 | blob |
S3_ENDPOINT | S3 端点 | S3 模式必填 | - |
S3_REGION | S3 区域 | 否 | auto |
S3_BUCKET | 桶名 | S3 模式必填 | - |
S3_ACCESS_KEY_ID | Access Key | S3 模式必填 | - |
S3_SECRET_ACCESS_KEY | Secret Key | S3 模式必填 | - |
S3_FORCE_PATH_STYLE | 强制路径风格 | 否 | true |
INSTANCE_NAME | 实例名称 | 否 | memos-on-edgeone |
APP_VERSION | 应用版本 | 否 | 1.0.0 |
备份与迁移
因为所有表就是 Blob 里的 JSON 文档,备份特别直白:把 db/*.json 和 instance/settings/* 原样导出来存好,恢复时再传回去即可——不用像 SQLite 那样做一致性快照。
Blob 官方定位是”运行时数据存储”,控制台只提供只读浏览(看得到命名空间和目录结构,但不能下载),命名空间创建和数据操作都要通过 SDK 完成。也就是说大批量导出/迁移建议用 @edgeone/pages-blob 配合 projectId + API Token 从外部脚本访问。
Blob 免费版单值上限 25 MB,db/memo.json 会随着笔记数量增长而变大。个人使用几千条笔记完全没问题;如果要放大量带附件的内容,建议尽早按上面的方式把存储切到 S3。
常见问题
Q:一定要用 EdgeOne 吗?能用 Cloudflare 吗? A:可以,这是两个独立项目。Cloudflare 版用 D1 + R2,EdgeOne 版用 Blob。
Q:构建失败了怎么办?
A:先看部署日志。常见原因是 Node 版本(必须是 22.11.0)或依赖没装全——根目录和 web 都要 npm install。如果日志里出现 @aws-sdk/* 相关的打包错误,注意 edgeone.json 里已经把它们放进 cloudFunctions.externalNodeModules,不要删掉。
Q:页面刷新后 404?
A:edgeone.json 里的 rewrites 被覆盖了。SPA 需要 /* → /index.html 的回退规则。
Q:注册接口报 500?
A:多半是 JWT_SECRET 没配或为空。
Q:为什么我的 KV 明明申请通过了却用不了? A:回到 为什么不用 KV 那一节——KV 当前仅在 Edge Functions 运行时可用,而这个项目的后端跑在 Cloud Functions 上。
小结
memos-on-edgeone 的价值在于把部署成本压到了最低:不用买服务器,不用建数据库,不用初始化,关联 GitHub、填一个 JWT_SECRET 就能跑。存储选型上的取舍也很明确——因为国内版 KV 需要申请审核、且仅限 Edge Functions、只有最终一致性,所以用户与配置数据统一放进了零申请、Cloud Functions 可用、支持单次强一致读的 Blob。
如果你喜欢这种”能托管就不运维”的思路,可以看看我博客里的其他 EdgeOne 折腾记录。项目开源,MIT 协议,欢迎提 Issue 和 PR。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!












