简介这是一套面向Java全栈学习者与个人博客搭建者的VueSpringBoot博客系统源码适合已掌握Java基础、希望完整实践前后端分离与权限管理的开发者参考。项目后台管理页面基于Vue与ElementUI构建用户端采用Thymeleaf模板后端整合Spring Boot、ShiroJwt、MyBatisPlus、MySQL与Redis并引入WebSocket实现消息推送重构版本简化了数据库操作对用户端多数目录做了缓存支持管理员在线分配角色权限。压缩包共530个文件以js、html、java、css为主另有png、jpg等图片资源及sql建库脚本、yml配置、md说明文档整体约4.27MB目录分层清晰便于按模块阅读。已有1344人学习下载可帮助读者理解博客系统的权限设计、缓存策略与前后端协作方式并作为二次开发或课程设计的参考底稿。1. 从一份“能跑起来但不敢交”的博客项目说起很多同学做 Java 课程设计或毕设时第一反应是找个现成模板改改结果前端 Vue 和后端 SpringBoot 各跑各的数据库连不上、跨域报错、打包部署一团乱。这套基于 Vue SpringBoot2 MySQL 的个人博客项目解决的正是“前后端分离项目怎么从零搭到能演示、能答辩”的问题。它适合两类人一是需要交完整课程设计的学生二是想快速摸清一套前后端分离 CRUD 项目骨架的初级开发者。整套方案围绕文章管理、分类标签、评论、用户登录这几条主线展开技术栈不花哨但每一层都能讲清楚为什么这么选、怎么落地。2. 需求与技术选型为什么是 Vue SpringBoot2 这套组合2.1 先把功能边界划清楚个人博客听起来简单真动手时最容易失控的是功能蔓延。我一般会先把需求压到“一个学期能做完、答辩能讲透”的范围通常包含四块文章模块发布、编辑、删除、分页列表、详情查看、分类与标签模块一篇文章归一个分类、挂多个标签、评论模块游客留言、按文章聚合展示、用户模块后台登录、会话保持。这四块覆盖了增删改查、一对多与多对多关联、分页、登录鉴权答辩时每个点都有话讲。超出这个边界的比如全文检索、富文本图片上传、点赞收藏建议作为“可选扩展”写进文档而不是硬做。血泪经验是功能列得越满最后能演示的越少。把核心四条链路跑通、数据能持久化、页面能正常跳转比堆十个半成品模块强得多。2.2 技术选型的取舍逻辑前端选 Vue核心原因是组件化和双向绑定能显著减少 DOM 操作代码列表渲染、表单绑定这类博客高频场景写起来直接。后端选 SpringBoot2 而不是传统 SSM 手配 XML是因为自动装配把数据源、MVC、JSON 序列化这些样板配置都收掉了启动类一跑就能对外提供接口对课设这种周期紧的场景很友好。数据库用 MySQL关系型建模天然适合文章—分类—标签这种关联结构外键和联表查询都是现成能力。层次选型在本项目中的职责前端Vue 路由 请求库页面渲染、路由跳转、调用后端接口后端SpringBoot2提供 REST 接口、业务逻辑、参数校验持久层MyBatis 或 JPA对象与表映射、分页查询数据库MySQL存储文章、分类、标签、评论、用户构建Maven / npm依赖管理与打包这里有个反直觉的点不要一上来就上微服务、上 Redis 缓存。博客的数据量和并发量根本用不到加了只会让部署和答辩解释成本翻倍。单体应用 分层结构是这个体量下最负责的选择。2.3 前后端分离的接口约定分离项目翻车最多的地方不是代码是接口对不上。我的做法是先定接口契约再写代码统一返回结构{code, message, data}分页统一传page和size时间字段统一用字符串格式传输避免时区玄学。前端请求库配一个统一拦截器后端返回 code 非成功时集中提示这样页面里就不用每个请求都写一遍错误处理。// 前端统一请求封装baseURL 指向后端服务响应拦截统一处理业务码 const request axios.create({ baseURL: /api, // 开发环境由代理转发到后端端口 timeout: 5000 }); request.interceptors.response.use( res { const body res.data; if (body.code ! 200) { // 业务失败统一提示避免每个页面重复判断 console.error(接口返回异常:, body.message); return Promise.reject(body); } return body.data; // 页面直接拿到 data减少解包代码 }, err Promise.reject(err) );这段封装的价值在于把“解包”和“错误提示”收敛到一处。baseURL写成/api是为了开发阶段走前端代理、生产阶段走同域部署避免硬编码后端地址导致换环境就崩。timeout设 5 秒是防止后端卡死时页面一直转圈。参数上code用 200 表示业务成功、和 HTTP 状态码区分开是为了让“请求成功但业务失败”这种情况也能被前端识别。3. 总体架构与数据模型三层结构与五张核心表3.1 分层结构与模块职责后端按 Controller—Service—Mapper 三层组织职责边界要卡死Controller 只做参数接收和结果包装不写业务判断Service 承载业务规则和事务Mapper 只负责 SQL 与对象映射。这样答辩时被问到“业务逻辑在哪”你能一句话指到 Service 层而不是在一堆 Controller 里翻。前端 Vue 页面 │ HTTP / JSON Controller 层 → 参数校验、统一返回 │ Service 层 → 业务规则、事务控制 │ Mapper 层 → SQL、结果映射 │ MySQL 数据库模块划分上文章、分类、标签、评论、用户各成一个包包内再分 controller/service/mapper/entity。跨模块调用只走 Service不允许 Controller 直接调别的模块的 Mapper。这条规矩看着死板但能避免后期改一个查询牵连一片。3.2 五张核心表的设计数据模型是整个项目的骨架表设计错了后面全是补丁。核心五张表用户表、文章表、分类表、标签表、评论表外加一张文章—标签关联表处理多对多。-- 文章表category_id 体现一篇文章属于一个分类 CREATE TABLE article ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(120) NOT NULL, content TEXT NOT NULL, category_id BIGINT NOT NULL, author_id BIGINT NOT NULL, status TINYINT DEFAULT 1, -- 1 已发布 0 草稿 create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_category (category_id), INDEX idx_create_time (create_time) ); -- 文章标签关联表联合主键天然防重复 CREATE TABLE article_tag ( article_id BIGINT NOT NULL, tag_id BIGINT NOT NULL, PRIMARY KEY (article_id, tag_id) );status字段留了草稿态答辩演示时可以展示“存草稿再发布”的流程是个加分点。create_time和update_time交给数据库默认值维护比在代码里手动 set 更不容易漏。关联表用联合主键而不是自增 id是因为同一篇文章挂同一个标签本来就不该出现两条记录主键约束直接把重复挡在数据库层。索引加在category_id和create_time上对应“按分类筛选”和“按时间倒序分页”这两个最高频查询。3.3 分页查询的落地写法博客列表几乎必然分页这里给一个 Service 层的典型写法把分页参数和查询条件一起下推给持久层避免全表查出来再内存截取。// 文章分页查询page 从 1 开始size 限制上限防止恶意大分页 public PageResultArticleVO pageArticles(int page, int size, Long categoryId) { if (page 1) page 1; if (size 1 || size 50) size 10; // 上限保护 int offset (page - 1) * size; ListArticleVO list articleMapper.selectPage(offset, size, categoryId); long total articleMapper.countByCondition(categoryId); return new PageResult(list, total, page, size); }size设上限 50 是防止前端传个 10000 把数据库拖垮这是踩过坑之后加的。offset由页码换算配合 SQL 里的LIMIT #{offset}, #{size}使用。返回结构里带total前端分页组件才能算出总页数。注意countByCondition和selectPage的条件必须一致否则会出现“总数对不上列表”的诡异现象这个后面避坑章节还会提。4. 核心模块实现文章、评论与登录三条链路4.1 文章发布与详情链路文章模块是项目的主干发布链路要处理标题非空、内容非空、分类必须存在这几个校验点。校验放在 Controller 用注解做基础非空业务校验比如分类 id 是否真实存在放 Service。PostMapping(/articles) public ResultLong create(RequestBody Valid ArticleForm form) { // Valid 触发字段级校验失败由全局异常处理器统一返回 Long id articleService.createArticle(form); return Result.ok(id); } // Service 内先校验分类存在再落库并绑定标签 Transactional public Long createArticle(ArticleForm form) { if (categoryMapper.selectById(form.getCategoryId()) null) { throw new BizException(分类不存在); } Article article new Article(); article.setTitle(form.getTitle()); article.setContent(form.getContent()); article.setCategoryId(form.getCategoryId()); article.setAuthorId(currentUserId()); articleMapper.insert(article); // 标签绑定放在同一事务避免文章存了标签没存 if (form.getTagIds() ! null) { articleTagMapper.batchInsert(article.getId(), form.getTagIds()); } return article.getId(); }Transactional加在 Service 方法上而不是 Controller是因为事务边界应该由业务方法决定。标签绑定和文章插入放同一事务是为了防止“文章写进去了、标签没写进去”的半成品数据。currentUserId()从登录会话里取不信任前端传的 authorId这是权限相关的基本纪律。详情链路相对简单按 id 查文章、联查分类名和标签列表组装成 VO 返回。这里要注意 N1 查询问题如果先查文章列表再循环查每篇的标签文章一多就是几十次查询。正确做法是用IN批量查标签再在内存里按文章 id 分组这个细节答辩时讲出来很能体现功底。4.2 评论模块的聚合展示评论按文章聚合查询时按article_id过滤、按时间倒序。游客留言不需要登录但要限制内容长度和频率否则演示时被人刷一屏就尴尬了。public ListCommentVO listByArticle(Long articleId) { // 只查该文章下未删除的评论按时间倒序 return commentMapper.selectByArticle(articleId); }评论表设计上留一个status字段做软删除而不是物理删除。原因是答辩演示时如果误删了评论软删除还能在数据库里找回来物理删除就真没了——后悔药这种东西能留就留。内容长度在入库前截断或校验通常限制 500 字以内前端 textarea 也加maxlength双重保险。4.3 登录与会话保持后台管理需要登录最轻量的做法是登录成功后把用户信息存进服务端会话前端后续请求带上会话标识。不引入复杂鉴权框架是因为课设场景下够用且好解释。PostMapping(/login) public ResultUserVO login(RequestBody LoginForm form, HttpSession session) { User user userService.checkPassword(form.getUsername(), form.getPassword()); if (user null) { return Result.fail(用户名或密码错误); } session.setAttribute(LOGIN_USER, user.getId()); // 会话内只存 id return Result.ok(new UserVO(user.getNickname())); }会话里只存用户 id 而不是整个用户对象是为了避免用户信息变更后会话里还是旧数据。密码校验用加盐哈希比对绝不明文存库这一点在文档里要写清楚答辩被问到安全性时是标准答案。前端登录成功后路由跳转到管理页未登录访问管理接口时后端返回未授权码前端拦截器跳回登录页。5. 避坑与排查这类项目最容易翻车的五个点5.1 跨域请求被浏览器拦下现象是前端控制台报跨域错误、请求根本没到后端。原因通常是开发阶段前端跑在一个端口、后端跑在另一个端口浏览器同源策略直接拦截。解决办法有两条开发环境在前端配置代理把/api转发到后端端口或者后端加全局跨域配置。我一般两个都配代理解决开发、后端配置兜底生产同域部署。注意代理配置改完要重启前端服务热更新不一定生效这个坑我踩过不止一次。5.2 分页总数和列表对不上现象是翻到最后一页出现空列表或者总页数算多了。原因基本是count查询和列表查询的过滤条件不一致比如列表查了status1但 count 没加这个条件。解决办法是把公共查询条件抽成一个方法或一段 SQL 片段两处复用从结构上杜绝不一致。改完用“总数为 0 时列表必须为空”这条断言自测一遍。5.3 时间字段前后端格式错乱现象是页面显示的时间差 8 小时或者直接显示一长串数字。原因是数据库存的是时间类型、后端序列化成了时间戳、前端又按字符串解析。解决办法是后端统一配置日期序列化格式前端统一按同一格式展示全链路只认一种格式。时区问题属于玄学范畴最稳的做法是数据库连接串里显式指定时区别依赖默认值。5.4 打包部署后接口 404现象是本地跑得好好的打成 jar 部署后前端请求全 404。原因通常是前端打包后的静态资源和后端接口路径没对齐或者代理配置只在开发环境生效、生产环境没配。解决办法是生产环境把前端构建产物交给后端静态资源目录托管或者用同域反向代理保证接口路径和开发阶段一致。部署前一定用生产模式构建跑一遍别拿开发模式当验收。5.5 关联数据删除留下孤儿记录现象是删了文章关联表里还留着它的标签绑定删了分类文章还挂着已不存在的分类 id。原因是删除只操作了主表。解决办法是在 Service 的删除方法里用事务把关联数据一起清理或者数据库层配级联。我一般选前者因为级联删除不可控代码里显式删更清楚也方便在文档里说明数据一致性是怎么保证的。6. 验收与答辩怎么把一套博客项目讲成完整方案验收这件事很多人栽在“功能都能点但说不清为什么这么做”。我的建议是把验收拆成三层功能验收、数据验收、讲解验收。功能验收按四条主链路走一遍——登录、发文章、看列表详情、留言每条链路准备一个正常用例和一个异常用例比如空标题提交、未登录访问管理页现场演示异常处理比只演示成功路径更能体现严谨。数据验收直接打开数据库展示文章、标签关联、评论三张表的数据确实落库且关联正确这一步能堵住“页面是假数据”的质疑。讲解验收是答辩的核心。准备一张架构图就是第 3 章那张分层图按“请求从页面发出→Controller 接收→Service 处理→Mapper 落库”的顺序讲一遍完整链路再挑一个模块深入讲设计取舍比如为什么标签用关联表而不是存逗号分隔字符串。下面这张表可以当答辩自查清单用验收项检查方式合格标准登录鉴权未登录直接访问管理接口返回未授权前端跳登录页文章 CRUD增删改查各走一遍数据落库、列表实时刷新关联关系查数据库关联表文章与标签绑定正确、无孤儿数据分页翻到最后一页总数与列表一致、无空页错位异常处理提交空标题、超长评论有明确提示、不白屏不报错栈答辩时被问到“这个项目有什么不足”别慌主动说清楚边界当前是单体应用、没做缓存和全文检索、并发量按课设规模设计。承认边界比硬吹功能更能让评委信服。我一般还会准备一段“如果继续做会怎么改”的话术比如引入缓存优化首页列表、把标签查询做成批量接口这体现的是工程判断力而不是功能堆砌。从那以后我每次带这类项目都会在动手前先把接口契约和表结构定死再按链路一条条实现和验收绝不边写边改结构。希望这套拆解能帮到你少走几段弯路。本文还有配套的精品资源点击获取