从 Demo 到长期运行:Vue 3 + Spring Boot 个人博客架构实践
个人博客真正困难的不是搭出页面,而是让发布、数据、图片、权限和恢复长期可靠。本文拆解一套可维护的全栈架构。
很多人第一次做个人博客,会把注意力放在首页配色、卡片阴影和动画上。真正开始长期维护以后才会发现,决定一个博客能否稳定运行的,往往是更朴素的问题:文章如何保存,发布失败如何回滚,图片放在哪里,搜索怎样不拖慢数据库,账号丢失以后能不能找回。 这篇文章不追求“十分钟搭完一个 Demo”,而是从长期维护的角度,拆解一套 Vue 3、Spring Boot、MySQL 与 Nginx 组成的个人博客。重点不是某个框架的语法,而是各层职责如何划分,以及为什么要这样划分。 ## 一、先画清楚系统边界 一个可维护的博客至少包含四个边界:浏览器负责交互,后端负责业务规则,数据库负责持久化,网关负责公网入口。 ```text 浏览器 ├─ 读取文章、搜索、评论、登录 ↓ HTTPS Nginx ├─ 静态资源与缓存 ├─ TLS、安全响应头、频率限制 └─ /api 请求转发 ↓ Spring Boot ├─ 权限、文章状态、审核、订阅 ├─ 输入校验与审计 └─ 数据访问 ↓ MySQL + 上传目录 ``` 边界清晰的直接好处,是问题发生时知道去哪里找。页面白屏优先看前端资源,接口返回 500 看后端日志,数据不一致检查事务和迁移,证书或跨域问题则检查 Nginx。一个请求不再是“黑盒里转了一圈”,而是经过可解释的路径。 ## 二、前端只负责表达状态 Vue 组件很容易越写越重:既请求数据,又判断权限,还处理持久化。更稳妥的做法是让组件表达三类状态:加载中、失败、成功。网络细节放进 API 模块,登录状态放进统一 store,路由守卫只负责页面级访问控制。 例如文章列表不应该一次取回所有数据再在浏览器分页。请求中明确携带 `page`、`size`、`category`、`tag` 和 `q`,服务端返回总数、总页数以及是否为最后一页。这样数据从十篇增长到一万篇,前端结构仍然不需要推倒重来。 还要避免把前端权限判断当成安全措施。隐藏“删除文章”按钮只是体验优化,真正的权限必须在后端再次验证。任何人都可以绕过界面直接构造 HTTP 请求,所以服务器必须是最终裁判。 ## 三、后端围绕业务动作设计 博客后端不只是文章表的增删改查。一个成熟的发布动作通常包含: 1. 校验标题、摘要、正文和发布时间; 2. 生成且检查唯一 slug; 3. 规范化分类与标签; 4. 记录历史版本; 5. 根据草稿、定时、发布状态更新时间; 6. 写入审计记录; 7. 提交事务后再触发订阅通知。 这里最容易犯的错误,是在数据库事务还没提交时就发邮件。如果后续写库失败,读者可能收到一封指向不存在文章的通知。更合理的顺序是先让核心数据可靠落库,再通过事务提交后的事件发送通知。邮件失败可以重试,但不能反过来破坏文章发布。 文章删除也适合分成“移入回收站”和“永久删除”。前者只是改变状态,保留评论和历史;后者必须显式确认,并按外键关系清理关联数据。把不可逆操作放到流程最后,是系统设计中很实用的一条原则。 ## 四、数据库迁移要和代码一起走 只在生产库里手动执行 SQL,短期看很快,时间一长就没人知道数据库为什么变成现在这样。使用 Flyway 一类迁移工具,可以让每次结构变化都有编号、有文件、可复现。 一条迁移的理想特征是:目标单一、执行时间可估计、旧数据有兼容方案。例如给已有账号增加隐私同意时间时,不能简单加一个非空字段让启动直接失败;应该先允许为空或为旧记录填入合理标记,再由新注册流程写入真实时间。 索引也不是越多越好。文章公开列表适合按状态和发布时间索引,标签关系适合唯一联合索引,全文搜索则交给专门的全文索引。每增加一个索引,读查询可能更快,但写入和磁盘成本也会增加,应该由真实查询决定。 ## 五、Nginx 不只是反向代理 公网入口承担着后端不擅长处理的一组工作:TLS、静态资源缓存、请求体大小、安全响应头和基础限流。 静态资源文件名带内容哈希后,可以设置一年缓存;HTML 入口则不能长期缓存,否则发布新版本后用户仍可能加载旧入口。登录、注册、找回密码和评论接口需要分别限流,避免一个宽泛规则误伤所有 API。 对于单页应用,还要区分两类路由。后台、登录等页面可以回退到 `index.html`;文章详情最好由后端返回带有标题、摘要、canonical 和结构化数据的 HTML,再由 Vue 接管交互。这样搜索引擎和社交平台在不执行 JavaScript 时,也能读到文章信息。 ## 六、图片上传必须当作不可信输入 文件扩展名叫 `.jpg`,不代表内容一定是 JPEG。上传流程至少要检查声明类型、文件魔数、文件大小和真实尺寸。尺寸应该在完整解码前读取,否则一张压缩体积很小、解码尺寸极大的图片可能消耗大量内存。 对普通 JPEG 和 PNG,可以在上传后移除元数据、按最大边缩放并重新编码。GIF 和 WebP 若暂时没有可靠编码器,也至少要完成头部校验和尺寸限制。文件名应由服务器生成随机值,不能信任用户提供的原始文件名。 ## 七、可观测性从最小闭环开始 个人博客不需要一开始就搭建庞大的监控平台,但必须有最小闭环:健康检查、结构化日志、定时备份、失败告警和恢复演练。 健康检查不应只判断 Java 进程是否存在,还要实际请求后端健康接口和公开首页。备份不能只备数据库,因为正文里的上传图片同样是内容的一部分。告警也不能只报故障,恢复时应再发一条消息,否则维护者不知道问题是否已经结束。 阅读趋势可以按天聚合,而不是保存每个访客的详细轨迹。对个人博客来说,“今天有多少次有效阅读、哪些文章近期更受欢迎”通常已经足够,同时也减少了隐私负担。 ## 八、发布流程要允许失败 可靠发布的关键不是保证永不失败,而是失败以后可以快速回到上一版本。一个实用流程包括: ```text 构建前端与后端 → 运行单元测试 → 创建不可变版本目录 → 原子切换 current 链接 → 重启服务 → 健康检查 → 失败则切回上一版本 ``` 上传目录和数据库不应该随着版本目录一起被删除,它们属于持久数据。应用文件可以只读,只有上传目录保留写权限。服务进程使用独立的低权限系统账号,也能显著降低意外命令的影响范围。 ## 结语 一套好的个人博客架构,不是把所有流行组件都装进去,而是让每个组件承担明确职责,让关键数据可以恢复,让每次发布可以回滚,让权限判断始终留在服务器。 当这些基础能力稳定以后,界面、搜索、订阅和互动功能才有可靠的生长空间。博客也会从一次性的作品,慢慢变成一套能够陪伴很多年的个人系统。