从头做一个基于GitHub Issues的评论系统

1375 words, 7 minutes to read / October 23, 2022

博客评论系统换了一个又一个,有的功能性太差,有的太臃肿,有的广告满天飞。由于各种原因还是全都删掉了。后来那段时间里甚至觉得没有评论系统也不是什么大不了的事情,但事实是,我做不到那样佛系,读者的反馈对于我来说还是很重要的,哪怕是一句无意义的 “test”,都能让我很开心 —— 原来真的有人在看我的文章。

思来想去还是决定加上评论系统,但是这次,我要自己来!在网上浏览了一些方案之后,我最终锁定了方案:实现一个基于 GitHub Issues 的评论系统。Issues 本来是用来提交软件问题的一个板块,由于做得太 🐮🍺,已经有不少开源项目拿它做评论系统甚至社交软件了。下面我们就来看看我具体怎么实现吧!

前置需求

  • 一台自己的服务器
  • 基础的前端开发知识
    • HTML / CSS / JavaScript
  • 基础的后端开发知识
    • 任意后端语言(本篇使用 NodeJS)
    • 如何发送与接收 http 请求

创建 OAuth App

这里使用了 GitHub 自己的 OAuth API 来作为通信桥梁,第一步自然就是创建一个 OAuth APP。点击这里可以直接跳转到创建页面:

GitHub OAuth creation

创建过程中只需要填写这三个参数:

  1. 你的 App 名字
  2. 你的博客主页 URL
  3. 用于接收 GitHub 数据的接口 URL

创建完成之后你会获得:

  • Client ID
  • Client secrets

请注意一定要保存好自己的 Client secrets,且不要告诉其他人!

在自己的博客中配置 GitHub 授权

具体步骤可以看官方给出的文档,但是这里有几点需要注意的地方:

第一个接口:

GET https://github.com/login/oauth/authorize
  • 这在博客里其实只是一个跳转链接,不要当接口来用(样式上可以是一个超链接,也可以是一个按钮)。
  • 参数redirect_uri的域名必须和创建过程中的Authorization callback URL一样
  • 参数redirect_uri可以是https://<redirect_uri>?r=<当前博客地址>,这样自己的服务器就可以知道在 OAuth 验证成功之后往哪里跳转了。
  • 参数scope 默认为空,但是如果你想让所有用户都可以正常评论,scope应该等于public_repo

假设:

redirect_uri = https://api.jw1.dev/gho
当前博客地址 = https://jw1.dev/2022/10/12/a01.html

那么跳转链接则为:

https://github.com/login/oauth/authorize?
client_id=<client_id>&
scope=public_repo&
redirect_uri=https://api.jw1.dev/gho?r=https://jw1.dev/2022/10/12/a01.html

# 换行是为了可读性,请不要在真实代码中使用换行符号

接收来自 Github 的请求并获取 access token

当用户授权之后,GitHub 会向你的接口发送一个 get 请求:

GET https://api.jw1.dev/gho?
r=https://jw1.dev/2022/10/12/a01.html&
code=<code>

该请求会带上一个 query 参数code,如果你在 OAuth 验证链接中填了redirect_uri,那么 query 中也会一并带上,这里我们可以暂存一下r参数。带上这个 code 参数,我们来到第二个接口:

POST https://github.com/login/oauth/access_token

必需参数有三个:

  • client_id
  • client_secret
  • code

如果请求参数全部正确,GitHub 会返回一个 URL encoded 字符串:

access_token=gho_16C7e42F292c6912E7710c838347Ae178B4a&scope=public_repo&token_type=bearer

你可以直接使用这个字符串,但是为了易用性,我们可以设置请求头,让 GitHub 返回 json 格式的数据:

Accept: application/json
{
  "access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a",
  "scope": "public_repo",
  "token_type": "bearer"
}

到这里,我们才完成 App 的验证,拿到了进入 GitHub 大门的钥匙:access_token

重定向到 OAuth 验证前的博客页面

拿到access_token之后,我们需要返回到用户离开时的页面,这时候之前存的r参数就能派上用场了:

r = 'https://jw1.dev/2022/10/12/a01.html'
redir_url = `${r}?token=${access_token}`

redirect(302, redir_url)

这里我们就成功回到了之前的页面,并且获得了access_token


// express代码
const express = require('express')
const router = express.Router()
const needle = require('needle')
const client_id = '你的client id'
const secret = '你的client secret'

let gho = router.get('/', function (req, res) {
  let code = req.query.code
  let redir = req.query.r

  needle.post(
    'https://github.com/login/oauth/access_token',
    {
      client_id: client_id,
      client_secret: secret,
      code: code
    },
    {},
    function (err, resp) {
      let params = resp.body.toString()
      res.redirect(302, `${redir}?${params}`)
    }
  )
})

module.exports = {
  gho
}

客户端获取数据

最难的部分算是解决了,接下来就是使用 token 获取数据了,这里其实反而没有什么难度,我就把用到的接口列出来好了:

  1. 获取评论数据

    https://api.github.com/repos/OWNER/REPO/issues/ISSUE_NUMBER/comments
    
  2. 创建评论

    https://api.github.com/repos/OWNER/REPO/issues/ISSUE_NUMBER/comments
    
  3. 删除评论

    https://api.github.com/repos/OWNER/REPO/issues/comments/COMMENT_ID
    

这里有一个小细节要注意一下,以上接口中提到的ISSUE_NUMBER即 GitHub Issues 的编号,关于这个编号怎么获取我走了一点点弯路,原本的思路是这样的:

  1. 获取博客 repo 下的所有 issues
  2. 查看是否已经为当前博客创建过 issue,如果有,则使用该 issue 的编号,如果没有,则创建一条新的

这个思路有几个很严重的问题:

  • 每次加载文章都需要重新获取所有 issues,加载速度慢
  • 把创建 issues 的权限交给了陌生用户,如果该用户删除 issue,那么你整个文章的评论都没了
  • 有几率重复创建 issue

针对以上问题,我重新调整了思路:

  1. 文章还在草稿阶段时,手动创建 issue
  2. 将 issue number 写死在文章的变量中

之前的问题将不复存在,而且节省了一个 请求数量 (GitHub 有着 5000 QPH 的限制,所以用的越少越好)

客户端交互

UI design for https://jw1.dev

写好布局,写好操作逻辑,这样一个评论系统就诞生了!样式和逻辑都在前端,你们想看都看得到,这个博客网站也是开源的(想抄就抄吧😎)。

感兴趣的朋友可以在下面评论区发一句 “test” 试一下。🤣


好了,今天就到这里,拜拜!👋