把 5 万行 Rust 博客推倒重来,最后只剩 6 千行
三天前,我决定把写了很久的 Rust 博客推倒重来。
起因很简单:每次想加一个功能,都要先在自研动态路由的一万六千行代码里找半天入口。
老版本跑在 actix-web 4 上,积攒的东西不少:Tera 模板、手写 HTML 配一堆 vendored JS(darkreader、mathjax、katex、mermaid)、JWT + ECC p256 + AES-GCM 三层认证、GeoIP 访客统计、音乐播放器同步,还有那套支持热重载的动态路由引擎。全部加起来 51,795 行 Rust。
新版本写完,6,286 行。砍掉 88%。
为什么是 topcoat
tokio-rs 的 topcoat 是个年轻的全栈框架:
- 服务端
view!宏直接在 Rust 里写 HTML - 异步组件可以直接查数据库
- 路由树就是模块树,
src/app/post/slug.rs天然映射到/post/{slug} href!宏让链接跟着模块移动
最打动我的一点是:前后端的边界消失了。老版本那些 JSON API handler 全部删掉,组件里 app_context::<Db>(cx) 拿连接池直接查。
数据库换成 sqlx 双后端,SQLite 和 MySQL 一套代码,DATABASE_URL 切换。仓储层只写双方言兼容子集,真正的方言差异(自增 id 回读、事务类型)收敛在三处 match 分支里。
砍掉的东西
- 自研动态路由 → topcoat 模块路由原生覆盖
- Valkey 缓存层 → 个人博客量级用不上
- JWT/ECC 双认证 → 单管理员博客,argon2 口令 + session cookie 足够
- GeoIP → 隐私负担大于价值
- 音乐播放器 → 与博客无关
- 自定义 allocator → 遇到性能问题再说
文章表从 15 张收敛到 9 张。
踩坑记
重写不是没有代价的,三天里最有价值的产出其实是坑:
-
SELECT前缀丢失了五次。sqlx 0.9 只接受字面量 SQL,我用脚本批量改写时反复把SELECT吃掉。测试抓住一次、编译器抓住一次、剩下靠全库扫描。教训:脚本改代码必须带断言。 -
textarea 的 value 属性。
view!对 textarea 的:value绑定输出成value="…"属性,而浏览器对 textarea 忽略这个属性——编辑页正文框直接是空的。初始文本必须作为元素内容渲染。 -
error_boundary 会吞重定向。登录守卫的 307 被布局的错误边界接住渲染成 500 卡片,fallback 里要
return Err(error)让它继续传播。 -
immediate-abort 四连坑:预编译 core 策略不兼容要 build-std、
build-std-features已废弃、topcoat asset bundle不透传-Z、dev profile 上会跟 build script 的预编译 core 打架。最终语义:仅 release 生效,脚本在 cargo 之前完成 Cargo.toml 补丁。
带图文章的上传也重做了:保存时扫描 (/uploads/posts/5-rust-6/path) 引用,相对路径统一重写到 /uploads/posts/{slug}/,预览面板列出缺失清单逐张补传——本地 Typora 写完直接粘,缺哪张传哪张。
现在的样子
Vercel 风格:中性灰单色系、1px 细边框、纯黑按钮、Geist 字体内嵌(woff2 进仓库,零外部请求)。首页是视频流式大小卡片交叉布局,文章页右侧固定大纲栏点击跳转,评论树状可折叠,RSS 在页脚,公安备案带徽标链接。
全部代码 6,286 行(不含测试),30 个测试用例,cargo build --release 出包配 systemd 单元直接上线。
推倒重来不一定是因为旧代码烂,有时候只是因为它承载了太多"当时觉得需要"的东西。