移植 Memos 笔记到 Cloudflare Workers 上运行


Memos 是一款非常轻量的自托管的备忘录中心。你可以把它当作个人笔记本、备忘录,多账号功能,实现一个轻量级的个人博客说说栏。之前比较喜欢用手机自带的备忘录或者电脑的记事本,但是这样有两个问题:

  • 不方便分享: 如果我分享给小伙伴,那么通常是需要截屏分享。
  • 数据共享不方便: 电脑和手机使用各自备忘录,但是需要数据互传时就非常不太方便,需要通过其他平台互传。

Memos 的一个好处,就是支持多平台,比如移动端你可以用 MoeMemos(Android 或者 iOS),桌面端你可以用 Memos 自带的 Web:

而且,Memos 还支持多账号,这样我们就可以和小伙伴一起使用 Memos,共同维护一个 Memos 服务,实现一个轻量级的说说栏。

Memos 的官方版本需要通过 Docker 部署到 VPS 或本地使用,这无形中提高了使用成本。本文详细介绍如何将 Memos 笔记应用完整迁移到 Cloudflare 边缘平台,使用 Workers + D1 + R2 替代原有的 Go + SQLite + 本地存储架构。

项目仓库地址:https://github.com/jkjoy/memos-on-cloudflare

功能特性

  • Markdown 备忘录(支持标签、代码块、任务列表、Mermaid 图表)
  • 多用户支持(管理员/普通用户)
  • 备忘录可见性(私有/工作区/公开)
  • 文件附件上传(最大 100MB)
  • 备忘录分享链接(可设过期时间)
  • 备忘录评论和表情反应
  • 音频录制 + AI 转写
  • SSO 单点登录
  • 多语言支持(中文、英文等 30+ 语言)
  • 暗色/亮色主题
  • 日历热力图
  • 标签管理
  • Webhook 通知

与原版 Memos 的区别

项目 原版 Memos 本项目
后端 Go + gRPC Cloudflare Workers + Hono
数据库 SQLite (本地文件) Cloudflare D1 (托管 SQLite)
文件存储 本地/S3 Cloudflare R2
AI OpenAI/Gemini API Cloudflare Workers AI
部署 Docker/二进制 wrangler deploy
运维 需要服务器 无服务器,零运维
前端通信 Connect RPC (protobuf) REST JSON

技术栈

层级 技术
运行时 Cloudflare Workers
后端框架 Hono
数据库 Cloudflare D1 (SQLite)
文件存储 Cloudflare R2
AI Cloudflare Workers AI (Whisper)
前端 React + Vite + TailwindCSS
认证 JWT (HS256) + bcrypt

前置要求

快速部署

1. 克隆仓库

1
git clone https://github.com/jkjoy/memos-on-cloudflare.git

进入项目文件夹

1
cd memos-on-cloudflare

2. 安装依赖

1
npm install

安装 Web 所需依赖

1
cd web && npm install && cd ..

3. 创建 Cloudflare 资源

创建 D1 数据库

1
wrangler d1 create cfmemos-db

创建 R2 存储桶

1
wrangler r2 bucket create cfmemos

创建 KV 存储空间

1
wrangler kv namespace create cfmemos-cache

[!caution]
务必记录下 创建 D1 数据库 及 创建 KV 存储空间 时返回的 ID 字符串,编辑 wrangler.toml 时需要使用。

4. 配置 wrangler.toml

将第 3 步创建 D1 时返回的 database_id 和 kv_namespaces id 填入 wrangler.toml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
name = "cfmemos"
main = "worker/src/index.ts"
compatibility_date = "2025-05-01"

[assets]
directory = "./web/dist"
binding = "ASSETS"
not_found_handling = "single-page-application"
run_worker_first = ["/api/*", "/file/*", "/u/*", "/explore/rss.xml"]

[vars]
INSTANCE_NAME = "cfmemos"
APP_VERSION = "0.28.0"
JWT_SECRET = "你的实际JWT_SECRET密钥"
CLOUDFLARE_ACCOUNT_ID = "你的Cloudflare账号ID;如果不需要AI转写功能则可不填"
CLOUDFLARE_API_TOKEN = "你Workers AI的Cloudflare_API_Token;如果不需要AI转写功能则可不填"

[[d1_databases]]
binding = "DB"
database_name = "cfmemos-db"
database_id = "你的实际数据库ID"

[[r2_buckets]]
binding = "BUCKET"
bucket_name = "cfmemos"

# Optional KV cache. Create a namespace with:
# wrangler kv namespace create cfmemos-cache
# Then uncomment this block and replace the id.
[[kv_namespaces]]
binding = "CACHE"
id = "你的实际KV储存空间ID"

[ai]
binding = "AI"

[!important]
如果要实现手机端,如:MoeMemosAndroid 和网页版 Memos on Cloudflare 同步,需要将 wrangler.toml 文件中的 APP_VERSION 值和 version.ts 文件中的 DEFAULT_APP_VERSION 值设定为Android手机端 APP 支持的 Memos versions 号。

5. 设置生产密钥

设置 JWT 密钥(务必使用强随机字符串)

1
wrangler secret put JWT_SECRET

6. 初始化数据库

1
npm run db:migrate:remote

7. 构建并部署

1
npm run deploy

部署完成后,访问 Workers 分配的域名,首次访问会进入管理员注册页面。注册的第一个用户为管理员账户。

8. 在 D1 数据库中激活 AI 引擎(可选择,实测转录效果很差)

部署完成后,进入 Cloudflare Dashboard → D1 → cfmemos-db → Console,运行以下 SQL 将 AI 提供商写入系统数据库(适配前端兼容性):

1
2
3
4
5
6
INSERT INTO system_setting (name, value) 
VALUES (
'instance/settings/AI',
'{"providers":[{"type":"WORKERS_AI","model":"@cf/openai/whisper","apiKey":"","apiKeySet":true},{"type":"CLOUDFLARE_WORKERS_AI","model":"@cf/openai/whisper","apiKey":"","apiKeySet":true}]}'
)
ON CONFLICT(name) DO UPDATE SET value=excluded.value;

[!caution]
切勿在 Memos 网页端的“系统设置 → AI”页面点击“保存”!前端 UI 无法原生存取 WORKERS_AI 类型,网页端点击保存会将数据库中的 providers 再次覆写为空数组 []。后续如需更新配置,请始终通过 D1 SQL Console 运行上述语句。

GitHub Actions 自动部署

推送到 main 分支会自动触发部署。需要在 GitHub 仓库 Settings → Secrets and variables → Actions 中添加:

Secret 说明
CLOUDFLARE_API_TOKEN Cloudflare API Token(需要 Workers Scripts:Edit、D1:Edit、R2:Edit 权限)
CLOUDFLARE_ACCOUNT_ID Cloudflare Account ID(在 Dashboard 右侧栏可找到)

工作流会自动完成:安装依赖 → 构建前端 → 执行数据库迁移 → 部署 Worker。

本地开发

需要两个终端窗口:

1
2
3
4
5
6
# 终端 1:启动 Worker 后端(端口 8787)
npm run db:migrate # 首次运行需要初始化本地数据库
npm run dev

# 终端 2:启动前端开发服务器(端口 3001,自动代理 API 到 8787)
npm run dev:web

浏览器访问 http://localhost:3001。

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
├── wrangler.toml          # Cloudflare 配置(D1、R2、AI 绑定)
├── package.json # 根 package,部署脚本
├── migrations/
│ └── 0001_initial.sql # D1 数据库 schema
├── worker/
│ └── src/
│ ├── index.ts # Hono 入口,路由挂载
│ ├── types.ts # Env 绑定类型定义
│ ├── routes/ # API 路由
│ │ ├── auth.ts # 登录/注册/刷新令牌
│ │ ├── memos.ts # 备忘录 CRUD + 评论/反应/分享
│ │ ├── users.ts # 用户管理 + 设置/PAT/通知
│ │ ├── attachments.ts # 文件上传(R2)
│ │ ├── files.ts # 文件下载服务
│ │ ├── instance.ts # 实例配置
│ │ ├── ai.ts # Workers AI 转写
│ │ ├── idp.ts # SSO 身份提供商
│ │ ├── shortcuts.ts # 快捷过滤器
│ │ └── sse.ts # 实时更新
│ ├── auth/ # JWT、密码哈希、PAT
│ ├── db/ # D1 查询模块
│ └── middleware/ # 认证中间件
└── web/
└── src/
├── connect.ts # REST 客户端(替代 Connect RPC)
├── contexts/ # React Context(实例、认证)
├── components/ # UI 组件
├── pages/ # 页面路由
├── locales/ # i18n 翻译文件
└── shims/ # @bufbuild/protobuf 兼容层

环境变量

变量 说明 必填
JWT_SECRET JWT 签名密钥,生产环境必须使用强随机字符串 是
INSTANCE_NAME 实例名称,显示在页面标题 否

生产环境通过 wrangler secret put 设置敏感变量,非敏感变量在 wrangler.toml 的 [vars] 中配置。

Cloudflare 资源绑定

绑定名 类型 用途
DB D1 Database 存储用户、备忘录、设置等所有结构化数据
BUCKET R2 Bucket 存储附件文件(图片、音频、文档)
AI Workers AI 音频转写(@cf/openai/whisper)
ASSETS Static Assets 托管前端构建产物

自定义域名

在 Cloudflare Dashboard 中为 Worker 添加自定义域名:

  1. Workers & Pages → cfmemos → Settings → Domains & Routes
  2. 添加自定义域名(需要域名已在 Cloudflare DNS 中)

常见问题

Q: 部署后访问显示空白页?

确认 npm run build:web 已执行且 web/dist/ 目录存在。wrangler deploy 会自动上传该目录。

Q: 访问 /api/* 返回前端 404 页面?

确认 wrangler.toml 的 [assets] 配置包含:

1
run_worker_first = ["/api/*", "/file/*", "/u/*"]

否则 Cloudflare 静态资源层可能会先处理请求,并把不存在的 API 路径回退到 SPA 的 index.html,最终显示前端 404 页面,而不是进入 Worker API 路由。

Q: 数据库报错 “table not found”?

执行 npm run db:migrate:remote 初始化远程数据库 schema。

Q: 如何备份数据?

1
2
3
4
# 导出 D1 数据库
wrangler d1 export cfmemos-db --remote --output=backup.sql

# R2 文件可通过 rclone 或 Cloudflare Dashboard 下载

Q: 上传大小限制?

附件上传硬编码为 100MB。Workers 免费版单次请求体限制为 100MB,付费版无此限制。

Q: 免费额度够用吗?

Cloudflare Workers Free Plan 包含:每天 10 万次请求、D1 5GB 存储、R2 10GB 存储 + 每月 1000 万次读取。个人使用完全足够。

Q: 录音按钮不存在,或者录音条旁边的“转录”图标丢失?

这是 AI 语音转写功能中最常见的问题,通常由以下 4 层原因导致,请按顺序排查:

  1. 第一层:浏览器“跟踪防护”拦截(概率最高)

    • 现象:点击 + 找不到录音按钮,或无法获取麦克风。
    • 解决:Edge/Chrome 的“严格跟踪防护”会屏蔽 navigator.mediaDevices 接口。请点击浏览器地址栏左侧 🔒 锁头图标,关闭 “此网站的跟踪防护”,并将 麦克风权限 设置为允许,按 Ctrl + F5 强制刷新。
  2. 第二层:Cloudflare KV 缓存死锁(后端改配置不生效)

    • 现象:SQL 写入或环境变量配置后,前端依然提示无 AI 服务。
    • 原因:Worker 会把配置缓存在 KV(cfmemos-cache)中。修改 D1 或环境变量不会自动刷新 KV。
    • 解决:进入 Cloudflare Dashboard → KV → cfmemos-cache → KV 对,点击 删除全部/批量删除 强行清空缓存。
  3. 第三层:wrangler deploy 冲掉 Dashboard 环境变量

    • 现象:重新部署代码后,语音转写功能突然失效。
    • 原因:命令行部署会覆盖 Dashboard 手动添加的变量。
    • 解决:确认 CLOUDFLARE_ACCOUNT_ID 和 CLOUDFLARE_API_TOKEN 已写入 wrangler.toml 的 [vars] 节点下。
  4. 第四层:网页前端 LocalStorage 缓存锁死

    • 现象:后端 GET /api/v1/instance/settings/AI 已有正确 JSON 数据,但 UI 仍不渲染图标。
    • 解决:按 F12 打开开发者工具 → 切换到 应用 (Application) → 点击 清除网站数据 (Clear site data),刷新网页并重新登录账号。

Q: AI 语音转写成功触发,但提示超时或无法识别?

  1. 控制录音时长:Workers AI 免费版对内存及 CPU 处理时长有严格限制,建议录音控制在 2~30 秒 的短语音。
  2. 激活 Workers AI 服务:登录 Cloudflare Dashboard,确保已进入过 AI → Workers AI 页面并同意了服务条款,否则 API 调用会被拒绝。