Spring Boot个人博客系统实战:从数据库设计到部署上线
2026/9/1 8:31:00 网站建设 项目流程

简介:这是一套基于Spring Boot开发的完整个人博客系统源码,面向Java Web初学者与Spring生态实践者,旨在帮助开发者掌握前后端分离之外的传统服务端渲染架构落地能力。资源包含全部源代码、MySQL建表SQL、Redis缓存配置及配套静态资源,涵盖用户认证、文章CRUD、评论交互与数据看板等典型业务模块,适合作为课程设计、毕业项目或技术栈整合学习范例。压缩包共598个文件,主体为29个核心Java类(含Controller、Service、Entity)、70个JS脚本、58个CSS样式、30个HTML模板及119个MyBatis映射XML文件,辅以图片、字体与配置文件,整体大小92.43MB。已有5042人学习下载,提供开箱即用的IDEA工程结构、Thymeleaf+jQuery前端渲染方案、Spring Security权限控制及Redis缓存集成实现,便于快速部署、调试与二次开发。 做个人博客系统这种项目的人,十有八九都是为了毕业设计、课程作业或者给简历上凑一个完整项目。但说实话,市面上打着“Spring Boot个人博客系统源码+数据库”旗号的东西一大堆,真正下载下来能一次跑通、代码结构又清晰的,真没几个。大部分不是缺依赖就是SQL脚本不全,再不济就是前端页面丑得没法看。我这次整理这套博客系统,不打算整那些花里胡哨的东西,就老老实实把一套能用、能扩展、能写进简历的完整工程拆开讲清楚——从数据库设计、后端接口到页面渲染、部署发布,每一步怎么来的、为什么这么做,都说明白。

这套系统给我的感觉就是典型的“麻雀虽小五脏俱全”项目:文章发布、分类管理、标签聚合、评论互动、后台登录、站点配置都齐了。它适合什么人看?特别适合两种人:一是准备用Spring Boot做毕设但不知道从哪下手的同学,二是学过SSM或者Spring Boot基础、想找个完整项目练手沉淀的初学者。如果你已经能独立写CRUD、懂基本的前后端交互逻辑,那这篇文章能帮你把项目从“会写功能”拉到“能做工程”的层次。要是零基础,也不用慌,我会把环境准备和手写SQL脚本的细节都交代清楚,照着一路敲下来,项目一定能跑。

1. 动手前先想清楚:一个博客系统到底要做什么

很多同学拿到“做一个博客系统”这种需求,第一反应就是打开IDEA直接建工程、写实体类。十有八九写到一半发现功能越加越多,或者表结构改来改去,最后代码全乱了。这种情况我见过太多,所以先把需求边界和模块划分定下来,再谈代码。

1.1 功能清单:别做“大而全”,做“够用且完整”

个人博客系统本质上就是一个典型的CMS内容管理系统。按照我的经验,一个能应付毕设答辩、又能体现技术含量的博客系统,核心功能就这几个:

  • 文章模块:文章的发布、编辑、删除、详情展示、列表分页,这是整个系统的地基。
  • 分类与标签:文章支持归属一个分类、打多个标签,用来做内容归档和聚合查询。
  • 评论模块:访客可以对文章发表评论,后台可以对评论进行管理(删除、审核)。
  • 后台管理:管理员登录后进入后台,维护文章、分类、标签、评论、站点基础设置。
  • 站点配置:博客名称、博主简介、首页公告、友情链接等,一般放在一张配置表里统一管理。

至于消息通知、站内信、多用户写作、复杂权限体系、第三方登录这些,一律不做。原因很简单:个人博客的场景就是“一个人写,一堆人看”,整那么多附加功能,只会把核心业务淹没掉。而且从毕设评审的角度看,你把文章、分类、标签、评论这几条链路做扎实了,比堆十来个半吊子功能强十倍。

1.2 用户角色与操作路径:以“后台管理”为圆心设计

这套系统的用户角色只有两个:管理员和访客。访客能做的事情非常有限——看文章、看分类、看标签、发评论;管理员能做的事情围绕“内容管理”展开——登录后台、写文章、管分类标签、删违规评论、改站点信息。

设计操作路径的时候,我习惯从页面反推接口。比如后台要有一个文章列表页,那后端必须提供“分页查询文章”的接口;列表页上要有“编辑”按钮,那后端必须提供“按ID查文章详情”的接口;编辑页要能保存,那后端必须提供“新增/更新文章”的接口。这种“页面推接口”的方式,比对着数据库表空想接口要高效得多,也直接决定了你的Controller层会有哪些方法。

2. 数据库先行:8张表撑起整个博客系统

“源码+数据库”这种交付形式,数据库脚本就是灵魂。我见过太多的项目源码分享,代码是一套的,SQL脚本又是另一套的,导入数据库就直接报错。所以这套系统里,我把SQL脚本命名为schema_and_data.sql,里面同时包含建表语句和初始化数据,保证拿到手就能跑。

2.1 核心表结构设计:冗余字段用在对的地方

整个博客系统我设计了8张表,别看数量不多,表与表之间的关系是够用的:

表名作用关键字段
t_user管理员用户表id, username, password, nickname, avatar, create_time
t_article文章表id, title, summary, content, category_id, cover_image, status, view_count, create_time, update_time
t_category分类表id, name, description, create_time
t_tag标签表id, name, create_time
t_article_tag文章-标签关联表id, article_id, tag_id
t_comment评论表id, article_id, nickname, email, content, parent_id, status, create_time
t_friend_link友链表id, site_name, site_url, description, create_time
t_site_config站点配置表id, config_key, config_value, description

这里有个很重要的设计细节,就是文章的category_id字段。按三范式来说,文章表里存category_id就是外键引用,但实际上为了查询方便,我额外在文章表里冗余了一个category_name字段。为什么这么做?因为在首页文章列表页,大概率要展示每篇文章属于哪个分类,如果每次都要去t_category表查一遍名称,一个页面十条文章就要多出十条关联查询。虽然MyBatis的关联查询可以做,但让SQL简单一点、让代码少一点,才是这种规模项目最该追求的。

2.2 评论表设计:用parent_id实现楼中楼

评论表里最容易忽视的字段是parent_id。这个字段的作用是记录“这条评论回复的是哪条评论”,如果不为空,就说明这是一条子评论。其实没有parent_id也能做评论功能,就是所有评论平铺一层,没法搞楼中楼。但是我强烈建议加上,因为加这个字段的成本很低,却能让评论模块显得完整很多,在毕业设计答辩时也是一个可以讲半天的设计亮点。

2.3 初始化数据与SQL脚本的导入细节

SQL脚本里除了建表语句,还需要包含一条管理员账号的INSERT语句和几个测试分类、几篇示例文章。注意一点:管理员密码不要用明文,直接用BCrypt加密后的字符串写进去。Spring Security自带的BCryptPasswordEncoder能直接校验,密码是admin123,加密串形如$2a$10$...。这样既方便你本地登录测试,又显得你懂密码安全的基本操作。

导入的时候有一个容易踩的坑:如果你的MySQL版本是8.0以上,建议在连接串里加上?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai,否则导入带有中文的SQL脚本可能出现乱码,或者遇到Public Key Retrieval is not allowed的错误。命令行导入时用source xxx.sql或者mysql -uroot -p < schema_and_data.sql都可以,但务必确认当前数据库的默认字符集是utf8mb4

3. 技术选型与工程骨架:别一股脑用最新版本

关于Spring Boot的版本,我看到热词里有“spring boot 2.1”“spring boot 2.6”“spring boot 4.x”这些,我直接给结论:这套博客项目用Spring Boot 2.7.x是最稳的。原因有两个,第一是2.7.x是2.x系列的最后一个大版本,文档和生态都极其成熟,你遇到的问题搜索基本都有答案;第二是很多学校机房或者老一点的教程还停留在2.x,用3.x反而容易因为JDK版本、Jakarta命名空间等问题卡住。JDK选1.8或者11都行,如果是3.x就必须17+,没必要给自己制造麻烦。

3.1 持久层选型:为什么我推荐MyBatis Plus

持久层这块,Spring Boot官方推荐的是Spring Data JPA,但是国内项目用MyBatis系的更多,毕设也大多是MyBatis或者MyBatis Plus。MyBatis Plus对个人项目来说太友好了:内置的单表CRUD方法让你不用写基础SQL,LambdaQueryWrapper可以链式构建查询条件,分页插件一套就支持MySQL分页。这套博客系统里,文章列表分页、评论分页、标签文章查询全都依赖它,代码量比纯MyBatis能少三分之一。

3.2 前端方案:Thymeleaf服务端渲染最省事

前端这块很多人纠结要不要前后端分离。我的明确建议是:个人博客系统用Thymeleaf做服务端渲染,不要上Vue。原因很简单,Vue那套意味着你要维护两套工程、处理跨域、考虑鉴权怎么对接,工作量直接翻倍。Thymeleaf的好处是服务端直接把数据填入HTML模板返回浏览器,静态资源(CSS、JS、图片)放在src/main/resources/static下,后端方法返回视图名称就能渲染页面,对毕设和练手项目来说再合适不过。

页面布局上,前台我建议找一个开源博客模板改一改,比如基于Bootstrap的免费模板,把里面的死数据替换成Thymeleaf的th:each循环;后台管理界面建议直接用AdminLTE或者layui这类现成框架。你自己手写CSS也不是不行,但成品效果大概率不如直接改一个成熟模板,毕竟毕设展示时颜值也是分。

3.3 工程目录结构:按业务模块分包,别按技术分包

工程包名我习惯用com.blog,再按业务模块划分:

com.blog ├── controller │ ├── admin(后台接口) │ └── front(前台接口) ├── service ├── mapper ├── entity ├── config │ ├── MybatisPlusConfig.java │ ├── WebMvcConfig.java │ └── InterceptorConfig.java ├── common │ ├── Result.java(统一返回体) │ └── PageResult.java(分页返回体) └── BlogApplication.java

很多初学者喜欢按controller/service/mapper/entity这样分,这种分法也没什么错,但一旦功能多了,controller包下会堆二三十个类,找起来很头疼。按admin/front分的好处是,你能一眼看出哪些接口给后台用、哪些接口给前台页面用,层次清晰,答辩的时候也更好讲。

4. 核心模块逐个拆解:文章、分类、评论的落地细节

骨架搭好了,接下来就是往里面填核心功能。我按“文章发布流程 -> 列表查询 -> 分类标签聚合 -> 评论互动”这条主线来讲,每个环节都是在实际开发中反复改过多次之后沉淀下来的写法。

4.1 文章发布:摘要与正文的分工

写文章功能首先要搞清楚:摘要和正文是两个字段,不是同一份数据。t_article里我设计了summary字段,用来存放文章摘要,在列表页展示;content字段存放完整正文。有的同学图省事,列表页直接截取正文前100个字符当摘要,这个做法不推荐——一是效率低,二是格式会很乱,Markdown截图出来可能是一堆符号。

后台文章编辑页我用了Markdown编辑器,前端引入editor.mdsimplemde都行。提交的时候,把编辑器里的内容原样POST到后端,后端只做HTML转义和长度校验,不做过多的内容处理。Markdown转HTML的渲染工作,可以放在前端页面加载时用marked.js完成,也可以后端用commonmark-java库转好再返回给前端。我更推荐后端渲染,因为这样文章详情页拿到的直接是HTML字符串,搜索引擎也能正常抓取内容。

4.2 文章列表分页:PageHelper还是MyBatis Plus分页插件

分页查询是整个系统最常用的操作,也是面试官最爱问的点。MyBatis Plus的分页插件用起来非常简单,在配置类里注册一个PaginationInnerInterceptor,然后Service层调用Page<Article> page = new Page<>(current, size);articleMapper.selectPage(page, wrapper);就能拿到分页数据。

当你查询文章列表时,需要带上分类名称、标签名称、发布时间等信息。分类名称通过冗余字段来拿,标签名称则是先查文章-标签关联表,再把标签列表组装进文章对象里。这里有个小细节:不要在一个循环里反复查数据库,比如遍历10篇文章、每篇都查一次标签,就是典型的N+1问题。正确做法是先查出所有文章ID,再用IN查询一次性把所有标签查出来,在内存里做组装。

4.3 分类与标签聚合页:一条SQL还是多条SQL

分类和标签的聚合查询,是博客系统最容易写乱的地方。拿“点击某个分类,展示该分类下所有文章”这个需求来说,最简单的两个方法:

  • 方法一:先查t_category表拿到分类ID,再用article.category_id去查文章列表。
  • 方法二:直接在文章表上写WHERE category_id = ?,连表查询都不需要。

这两个方法没本质区别,推荐用方法二,因为t_article表已经冗余了category_idcategory_name,一条查询就能搞定,不需要JOIN,也不需要额外查分类表。

标签聚合稍微麻烦点,因为文章和标签是多对多关系。查“某个标签下的文章列表”需要三步:先查t_tag表拿标签ID,再查t_article_tag表拿到所有文章ID,最后用article.id IN (...)查询文章表。三步操作在逻辑上必须保证事务性,所以在Service方法上加上@Transactional注解。这三个查询没有写在一个SQL里,而是分开查,为什么?因为代码可读性更高、索引利用率更好,等数据量真的大了再考虑用JOIN去优化也不迟。

4.4 评论模块:表单提交、防刷与展示

前台评论表单比较简单,访客填昵称、邮箱和评论内容就能提交。但是这里至少有四个细节要注意:

第一,parent_id的处理。提交评论时前端带一个隐藏域,标记回复的是哪条评论;如果是一级评论,parent_id就是0。前端展示评论时,后台按parent_id把评论组织成树形结构返回。

第二,评论内容过滤。不管前台还是后台,提交内容都要做HtmlUtils.htmlEscape转义,否则用户提交一段<script>就变成XSS攻击了。多写一行转义代码,能帮你避开一个非常大的安全隐患。

第三,评论审核。个人博客的评论建议默认状态为“待审核”,管理员在后台看到评论列表后选择通过或删除。虽然多了一步操作,但可以防止垃圾评论直接展示在全省。

第四,评论数统计。文章详情页会显示评论数,这个数字如果每次实时统计一次COUNT(*)也不是不行,但为了展示方便,我通常在t_article表里加一个comment_count字段,每次新增评论时加一、删除评论时减一,查询列表时直接读这个字段即可,省去一次关联统计。

5. 前端页面与后台管理:服务端渲染也可以很顺手

后端接口写完了,剩下就是页面渲染的逻辑。这一步很多同学会卡住,因为对Thymeleaf的语法不熟。多说一句,Thymeleaf的语法核心就那几个:th:text往标签里填文本,th:each做循环遍历,th:href拼URL,th:if/th:unless做条件判断。把这些掌握住,页面基本就能写了。

5.1 前台页面骨架与通用模板片段

前台页面我建议拆成三个公共片段:头部导航(header.html)、底部版权(footer.html)和侧边栏(sidebar.html)。用Thymeleaf的th:fragment定义片段,其他页面通过th:replace引入。这样做的好处是,改导航栏只要改一处,所有页面同步更新,不会出现样式不统一的问题。

首页的数据来源是从Controller的Model里传过去的。例如首页需要展示文章列表、分类列表、最新评论和站点信息,就在Controller方法里分别查询并model.addAttribute()塞进去。页面里用${page.records}拿到文章列表,用${categories}遍历分类,用${siteConfig}读取博客名,模板引擎会自动渲染成HTML字符串返回给浏览器。

5.2 后台管理页面与权限拦截

后台管理的所有页面统一放在/admin路径下,通过一个登录拦截器控制访问权限。我这里不用Spring Security的完整登录流程,而是用一个更轻量的方案:用户登录成功后,把用户ID存进Session,然后写一个HandlerInterceptor,在preHandle里检查Session里有没有登录标记,没有就重定向到/admin/login。这样做代码量小、也够用。

既然是轻量方案,那后台接口的权限控制就都依赖这个拦截器。需要注意一个小坑:拦截器的excludePathPatterns必须把登录页、登录接口、静态资源都排除掉,否则你连CSS和JS都加载不出来。

后台管理的页面布局一般用AdminLTE,左侧菜单栏固定,右侧内容区动态加载。每个管理页面就是一张表格,配一个“新增”按钮和每行的“编辑/删除”按钮。这里没必要做到Ajax异步提交,直接表单同步提交、控制器重定向回列表页是最稳的做法——少写一堆前端异步代码,功能逻辑一样完整。

5.3 文件上传与静态资源映射

文章封面图片、头像这些需要文件上传功能。Spring Boot里文件上传很简单:前端用<input type="file">,后端Controller用MultipartFile接收,然后保存到本地磁盘目录。但是有一个关键配置容易忽略——默认的Spring MVC静态资源映射只覆盖classpath:/static/,你上传到磁盘的文件如果不额外配置映射,浏览器是访问不到的。所以需要在WebMvcConfig里加一句:

@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler("file:" + uploadPath + "/"); }

这样上传目录就变成了一个可通过URL访问的静态资源目录,图片上传后直接存/upload/xxx.jpg这种路径,文章里引用图片、展示封面都靠它。上传时最好给文件名拼一个时间戳或者UUID,避免重名互相覆盖。

6. 这几个坑,跑项目时十有八九会遇到

说实话,我自己搭这套博客系统的时候,没少被各种奇奇怪怪的问题绊住脚。这里整理几个我在调试过程中觉得最典型、也最值得注意的坑,希望你能绕开。

6.1 MySQL时区问题导致的时间差8小时

这个坑出现的频率极高,而且表现很诡异:文章发布时间存进去是2025-01-01 12:00:00,查出来却变成了2025-01-01 04:00:00,整整少了8小时。原因就是驱动和数据库的时区设置不一致。解决办法是在application.yml里的数据库连接串上显式指定时区:

url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai

同时确认MySQL服务本身的时区设置,执行SHOW VARIABLES LIKE '%time_zone%';看到System或者+08:00都可以。两个地方对齐了,时间就不会乱跳。

6.2 字段自动填充:createTime竟然为null

MyBatis Plus新增文章之后,你会期望数据库自动填好的create_time能回填到实体类的createTime字段里。但默认情况下不一定行,你需要用MyBatis Plus的字段自动填充功能。在实体类的createTime字段上加@TableField(fill = FieldFill.INSERT),然后写一个MetaObjectHandler的Bean,在insertFill方法里给createTimeupdateTime赋值。这样做的好处是,新增文章后端代码里不需要手动setCreateTime(new Date()),代码更干净。

6.3 前端页面展示Markdown:为什么HTML源码被原样输出了

这个坑是在做文章详情页的时候遇到的:数据库里的Markdown原文被th:text显示在页面上,全都是#**这些符号,而不是渲染后的标题和加粗。原因是Thymeleaf的th:text会把内容当纯文本转义输出,要想渲染成HTML,必须使用th:utext。前后台展示文章内容的时候用th:utext="${article.content}",前提是你已经对内容做了可信处理(后端转HTML或者已经清洗过XSS),否则会带来注入风险。

6.4 SQL语句里的关键字冲突

文章表里有个comment_count字段,还有个status字段,status在MySQL里不属于保留字,但某些版本里容易和关键字冲突。为了避免莫名其妙的SQL报错,尽量在涉及statuscomment这些有歧义的字段名时,在SQL语句里用反引号包一下,或者直接换个字段名,比如comment_status。这是个很小的习惯,但是能帮你省下很多排查时间。

6.5 部署到服务器后,上传文件无法访问

本地跑的时候文件上传正常,一到云服务器就404,80%是因为部署方式导致的路径问题。假如你在Linux上用的是java -jar blog.jar,那么相对路径“upload/”是相对当前用户的home目录的,不是jar包所在目录。建议在配置文件里指定一个绝对路径,比如/home/blog/upload/,并在WebMvcConfig里把那个目录映射到/upload/**。另外检查Linux目录权限,确保运行jar包的账号有写权限——这个坑我当年也踩过,排查了整整一个晚上。

7. 从本地到服务器:打包部署的完整链路

项目能跑通是一回事,能部署到公网让人访问是另一回事。毕设答辩的时候,如果能在现场展示一个公网访问的博客地址,效果会好很多。部署这条路不复杂,但有不少细节需要确认。

7.1 打包前的关键配置

首先,application.yml里要区分开发环境和生产环境。我习惯把数据库连接、上传路径这些外部化到application-prod.yml里,用spring.profiles.active=prod切换。打包之前先把测试数据库的连接改掉,不然部署到服务器上会连到你本机的数据库去,那自然是连不通的。

其次,确认pom.xml里打包方式是jar而不是war。Spring Boot内嵌Tomcat的工程直接用mvn clean package打包即可,产物在target/目录下。

7.2 服务器部署步骤

服务器上只要装好JDK 1.8和MySQL,把SQL脚本导入数据库,然后执行:

java -jar blog-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod

加一个--server.port=80也行,如果想直接用80端口就不用再配Nginx转发。不过我更推荐前面再挂一个Nginx做静态资源缓存和反向代理,这样博客的静态资源(CSS、JS、图片)访问速度更快,后端进程也相对安全,毕竟Tomcat直接暴露在公网不是一个好习惯。

7.3 一个小建议:日志别忽略

开发阶段很多人习惯用System.out.println打日志,部署之后这点一定要改掉。用Slf4jLogger,把日志输出到文件,排查问题的时候才知道发生了什么。配置很简单:

logging: file: name: logs/blog.log level: com.blog: info

这样线上报错看一眼blog.log就能定位问题,比黑屏终端滚动强多了。

8. 这套博客还能怎么扩展

项目做到能跑、能演示、能部署,其实你的收获已经不小了。如果还想让这个项目的含金量再上一层楼,我个人建议按下面的优先级去扩展。

第一优先级是搜索功能。现在文章找起来只能靠分类和标签,加一个基于LIKE的标题搜索很简单,五分钟的事;如果想让搜索体验更好,可以集成Elasticsearch,不过个人博客的数据量完全没必要,MySQL的LIKE '%关键字%'在万级数据下性能完全够用。

第二优先级是静态化。每篇文章详情页在首次访问或文章更新时生成一个静态HTML文件,Nginx直接读取磁盘文件返回,不经过Java应用。这个优化思路在面试里讲出来是加分项,但我不建议毕设阶段做,容易把自己绕晕。

第三优先级是接入第三方登录。如果博客要对外开放,GitHub或微信扫码登录是个不错的亮点。不过我个人的建议是,除非你技术底子已经比较扎实,否则还是先专注于把现有功能打磨好,扩展功能容易改出一堆隐藏bug。

我自己的体会是,做这种“源码+数据库”类型的完整项目,最锻炼人的不是某个技术点本身,而是整体流程的把控:从需求分析到表设计,从接口联调到部署上线,每一步都踩一遍坑、填一遍坑,你对Spring Boot这整个生态的感知就不再是某个章节的知识点了,而是串成了一条线。这套博客系统做完,再回头去看那些散落在教程里的知识点——AOP、拦截器、事务、分页、文件上传、自动配置——你会发现自己真的看懂了。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询