自己动手写动态博客
2022年3月20日
编程
# 缘起 2019年,我写了一个动态博客,并发表了一篇博客《[自己动手写动态博客](https://hpdell.github.io/%E7%BC%96%E7%A8%8B/dynamic-blog/)》,主要介绍了基于 Quasar 和 Express 的动态博客框架。但是后面这个动态博客被废弃了,原因是多方面的。一方面项目的部署没有基于 Docker,导致维护起来比较复杂,经常发生无法访问的情况。另一方面,该项目是前后端分离的,文章内容通过接口进行获取,再加上没有 SSR,所以就算想要提交搜索引擎,也很难进行搜索。此外,项目缺乏一个高可用性的后端管理平台。再加上当时网页设计水平有限,一些设计比较反人类,因此最终还是继续使用了基于 GitHub Pages 的静态博客。 其实在做这个动态博客之前,并没有真的打算做一个博客出来,只是为了试一下 [django-vditor](https://pypi.org/project/django-vditor/) 这个包,甚至项目文件夹名称都是 `test-django-vditor`,因为 [Vditor](https://b3log.org/vditor/) 差不多已经是前端最强 Markdown 编辑器了。倒是初衷和博客有一定关系。2020年初设计了 GWmodel Lab 的[主页](http://gwmodel.whu.edu.cn),内容很全。由于网页是部署在群晖的 Web 服务器上,限制很大,所以项目虽然是前后端分离,但是总体上是一个静态网页,所有的 API 全都保存成了 JSON 文件。但是维护起来非常麻烦。这个主页一共分为了三个仓库:React 前端、Markdown 内容、Django 管理平台。当需要增加内容时,先在本地写好 Markdown 内容,使用 Git 进行版本管理,然后在 Django 中向数据库中添加相应的记录,再将接口响应内容保存成 JSON 文件,最后将 React 前端和内容以及接口相应内容部署到 Web 服务器上。当我还在实验室的时候,整个流程已经被打通,更新还算方便。但是交接给师弟的时候,想要讲清楚整个流程,就非常难了。 那么有没有一种比较方便的管理方法呢?有,其实只要找到一种方法,让 Django 直接可以输出整个网页,然后使用一些脚本将这些网页保存成 HTML 文件,再发布到网页服务器上就可以了。这个过程甚至可以让 Django 自己完成。配合 Django 的管理平台,更新内容就不复杂了。进一步地,如果要对搜索引擎友好,可以抛弃前后端分离的思路,反而使用 Django 的模板引擎渲染页面。 基于以上思路,我做了这个动态博客,就当作实验室主页的一个小 Demo。 # 框架 这个动态博客的框架非常简单,就是 Django + Bootstrap,没有 MVVM 框架,没有前后端分离,以至于写的时候仿佛回到了自己 2016 年用 Bootstrap 写网页的时代。 但是这个框架对于个人博客来讲,就显得很方便了。毕竟个人博客的项目复杂度不高,没有使用前端框架的必要,也没有前后端分离的必要,事实上,前后端分离反而会带来很多其他麻烦。而且 Django 现在的功能已经非常强大了,很多功能(例如图片上传、权限管理)只需要增加一些配置就可以解决,其管理平台更是可以让我们少些很多代码。经过几年的发展,Bootstrap 使用起来也很方便了,而且样式并不过时,也很简洁,很适合个人博客。 下面介绍一些具体的细节。 ## 数据库 似乎每一个教关系数据库的教程,都会用博客作为案例场景进行介绍。这是因为这个场景非常简单,但是涉及了一对多、多对多两种关系。通常,会有四张表:文章(Post)、分类(Category)、标签(Tag)和作者(Author)。本框架暂时省略了作者表,因为目前来说作者只有我一个人。数据库中主要的表和它们的关系如下图所示。 ```mermaid erDiagram Post }o--|| Category : "belongs" Post }o--o{ Tag : "has" User ||--|| Profile : "has" ``` 其中几个类的具体情况是 ```mermaid classDiagram direction RL class Post { title : CharField content : TextField date : DateField cover : ImageField category : ForeignKeyField[Category] tags : ManyToManyField[Tag] } class Category { name : CharField } class Tag { name : CharField } class Profile { user: OneToOneField[User] avatar: ImageField content: TextField } ``` 可以说这真的是最精简版的博客数据库了。当然数据库中实际存在的还是有 Django 自带的权限表 Group 和 User。为了使用 Vditor,实际实现时还是要把 TextField 替换成 VditorTextField,才能在 Django Admin 中使用 Vditor 进行编辑。 ## 视图和模板 视图这部分没什么好说的,主要是以下几个视图: - Post 的增删查改 - User 的登入登出 - Home 登入登出功能主要是限制 Post 增删改的权限。但是如果不要求在前端进行文章操作,完全可以不要登入登出和 Post 的增删改操作(比较适合导出成静态页面)。 主页 Home 的渲染是根据 Profile 中关联的第一个 User 表中用户的记录。由于这个项目在使用 Docker 部署的时候,必然会创建一个超级用户,这个超级用户就是作为第一个用户存在的。Home 中最左侧的介绍以及中间的头像,就是从数据库中取出这个超级用户的相应记录进行渲染的。 模板几乎与视图一一对应,只不过为了避免写重复的代码,将模板进行了分解,使用 Django 提供的 `extends` 关键词进行模板扩展。主要继承关系如下所示。 ```mermaid classDiagram base <|-- index base <|-- post_base post_base <|-- list post_base <|-- detail post_base <|-- edit ``` 日后还可以在此基础上添加其他的模块,例如相册等。 # 部署 该项目提供了 Dockerfile 用于构建 [Docker 镜像](https://hub.docker.com/r/hpdell/myzone-django),可以直接使用 Docker 部署。事实上,该项目就是利用 VSCode 的远程开发功能在 Docker 容器里面开发的。同时仓库中也提供了一个 docker-compose 配置样例,可以直接部署。下图是在服务器上部署后的效果。 ![image.png](/media/870963a7-42f5-4d62-81c2-8876b1949943_image.png) 相应的 docker-compose 配置如下: ```yml version: '3.8' services: app: image: hpdell/myzone-django:latest volumes: - upload-data:/code/uploads ports: - "8080:8000" depends_on: - db links: - "db:db" restart: unless-stopped environment: - DJANGO_SUPERUSER_USERNAME= - DJANGO_SUPERUSER_EMAIL= - DJANGO_SUPERUSER_PASSWORD= - MYZONE_HOST=huyg.site db: image: postgres:latest restart: unless-stopped volumes: - postgres-data:/var/lib/postgresql/data environment: POSTGRES_USER: POSTGRES_DB: POSTGRES_PASSWORD: volumes: postgres-data: null upload-data: null ``` 注意配置中将 `uploads` 文件夹(上传的图片所保存的位置)永久性保留了下来,这样每次代码更新,直接拉取最新镜像并重新部署容器即可(在 Protainer 中甚至只需要点点鼠标)。 # 结语 整个博客开发时间,满打满算可能有四天,就已经实现了绝大多数功能(有一些在文章中没有介绍,比如本地化、移动端适配),相比之前那个动态博客,开发难度降低了很多,而且可扩展性也很强。这得益于 Django 的强大功能,以及最简化的项目框架,这样我觉得没有选择最火热的 Vue/React 和前后端分离框架是一个非常正确的决定。这样我想起一段往事。 > 去年我在进行毕设答辩的时候,有一个评委老师问我这样一个问题:“你觉得模型是越复杂越好,还是越简单越好?”我给出的回答是:“模型的复杂度要与实际问题相匹配。” 虽然我不知道这个回答有没有让老师满意,但在开发做项目时,我觉得也是同样的情况,用的技术栈也不是越强大越好,而是要和项目内容相匹配。用一些简化的技术去开发复杂的项目,难度一定会越来越大;而用一些太过强大的技术开发简单的项目,反而会带来很多不必要的麻烦。但是,这并不是说 Django 模板系统不强大,只是基于模板的渲染系统在项目规模更大时会变得比较麻烦,此时采用前后端分离的框架会更好。 当然,这个博客还有一些功能有待完善: * [ ] 草稿功能。思路是在 Post 中增加一个 Draft 字段来表示是不是草稿,前端再增加一个草稿箱的列表页面。 * [ ] 自动保存。思路是借助 Vditor 自带的缓存功能实现。 * [ ] 退出提醒。退出编辑页面时提示用户数据可能没有保存,避免文稿丢失。 希望这个博客能够稳定运行更长的时间。
感谢您的阅读。本网站 MyZone 对本文保留所有权利。