ORTHANT 博客技术架构详解
ORTHANT 由哪些部分组成,一篇文章从保存到上线经过哪些环节,登录、阅读授权、搜索、评论和浏览统计分别放在哪里,以及这样设计的取舍。
ORTHANT 是我一个人写、一个人维护的博客。搭它的时候我给自己定了三个条件:不维护常驻的服务器;文章用 Markdown 写、存在 Git 里,以后换写作工具也不用迁移;读者打开页面要快,评论、统计这些附加功能出了问题,也不影响读正文。
现在的结构基本就是照这三条拼出来的:文章在发布时提前生成成网页,放在 Cloudflare Pages 上;少数必须在访问时处理的事情,交给 Pages 自带的服务端函数;评论借用 GitHub 的讨论区。
1. 整体结构
整个网站由下面几部分组成:
| 部分 | 用的什么 | 负责什么 |
|---|---|---|
| 内容 | Markdown + Git,托管在 GitHub 私有仓库 | 文章、图片、网站模板,以及它们的全部修改历史 |
| 生成 | Hugo 和 Hextra 主题 | 把文章、导航和模板生成成网页,顺带生成搜索索引 |
| 托管 | Cloudflare Pages | 把生成好的网页发给读者 |
| 服务端 | Pages Functions | 站长登录、在线保存、阅读授权、浏览计数 |
| 数据库 | Cloudflare D1 | 浏览次数,以及授权码的尝试记录 |
| 评论 | giscus + GitHub Discussions | 评论的显示和存储 |
串起来看,一篇文章的路径是:写好后提交到 GitHub,Cloudflare 发现有新提交,用 Hugo 重新生成整站,再部署上线。读者访问时拿到的是已经生成好的网页;登录、保存、授权和计数这几件事才会用到服务端函数。
文章正文只存在 Git 里。数据库和讨论区只放浏览数和评论,不放正文。
2. 一篇文章怎么发布
我可以在本地编辑器里写,可以在 GitHub 网页上改,也可以直接在博客里在线编辑。三种方式最后都是往同一个仓库提交,网站只认仓库里的那一份,所以不会出现几份正文互相同步的问题。
在线编辑这块换过几次。最早接的是 Pages CMS,后来换成自己部署的 Decap CMS。它们都有一个独立的后台,Decap 的草稿还要走单独的分支和合并请求,写一篇文章得在后台和 GitHub 之间来回几趟。现在改成在文章页里直接编辑:登录后点「编辑本文」,改完点「保存并发布」。
点下保存之后,大致经过这几步:
- 服务端确认是我本人在操作,并且对这个仓库有写权限。
- 检查这篇文章在我打开编辑页之后,有没有在别处被改过。如果改过,这次保存会停下来,提示我重新打开对比,不会把别处的修改覆盖掉。
- 把正文和这次新插入的图片放进同一次提交,免得文章上线了、图片还没跟上。
- Cloudflare 发现仓库有新提交,用 Hugo 重新生成整站并部署。
- 编辑页等部署完成,确认线上已经是新版本,再提示「已上线」。
这里有两个时间点。「已保存」是内容进了仓库,「已上线」是读者能看到新页面,两者之间隔着一次构建。平时要等一会儿,构建排队或者失败时会更久。编辑页把这两个状态分开显示,我就不用再去 Cloudflare 后台确认。
文章上显示的「发布于」,取的是这篇文章第一次以正式状态进入主分支的时间;「更新于」取的是这个文件最后一次修改的时间。
3. 读者打开页面时
文章在发布时就已经生成好了。读者打开页面,服务器直接把现成的网页发过去,不需要临时去 GitHub 读文章,再拼成页面。
请求到达时会先经过服务端函数检查一下。普通文章直接放行;项目案例要先确认有没有阅读权限,没有的话返回的是授权码页面,正文根本不会发到浏览器里。检查过程出了错,就按没有权限处理,宁可暂时打不开,也不让案例意外公开。
页面显示出来以后,其余功能各自按需加载:浏览次数单独发一个请求;评论区快滚动到时才加载;搜索索引在第一次打开搜索时才下载;编辑器只有登录后才加载。这些部分出了问题,已经显示的正文照样能读。
4. 三种身份
站里的「登录」有三种,权限互不相通:
| 身份 | 怎么获得 | 能做什么 |
|---|---|---|
| 站长 | 用 GitHub 账号登录 | 写文章、看项目案例、管理评论 |
| 授权读者 | 输入阅读授权码 | 看项目案例和附件 |
| 评论者 | 在评论区用 GitHub 授权 | 在公开文章下评论 |
站长登录通过一个只授权给博客仓库的 GitHub App 完成。登录状态加密后保存在浏览器里,最长 8 小时,服务端不单独保存会话。每次保存文章,服务端都会重新向 GitHub 确认权限,页面上能看到编辑按钮,不等于就能写入。
阅读授权码是一个共享密码,验证通过后 8 小时内不用再输。它适合把案例给少数人看,但拿到授权码的人能看到全部案例,也能把内容复制走,所以案例在写的时候就要先脱敏。
评论登录由 giscus 单独处理,和前两种互不相干:能评论不代表能看案例,更不能改文章。
5. 搜索、评论和浏览统计
这三样都显示在文章页上,运行的位置和数据存放的地方却各不相同。
搜索在浏览器里完成。构建时 Hugo 会生成一份公开文章的索引,包括标题、章节、标签和正文。第一次搜索时浏览器把它下载下来,之后的查询都在本地进行,不用每输一个字就请求服务器。项目案例不放进这份索引,也就不会从搜索结果里漏出去。
评论放在 GitHub Discussions,通过 giscus 嵌进文章页。评论用的是一个单独的公开仓库,和存放文章的私有仓库分开。现成的账号和讨论功能拿来就能用,评论多了也不用重新构建网站;代价是发言需要 GitHub 账号,文章换地址时要手动迁移原来的讨论。
浏览统计存在 Cloudflare 的 D1 数据库里。同一个浏览器 30 分钟内反复打开同一篇文章只算一次,我自己登录后的访问和编辑预览不算。计数跟着文章的固定编号走,改标题、重新部署都不会清零。它统计的是去重后的浏览次数,和访客人数不是一回事。
6. 这样做的代价
这套结构适合一个人写、以阅读为主的博客,但有几件事要接受:
- 发布不是即时的。保存后要等一次构建,读者才能看到新内容。
- 依赖的平台比较多。Cloudflare、GitHub、giscus 任何一个出问题,对应的功能就用不了,好在正文只依赖 Cloudflare。各个平台的免费额度也有上限,访问量大了要留意。
- 数据分在三个地方。文章和图片在 Git 里,评论在讨论区,浏览数在数据库。Git 能找回文章的任何一个版本,但找不回评论和计数,这几样要分开备份。
- 权限比较粗。站长只有一个账号,案例只有一个共享授权码。以后如果需要按人授权、单独收回,就得换一套账号体系。
对现在的 ORTHANT 来说,这些代价都能接受。哪部分改了,我会回来更新这篇。
评论
使用 GitHub 登录后参与讨论,评论将公开保存在 GitHub。
站长管理删除会同步到 GitHub,无法撤销。
阅读到这里时加载评论。