Swordreforge's blog

把 5 万行 Rust 博客推倒重来,最后只剩 6 千行

阅读 114评论 2

三天前,我决定把写了很久的 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 张。

踩坑记

重写不是没有代价的,三天里最有价值的产出其实是坑:

  1. SELECT 前缀丢失了五次。sqlx 0.9 只接受字面量 SQL,我用脚本批量改写时反复把 SELECT 吃掉。测试抓住一次、编译器抓住一次、剩下靠全库扫描。教训:脚本改代码必须带断言。

  2. textarea 的 value 属性。view! 对 textarea 的 :value 绑定输出成 value="…" 属性,而浏览器对 textarea 忽略这个属性——编辑页正文框直接是空的。初始文本必须作为元素内容渲染。

  3. error_boundary 会吞重定向。登录守卫的 307 被布局的错误边界接住渲染成 500 卡片,fallback 里要 return Err(error) 让它继续传播。

  4. 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 单元直接上线。


推倒重来不一定是因为旧代码烂,有时候只是因为它承载了太多"当时觉得需要"的东西。

评论

▸admin2026-10-03 02:30回复

Keep it simple and silly.

▸匿名2026-10-08 11:42回复

nice!!!!!!

回复 @admin:取消回复