Spring Boot个人博客系统实战:从分层架构到打包部署完整方案
发布时间:2026/9/17 2:39:34 作者:尧图编辑部 阅读量:1,286

简介基于Spring Boot的Java毕业设计《个人博客系统的设计与实现》源码包面向计算机类毕业生与课程设计学生解决个人博客从后台管理到用户交互的完整流程。系统采用B/S架构依托MySQL数据库实现管理员、博主、用户三种角色涵盖用户管理、文章发布与分类、系统公告、轮播图、收藏评论等功能是一套前后端分离的全栈学习项目。资源包共777个文件容量78.92MB包括Java后端源码、Vue前端页面、JS/CSS/HTML静态资源、SQL数据库脚本、说明文档与演示视频并附有安装、运行和打包脚本便于快速启动部署。目前已有131人学习下载通过完整源码可深入了解多角色权限设计、数据库表结构及评论收藏逻辑演示视频直观展示运行效果说明文档辅助完成课题设计与答辩准备。1. Spring Boot 个人博客系统一个能打通全流程的毕业设计选题每年这个时间点都会看到一批人被 Spring Boot 项目卡住要么代码全部堆在 Controller 里没有分层要么数据库表建出来连评论和文章的外键关系都没想清楚要么答辩前一天发现项目在别人机器上根本启动不起来。个人博客系统之所以在 Java 毕业设计里反复出现核心原因是它的边界足够清楚文章、分类、标签、评论、用户登录这几个模块组合起来恰好覆盖了 Spring Boot 后端开发的主干流程——实体映射、接口设计、参数校验、权限拦截每一项都对应面试和答辩时的高频追问。这篇文章直接给出一套完整可落地的方案从表结构、工程配置到接口调用再到最后打包部署照着做就能得到一份能演示、能答辩、也能继续扩展的代码。2. 技术选型与工程结构Spring Boot 框架版本与服务端渲染方案2.1 Spring Boot 框架版本选型2.7 而不是 3.x 的理由个人博客这类毕设我一般建议直接选 Spring Boot 2.7.x而不是追新上 3.x。原因很实际2.7 依然是当前教程、面试八股文和网上的排错帖覆盖面最广的版本JDK 8 环境下就能直接跑而 Spring Boot 3 强制要求 JDK 17实验室电脑和老旧 Maven 仓库经常会出现依赖冲突或启动失败的问题。对毕设项目来说稳定、能演示、资料好查优先级远高于版本新。组件推荐版本/方式说明Spring Boot2.7.18兼容 JDK 8网上可查的异常案例最全MyBatis-Plus3.5.3.2简化单表 CRUD 和分页后期换 MyBatis 同理MySQL8.0 或 5.7开发阶段用 8.0字符集统一 utf8mb4Thymeleaf随 Boot 版本管理服务端渲染一个进程跑完页面和接口这里没有选前后端分离方案也是考虑性价比。Spring Boot 服务端渲染时登录、文章列表、详情页都交给 Thymeleaf 模板处理省去 Vue 工程打包、跨域配置、Token 维护这些环节。前后端分离是加分项但不该成为毕设的第一道门槛。2.2 包结构与顶层设计源码到手先看这个分层拿到源码压缩包之后第一件事不是急着跑而是先看包名分层。一个能拿得出手的 Spring Boot 工程至少应该有下面这几层com.example.blog ├── common // 通用返回结果 Result、异常处理 ├── config // 配置类MyBatis-Plus 分页、拦截器注册 ├── controller // 页面跳转与接口入口 ├── entity // 数据库对应的实体类 ├── mapper // MyBatis-Plus 的 Mapper 接口 ├── service // 业务接口和实现类 └── util // MD5、日期格式等工具这个结构的核心逻辑是单向依赖Controller 只调 ServiceService 只调 Mapper实体类不掺杂业务逻辑。常见的问题是有人把 SQL 直接写在 Controller 里答辩时一旦被问“如果评论量增长你怎么做分页”就会卡住。分层清晰之后每个模块的替换成本都很低这也是面试官愿意往下聊的前提。2.3 依赖与配置pom.xml 和 application.yml 的最小集合Spring Boot 项目的起步依赖是固定的下面这几个是博客系统的最小集合parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependencyweb负责 MVC 和内嵌 Tomcatthymeleaf负责页面渲染mybatis-plus-boot-starter负责数据库操作。MyBatis-Plus 的版本不能省Boot 2.7 的依赖管理没有覆盖它不同大版本之间的分页插件写法有差异。配置文件的重点是数据源下面是一份能直接用的application.ymlserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/blog?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 thymeleaf: cache: false mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0serverTimezoneAsia/Shanghai在国内环境下必须配否则 MySQL 驱动会报时区异常。map-underscore-to-camel-case让数据库的create_time自动映射到实体的createTime。最后三段是逻辑删除配置文章或评论做删除时实际执行的是UPDATE而不是DELETE这个字段在后面的实体类里要对应存在。:8080端口如果被占用改成server.port: 8081即可但启动命令里的访问地址也要同步调整。3. 数据库设计到接口骨架Spring Boot 博客表结构与第一个接口3.1 建表 SQLarticle、category、comment、user 四张表的字段设计个人博客系统的表不需要设计得很复杂但每张表都要有存在的理由。我的习惯是四张表起步用户表保存登录信息分类表保存文章分类文章表是核心表评论表单独拆出来方便后续做审核或嵌套回复。CREATE DATABASE IF NOT EXISTS blog DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE blog; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50) DEFAULT , create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ) ENGINEInnoDB; CREATE TABLE article ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content TEXT, category_id INT, user_id INT, views INT DEFAULT 0, deleted TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_category (category_id) ) ENGINEInnoDB; CREATE TABLE comment ( id INT PRIMARY KEY AUTO_INCREMENT, article_id INT NOT NULL, user_id INT NOT NULL, content VARCHAR(500) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB;deleted字段是配合上一章的逻辑删除配置用的值为 1 表示已删除。文章表的views字段用来记录访问量属于典型的“预留一个计数器的位置”答辩时可以顺势引出 Redis 缓存方案。category_id上的索引idx_category是有意设计的按分类查文章是最常见的前台查询这一步能在口试环节体现基本的索引意识。外键我没有建逻辑外键由 Java 代码控制MyBatis-Plus 关联查询时再补 JOIN这样删除分类时不会被数据库约束卡住。3.2 实体类与 MapperMyBatis-Plus 的字段映射四张表对应的实体类写法一致这里以Article为例Data public class Article { private Integer id; private String title; private String content; private Integer categoryId; private Integer userId; private Integer views; TableLogic private Integer deleted; private LocalDateTime createTime; TableField(exist false) private String categoryName; }Data来自 Lombok自动生成 getter/setter减少样板代码。TableLogic标记的字段走逻辑删除执行删除操作时 MyBatis-Plus 会自动改为UPDATE article SET deleted 1 WHERE id ?。categoryName是表中不存在的字段加TableField(exist false)是为了在联表查询时接收分类名称这样前端展示就不需要再发第二次请求。Mapper 接口只需要继承BaseMapper基础的单表 CRUD 就不用自己写了。这就是题目里 MyBatis-Plus 的意义全表字段只要和实体对应代码量能砍掉大半剩下的精力可以全部放在业务查询上。3.3 启动项目第一个接口与 curl 验证完成建表和实体类后写一个ArticleController来验证链路先不引入 Service只查一条文章RestController RequestMapping(/api/article) public class ArticleController { Autowired private ArticleMapper articleMapper; GetMapping(/{id}) public Result getArticle(PathVariable Integer id) { Article article articleMapper.selectById(id); return Result.success(article); } }这里的Result是统一响应体标准格式是{ code: 200, data: ... }。先跑通这个接口项目就算活起来了。启动方式有两种IDE 里直接运行BlogApplication的main方法或者打包后用java -jar。开发阶段推荐前者能直接看到控制台日志。项目起来后用下面这条命令验证curl http://localhost:8080/api/article/1如果数据库里插入了 id 为 1 的文章返回的 JSON 里能看到title和content。如果返回 404先看控制台启动日志里有没有报错如果返回空数据检查 MySQL 里是否真的插入了数据以及application.yml的库名是否写成了blog。这一步是后面所有模块的基础接口跑不通之前不要急着写页面。4. Spring Boot 博客核心业务文章分页、评论事务与管理端登录4.1 文章分页查询current、size 参数与服务层写法博客首页展示文章列表不能一次性把全表查出来必须分页。MyBatis-Plus 提供了现成的分页插件先要在配置类里注册Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }PaginationInnerInterceptor指定数据库类型为 MySQL分页 SQL 会自动拼接LIMIT。接着在 Service 里写分页查询方法这里可以直接体现出current和size两个核心参数public PageArticle pageArticles(long current, long size, Integer categoryId) { PageArticle page new Page(current, size); LambdaQueryWrapperArticle wrapper new LambdaQueryWrapper(); wrapper.eq(categoryId ! null, Article::getCategoryId, categoryId) .orderByDesc(Article::getCreateTime); return articleMapper.selectPage(page, wrapper); }Page对象接收两个参数current是页码从 1 开始size是每页条数。LambdaQueryWrapper里的eq第一个参数是条件开关当categoryId为 null 时不拼接该条件这样同一个方法既能查全部分类又能按分类过滤。orderByDesc按创建时间倒序保证新文章排前面。Controller 从请求参数里接收这两个值再传给 Service。前端表格里通常对应一组固定参数current1size10就是取第一页十条。需要注意PaginationInnerInterceptor注册失败时selectPage不会报错但返回的是全量数据容易被忽略。验证方式是看日志里 SQL 是否带了LIMIT。4.2 发表评论的事务控制Redis 缓存与事务的边界评论模块的逻辑很简单但有一个典型的事务场景插入评论成功后同步更新文章表的评论数。如果第二步失败第一步的数据就会残留所以必须放在同一个事务里。Transactional(rollbackFor Exception.class) public void addComment(Comment comment) { commentMapper.insert(comment); LambdaUpdateWrapperArticle wrapper new LambdaUpdateWrapper(); wrapper.eq(Article::getId, comment.getArticleId()) .setSql(comment_count comment_count 1); articleMapper.update(null, wrapper); }Transactional注解加在 Service 实现类的方法上rollbackFor Exception.class表示任何异常都回滚不只是运行时异常。setSql用来执行自定义 SQL 片段直接让数据库字段自增避免先查出来再写回去的并发覆盖问题。这里有一个常见误用Transactional写在 Controller 方法是无效的因为 Spring AOP 需要通过代理对象调用才能生效。同类的错误还包括“自己调自己”——在一个 Service 方法里调用另一个带事务注解的内部方法事务同样不生效。调试验证的方式很简单在插入评论后制造一个除零异常看数据库里有没有残留数据。评论内容的校验不能只在数据库层做。NotBlank(message 内容不能为空)加在评论 DTO 的content字段上Controller 参数加Validated这样空评论在入口就被拦截不会进入业务层。这个细节在答辩时是加分项。4.3 登录与拦截器从 MD5 到 BCrypt 的密码处理用户登录是管理后台的入口。密码不能明文存库也不建议用 MD5因为现在的彩虹表查 MD5 几乎秒破。Spring Security 的加密类可以直接拆出来单独用不需要引入大而全的安全框架String encoded BCrypt.hashpw(rawPassword, BCrypt.gensalt()); boolean matched BCrypt.checkpw(rawPassword, encoded);BCrypt.gensalt()每次生成的盐都不同所以同一个密码两次加密的结果不一样数据库里即使泄露了密文也无法反推原文。校验时调用BCrypt.checkpw内部会从密文中提取盐进行比对。注册用户时用hashpw存储登录时用checkpw验证。登录状态的维护用拦截器最直观。写一个LoginInterceptor实现HandlerInterceptor在preHandle里检查 sessionpublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(/admin/login); return false; } return true; }注册到配置类里只拦截/admin/**路径放行登录页和静态资源Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/admin/**) .excludePathPatterns(/admin/login, /css/**, /js/**); }addPathPatterns定义拦截范围excludePathPatterns定义白名单。.rar源码包里的演示视频往往是录到登录之后能正常进管理页、发布一篇新文章这个流程拦截器配置正确与否就体现在这一步。如果登录后被重定向回登录页优先检查 Session 是否在登录成功后写入了loginUser这个 key。5. 打包部署与答辩扩展让源码在你自己的电脑上也能跑5.1 用 Maven 打包并启动跳过测试是关键开发环境下 IDE 直接运行没问题但演示和提交成果时需要打包成可执行 Jar。打包命令是mvn clean package -DskipTests-DskipTests跳过单元测试执行但会保留测试代码的编译。如果连编译都不想编用-Dmaven.test.skiptrue。打包完成后target目录下会生成blog-0.0.1-SNAPSHOT.jar。这部分在说明文档里会写着“使用 JDK 8 运行”实际执行时验证一下版本java -version java -jar target/blog-0.0.1-SNAPSHOT.jar部署到服务器或另一台电脑时数据库要先建好并导入 SQL 文件mysql -u root -p blog blog.sql项目默认从application.yml连接本机 MySQL如果数据库不在同一台机器需要把jdbc:mysql://localhost:3306/blog改成对应 IP。部署完成后按下面的清单验收一遍验证点地址预期结果首页文章列表/能看到分页后的文章列表文章详情/article/1文章内容和评论正常显示后台登录/admin/login用户名密码正确可进入发布文章登录后访问/admin/article/add表单提交后列表出现新文章5.2 答辩可以演示的三个改进点代码能跑只是及格线能讲出扩展思路才是加分的地方。第一是查询缓存文章详情是典型的热点数据在查询方法上加Cacheable(cacheNames article, key #id)引入 Spring Cache 和 Redis第二次访问同样的 ID 时直接走缓存。讲的时候可以说明缓存更新策略文章编辑后就CacheEvict清掉旧缓存。第二是参数校验的完整性。除了评论内容非空校验文章标题长度限制、分页参数上限都可以加上防止暴力请求。第三是启动 banner 的小设计src/main/resources下放一个banner.txt项目启动时控制台显示个性化的 ASCII 图标这种细节在演示视频里容易被记住。对应的 Java 面试题也是同一套逻辑Spring Boot 的自动配置原理、MyBatis-Plus 和 MyBatis 的区别、拦截器和过滤器的执行顺序。把这些串起来个人博客系统这个选题就能从“能运行”变成“讲得清、问不倒”。想要给文章分类做缓存或者给评论加敏感词过滤都是在这套骨架上的增量开发数据结构不用变。最后的建议是把演示视频录一遍从启动到发布文章的完整流程凡是录视频时手抖的地方都是答辩前要重点再测的地方。本文还有配套的精品资源点击获取