BING
足下何人,來此作甚
原创

NodePress 可以登录了

共 3,717 字,需阅读 9 分钟59 次阅读

2026 年 2 月初,我在 NodePress 中设计并上线了全新的独立用户系统。起因是一个看起来很小、却怎么也绕不过去的 BUG。

从多说到 Disqus,再到弃坑

要说清楚这件事,得从 2017 年讲起。

那时候国内有一款类似 Disqus 的社会化评论产品叫「多说」,Surmon.me 最早的评论能力就是通过多说接入的。直到多说关闭,我才实现了第一版自建的 署名评论系统 ,只有署名访客可以留下评论,名字和邮箱都可以是假的,所以这套方案本质上不具备任何身份意义。

2022 年,我 接入了 Disqus ,由于 Disqus 在国内无法访问,这中间还实现了一套复杂的代理流程。简单来说,就是把 Disqus 当成一个外挂的身份系统:用户可以通过登录 Disqus 来「登录」到 Surmon.me,然后进行评论。这在当时算是一个相对完整的方案。

但其实,这套方案一直有些鸡肋。展示评论时我优先拿的都是本地数据,用户在站内评论会在本地数据库和 Disqus API 各存一份,Disqus 除了一个没什么人用的登录按钮,几乎没有太大存在感。重度强迫症的我一直想把它移除,只是没找到合适的时机。

现在,这个时机,在 2026 年到来了。

AI 打破寂静

也是上个月,我在 NodePress 中实现了 AI 评论回复能力。按照当时版本的评论流转逻辑,AI 产生的评论数据必须先在 Disqus 存一份(发布),成功之后再在本地存一份。但 AI 是以「署名访客」身份进行评论的,这种身份对于 Disqus 来说属于「匿名用户」。

问题来了:Disqus 对于匿名用户产生的评论,无论如何都无法做到直接发布,它总会进入 pending 状态。也就是说,AI 回复确实产生了,但它会卡在 Disqus 的审核后台,用户永远看不到。

为了解决这个问题,我又重新翻开当年的技术笔记,梳理这层官方文档语焉不详、社区问答没有回应的世纪大 BUG。调研下来,这个问题有两种思路:

  1. 新评论直接 approve:在匿名用户发布评论之后立即通过审核。但这里存在权限问题,Disqus API 多年没有更新,重要问题也没有文档说明,在常规技术上这条路完全走不通,在 2022 年就走不通,今天还是走不通。

  2. 不同步到 Disqus:干脆让机器人的评论只存本地,不走 Disqus。但这样会带来另一个更严重的问题:机器人的评论在 Disqus 那边没有 ID,用户来回复这条评论时,评论的 extras 上所挂载的 disqus_post_id 元信息就是 null,永远在 Disqus 那边无法成功关联,以后这个功能也没有任何扩展空间。

两条路都走不通,只能下定决心:完全废弃 Disqus

设计新的用户系统

废弃 Disqus 之后,评论的身份问题需要从根上重新解决。我的思路是:集成 Google 和 GitHub 的 OAuth 登录,建立一张独立的 user 数据表,用户通过第三方 OAuth 登录回调来验证身份,系统本身不设密码。只要第三方不倒闭,用户系统永远稳定可用,同时也避免了存储密码的复杂性和数据泄露的风险。

但在开始动手之前,有一个设计上的核心问题需要梳理清楚:NodePress 的「用户」到底是什么含义?

NodePress 最初只是设计为单管理员系统,管理员通过后台发布和管理数据,前端的所有写操作只有署名访客这一种角色。引入新的用户系统时,我希望维持原有这套系统的简单,不让 user 成为核心业务。即便将来整个 user 表被替换成其他方案,核心数据也应该能正常工作。

所以,即便新增了 user 系统,原有的写操作(如:评论、点赞)等需要消费 user 数据的地方都会继续维持原有的的署名访客的字段和信息,并在原有的结构上多存一个 user 字段(实际存储的是 user.ObjectId)。

类似于一份「作者」快照 + 用户 ID。

        
TypeScript
123456789
interface Comment { // ... user: User | null author_name: string author_email: string | null author_website: string | null author_type: CommentAuthorType // ... }

用户层级

整体梳理下来,在新设计中,NodePress 里存在三种角色:

  • Guest(署名访客):无需任何登录,填写名字和邮箱即可评论。访客不构成用户实体,不存入 user 表。
  • User(普通用户):通过 Google 或 GitHub OAuth 登录的前台用户,存入 user 表,拥有稳定的 ID,可以查询自己的历史评论、点赞等数据,也可以一键导出或删除账户。
  • Admin(管理员):通过后台单入口登录,只需输入密码获得 Token 即可进入管理后台。不考虑多管理员的情况,不与任何社交账户绑定,不存入 user 表。

除了普通用户和管理员,还有两个特殊身份需要处理:版主(Moderator)AI 机器人

用户身份特征

整个网站只会有一个版主(也就是博主本人),这个身份在几个地方会被消费:

  • 前端用来展示特定的身份标记。
  • AI 评论机器人用来判断是否停止自动回复。
  • 邮件回复时用来确认昵称。

这份特征与数据权限没有任何关系,只是作为业务语义上的一种区分。

一开始考虑过把版主的 user_id 硬编码到配置里,或者约定 user_id = 0 就是管理员。但这两种方式都过于 hack,不如直接在 user 数据表里加一个 role 字段,存储普通用户和版主的区别。这样身份特征存在数据里,灵活性更好,也便于日后扩展(比如现在有一种用户的 username 右侧会有一个 Patron 的 badge 就是通过这里实现的)。

AI 身份特征

AI 机器人的身份消费场景和用户类似,主要用于前端展示身份标记,以及 AI 在创建评论时填入评论者信息。

user 表的设计是完全面向人的,有 nameemailurl,会绑定社交账户,前端也会展示这些信息。AI 不需要这些,也不应该把它归入用户数据。

所以最终的方案是硬编码 author 信息:AI 的信息配置为固定的 { name: 'AI', email: 'ai@surmon.me' },AI 在创建评论时直接填入这个信息,不在 user 表中创建任何记录,前端通过识别评论中 extras 字段的特殊标记信息来渲染对应的展示样式。

落地实现

设计确定后,实施步骤还算清晰:

  1. 新建一个 user 数据模型,无密码设计,完全通过三方登录回调来验证用户身份。
  2. 集成 Google 和 GitHub 的 OAuth 登录,与本地用户记录绑定。
  3. CommentVoteFeedback 以及后续所有允许用户写入数据的表,全部加入 user 字段,存储用户的 ObjectId 用于查询关联。

这次改动的规模不算大,代码实现也并不多,但是 OAuth 的联调测试实在是太磨人了。

不过,自建系统确实解决了非常实在的问题:AI 评论再也不会卡在审核队列里,每一条评论背后都可以追溯到一个真实的用户身份,系统整体也比之前干净了很多。

最终在前端实现了简易的用户管理面板:

在管理后台也可以轻松地看到用户的各种信息:

AI 也可以正常回复评论了:

最后,与其说是 Disqus 陪伴了 Surmon.me 四年,倒不如说是 Surmon.me 给 Disqus 免费打了四年广告,是时候说再见了。

快点击评论区右上角的按钮登录试试吧~

(完)

署名 - 非商业性使用 4.0 国际 https://surmon.me/article/306
0条评论
在下有一拙见,不知...
期待你的捷足先登