# 简述 [作者本人的博客](https://jqh.zone)于2019年末疫情下漫长的寒假中开始着手构建,若按每天八小时工作量,则花费的时间折合半年左右,博客至今仍在不断完善。写这一系列文章的目的是总结构建博客的过程,发现优点和不足。如果能给初学者(当然本人也是个初学者)一些帮助和思考,本人不胜荣幸。 # 基本结构 说是从零开始,其实使用SpringBoot和Vue本身就可以说站在巨人的肩膀上。**网站采用MySQL数据库,由SpringBoot搭建后端API服务,使用Redis实现缓存和部分业务功能,使用Vue构建前端应用。** # Vue与SEO 其实使用Vue搭建博客应用并不是好的选择。由于其前端渲染的特性,严重影响SEO。简单来说就是爬虫爬到的是静态的html和js,动态数据在ajax请求中,爬虫获取不到。本网站的解决方案参考了[这篇文章](https://segmentfault.com/a/1190000019623624)的第四种方案,即一种旁路机制。文章中介绍的是使用PhantomJs处理爬虫的请求,即使用PhantomJs在后端生成完整页面返回给爬虫。本网站借鉴了旁路的机制,即通过nginx判断User-Agent是否为爬虫,从而将爬虫请求重定向到特殊处理服务上。但本网站直接将爬虫请求重定向到了thymeleaf模板上,通过thymeleaf重新做了一个专门给爬虫看的页面,并做针对性的优化,如使用有语义的class和标签等。 但是这样做并不算是很好的选择。本人是本着多学一门技术的想法而使用的Vue,实际上很多做的很好的博客都是采用的后端渲染。如果不是为了学习Vue,或者是对Vue丰富便捷的前端交互实现有着执着追求,那么其实没必要这么折腾。**同样,对于本系列文章中所做的各种选择都仅供参考,并不是最好的选择。** --- **2021年12月18日新增** --- 在必应官方的站长手册中找到了一篇官方文章的链接,描述了这种方法。详见这篇文章:<a class="r-link" href="/blog/post_detail/530">【译】JavaScript,动态渲染以及伪装</a> # 设计数据库 开发业务系统一般流程是需求确定后开始设计数据库表结构。那么博客需要什么呢? **首先,博客最重要的就是文章。** 文章最基本的属性有标题、内容、创建时间。当然还有必须的id字段。如果考虑交互操作的话,还会有点赞数、转发数、评论数。这里的三个字段可以算作冗余字段,因为可以通过select count操作来从相关的表中实时的获取对应数值。但博客一般是读远大于写的(不可能每个读到的人都注册帐号给你点赞),所以在写的时候记一下增量,读的时候就能提升效率。因为是个人项目,或者直白的说因为我水平不行,无法保证记录的增量绝对准确,可能由于网络问题或并发问题导致数据有误,所以定时通过select count操作执行一下数据校验是有必要的。后续会详细说明。 **如果有交互功能,那必然要有用户的概念。** 用户是各类系统中都有的通用概念,基本字段一般就是用户ID、用户名、密码。其中用户名可以唯一也可以不唯一。但主流社交网站如微博、B站都要求用户名唯一。用户名设置唯一的好处是搜索用户方便、可以实现@功能。所以有一定社交属性最好将用户名设为唯一。用户ID可以使用MySQL的自增ID,如果考虑分布式系统的话可以使用分布式自增ID或UUID。但UUID太长了,用户可能不喜欢(反正我不喜欢)。密码可以存用户输入密码的加盐MD5。存明文的话万一爆库,参考CSDN爆库事件。本网站的做法是用户注册时即将输入的密码在前端执行MD5后发送给后端,后端随机生成一个32位的字符串作为MD5的盐(salt),然后将前端传过来的密码的MD5和随机生成的盐拼接起来,再执行一次MD5,将结果作为最终存储在数据库的密码。同时随机生成的盐也存储到用户表中。在登录校验的时候从数据库中取出盐和密码,再执行一遍上述操作即可。同理,设有冗余字段关注数、粉丝数、发帖数等。有了用户,文章就有必要增加发表人ID的字段。 **文章多了必然要有分区的概念。** 可以是简单的单一层级的分区,也可以是两层、三层或无限层的树形分区。不管是哪种分区,其原则都是一篇文章只能出现在一个分区内,否则就不是分区的概念了。如果有此类需求可以考虑增加标签的概念。分区的基本字段有分区ID和分区名称。如果是树形分区,还要有上级分区ID。这样,文章就需要加一个分区ID字段以标明所属分区。分区一般在博客首页需要展示,并且文章详情也要展示分区路径,即根据所属分区ID找到所有上级分区。如果是树形分区的话,在新增和修改分区时还需要校验是否构成环(即自己是自己的父节点)。而且分区的数据量不会太大。所以,分区信息直接在服务端程序启动时读取到内存中进行管理是比较好的。这样,在修改分区信息时同步修改内存和数据库即可。 **评论是最重要的互动功能。** 评论一般有两级,即对文章的评论和对评论的评论。这里记为一级评论和二级评论。对二级评论可以进行回复,回复本身也算作二级评论。一级评论和二级评论可以分开存储,不容易造成混乱。一级评论的基本字段有评论ID、所属文章ID、评论内容、二级评论数、点赞数。二级评论的基本字段有评论ID、所属文章ID、所属一级评论ID、评论内容、点赞数。原则上讲,二级评论从属于一级评论,可以不存文章ID。但多存一个文章ID并不费事,在应用中很可能需要读取二级评论所属的文章ID。这种冗余还是比较有用的。 **点赞也是非常重要的互动功能。** 通常文章、评论都可以点赞。对不同类型的对象的点赞可以存到不同的地方,也可以存在同一个地方然后通过一个字段区分。本网站采用第二种方法,主要是图省事。点赞的基本字段有点赞对象ID、点赞对象类型、点赞用户ID、点赞时间。点赞的特点是读写频率都相对较高,所以很有必要建立缓存。本网站采用的方法是,当日的点赞完全存储在redis中,设置定时任务,每天将redis中的点赞数据固化到MySQL中。点赞还有一个重要需求就是对一篇文章或评论判断当前用户是否点过赞。判断时一般只判断redis中是否包含,而不会判断MySQL中是否包含。这样,如果每天刷新缓存数据,那点么点赞的有效期就是一天,即第二天就可以再次点赞。这里由于是个人项目,设计的较为简单,大型社交平台往往对点赞有非常复杂的缓存机制。B站会缓存一个用户近600次的点赞,也是有限制的。甚至对于数据量特别大的平台,较早的点赞记录直接丢掉,只保存总数值也是有可能的。 **点赞和评论需要通知用户,所以还要建立通知机制。** 通知可以选择站内通知,也可以选择站外,如邮件或微信通知。站外通知有成本或资质的限制,所以本网站采用站内通知。其机制就是点赞和评论时存储一条通知数据,用户登录系统时查询自己相关的通知即可。通知的基本字段有通知ID、接收者ID、通知内容类型、通知内容ID、通知发送时间。同点赞一样,通知数据读取频率较高,一般也需要缓存。本网站选择了不固化通知,直接存在redis中,即在redis中为每个用户创建一个通知列表,并设置最大长度,自动删除较早的通知。也是图省事。有通知时,一般会在网站界面的某个地方显示通知数量。获取通知数量的方式有两种,一是可以在每一次请求时都从缓存中读取通知数量,二是可以通过WebSocket技术实时推送新通知。本网站采取第一种方案,较为简单。 如果要更加社区化的话,还可以增加关注、转发等功能,分区还可以增加权限机制。有关注和粉丝的话还会有动态功能,即feed流。本网站是有这些功能的,但比较简陋,有时间的话可以简单介绍一下。