This website requires JavaScript.
BING
Because the mountain is there
OG

2022 新年好,重回 Disqus

7,114 words, 18 min read2022/01/02 AM3,236 views

先祝你 2022 新年好!


上次折腾 Disqus 已经是 4 年前的事情 了。

前段时间重构后有很强烈的冲动,希望评论能力可以借助社会化系统活跃起来,于是又大刀阔斧改造了一番。

下文以要实现的业务及解决的问题为序。

接入 Disqus

众所周知,Disqus 是完完全全彻彻底底被墙了的,在国内没有任何办法做到 “正常使用”,社区也有不少代理实现,无外乎两种:

1. 代理模式 + 原生模式

这是一种重在前端的方案。

前端检测用户网络的可用性,决定加载 “服务端代理转发请求后交予前端渲染的本地评论框” 还是使用 “原生的 Disqus embed”,之前我在 《关于多说和 Disqus 的选择》 里画过图。

这个方案的缺陷是,“代理模式下“ 用户无法发布评论,因用户的网络始终是与 Disqus 隔离的,如果要做 Cookie 代理透传,可行性极低。

2. 完全代理,使用 Disqus API 授权

这是一种重在服务端的方案。

不再使用原生的 Disqus embed,前端完全自行控制渲染 UI,服务端对接全套的 Disqus API,并接入 Disqus 的 OAuth API,由用户通过前端发起 OAuth 授权,授权后产生的 Access Token 由服务端维护;这样一来,用户的一切数据通信都是在与服务端进行,仅 OAuth 授权环节要求用户的网络环境畅通无阻。

同时,由于这种方案假设了用户的网络是通的,所以必须支持访客评论方案,巧的是 Disuqs 的 API 中是 支持访客评论 的,仅需 后台简单配置 即可。

在这次的改造中,我实施的是第二种方案,还因此特意将服务器迁到了 HK。

Disqus API

Disqus 的 API 文档 很简约,但不友好的地方在于,其基本概念缺乏解释,但名字却又无法直抒其意。

快速读懂 Disqus API 文档仅需要了解两个基础概念即可:

1. Thread

相当于 “文章”,或 “页面” 单元,在 NodePress 中对应 Article,在你的系统中可能叫 Post 之类的。

Disqus Threads 的后台管理页面 - Discussions

2.Post

相当于 “评论” 单元,在 NodePress 中对应 Comment,一条 Post 数据就是一条评论。

所有的 Post 都有自己的 thread 字段,该字段即是 “这条评论所在的页面的 ID”。

Disqus Posts 的后台管理页面 - Moderate

其他的次要概念

  • Forum:论坛,对应应用级别的单元,即一个 Disqus 应用就是一个 Forum,大部分时候我们的一个账户往往只有一个论坛,可以忽略
  • Category:分类,即 Thread 所属的分类,被 Disqus 用于做一些内容推荐之类服务所用,如不需要可忽略
  • 还有很多... 有兴趣自己去研究,反正命名一个比一个奇葩

API 的坑

你会在 Disqus API 的很多个页面看到 “Requires authentication” 这样的一个说了等于没说的权限说明。

经过我的大量测试,我发现:Disqus 的管理后台也大量使用了自己的 Open API,但其并未使用 Access Token 进行鉴权,而是采用 Session,所以 “Requires authentication” 是包含 Session 这种方式的,但对外开放的 API 仅支持 Access Token 这种方式,所以当某个文档里说 “有一些字段只有你身为最高管理员身份时才可用”,其指的并不是 “你的 Access Token 需要包含 Manage Forums 权限”,而是特么写给自己人看的 “你得是在官方后台通过帐密登录的 Session 才会拥有所谓的最高管理权限”。

如果你也在排查类似的问题,看到这里,则可以放弃一些无谓的尝试了,之前 Fooleap's Blog - Disqus API 的权限问题 里也提到过这一点。

新站的接入

如果是新站点接入的话,简单来说,只需要非常简单的几个 API 便可实现业务需求:

如果需要一些增强能力,可以再加上:

旧站的接入

由于 NodePress 已经运行有近 5 年了,从最早决定自建评论始,至今日已留下了大量的存量数据,如果要接入 Disqus,则要解决数据同步的问题。

数据的同步,即分解为 Disuqs > NodePress 和 NodePress > Disqus 之间的数据流动。

NodePress 至 Disqus

这个很好解决,Disqus 提供了导入数据的能力,支持特定的几种平台格式,同时支持 基于 Wordpress 的 WXR 格式的扩展格式,你只需要:

  1. 导出自有系统中的全部评论数据
  2. 格式化为指定的 WXR 格式
  3. 到 Disqus 后台上传

Disqus import 后台可以看到任务进度和状态,内容不合法(少于 3 个字符)、PID 无效、之类的无效数据会被暴露出来。

这里是 NodePress 的相关处理代码

Disuqs 至 NodePress

初始想法是,迁移后,API 都直接转发到 Disqus,原 NodePress 纯粹为 Comment 数据的备份所用,NodePress 后台依旧可管可查。

但是!Disqus 的 webhook 仅为 Wordpress 服务,并不对外开放。

当然更详细的文档中也提到了一些其他的同步方法,无非是定时定量取数据,直到取完,实在是拉胯。

到了这里,我有了一个大胆的想法!既已完全包装了 API,完全控制了 UGC 入口,何不直接数据双写,用户发布评论时,NodePress 写一份,Disqus 写一份。

妙啊!这样做的好处有很多:

  • 前端的改造也少了很多,只需要替换下 API 路径
  • NodePress 中原自有业务与新增的 Disqus 业务可最大程度的解耦
  • 访客评论者的个人信息将被自动存储至 NodePress,邮件通知服务不受影响
  • 前端的数据依旧直接从原 NodePress Comment API 拉,而不是转发至 Disqus,速度自然还是以前那么快

但是,这样做就必须保证两端数据的同步:NodePress 后台的任何改动都应该同步至 Disqus 做对应的操作。

当然,由于偷懒,我就没做这方面的处理。

于是乎,我简单地用 API 在后台实现了三个页面,你可以在 veact-admin 的 DEMO 站点看到:

  1. 在 NodePress 后台查看所有 Disqus Post
  2. 在 NodePress 后台查看所有 Disqus Thread
  3. 在 NodePress 后台进行和 Disqus 之间的数据同步(重要)

Posts 和 Threads 都是只能看,不能操作。

Synchronize 则支持了完整的双向数据同步能力:

  1. 将 NodePress 的全量 Comments 数据导出为指定的 WXR 格式并下载,手动导入至 Disqus
  2. 在 Disqus 后台导出所有全量数据,并上传至 NodePress 进行合并
数据流操作后台
数据流操作后台

前端登录机制

其实这部分完全按照 Disqus 官方文档 的说明实施即可,都是老生常谈的 OAuth 授权套路,这里有 NodePress 的相关实现

  1. 前端发起 Authorize URL 授权页 Window
  2. 用户确认授权后,Disqus 回调指定 Nodepress callback 并附带 code
  3. Nodepress callback 收到 code 后换 Access Token + 种 Cookie + 关页面
    • 后续用户对 Nodepress 的一切请求则会携带包含 Access Token 的 Cookie
    • Cookie 失效则某些需鉴权的接口报错
  4. 前端监听 “授权页 Window” 关闭,窗口关闭后自动获取用户信息(NodePress.GetDisqusUserInfo
  5. 得到信息后缓存至本地 LocalStorage,并展示至相关 UI

评论发布

前面说了,发布评论流程

  • 之前是 NodePress.CreateComment
  • 则现在是 NodePress.CreateUniversalComment

NodePress.CreateUniversalComment 简单来说是以下步骤:

  1. isCommentableTarget 确保目标的 Article 是存在的,且允许评论
  2. makeSureThread 确保目标的 Thread 是存在的,如不存在则创建
  3. isNotBlocklisted 确保评论通过了 NodePress 自身的黑名单机制
  4. getDisqusPostIDByCommentID 如果目标评论存在 PID,即回复型评论,则需要查出 PID 对应的 thread ID,以为接下来的创建评论服务
  5. createDisqusComment 通过 Disqus API 创建评论(包含访客评论)
  6. approveDisqusPost 如果创建的是访客评论,即用户未登录,则需要立即使用管理员 Access Token 进行 Approve 操作,否则,此评论将会是待审核状态
  7. createComment 将创建成功的 Disqus Post 数据混合关键字段至 comment,并在 NodePress 创建数据
业务流程
业务流程

流程也不复杂,但是 Disqus 似乎还存在着一些缓存、CDN 引起的 BUG:

approveDisqusPost 这一步经常会失效,提示 “You do not have admin privileges on post xxx” 之类的错误,但 Reset Access Token 之后又当即可用,过段时间后就会出现异常。

createDisqusComment 是不能够指定 state ip_address 之类的字段的,正如上文所述,这些字段仅用于 Session 登录的管理员用户。

由于在第 7 步,创建本地数据时,已存储了 Disqus 相关的元数据,所以现在前端也自然而然借助这些元数据实现删除功能了。

以上即此次实现的核心业务,NodePress 相关源码在这里

缺陷

目前的缺陷:

  • 如果用户的评论是在 Disqus 产生的,NodePress 将无从感知,从而数据不同步,不过这是个非常小概率事件。
  • 无论在 NodePress 还是 Disqus 后台做了某些数据修改,如审批删除评论之类的,两端都不会同步。
  • 由于 approveDisqusPost 接口经常失败,导致可能刚发布的访客评论在 Disqus 是看不到的,因为其是 “待审批状态”。

参考

快在页面下方试试新的评论能力吧!

完。

Creative Commons BY-NC 3.0 CNhttps://surmon.me/article/191
11 / 11 comments
Guest
Join the discussion...
  • LRboy
    Lrboy🇭🇰HKChai WanWindowsEdge
    #2358

    我也是来测试一下【评论】

              
    • 1
    print("Hello world")
  • 帅逼
    帅逼🇭🇰HKCentralMac OSChrome
    #2246

    我是来试试评论的

  • 528
    528🇨🇳CNHangzhouAndroidQuark
    #2224

    手动审批那里(见此)链接,是用梯子才能看?我正常点不开

    • Surmon
      Surmon🇨🇳CNShanghaiMac OSChrome
      #2225

      reply:

      是啊,所以才有这篇文章的

    • 528
      528🇨🇳CNHangzhouAndroidChrome
      #2226

      reply:

      给你点赞|•'-'•)و✧

  • Alexander
    Alexander🇭🇰HKCentralWindowsChrome
    #2220

    请问文章中画流程图的工具是啥?

    • Surmon
      Surmon🇨🇳CNShanghaiMac OSChrome
      #2221

      reply:

  • Su
    Su🇨🇳CNShanghaiiOSMobile Safari
    #2219

    Form Safari mobile

  • Linus Torvalds
    Linus torvalds🇨🇳CNShanghaiiOSWeChat
    #2218

    From Wechat

  • Su
    Su🇨🇳CNShanghaiiOSWebKit
    #2217

    From Zhihu

  • Surmon
    Surmon🇨🇳CNShanghaiMac OSChrome
    #2216

    好支棒 ❤️‍🔥