砺匣|Forgebox

← 个人博客

自己写一个只给自己用的系统:选型、取舍与踩过的坑

2026-10-03 AstroVue3Spring Boot部署复盘

这个站上线了。

它由两个东西组成:一个给人看的静态门户,和一个只给自己用的系统。后者没有注册、没有多用户、没有团队协作、没有分享按钮——它的全部用户就是我一个人。

这篇文章记录它的选型和几个真实的坑。写下来一半是为了以后不重复犯错,一半是因为「只给一个人用的系统」这件事本身,比想象中更有意思。

为什么不直接用现成的工具

先回答一个必然会被问的问题:笔记有各种云文档,看板有各种 SaaS,为什么要自己写?

三个原因。

一是需求太窄。我要的不是一个通用工具,而是一个刚好贴合自己习惯的东西——每天打开只有一个入口、只有今天该做的事、数据都留在自己手里。通用工具为了照顾一万种用法,界面和模型都必须足够抽象,而我只需要其中一种。

二是数据主权。自己写的系统,数据在自己的数据库里,备份策略自己定,服务什么时候下线自己说了算。

三是这是练手项目里最划算的一种。因为只有一个用户,我可以砍掉一整套本来会吃掉大量时间的复杂度,把精力全部放在真正想练的地方——部署、数据建模、前后端配合、边界情况。

选型:能静态就静态

整体原则一句话:能用静态的就不要用动态的。每一处少掉的服务端渲染,都是一处少掉的故障点,在 2 核 2G 的机器上尤其是。

部分选型为什么是它
门户Astro 5(静态输出)内容型站点,构建期就把 HTML 生成好,线上只有 nginx 读文件,没有运行时、没有数据库连接、几乎压不垮
系统前端Vue 3 + Vite + Element Plus + ECharts后台型交互密集,需要组件库;纯静态构建后交给 nginx,不需要 SSR
后端Spring Boot 3.5 + Java 21 + Spring Data JPA自己最熟,生态成熟,一个 jar 跑起来就完事
数据库MySQL 8(本地开发用 H2 文件库)正式环境要一个真正的库;本地开发不想为了改一行代码先去开 Docker
部署Docker Compose:nginx + app + mysql一台机器三个容器,配置就是三段 YAML

这里有一个刻意的取舍:门户不接后端 API。它是纯静态的,文章在构建期就已经是 HTML。这带来两个直接好处——门户不会因为后端挂了而挂;静态站点的访问成本几乎为零,2 核 2G 完全扛得住。

代价是内容更新要重新构建部署一次。对一天更新不了一次的个人博客来说,这个代价完全值得。

单用户的取舍

既然只有一个用户,我列了一份「可以不做」的清单:

  • 不做注册、不做邮箱验证、不做找回密码
  • 不做数据隔离,不做权限体系
  • 不做操作日志,不做审计
  • 不做多端同步

登录方式就是一个账号加密码换一个 JWT,整套身份体系大概几十行代码。

但有一个地方我没省:所有业务表都预留了 user_id 字段。现在它永远是同一个值,看起来像废话。可一旦哪天想法变了,加字段的代价远高于当初留一个字段——表结构不用重建,查询条件加一行就够。省事的边界,应该划在「不堵死未来的可能性」上。

2 核 2G 是一道硬预算

2 核 2G 听起来很小,但把账算清楚之后其实挺宽裕。关键是要提前把内存分完,而不是让几个进程互相抢:

进程预算说明
MySQL~128M调小 innodb_buffer_pool_size,个人库的数据量根本用不到默认值
JVM512M 上限用 -Xmx512m 明确封顶,不然 JVM 会按机器内存的百分比去要,在容器里很容易被杀
nginx几十 M只做静态文件和反向代理,几乎不占
缓存不装 Redis单用户场景没有热点数据要缓存,会话状态就在 JWT 里

最容易翻车的是 JVM:容器里不显式设 -Xmx,JVM 会读宿主机的内存总量去算堆大小,然后再被 cgroup 限制住——结果是容器被杀掉,日志里只留一个 exit code 137。

另外,2G 的机器我加了一块 swap。不是为了让程序跑得更快,而是为了让它在内存尖峰的那一瞬间慢一点,而不是直接死掉。

一个域名,两种用途

门户和系统要放在同一个域名下。有两条路:子域名(system.example.com)或者路径分流(example.com/system/)。

我选了路径分流。理由是成本:路径分流只需要一张单域名证书、一个 nginx server 块;子域名要多一张证书、多一个 server 块,续期和配置都多一份要维护的东西。为了一个自己用的系统,这个复杂度不值得。

代价是前端构建必须正确设置 base path,静态资源路径也要小心隔离。这里踩了一个很典型的坑,见下面「坑 4」。

内容怎么从系统里流到门户上

这是整个项目里我觉得最值得记的一个设计问题。

我想要的体验是:在系统里写东西,标一个「公开」,它就变成门户上的博客文章。同一份内容,两处使用。

最直觉的做法是门户在运行时调后端 API 拿文章。我否掉了——那样门户就不再是静态站,它会依赖后端存活,还要处理加载态、错误态,以及搜索引擎抓不到正文的问题。

最后的做法是构建期导出:

  1. 系统里的笔记带一个可见性开关(私有 / 公开)
  2. 一个导出脚本登录后端,拉取全部公开笔记,按 slug 落盘成带 frontmatter 的 Markdown 文件,写进门户仓库
  3. 门户用 Content Collections 读这些文件,构建成列表页和详情页
  4. 上线门户

于是门户依然是纯静态的,文章还顺手获得了 git 历史——哪一篇什么时候改过,对一个文件跑 git log 就看得到。

导出脚本里有个小设计值得说:它维护一个 .generated-manifest.json,记录上次自己生成过哪些文件,清理时只删清单里的。这样手工写的文章和自动导出的文章可以共存在同一个目录,脚本不会误删手写的那些。

踩过的坑

下面四个都是真实发生过的,不是从文档里抄的。

坑 1:MySQL 的 TEXT 会静默截断

我有一批很长的 Markdown 文档,单篇最多四万字左右。一开始正文列用的是 TEXT。

TEXT 的上限是 65535 字节,不是字符。在 utf8mb4 下一个中文占 3 字节,所以 TEXT 实际只能存大约 2.18 万个中文字。

更麻烦的是,MySQL 在非严格模式下遇到超长内容不报错,只发一个 warning,然后把多出来的部分悄悄截掉。你会以为保存成功了,直到某天发现文章后半篇不见了。

正确做法是直接用 MEDIUMTEXT(上限 16MB)。这个类型必须在建表时就定对——已经上线并有真实数据的表要改字段类型,得写新的迁移文件,代价高得多。

教训:涉及人类书写内容的字段,默认往大里选,别按「现在够用」选。

坑 2:Astro 的内容缓存

我把一篇文章从「公开」改回「私有」,导出的 md 文件也删掉了,重新构建——那篇文章还在线上。

排查了一圈,问题是 Astro 5 的 Content Layer 会把内容缓存在 node_modules/.astro 里。我当时删了 dist 和 .astro,但没删 node_modules/.astro,缓存里的旧条目依然被用上。

修法是让构建脚本自己先清干净:

{
  "scripts": {
    "clean": "rm -rf .astro node_modules/.astro dist",
    "build": "npm run clean && astro build"
  }
}

教训:构建缓存的位置不止一处。当「源码删了但产物还在」时,先怀疑缓存,而且要确认自己清掉的是真正的那个目录。

坑 3:数据库迁移文件只增不改

我用 Flyway 管理表结构。有一次发现一个已经执行过的迁移文件里有个笔误,就顺手改了——结果服务起不来了,容器反复重启。

原因是 Flyway 会记录每个已执行迁移的校验和。文件内容一变,校验和不匹配,它就拒绝启动。这在设计上是正确的:迁移是历史,历史不该被改写。

正确做法是新增一个迁移文件去修正。只有在开发期数据完全不重要的时候,才可以直接重建数据库——而且必须在有真实数据之前做完这件事。

坑 4:nginx 的 ^~ 和那个「HTTP 200 的白屏」

前端构建产物放在 /system/ 路径下。部署完打开页面,白屏,但 HTTP 状态码是 200。

原因在 nginx 的 location 匹配顺序。我写了 location /system/ 来代理这个前端,但还有一条按文件扩展名匹配的正则 location(.js、.css 之类)。nginx 的规则是:前缀匹配遇到正则匹配时,正则优先。于是 /system/assets/index-xxxx.js 这样的请求被正则那条抢走了,返回了一段 HTML;浏览器拿 HTML 当 JS 解析,失败,白屏。

但状态码依然是 200,所以只看状态码会得出「服务正常」的错误结论。

修法是给前缀加上 ^~,明确告诉 nginx「这个前缀匹配成功后就不要再试正则了」:

location ^~ /system/ {
    # ...
}

教训:白屏先看 Content-Type,别只看状态码。curl -sI <资源地址> 一眼就知道返回的是不是 HTML。

接下来

这个站目前还是一期工程,接下来最想做的几件事:

  • 把录入的摩擦再降一点——手动录入是当前整条数据链路上最慢的一环
  • 补上备份的恢复演练。「有备份脚本」和「真的能恢复」是两回事,后者才算数
  • 把毕业设计的内容搬上「毕设展」

如果你也在动手写自己的东西,希望这几条坑能帮你少走一段路。

返回文章列表