学习思考
🗒️自托管Utterances教程:基于Github Issues的轻量级博客评论系统
00 分钟
2023-7-8
2023-7-8
type
status
date
slug
summary
tags
category
password
icon
本文主要介绍基于Github Issues的轻量级博客评论系统Utterances。文章前半部分主要介绍Utterances的配置与使用,后半部分主要介绍如何利用近年来流行的Serverless化平台Cloudflare Workers自托管Utterances。

Utterances简介

Utterances是一个基于Github Issues的轻量级评论系统,可用于博客、Wiki等。它具有以下优点:
  • 开源
  • 不追踪,无广告,始终免费
  • 所有的数据都存储在Github Issues
  • 样式基于Github的Primer设计语言
  • 夜间模式
  • 轻量级;原生TypeScript;在“常青树”浏览器上不使用网络字体,JavaScript框架或Polyfill。

快速上手

  1. 在GitHub上新建一个公开仓库(Repository),安装Utterances GitHub App至该仓库。
  1. 在你的网页需要插入Utterances评论的位置,粘贴以下代码(username,reponame分别修改为你的GitHub用户名,仓库名)。
    1. <script src="https://utteranc.es/client.js" repo="username/reponame" issue-term="pathname" theme="github-light" crossorigin="anonymous" async> </script>
  1. 刷新网页就可以看到Utterances评论框了。
如果想进一步配置或者自托管Utterances,可以继续看下面的内容。

配置

下文将介绍以下代码中repoissue-termlabeltheme几个选项的配置。
<script src="https://utteranc.es/client.js" repo="username/reponame" issue-term="pathname" label="💬comment" theme="github-light" crossorigin="anonymous" async> </script>

repo:设置存放评论的仓库

Utterances 使用Github Issues存储评论,所以需要一个仓库。你可以新建一个公开仓库专门用来放评论,也可以使用原有的仓库。要设置存放评论的仓库只需要将repo="username/reponame"这一行中的username改为你的GitHub用户名,reponame改为你的仓库名,其它不变。
仓库需满足以下条件:
  • 仓库必须为公开仓库,私有仓库访客无法查看对应Issues上的评论。
  • 确保在仓库中安装了Utterances的GitHub App,或是你自己注册的GitHub App(自托管),否则用户将无法发表评论。
  • 如果你的仓库是派生(fork)出的,请在仓库的Settings选项确认FeaturesIssues已勾选。

issue-term:博客文章和Issue映射

Utterances使用以下几种规则建立博客文章和GitHub Issues的映射:
  • Issue标题包含页面路径名(issue-term="pathname"Utterances将查询issue标题是否包含博客文章的路径名(pathname)。如果未找到匹配的issue,则当有人首次对你的博客文章发表评论时,Utterances会自动创建一个以此文章路径名为标题的issue。
  • Issue标题包含页面URL(issue-term="url"Utterances将查询issue标题是否包含博客文章的URL。如果未找到匹配的issue,则当有人首次对你的博客文章发表评论时,Utterances会自动创建一个以此文章URL为标题的issue。
  • Issue标题包含页面标题(issue-term="title"Utterances将查询issue标题是否包含博客文章的标题。如果未找到匹配的issue,则当有人首次对你的博客文章发表评论时,Utterances会自动创建一个以此文章标题为标题的issue。
  • Issue标题包含页面og:title(issue-term="og:title"Utterances将查询issue标题是否包含博客文章的Open Graph标题(og:title)。如果未找到匹配的issue,则当有人首次对你的博客文章发表评论时,Utterances会自动创建一个以此文章og:title为标题的issue。
  • 特定的issue编号(issue-number="具体数字"Utterances按issue编号加载特定的issue。Utterances不会自动创建issue。
  • Issue标题包含特定项(issue-term="你设置的特定内容"Utterances将查询issue标题是否包含你设置的特定项。如果未找到匹配的issue,则当有人首次对你的博客文章发表评论时,Utterances会自动创建一个以你设置的特定项为标题的issue。

label:Issue标签

如果你使用原有的仓库,但是担心Issues页面评论和问题混杂在一起,Utterances支持设置标签(Label)来区分它们。设置label="你的标签内容",Utterances将在创建issue时使用你设置的标签。
  • 标签名区分大小写。
  • 标签必须存在于你的仓库中(须提前在GitHub Issues页面创建好,不能使用不存在的标签)。
  • 标签名支持Emoji。例如:
    • label="💬"

theme:主题

Utterances有多种主题,其中包括多款夜间模式主题。
  • GitHub Light:theme="github-light"
  • GitHub Dark:theme="github-dark"
  • GitHub Dark Orange:theme="github-dark-orange"
  • Icy Dark:theme="icy-dark"
  • Dark Blue:theme="dark-blue"
  • Photon Dark:theme="photon-dark"
你可以在文章末尾处的下拉框中选择主题以查看效果,点击此处跳转到文末。

自托管

自托管Utterances主要包含以下两个项目的部署:
  • Utterances:前端静态网站,评论系统的界面显示,部署在GitHub Pages
  • utterances-oauth:后端API,主要功能是授权,鉴权和创建Issues,部署在Cloudflare Workers

准备环境

  • Debian、Ubuntu或是其他Linux发行版(教程里用的是Debian 10)
  • GitHub账号(用于申请GitHub App及创建仓库等)
  • Cloudflare账号(用于申请Cloudflare Workers)
为方便说明配置,教程中有以下假设,如有雷同,纯属巧合。
  • GitHub用户名:example
  • GitHub Pages域名:example.github.io
  • Cloudflare Worker子域名:example.workers.dev
  • 博客域名:blog.example.com
  • Utterances部署域名:utterances.example.com
  • utterances-oauth部署域名:api.utterances.example.com
你需要替换教程中的相关关键字。

注册GitHub App

打开https://github.com/settings/apps/new注册自己的GitHub App,仅需要填写以下内容,其它默认:
项目
GitHub App name
你的博客的名字
Description
你的博客的描述
Homepage URL
你的博客的网址
User authorization callback URL
utterances-oauth部署域名,以/authorized结尾。例如:https://api.utterances.example.com/authorized。如果你使用Cloudflare Worker子域域名,应为:https://utterances-oauth.example.workers.dev/authorized
Webhook URL
必填项,但是Utterances不使用此项。随意填,例如你的博客网址。
Repository permissions
Issues: Read & Write,不需要其它权限。
Where can this GitHub App be installed
仅此账号(Only on this account)
注册成功后,系统会提示你生成私钥(Generate a private key),点击生成(这是必须的步骤,否则无法使用GitHub App)。但是Utterances不使用私钥,无需记住其值。
记下Client IDClient secret的值,后面会用到它。

在Cloudflare Workers上托管utterances-oauth

利用近年来流行的Serverless平台Cloudflare Workers,你可以方便的部署utterances-oauth。免费版每天提供10万次请求,足够一般使用了。

构建utterances-oauth

  1. 安装YarnGit
  1. 克隆(clone)utterances-oauth
    1. git clone https://github.com/utterance/utterances-oauth cd utterances-oauth
  1. 安装依赖
    1. yarn install
  1. 配置环境变量
    1. 在utterances-oauth根目录下新建一个名为.env的环境变量配置文件,配置如下变量:
      • BOT_TOKEN:创建GitHub Issues时将使用的GitHub个人访问令牌(Scopes仅需勾选public_repo点此生成)。
      • CLIENT_SECRET:OAuth web application flow中要使用的Client secret,即注册GitHub App时记住的Client secret的值。
      • STATE_PASSWORD:32位密码,用于加密request headers/cookies中的state点此生成
      • ORIGINS:来源域(origin)列表。多个来源域以逗号分隔,用于跨域资源共享Cross-Origin Resource Sharing(CORS)。
      示例如下(替换*号内容为你自己的值,ORIGINS网址改为部署Utterances的网址):
      BOT_TOKEN=**************************************** CLIENT_ID=******************** CLIENT_SECRET=**************************************** STATE_PASSWORD=******************************** ORIGINS=https://utterances.example.com,http://localhost:4000
  1. 执行命令yarn run build构建utterances-oauth,编译后的文件位于dist/index.js

部署utterances-oauth

本教程提供三种部署的方法:
  1. 网页端:最简单,按照页面说明即可轻松部署。适合喜欢图形化用户界面(GUI)的用户
  1. cfworker:cfworker是Cloudflare Workers的一个功能强大的工具集,由Utterances作者开发。适合喜欢命令行界面(CLI)的用户
  1. GitHub Actions:通过GitHub Actions持续部署。适合没有本地环境,也不方便安装的人。通过GitHub Actions提供的开发环境,下载、构建、部署utterances-oauth
推荐使用第三种通过GitHub Actions部署的方法,这样每次版本更新的时候只需要push更新的内容到GitHub上,GitHub Actions就会替我们自动部署,一劳永逸。

通过Cloudflare Workers网页端部署

登录网页版Cloudflare,进入Workers页面新建一个Worker,将生成的dist/index.js文件内容复制到Script框内(删掉默认生成的代码),修改Worker名称为utterances-oauth,点击Save and Deploy(保存并部署),并配置好路由等即可。

通过cfworker部署

cfworker是Cloudflare Workers的一个功能强大的工具集,由Utterances作者开发,其提供了直接部署的方法,需要你在.env文件中添加以下变量
  • CLOUDFLARE_EMAIL:注册Cloudflare时的邮箱
  • CLOUDFLARE_API_KEY:部署Cloudflare Workers需要的全局API Key(Global API Key)
  • CLOUDFLARE_ZONE_ID:你的Cloudflare Zone ID,在你的域名的预览页(Overview)
  • CLOUDFLARE_ACCOUNT_ID:你的Cloudflare Account ID,在你的域名的预览页(Overview)
  • CLOUDFLARE_WORKERS_DEV_PROJECT:你的Cloudflare Worker项目名,例如utterances-oauth。项目名必须满足以下要求:
    • 以字母开头
    • 以字母或数字结尾
    • 仅包含字母,数字,下划线和连字符
    • 不超过63个字符
CLOUDFLARE_EMAIL=**************** CLOUDFLARE_API_KEY=************************************* CLOUDFLARE_ZONE_ID=******************************** CLOUDFLARE_ACCOUNT_ID=******************************** CLOUDFLARE_WORKERS_DEV_PROJECT=******
在utterances-oauth根目录下的package.json文件中找到此行代码
"deploy": "cfworker deploy --name utteranc-es --route 'api.utteranc.es/*' src/index.ts"
修改为以下内容,注意域名后的/*要保留。
"deploy": "cfworker deploy --name utterances-oauth --route 'api.utterances.example.com/*' src/index.ts"
如果你使用Cloudflare Worker子域名,则应为:
"deploy": "cfworker deploy --name utterances-oauth --route 'utterances-oauth.example.workers.dev/authorized/*' src/index.ts"
执行命令:
yarn run deploy

通过GitHub Actions部署

通过GitHub Actions部署不需要.env的环境变量配置文件,任何时候绝不能将此文件上传到公开GitHub仓库,所有涉及密码等信息的值会通过GitHub 加密密码来存储。
  1. 派生(fork)此仓库https://github.com/utterance/utterances-oauth
  1. 在此仓库新建一个cloudflare-workers.yml文件,此文件位于仓库根目录的.github/workflows/目录下。文件内容如下:
    1. name: Deploy utterances-oauth on: push: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [14] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - name: Install Dependencies run: yarn install # Add .env before build - name: Add .env run: | cat > .env <<EOF BOT_TOKEN=$BOT_TOKEN CLIENT_ID=$CLIENT_ID CLIENT_SECRET=$CLIENT_SECRET STATE_PASSWORD=$STATE_PASSWORD ORIGINS=$ORIGINS EOF env: BOT_TOKEN: ${{ secrets.UTTERANCES_BOT_TOKEN }} CLIENT_ID: ${{ secrets.UTTERANCES_CLIENT_ID }} CLIENT_SECRET: ${{ secrets.UTTERANCES_CLIENT_SECRET }} STATE_PASSWORD: ${{ secrets.UTTERANCES_STATE_PASSWORD }} ORIGINS: https://utterances.example.com - name: Build run: yarn run build # Add wrangler.toml required by Wrangler - name: Add wrangler.toml run: | cat > wrangler.toml <<EOF name = "utterances-oauth" type = "javascript" routes = [ "api.utterances.example.com/*" ] account_id = "$ACCOUNT_ID" zone_id = "$ZONE_ID" EOF env: ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} ZONE_ID: ${{ secrets.CF_ZONE_ID }} # Deploy to Cloudflare Workers with Wrangler - name: Deploy to Cloudflare Workers with Wrangler uses: cloudflare/wrangler-action@1.2.0 with: apiToken: ${{ secrets.CF_API_TOKEN }}
  1. 按教程“配置环境变量”小节,修改第40行ORIGINS的值,修改第52行routes的值。
  1. 打开GitHub上派生后的仓库,在仓库名称下,单击Settings(设置),在左侧边栏中,单击 Secrets(密码)添加密码,详细步骤见为仓库创建加密密码
    1. 需要添加如下几个密码:
      • UTTERANCES_BOT_TOKEN
      • UTTERANCES_CLIENT_ID
      • UTTERANCES_CLIENT_SECRET
      • UTTERANCES_STATE_PASSWORD
      • CF_ACCOUNT_ID
      • CF_ZONE_ID
      • CF_API_TOKEN
      具体密码的值参考“构建utterances-oauth”和“通过cfworker部署”这两个章节中的环境变量的值。
      其中CF_API_TOKEN为Cloudflare API Tokens,可在Cloudflare处生成API令牌模板API tokens templates选择Edit Cloudflare Workers,以限制此API Tokens权限为仅能编辑Cloudflare Workers。
  1. 将修改后的文件push到GitHub仓库上。之后每当你push更新到master分支时,GitHub Actions会自动将代码部署到Cloudflare Workers。

测试

用浏览器打开https://api.utterances.example.com,如果你使用Cloudflare Workers子域名则打开https://utterances-oauth.example.workers.dev,如果看到页面显示alive字样,则说明utterances-oauth你已经部署成功。
如果你想本地测试的话执行命令yarn run start

在GitHub Pages上托管Utterances

  1. 派生此仓库https://github.com/utterance/utterances
  1. 克隆你刚刚派生的仓库(修改example为你的GitHub用户名):git clone https://github.com/example/utterances
  1. package.json文件中找到此行代码
    1. "predeploy": "yarn run build && touch dist/.nojekyll && echo 'utteranc.es' > dist/CNAME",
      修改为"predeploy": "yarn run build && touch dist/.nojekyll",
      注意最后有逗号,
  1. 修改src/utterances-api.ts文件中的网址为你的utterances-oauth网址
    1. export const UTTERANCES_API = 'https://api.utterances.example.com';
      如果你使用Cloudflare Workers子域域名则修改如下:
      export const UTTERANCES_API = 'https://utterances-oauth.example.workers.dev';
  1. 按照教程配置所述,修改src/index.html文件中如下部分的srcrepoissue-term等配置项。记得在存放评论的仓库上安装你注册的GitHub App。
    1. <if condition="NODE_ENV === 'production'"> <script src="https://utteranc.es/client.js" repo="utterance/utterances" issue-term="homepage" crossorigin="anonymous" async> </script> </if> <else> <script src="http://localhost:4000/client.js" repo="jdanyow/utterances-demo" issue-term="pathname" crossorigin="anonymous" async> </script> </else>
      例如修改client.js为部署到GitHub Pages的链接:
      src="https://example.github.io/utterances/client.js"
  1. 执行yarn run deploy部署到GitHub Pages。
部署后打开你Utterances项目的GitHub Pages页面https://example.github.io/utterances/,在该页面底部你可以测试评论等功能,测试完毕后你就可以按照“快速上手”,“配置”等教程上线你的博客评论功能了。
如果你不想使用GitHub Pages,只需将第6步改为执行yarn run build,并将/dist文件夹下生成的所有文件上传到其他支持静态网页的平台即可。
如果你想本地测试的话执行命令yarn run start

FAQ

博主为什么选择 Utterances

之前一直想为博客添加评论系统,刚开始考虑的是国外的Disqus,因为在国内无法访问,所以查找过一些使用Disqus API的方法,如DisqusJSDisqus PHP API,但是它们都需要后端程序,功能上也有一些局限性。之后偶然发现有人用Github Issues存储博客评论,如:GitmentGitalk以及本文介绍的Utterances,都是基于Github Issues开发的评论系统。
目前Gitment已经很久没更新了,Gitalk和Utterances都在持续更新。其中Utterances使用Primer设计语言,这是Github官方使用并开源的设计指南,所以Utterances有和Github Issues类似的漂亮样式。Utterances使用了更精细化的权限管理(详见FAQ:Utterances需要哪些权限),这也是笔者最后选择Utterances的原因之一。

Utterances的原理

Gitment、Gitalk和Utterances的原理都是基于GitHub Issues自带的评论功能,在访问博客时通过GitHub API查询对应博文下Issues的评论,显示在自己的网页上,评论时访客登录Github账号,授权应用,在对应GitHub Issues下发布评论。

Utterances需要哪些权限

Utterances通过GitHub App来操作GitHub Issues。不同于OAuth Apps,GitHub App提供了精细化的权限管理,且GitHub App仅能作用于安装了它的仓库,闲情见GitHub App与OAuth Apps区别
当你点击登录(Sign in to commnet)时,GitHub会有一个界面显示你需要授权GitHub App哪些权限。其中“确定你和GitHub App都可以访问的那些资源(Determine what resources both you and GitHub App can access)”这句里的“资源”指的是你GitHub账号权限和GitHub App权限的交集。由于我们在注册GitHub App时仓库权限仅勾选了Issues: Read & Write,所以Utterances仅能在安装了它的仓库上读写该仓库的Issues

如何解决第三方Cookie的问题

Chrome浏览器自80版本开始默认设置SameSite=Lax。越来越多的用户也开始打开“阻止第三方Cookie”的选项。
Utterances仅使用Cookie保存登录信息以避免重复登录的问题,如果你使用Utterances GitHub App的话会存在Cookie跨域问题,这也是很多人选择自托管的原因之一。
要解决这个问题,你需要将博客和Utterances-outh部署在同一域名下。目前Chrome、Firefox、Microsoft Edge Chromium 把同一域名下的子域Cookie视为第一方Cookie。
假如你的博客网址是https://blog.example.com,那么Utterances-outh可以部署在https://api.example.com或是https://api.utterances.example.com下。

参考

在下面的下拉框中选择Utterances的主题以查看效果,点击此处返回theme:主题章节。
主题:
GitHub Light   GitHub Dark   GitHub Dark Orange   Icy Dark   Dark Blue   Photon Dark
  • 本文作者: ConGerh
  • 版权声明: 本博客所有文章除特别声明外,均采用  BY-NC-SA 4.0 许可协议。转载请注明出处!
 
 
致谢:
💡
有关Notion安装或者使用上的问题,欢迎您在底部评论区留言,一起交流~
 
 

评论