ChunLong Blog

ChunLong Blog

LOADING

从 giscus 到自建评论和点赞

2026-09-06967 字 · 约 3 分钟0 次阅读

这个博客的评论区曾经是 giscus——基于 GitHub Discussions 的开源评论组件,一段 script 标签就能用。但用了一阵之后,我把它拆了,自己写了一套。这篇文章记录为什么拆,以及拆的过程中那些值得记下的设计。

为什么要拆掉 giscus

giscus 的问题不在它本身,而在于"借来的东西不合身":

  • 评论数据在 GitHub Discussions 里,我的数据库完全不认识它们——站内搜索搜不到评论,后台也没法管理(删广告评论要去 GitHub 操作);
  • 访客必须登录 GitHub 才能评论。对技术博客这似乎合理,但我的访客里还有不少朋友是非技术背景的;
  • 外挂 script 和站内的毛玻璃、动效体系永远隔着一层,视觉上是"贴上去的"而不是"长出来的"。

我想要的其实是一套原生的、访客友好的、数据归我自己的互动系统。

多态评论表:文章和说说共用一套

设计评论表时最自然的想法是分两张表(文章评论、说说评论),但那样后台管理、渲染组件、API 都要双份。最后用了多态设计:

export const comments = sqliteTable("comments", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  refType: text("ref_type").notNull(),   // "post" | "moment"
  refId: integer("ref_id").notNull(),    // 文章 id 或说说 id
  visitorId: text("visitor_id"),          // 游客身份
  githubUserId: integer("github_user_id"), // 登录身份(二选一)
  content: text("content").notNull(),
  parentId: integer("parent_id"),         // 一层回复
  deletedAt: integer("deleted_at"),       // 软删除
  createdAt: integer("created_at").notNull(),
});

refType + refId 指向被评论的对象,文章和说说共用同一张表、同一个 API、同一个渲染组件。软删除(deletedAt)代替物理删除——楼层引用不至于因为删一条评论而错乱。

NOTE

回复只做了一层(parentId 指向根评论)。博客场景里多级嵌套的阅读体验通常不如"平铺 + @提及",这是我刻意的选择。

身份:匿名也能说话,登录获得头像

身份系统是双轨的:

  • 游客:浏览器 localStorage 里一个匿名 UUID,可以直接评论和点赞,零门槛;
  • GitHub 登录:点一下授权,回来就有了头像和昵称。

OAuth 的实现坚持了一条安全底线:client secret 永远不出服务器。浏览器只被重定向到 GitHub 授权页,回调落到我自己的服务端路由,code 换 token 的请求在服务器内部完成,带着 state 防 CSRF。整个流程四条路由:发起、回调、登出、查我。

会话是 httpOnly 的 JWT cookie,和管理员的会话分开签发。github_users 表只存公开资料(login、头像、bio),不存 token。

最微妙的一笔:游客点赞回填

上线前的某个深夜我意识到一个问题:一个访客先用游客身份给三篇文章点了赞,后来一时兴起登录了 GitHub——那三个赞还是匿名的,头像列表里没有他。

于是我做了回填:登录成功的瞬间,服务端把这个游客 UUID 名下的所有点赞和评论,原子地迁移到 GitHub 身份下。用户什么都不用做,点过的赞"跟人走"了。

UPDATE post_likes SET github_user_id = ? WHERE visitor_id = ?;

TIP

匿名到实名的身份合并是所有"低门槛 + 可登录"双轨系统的必修课。上线时不想清楚,日后数据洗起来会哭。

后台:管理也是产品的一部分

评论管理进了后台:按文章聚合、审核、软删除、恢复,外加一份点赞/评论的统计榜单(哪篇文章被赞得最多、哪条说说评论最热)。这份榜单意外地成了我写作的反馈回路——数据告诉我大家在看什么。

复盘

拆掉 giscus 换来的是三样东西:数据主权(评论进 SQLite,搜索、备份、迁移一体)、体验一致(评论框也是毛玻璃卡片)、玩法自由(点赞可以做成头像朋友圈列表,giscus 给不了)。

成本是两周的全栈开发和一直要背的安全责任。但如果重来一次,我还是会拆——博客的乐趣一半在写,另一半在造。

评论