起因
我一直想在文章地步加个评论框:
- 网站自带评论框 —— 留言不要求登录,流程越简单越好
- 长毛象评论也显示 —— 我转发的那条 toot 下面的回复,自动同步到对应文章页面
方案
Claude 给了 5 种主流方案的对比:
| 方案 | 用户门槛 | 部署复杂度 | dream 主题原生支持 |
|---|---|---|---|
| Utterances | 需 GitHub 账号 | ⭐ 5 分钟 | ✅ |
| Disqus | 任意社交账号 + 广告 | ⭐ 10 分钟 | ✅ |
| Waline | 邮箱即可,可匿名 | ⭐⭐⭐ 30-60 分钟 | ✅ |
| Twikoo | 邮箱即可,可匿名 | ⭐⭐ 20-30 分钟 | ✅ |
| 长毛象评论 | 需长毛象账号 | ⭐⭐⭐ 自己写 | ❌ |
Waline 是中文工具里最成熟的"可匿名 + 自托管"方案,dream 主题内置支持,于是我选择了Waline。
后端选型
Waline 需要一个后端服务(Node API + 数据库)。问 Claude 部署位置时我选择统一在 Cloudflare 生态。
我自己搜了一下找到 Cloudflare 可以使用的第三方 port wuyilingwei/Waline_On_Worker
(作者明确声明是"AI 辅助开发,生产前自行评估风险"):
一个运行在 Cloudflare Workers 上的 Waline 评论系统后端实现,使用 D1 (SQLite) 作为数据存储。实现了 Waline 的绝大多数功能。
部署 Waline 后端
整个流程(用了 ~20 分钟):
# 1. 装 pnpm(之前没用过)
brew install pnpm
# 2. 克隆 repo 到统一的 GitHub 文件夹
cd ~/Documents/GitHub/
git clone https://github.com/wuyilingwei/Waline_On_Worker.git
cd Waline_On_Worker
# 3. 装依赖
pnpm install
# 报错:[ERR_PNPM_IGNORED_BUILDS] esbuild、sharp、workerd 需要构建脚本批准
pnpm approve-builds # 交互式批准这三个 native 包
# 4. 创建 D1 数据库
npx wrangler d1 create waline-db
# 输出 database_id = ""
- Repo 里没有
wrangler.toml模板。需要手动建一个:
name = "waline-comment"
main = "src/index.ts"
compatibility_date = "2024-12-01"
[[d1_databases]]
binding = "DB"
database_name = "waline-db"
database_id = "database_id"
[vars]
SITE_NAME = "sitename"
SITE_URL = "https://mysite.com"
SECURE_DOMAINS = "https://mysite.com"
继续:
# 6. 初始化 D1 数据库 schema
pnpm run db:init
# ⚠️ 这条命令默认跑在本地开发数据库!schema 没到线上!
# 7. 生成 JWT 密钥
openssl rand -hex 32
# 复制输出
npx wrangler secret put JWT_SECRET
# 粘贴
# 8. 部署 Worker
pnpm run deploy
# 输出:https://waline-comment.yoursite.workers.dev
# 9. 浏览器打开 /ui/register 注册第一个用户(自动成为 admin)
500 Internal Server Error
在 /ui/register 注册时 500 报错。因为第 6 步的 pnpm run db:init 在 package.json 里是 wrangler d1 execute waline-db --file=./schema.sql —— 没有 --remote,新版 wrangler 默认跑在本地开发库,线上库没初始化。
修复:
npx wrangler d1 execute waline-db --remote --file=./schema.sql
Hugo 集成
后端搭建完毕后,前端要把评论框接上:
# hugo.toml
[params]
waline = true
walineServer = "https://waline-comment.leonashao.workers.dev"
dream 主题内置支持 waline = true,但默认 init 配置不太好看,所以覆盖了 layouts/partials/waline.html:
<div id="waline" class="waline-wrapper"></div>
<script type="module">
import { init } from 'https://unpkg.com/@waline/client@v3/dist/waline.js';
init({
el: '#waline',
serverURL: "https://waline-comment.leonashao.workers.dev",
dark: 'html.dark',
lang: 'zh-CN',
requiredMeta: [], // 都不强制;昵称留空即匿名
pageview: false, // 浏览量交给 GA
reaction: false, // 关掉评论赞踩
meta: ['nick', 'mail'], // 只显示昵称 + 邮箱,去掉网址
locale: {
nick: '昵称(必填)',
mail: '邮箱(可选)',
placeholder: '欢迎留言(◍•ᴗ•◍)',
sofa: '快来抢沙发!',
},
});
</script>
长毛象评论
主要是 Mastodon API 的客户端实现。layouts/partials/mastodon-comments.html 是一个独立 partial,frontmatter 里有 mastodon 字段才渲染:
{{ with .Params.mastodon }}
<section class="mastodon-comments" data-instance="m.cmx.im" data-status-id="{{ . }}">
<h3>长毛象上的讨论</h3>
<div class="mastodon-comments-list">加载中...</div>
</section>
<script>
fetch('https://m.cmx.im/api/v1/statuses/' + statusId + '/context')
.then(r => r.json())
.then(data => {
// 渲染 data.descendants 里的 replies
});
</script>
{{ end }}
完整代码 ~100 行 JS,处理:直接回复 toot 的过滤(不显示回复的回复,避免嵌套)、HTML 转义、头像渲染、时间格式化、错误处理。
用法:每次想要某篇文章带长毛象评论,就:
- 在长毛象上发一条 toot 提到这篇文章
- 复制 toot 的 ID(URL 最后一段)
- 加到 frontmatter:
mastodon: "111234567890123456" - push,~2 分钟后该文章页底部就会出现"长毛象上的讨论"板块
没加这个字段的文章页面就不显示,纯 Waline。