把自建 Memos 变成博客的动态数据源:Memos 负责发布,博客负责展示。新增或修改公开动态后,动态页会通过服务端接口读取,不需要重新写 Markdown 文章。
目标结构
- Memos
在
memos.moodlog.cn中发布一条动态,并把可见性设为公开。- Nuxt 服务端接口
博客通过
/api/moments请求 Memos,只保留指定账号的公开、正常状态动态。- 动态页面
/moments负责展示时间、标签、Markdown、图片和附件,并提供加载更多。
这种结构的关键是:令牌不进入浏览器,数据筛选放在服务端完成。当前实例已经开启公开访问,因此博客不需要携带访问令牌。
Memos API 的基本接入
Memos v0.31 的匿名列表接口是:
GET https://memos.example.com/api/v1/memos
博客接口只接受一个分页参数:
GET /api/moments?pageToken=下一页令牌
不要把用户传入的 URL 当作代理地址。服务端应该固定 Memos 来源,并固定查询条件:
const response = await $fetch<MemosResponse>(
'https://memos.example.com/api/v1/memos',
{
query: {
pageSize: 10,
pageToken,
state: 'NORMAL',
orderBy: 'pinned desc, create_time desc',
filter: 'creator == "users/Zhang" && visibility == "PUBLIC"',
},
},
)
如果实例保持私有,令牌也应该只放在服务端环境变量中,不能写进 Vue 组件、公开仓库或浏览器请求。当前公开模式下,匿名读取已经足够,就不需要额外保存令牌。
服务端再次过滤
上游过滤不是唯一防线。收到数据后,还要再次检查:
creator是否是指定账号visibility是否为PUBLICstate是否为NORMALparent是否为空,避免把回复当成独立动态name是否符合memos/{id}格式
接口只返回页面真正需要的字段,不把创建者、可见性等内部字段原样透传给前端。
Markdown 和附件
动态正文支持基础 Markdown,但不要直接渲染 Memos 返回的原始 HTML。当前实现使用 markdown-it,并关闭原始 HTML:
const markdown = new MarkdownIt({
html: false,
linkify: true,
breaks: true,
})
图片、链接只允许 http 和 https,拒绝 javascript: 等危险协议。附件按类型分别展示:
- 图片:图片网格
- 视频:原生
<video controls> - 音频:原生
<audio controls> - 其他文件:附件链接
为什么一张图片会显示两次
Memos 的一张图片可能同时出现在两个位置:
- Markdown 正文:
 attachments字段:attachments/xxx
如果把正文和附件直接分别渲染,就会得到两张相同图片。
解决方式是先解析 Markdown 图片,取出资源标识,再过滤附件:
function imageKey(value: string) {
// Memos 的以下地址都指向同一个附件资源:
// /file/attachments/id
// /file/attachments/id/name.webp
// /file/attachments/id/name.webp?thumbnail=true
return resourceIdFromMemosFileUrl(value)
}
这里应该按附件 ID 判断,而不是按文件名判断,因为两个不同附件可能恰好使用同一个文件名。正文图片保留在原本位置,只有重复的附件图片被隐藏。
正文顺序优先 按资源 ID 去重 不猜文件名
动态页的缓存和分页
动态接口每页读取 10 条,并在服务端缓存 60 秒。缓存可以减少 Memos 请求,但也意味着刚发布的动态可能最多延迟约一分钟出现。
页面加载更多时只携带 nextPageToken,不要把所有动态一次性加载。请求失败时要保留当前页内容,并按照原来的分页方向重试,避免“加载下一页失败后重新请求第一页”。
页面效果
动态卡片包含:
- 时间线 显示作者、发布时间和置顶状态。
- 正文 支持 Markdown、代码块、引用和链接。
- 媒体 支持图片、视频、音频和普通附件。
- 移动端 卡片、标签和媒体自动适配窄屏。
- 空状态 没有公开动态时显示友好的提示,而不是伪造示例内容。
发布和验证清单
# 本地回归测试 node scripts/test-moments.mjs # 代码检查 pnpm exec eslint app/pages/moments.vue server/utils/memos.ts server/api/moments.get.ts # 生产构建 pnpm build
最后访问:
https://your-domain.example/moments
如果 Memos 中只有一张图片,动态页也应该只呈现一张;如果正文里没有引用附件图片,附件区才补充显示它。
动态页适合记录短内容,正文文章适合沉淀长内容。让 Memos 负责快速发布,让博客负责长期展示,两者各自做好擅长的事。

评论区
评论加载中...