阅读可以提升我们的认知水平、扩展视野并提升技术能力。当阅读别人的文字的时候总会有一种想要表达自己观点的冲动,因此,从“读”到“写”会是一个自然而然的过度。 我有尝试过各种博客平台或工具,可总觉得好像都不够好用。并不是我太过矫情,而是作为一个技术人,总会有各种各样的“怪癖”。我理想中的博客平台是这样的:
- “数据”始终归属于个人,而非平台;
- 以Markdown作为标准格式,这样可以保证迁移到任何其他平台也能原样展示;
- 基于Git仓库,这样可以支持版本管理和CI&CD的能力。 基于以上“怪癖”,我便萌生了搭建自己的博客系统的想法。
为什么有这么多要求?
博客不就是一个记录和分享文字的工具,至于搞这么复杂嘛?我想告诉你,至于。具体的理由我们分别来讲:
“我”产生的数据到底是不是“我”的?
我想问一个最核心的问题,即:我产生数据的所有权是谁的?是我自己还是平台? 我想大部分人可能没有想过这个问题。可能有人不假思索的回答:肯定是我自己的呀。是这样吗? 我再问另一个问题。作为现在被各种平台“捆绑”的我们,偶尔有没有一种想要“逃离”的冲动?所有的聊天记录在微信上,听的歌曲在网易云或是QQ音乐中,发布的动态则在微博上。。。如此种种,假设,我只是假设:有一天我想换个音乐软件,我的喜好歌曲、收藏的专辑这些能自动迁移到新的软件上吗?假设有一天我不再使用微博,而想换成小红书,我之前发表的想法、文字能无缝迁移嘛?貌似并不行,“平台”不会开放这种能力。可是,这些不是我自己的“数据”嘛?为啥我想“拿到”或是“迁移”就这么难?回过头来,我们再想想“数据”是不是你自己的?
如果数据始终归属于个人,以博客举例,那么我应该可以:
- 任何平台宕机或下线都不会弄丢我的博客数据;
- 自由的修改和发布我的博客;
- 自由选择我的博客发布平台,并且可以随时更换和迁移;
- 随时搜索、整理和归档我所有的博客; 我想:应该没有任何一个平台能够做到上述这些。
为什么选择Markdown作为博客存储格式
Markdown是一种通用且简洁的文本格式,大部分平台对于Markdown都可以很好的识别和展示。这样可以保证产出一份内容,可以同时被发布到多个平台,且各平台展示相对统一,不会有太大的偏差。 其实HTML作为通用网页格式也可以,但是相比于Markdown,不够简洁,且手写较为复杂、存储冗余,不易扩展。 实际阅读和浏览中,也可以看到大部分博客和文档等都是基于Markdown格式来编写和发布的,Markdown堪称技术人最喜爱的文本格式了。
为什么要接入Git?
Git是一个版本管理系统,并不局限于管理“代码”。如果将博客内容存储于Git上,会有很多好处,比如:
- 作为存储博客内容的仓库。这样可以保证数据的安全性和稳定性。存在本地磁盘数据可能会丢失,存在单机数据库的数据可能会被清除,但存在Git永远不会丢;
- 博客内容版本管理。有了Git版本管理,我们可以随时追溯版本变更、随时回滚和前后对比,同时也相当于提供了多副本存储的能力;
- 提交即发布。 基于Git的CI&CD,可以很方便的构建我们的自动化发布流程,让多平台发布不再繁琐。
如何实现这些能力?
我画了一个简单的系统架构图:
我的博客系统共包含四个子系统,分别是:
- Github:负责博客内容的存储和版本管理,以及基于Github Action实现自动推送到后端服务进行数据库存储;
- 后端服务:负责和数据库的对接和提供API给前端、Github Action来调用,实现博客内容的增删改查;同时也负责调用各博客平台的API进行多平台发布;
- 数据库:负责博客内容、标签、分类以及用户的数据存储;
- 前端界面:负责拉取后端数据,同时给读者提供一个好的阅读体验。
这样设计的好处有很多,比如:
- 多级数据副本。可以看到数据同时存储于Github仓库和数据库中,即使数据库数据丢失,可以重新基于Github来进行快速同步;
- 各子系统相互解耦,各司其职。比如前后端可以选择任意的技术栈,数据库也可以是任意的关系型数据库,对子系统架构无任何要求。
- 基于Github Action的自动推送。本地编辑并提交后,Github Action可以自动基于文件变更来调用后端API来实现博客的增删改;同时后端服务可自动调用其他博客平台的API来同步进行博客推送,实现多平台发布的能力。
以下是各个系统的详细介绍:
数据库
数据库采用了Postgres来存储数据,数据库设计参考ER图:
Github
Github除了提供存储和版本管理之外,核心功能是基于Github Action的自动发布流程。我是这样设计的:
- 采用固定的目录结构来存储博客内容。仓库结构参考下图,其中:
- 博客分类来作为一级目录;
- 然后就是博客本身,每篇博客的
Slug
作为二级目录名称; - 由于同时提供中文和英文内容,因此文件名同样采用
Slug
来命名,其中默认为中文内容,英文内容以.en
结尾。
- 每次提交都会自动获取和上次提交的Diff,基于Diff来判断博客的增删改,从而调用不同的API来实现数据库中数据的自动更新。这里提供了Workflow参考:
其中
blog-auto-push
是我自定义的一个Github Action,逻辑也相对简单,就是基于文件的更改来判断增删改,然后调用不同的API来实现。
后端服务
后端服务基于NestJS + Prisma来实现。可能会有人问,为什么不选Java?原因就是作为一名前端工程师,对JavaScript最熟悉,而基于NestJs可以很容易上手并开发后端应用。
- NestJs提供了开箱即用的程序架构,底层可选择基于ExpressJs或者Fastify进行实现,同时提供了API鉴权、日志和Swagger等配套能力。
- Prisma提供了一套基于Node和Typescript的对象关系映射(ORM),可以很方便的进行数据库设计、迁移和发布。
在后端服务中,除了基本的增删改查之外,核心需要处理来自Github Action的API调用。注意数据库中是以
Id
作为主键进行存储的,但是在Workflow中,必须要根据Slug
来进行识别,因此必须要保证Slug
(也是每篇博客的目录名称)全局唯一。 其中,基于Workflow来更新博客的API是这样设计的:
async upsertArticleByMarkdownFile(dto: {
/** 唯一ID */
slug: string;
/** 中文内容 */
content: string;
/** 英文内容 */
enContent: string;
}): Promise<boolean>
在接收到该API调用后,需要做这些:
- 基于
front-matter
进行属性定义和正文内容分离;- 从属性中获取分类、标签和封面图片等;
- 基于正文中计算出“摘要”并进行存储
- 基于
Slug
判断新增还是更新,然后分别调用创建和更新的Service。
博客前端
前端界面可以很简单,也可以相对复杂。
简单来说,只需要获取博客内容进行展示即可。当然,我们需要一个Markdown
转HTML
的工具,最终转换成HTML
来进行页面渲染。
如果要做到复杂,则我们需要:
- 支持黑白主题、主持中英文切换;
- 支持按照分类、标签进行筛选;
- 支持自动获取大纲;
- 支持响应式布局以设计支持不同屏幕大小的展示;
- 自动SEO优化等等
当然,这些是一个优秀的博客网站所必须要具备的能力。市面上很多博客构建工具都自带这些能力,但是,谁让我本身就是个前端工程师呢,我选择自己从零开始,从确定技术栈开始,研究各种布局、中英文切换、黑白主题等等,这个过程也是其乐无穷,学到了很多之前没有涉及到的知识。
更多工具分享
完成上述内容开发,我们已经有了自动化博客发布系统,也已经完成了我们最开始的目标。在这个过程中,同时还发现了一些好用的工具来进一步提升我们写博客的幸福感。
- Obsidian :著名的写作和知识管理工具,他的宣言“你的想法是你自己的”和我们本篇博客的主张不谋而同。基于Obsidian,我们可以将我们的Git仓库直接创建为Obsidian的仓库,这样可以借用Obsidian编辑器来提升码字效率;同时,Obsidian丰富的插件生态也为我们提供了各种好用的能力,其中,图片自动上传插件就非常好用。
- 基于 PicGo + Cloudfare R2 + Image auto upload Plugin实现实现粘贴并自动上传的功能,如果大家感兴趣,我可以单独发一篇博客来介绍这些工具,具体包括:
- 基于
Cloudfare R2
对象存储免费搭建个人图床; - 基于
PicGo
实现图片自动上传到个人图床; - 基于
Image auto upload Plugin
实现图片粘贴上传。
- 基于
最后的最后
写到这里,我们这篇博客内容已经够多了,就不再展开了。
这里是奇·说,我会努力定期带来更多有意思的分享,感兴趣的同学可以持续关注。