从 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 链接
→ 重启服务
→ 健康检查
→ 失败则切回上一版本
```

上传目录和数据库不应该随着版本目录一起被删除,它们属于持久数据。应用文件可以只读,只有上传目录保留写权限。服务进程使用独立的低权限系统账号,也能显著降低意外命令的影响范围。

## 结语

一套好的个人博客架构,不是把所有流行组件都装进去,而是让每个组件承担明确职责,让关键数据可以恢复,让每次发布可以回滚,让权限判断始终留在服务器。

当这些基础能力稳定以后,界面、搜索、订阅和互动功能才有可靠的生长空间。博客也会从一次性的作品,慢慢变成一套能够陪伴很多年的个人系统。