给 Memos 加一个更适合长期维护的内容同步入口

我一直把 Memos 当作日常记录入口:轻量、随手、没有太多编辑负担。另一方面,我也有一个更完整的 Second Brain 站点,用来承载整理后的公开文章、会员内容和长期知识库。

这两个系统的定位不同。Memos 适合快速捕捉,Second Brain 适合长期沉淀。真正的问题不是要不要二选一,而是如何让一条 memo 在需要的时候,以最低成本同步成站点里的 post。

这次我对 Memos 做了一次小改造:保留它作为输入端的轻量体验,同时给每篇 memo 增加同步到 Second Brain 的能力。

背景

原来的 Memos 里已经有一个类似 同步到 GitHub Repo 的入口,主要服务于把 memo 同步到 Jekyll 或静态站点仓库。

但随着内容系统演进,这条链路对我来说已经不再是主路径:

  • Jekyll 站点不再继续作为主要维护对象。
  • Second Brain 已经成为新的内容承载层。
  • 旧的 GitHub Repo 同步入口仍然有价值,但不应该默认占据 memo 菜单。

所以这次改造的目标很明确:

  1. 默认隐藏旧的 同步到 GitHub Repo 入口。
  2. 在系统设置里增加开关,允许需要时重新展示这个旧入口。
  3. 在每篇 memo 的更多菜单中新增两个入口:
    • 同步到公开站
    • 同步到成员站
  4. 同一篇 memo 重复同步时,应该更新已有 post,而不是重复创建。

为什么是两个同步入口

公开站和成员站在内容系统里代表两种不同发布范围。

如果只做一个通用的“同步到 Second Brain”按钮,后续还需要再选择可见性,操作会变重。Memos 的使用场景往往是碎片化的,菜单动作越明确越好。

所以我把动作拆成两个入口:

  • 同步到公开站:把 memo 同步成公开 post。
  • 同步到成员站:把 memo 同步成成员可见 post。

这两个入口是互斥选择,不是批量同步。这样可以降低误操作概率,也避免同一篇 memo 同时被错误地添加到多个目标集合。

核心设计:以 memo_id 做幂等同步

同步功能最容易踩的坑是重复创建。

如果每点击一次同步按钮,目标站点就新增一篇 post,很快就会出现多篇内容相同但 ID 不同的文章。这样的数据后续很难清理,也会破坏内容系统里的引用关系。

因此这次同步接口采用了 upsert 设计:

  • Memos 侧发送当前 memo 的唯一 ID。
  • Second Brain 侧使用这个 ID 作为 memo_id
  • 如果目标系统中不存在对应 memo_id,就创建新 post。
  • 如果已经存在,就更新原 post。

这样一来,同一篇 memo 可以反复同步。第一次是发布,后续都是更新。

为什么让 Second Brain 提供接口

还有一个关键取舍:同步逻辑应该写在 Memos 里,还是写在 Second Brain 里?

我选择让 Second Brain 提供一个专门的自动化接口,Memos 只负责调用。

这样做的好处是:

  • Memos 不需要理解 Second Brain 内部的数据结构细节。
  • Second Brain 可以自己控制 post 的创建、更新、slug、visibility、时间字段等规则。
  • 未来如果要给 AI Agent 或 MCP 暴露同样能力,可以复用同一个服务边界。
  • 两个项目之间不会因为数据库结构变化而产生过强耦合。

从系统边界看,Memos 是内容来源,Second Brain 是内容归档与发布系统。同步接口应该属于内容归档系统。

接口形态

Second Brain 新增了一个自动化接口:

POST /api/automation/memos/sync-post

请求大致包含:

{
  "memoId": "memo 的唯一 ID",
  "content": "memo 正文",
  "visibility": "public 或 members",
  "createdAt": "memo 创建时间",
  "updatedAt": "memo 更新时间"
}

返回结果包含同步后的 post 信息,例如 post ID、slug、可见性和是否为新建。

这个接口本身不依赖浏览器会话,也不依赖人工登录流程,适合被 Memos 后端、自动化脚本或未来的 Agent 工具调用。

独立 Secret,而不是复用已有管理密钥

这类自动化接口需要鉴权,但不应该随便复用系统里已有的高权限 secret。

我为 memo sync 单独设计了一个 secret,只服务于这一个能力。Memos 请求接口时通过专用 header 传递它:

x-memos-sync-secret

这样做的原因很简单:

  • 泄露半径更小。
  • 轮换时不会影响后台管理 API。
  • 不会影响其他导出或自动化任务。
  • 从日志、配置和排障角度都更容易判断用途。

一个接口对应一个足够窄的 secret,是很值得坚持的工程习惯。

Memos 侧的配置拆分

另一个需要特别注意的是配置 Key。

旧的 GitHub/Jekyll 同步功能已经有自己的配置。如果直接把 Second Brain 的地址和 secret 塞进去,短期看省事,长期会让两个功能互相污染:

  • 旧功能可能意外读取到新字段。
  • 配置页面语义变得混乱。
  • 后续迁移和清理成本变高。

所以我给 Second Brain 同步单独使用了新的配置 Key。旧的 GitHub 同步配置只继续服务旧功能,新功能使用自己的独立配置。

这个改动不大,但很关键。它让“隐藏旧入口”和“新增新同步能力”变成两个互不干扰的变化。

UI 上的取舍

在 memo 的更多菜单里,最终保留了三类动作:

  • 默认展示:同步到公开站
  • 默认展示:同步到成员站
  • 默认隐藏:同步到 GitHub Repo

旧入口不是删除,而是默认隐藏。系统设置里提供开关,用来控制是否展示它。

这比直接删掉旧功能更稳妥:

  • 对现有用户或历史工作流更友好。
  • 如果短期还需要 Jekyll 同步,可以手动打开。
  • 默认界面更贴近当前主路径,不会让菜单长期堆积过期功能。

发布链路

这次改动涉及两个项目:

  1. Memos:负责菜单、设置项、后端调用和用户操作入口。
  2. Second Brain:负责自动化同步接口和 post upsert 逻辑。

发布时要注意顺序:

  1. 先发布 Second Brain 接口。
  2. 配置好专用 secret。
  3. 验证未授权请求会被拒绝。
  4. 再发布 Memos。
  5. 在 Memos 系统设置里配置目标地址和 secret。
  6. 从 memo 菜单触发一次公开站和成员站同步,确认创建与重复更新都正常。

跨项目功能最怕“一边已经发了,另一边还没准备好”。先让被调用方稳定,再发布调用方,是更稳的顺序。

这次改造带来的收益

完成后,Memos 仍然是原来的 Memos:打开快、记录快、没有额外心智负担。

但每篇 memo 多了一个很实用的出口:

  • 临时想法可以继续留在 Memos。
  • 值得公开沉淀的内容,可以同步到公开站。
  • 更适合内部或会员可见的内容,可以同步到成员站。
  • 同一篇 memo 修改后再次同步,会更新已有 post。

这不是把 Memos 改造成 CMS,而是在它和内容站之间补了一条轻量、明确、可维护的桥。

一些经验

这次改造里最值得保留的经验有几条:

第一,旧功能不要急着删除。默认隐藏加设置开关,通常比直接删除更平滑。

第二,跨系统同步一定要幂等。只要用户可能重复点击,就必须把重复同步当成正常路径处理。

第三,自动化接口要有独立 secret。不要为了省配置复用高权限管理密钥。

第四,配置要按功能边界拆开。新功能不要塞进旧功能的配置对象里,否则后面一定会付出维护成本。

第五,服务边界要放在拥有数据的一侧。Second Brain 拥有 post 数据结构,所以 post 的创建和更新规则应该由它自己提供接口承载。

结语

这次对 Memos 的修改并不复杂,但它把日常记录和长期内容沉淀之间的距离缩短了很多。

对个人知识系统来说,最重要的往往不是再做一个庞大的编辑器,而是让已有工具之间形成顺手的流动:记录时足够轻,整理时足够稳,发布时足够明确。

Memos 继续负责捕捉,Second Brain 继续负责沉淀。中间这条同步链路,只做必要的事。


继续阅读