SpringBoot+Vue3+MyBatis个人博客系统开发实战:从架构到部署
1. 项目整体设计思路与方案选型1.1 技术栈选择背后的真实考量SpringBoot Vue3 MyBatis 这套组合是当下个人博客系统最稳的搭配之一。从后端到前端每一层的选型都有明确的理由不是看着流行就无脑跟风。后端用 SpringBoot核心在于它把 Spring 家族的配置地狱彻底收拾干净了。以前搭 SSM 项目光是 XML 配置文件就能写几十行现在起步一个 starter 依赖加上自动配置几分钟就能跑起来一个 Web 服务。个人博客这种体量的项目不需要分布式那一套重型中间件SpringBoot 的轻量特性刚好匹配。持久层选 MyBatis考虑的是灵活性和可控性。个人博客的查询往往伴随着动态条件——按分类筛选、按标签匹配、按关键词搜索这些场景用 MyBatis 动态 SQL 处理起来相当顺手。更重要的是SQL 写在自己手里遇到慢查询可以随时优化不像某些 ORM 框架那样包了一层又一层出了问题无从下手。前端用 Vue3最大的收益是组合式 API 带来的逻辑复用能力。博客系统的管理后台和前台展示逻辑差异很大使用组合式函数可以把标签获取、文章整理、用户状态这些跨模块逻辑抽出来复用代码量比 Options API 少不少。配合 Vite 开发服务器热更新速度大概是 Webpack 时代的十几倍联调体验完全是两个档次。数据库选 MySQL 没什么悬念互联网仓储层的默认答案。个人博客的读写比大概在 10:1 甚至更高MySQL 在查询密集型场景下做得足够好加上 InnoDB 对事务的支持文章评论、用户授权这些需要一致性的业务也能兜住。1.2 前后端分离架构给开发流程带来的变化前后端分离意味着前端页面和后端接口可以并行开发不必相互等待。实际开发中我习惯先约定好接口文档把请求路径、参数、响应结构敲定然后前端同事直接用 Mock 数据跑页面后端专心写接口逻辑最后联调一次搞定。这种模式对个人开发者同样有价值。我自己在做这套博客系统时先完成后端所有接口和数据库表设计再着手写 Vue3 页面。因为界面细节往往会在开发过程中不断调整后端接口是稳定的契约前端随便折腾都不影响已完成的接口逻辑开发节奏非常清晰。接口交互统一走 JSON前端通过 Axios 封装请求后端用拦截器处理跨域和鉴权。这种交互方式让系统边界非常干净未来如果想给朋友开放数据接口或者接一个小程序客户端复用成本极低。1.3 数据库表结构规划个人博客系统的数据规模不大但表之间的关系需要设计得清爽。我的方案是五张核心表用户表、文章表、分类表、标签表、文章标签关联表。用户表字段包括主键、用户名、密码、邮箱、头像地址、角色标识和创建时间。密码必须用加盐哈希处理我会用 BCrypt 算法这个后面在鉴权模块会细说。文章表是核心表字段涵盖主键、标题、摘要、正文内容、封面图地址、浏览量、点赞数、评论数、所属分类 ID、创建时间和更新时间。这里有个容易被忽略的点正文内容类型我用的是 LONGTEXT而不是 VARCHAR一篇图文并茂的 Markdown 长文轻松超过 65535 字节这个坑我踩过。分类表和标签表结构类似都是主键加名称区别在于分类是一级结构标签是多对多关系所以额外增加了一张关联表。为什么不给文章表直接加标签字段因为一个标签可能对应几十篇文章反查需求很常见多对多必须拆表。索引设计上文章表对创建时间建了普通索引排序分页走索引不会产生文件排序。分类表和标签表给名称加唯一索引防止重复数据在生产环境上悄悄出现。2. 核心模块拆解与关键实现2.1 后端工程搭建与统一响应封装工程结构采用标准的包分层controller 负责接口路由service 处理业务逻辑mapper 做数据访问entity 映射数据表dto 承载前端交互数据。建议团队开发时在 controller 层引入 VO 概念把给前端展示的字段和数据库字段做一次彻底的切割避免把内部字段意外暴露出去。统一响应封装是每次写接口必须考虑清楚的第一步。前端收到响应时需要知道一个请求是成功还是失败数据是什么如果失败原因是什么。我的封装结构是一个通用响应对象包含状态码、消息和数据三个字段。状态码用 200 表示成功400 表示参数错误401 表示未登录500 表示服务端异常。前端拿到非 200 的响应时可以直接用统一的弹窗组件提示不用每个页面自己去判断。代码层面用泛型实现这个统一响应类提供静态方法直接构造成功或失败的结果。这样一来 controller 层的代码会非常干净每个接口就差一步直接返回业务数据剩下的壳子都是模板化的。2.2 JWT 鉴权与登录态管理个人博客系统的鉴权用 JWT 是最省心的方案。服务端在用户登录成功后签发一个加密签名的令牌客户端后续请求带上这个令牌服务端解析验签后就能识别用户身份。JWT 由三部分组成头部、负载、签名。头部声明算法负载存用户信息签名用密钥加密。注意不要往负载里塞敏感数据比如密码因为 JWT 的负载只是 Base64 编码扫描工具或浏览器插件可以直接解码泄露风险很大。一般只放用户 ID、角色和过期时间。后端集成 JWT 的流程是写一个拦截器或过滤器拦截需要登录的接口路径从请求头的 Authorization 字段取出令牌字符串签名校验通过后把用户 ID 放入线程上下文后续业务逻辑直接调用查询方法获取当前用户信息。密码校验是配套环节用户表存的是 BCrypt 加密后的哈希值。BCrypt 自动加盐相同密码每次加密结果都不相同自带彩虹表防御能力。工具类里一行调用即可完成校验不用自己写加密算法。2.3 文章模块的实现细节文章模块是整个博客系统的核心。后端接口我拆成两块管理员接口和管理端接口各司其职。管理员接口需要登录和角色权限校验负责文章的增删改浏览接口对游客开放负责分页查询和详情查看。创建文章时标题和内容是常规字段但有几个细节需要特别处理。第一是文章摘要前端编辑器如果没单独填写摘要后端要自动从正文中截取前 150 个字符作为降级方案避免列表页卡片出现空白。第二是 Markdown 代码块里的特殊字符直接把正文内容插入数据库没问题但如果有富文本编辑需求要考虑 XSS 攻击前端渲染时应把内容当作纯文本处理或者依赖专门的 Markdown 渲染库做安全过滤。分页查询用 MyBatis Plus 自带的分页插件一条语句搞定分页计算。列表查询返回文章概述信息不加载正文内容数据库的 SELECT 字段列表里明确排除正文列这样在大数据量下能有效降低数据库 IO 压力。详情页打开时再按 ID 查完整内容同时把浏览量加一。新增文章时还有一个关联操作保存文章标签。事务必须保证文章主表数据插入成功的同时关联标签记录一并写入。如果先插入文章成功再插入关联表失败就会产生脏数据让文章带着不存在的标签 ID。所以这两个操作必须放在同一个事务里加上 Transactional 注解兜底。2.4 Vue3 前端架构与页面实现Vue3 工程用 Vite 构建目录结构按照功能划分api 目录集中管理所有后端接口调用views 目录放页面组件components 目录放通用组件router 目录配置路由表store 目录用 Pinia 管理全局状态。接口封装是前端工程化的第一课。Axios 实例统一创建设置请求超时时间添加请求拦截器自动把 JWT 令牌塞进请求头。响应拦截器判断状态码非 200 的统一弹出错误提示401 的自动清理登录状态并跳转登录页。这样每个页面编写业务代码时完全不用关心鉴权和错误处理逻辑只管在成功回调中处理数据即可。博客前台页面设计上我分了几个核心视图首页文章卡片流、文章详情页、分类归档页、标签页和关于页。首页打开时调用文章分页接口使用无限滚动或者分页按钮加载数据。我这里倾向于分页按钮因为 SEO 的考虑上首页的页码链接更容易被搜索引擎收录无限滚动对爬虫不友好。文章详情页值得单独说一说。输入 URL 里带文章 ID然后发起接口请求。页面顶部渲染标题、作者头像、发布时间和分类信息接着是大面积的 Markdown 正文渲染区域。正文由前端用 Markdown 解析库处理自定义一些代码高亮主题。评论区放在正文下面用户登录后可以发表评论游客则看到登录引导。管理后台使用 Vue Router 嵌套路由父路由是后台布局组件子路由切换文章列表、写文章和系统设置等页面。后台所有路由统一通过路由守卫拦截没有登录状态的用户无法进入角色不是管理员的直接打回首页。2.5 Pinia 状态管理与权限控制全局状态放在 Pinia 中管理最核心的 store 是用户状态。用户登录成功后服务端返回 JWT 令牌和基础用户信息这两个数据存进 store 并同步写入 localStorage。页面刷新后store 初始化时从 localStorage 读取数据恢复登录态保证用户不会因为刷新就被踢下线。权限控制通常通过两个层次实现。路由守卫层检查目标路由的 meta 信息如果标记了需要管理员权限而当前用户角色不符直接跳转 404。页面内部层对修改和删除按钮做 v-if 判断非管理员用户看不到这些操作入口。双保险设计确保即使有人通过浏览器调试工具强行改动接口参数后端也能拦截住。3. 实操过程与核心环节实现3.1 开发环境准备清单开始动手前先检查一下本地环境是否齐备。JDK 要求 8 以上我这边用的是 11 长期支持版。Maven 3.6 以上负责依赖管理。Node.js 推荐 16 以上版本Vite 4 对 Node 版本有硬性要求。IDE 方面后端用 IntelliJ 系列前端用 VS Code插件装好 Vue 扩展和 ESLint 就够了。MySQL 推荐使用 5.7 以上版本或者 MySQL 8.x注意连接驱动要匹配版本。MySQL 8 的默认认证插件是 caching_sha2_passwordSpringBoot 项目里需要用对应的驱动类否则会报无法连接的错误。这一点是新手常见卡壳点。初始化数据库时建议顺手建一个专用账号账号权限只开放给当前数据库不要把 root 密码直接写在项目配置里安全习惯要早养成。3.2 后端项目搭建保姆级步骤第一步用 Maven 骨架或直接在构建工具里创建 SpringBoot 工程依赖包里勾选 Spring Web、MyBatis 场景启动器、MySQL 驱动、Lombok 和 Spring Security不过 Spring Security 在这里主要用于密码加密工具完整拦截框架可以简化处理。第二步配置数据源。在配置文件中填写数据库地址、账号和密码。注意时区参数要带上否则连接 MySQL 8 会报时区错误。完整配置示例如下spring: datasource: url: jdbc:mysql://localhost:3306/blog_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: blog_user password: your_secure_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.blog.entity第三步编写实体类和 Mapper 接口。实体类字段和数据库字段一一对应使用 Lombok 的注解生成 getter/setter。Mapper 接口写方法名和参数SQL 语句放在同名的 XML 文件中命名空间指向接口全限定名这是 MyBatis 的运行契约。第四步实现 Service 层。这里的原则是接口和实现类分离业务逻辑写在实现类中事务注解加在具体方法上。比如修改文章时先更新文章表再删除旧的标签关联重新插入新关联这两个步骤必须在一个事务内。第五步Controller 层定义 RESTful 接口。路径设计遵循资源导向原则比如文章相关的接口GET /api/article/list # 分页查询文章列表 GET /api/article/{id} # 查询文章详情 POST /api/article # 新增文章管理员 PUT /api/article/{id} # 修改文章管理员 DELETE /api/article/{id} # 删除文章管理员3.3 Vue3 前端工程创建与实现创建 Vue3 工程用 Vite 官方脚手架一条命令完成初始化。然后按需安装依赖Axios 负责 HTTP 请求Vue Router 处理路由Pinia 做状态管理Markdown 解析选轻量库代码高亮默认使用 highlight.js。路由表配置采用懒加载方式按需导入页面组件。前台路由和后台路由分开写后台部分使用嵌套结构子路由挂在后台布局下便于统一展示侧边栏和顶部导航。每一条后台路由增加 meta 字段写入角色要求。Request 工具类封装是整个前端项目的骨架。我习惯在所有页面开发之前先把 Axios 实例、拦截器、统一类型定义做完。之后写页面时每建一个接口请求函数就是复用一行代码。请求头必须加上 Content-Type 为 application/json和 SpringBoot 接收 RequestBody 参数配对否则后端解析不到参数。页面开发顺序上我建议先把路由骨架搭好再依次填充页面。首页和文章详情页是核心展示页面优先实现。管理后台先实现登录页和后台主框架再逐步补充文章管理界面。联调时前端开发服务器默认端口是 5173后端接口跑在 8080跨域让服务端统一放开开发环境的代理配置方便但不跨域配置会让前端每次请求都被浏览器拦截。3.4 前后端联调跨域配置联调阶段面对的第一个坑就是跨域。浏览器同源策略规定不同端口之间的请求默认被拦截。前端页面的源是 http://localhost:5173后端接口的源是 http://localhost:8080协议和域名相同但端口不同所以存在跨域问题。开发环境推荐用 Vite 的代理功能解决跨域。在配置文件里为 /api 前缀的请求设置代理把目标指向后端地址。这样前端代码里请求的地址是相对路径 /api/login浏览器看到的是同源请求自然不存在跨域问题。生产环境的跨域交给 Nginx 处理。Nginx 配置反向代理把前端页面和后端接口的请求分发到对应服务。同时在后端保留 CORS 配置兜底避免意外情况直接弹浏览器跨域错误。注意生产环境和开发环境的接口地址不同推荐在代码中通过环境变量区分。4. 常见问题排查与避坑记录4.1 Mapper 找不到 SQL 语句的排查方法项目中高频率出现的经典报错是提示 Invalid bound statement。原因基本都指向 SQL 映射文件和 Mapper 接口没有正确建立联系。排查顺序建议从下往上走先确认 resources 目录下 mapper 文件夹里的 XML 文件存在且命名正确然后检查配置文件里的 mapper-locations 路径是否匹配再看 XML 文件的 namespace 是否写成了接口全限定名最后核对 SQL 的方法 ID 是否和接口方法名一致。这几个环节任何一处不对MyBatis 都找不到对应的映射语句。我第一次遇到这个报错是路径漏写了一个星号配置写成了 classpath:mapper/*.xml实际目录是 mapper 下的二级子目录调整配置后问题解决。4.2 时间字段精度与格式问题后端把 LocalDateTime 序列化传给前端时默认输出格式是 ISO 标准的带 T 字符串比如 2025-01-15T10:30:00。前端拿到这个字符串直接渲染会显示类似 2025-01-15T10:30 的样式很不友好。解决办法是在统一响应里引入格式化依赖对时间类型做序列化格式化统一输出成 yyyy-MM-dd HH:mm:ss。另外 MySQL 的 datetime 类型默认精度是秒如果业务需要毫秒级时间戳表字段要用 datetime(3)。4.3 图片上传与虚拟路径映射博客系统的封面图通常需要上传到服务器本地磁盘。文件保存路径放在项目 resources 目录里是一个常见错误因为项目重新打包部署时会让 resources 目录被清空或者覆盖图片丢失得不偿失。推荐把上传文件保存到服务器固定目录比如 /data/blog/images和项目代码彻底隔离。然后配置虚拟路径映射让 /upload/** 前缀的 URL 直接映射到这个磁盘目录。后端代码中图片访问地址完整保存前端展示时拼接域名和映射前缀。4.4 部署踩坑打包与运行环境前后端分离项目部署时有两个环境容易出错。后端用 Maven 打包生成 Jar 包运行习惯是直接调用系统脚本执行启动可以加参数指定端口。MySQL 连接信息如果用了环境变量记得在服务器上提前导出。前端打包时Vite 默认构建目标目录是 dist。打包完成后把静态文件交给 Nginx 托管Nginx 配置里要处理前端路由的刷新问题。Vue Router 默认使用 History 模式URL 中不包含哈希标记刷新页面时 Nginx 需要把找不到的资源路径全部指向入口 HTML否则会出现静态资源白屏的问题。4.5 性能优化与缓存策略个人博客系统的性能瓶颈往往在数据库端。热点文章被反复点击浏览时每次打开详情页都会实时查询数据库其实完全没必要。用 Redis 或者简单的本地缓存把文章详情页缓存几分钟流量上来后数据库压力会下降一个数量级。我的做法是引入 Spring Cache 抽象给文章详情接口加缓存注解设置 5 分钟过期时间。前端静态资源缓存交给 Nginx 配置dist 目录下的带哈希文件名设置长缓存入口 HTML 设置 no-cache这样浏览器既能缓存静态文件加速再次访问部署新版本时也能第一时间拉取最新入口。5. 个人体验与扩展方向5.1 这套系统还能怎么延伸博客系统的技术边界可以随意扩展。SEO 优化方面可以做服务端渲染改造解决前端页面在搜索引擎收录上的天然劣势。目前很多团队已经用 Nuxt 无缝衔接 Vue3 技术栈改造起来比想象中的小。评论区增加多级嵌套功能需要引入无限极树的递归组装算法对有歧义的分页查询方式也要谨慎处理。搜索功能目前是基于 SQL 的 LIKE 模糊匹配数据量过万之后查询速度会明显下滑届时接入全文检索组件是更优的方案。5.2 我的合作感言用这套技术栈完成一个完整的博客系统整个过程让我对前后端分离的协作模式有了更透彻的理解。接口先行、数据驱动、职责切割这些事情听着简单真正动手时才能体会到设计决策对开发效率的直接影响。对于想进阶的开发者强烈建议在现有基础上加一些个性化模块比如文章归档日历、阅读时长统计、访客地图技术学习就是在不断动手的过程中沉淀下来的。