Qings 记录思考与代码 2026-08-03T10:00:00Z http://localhost:9100/ HTMX 与局部刷新:让老页面焕发新生 http://localhost:9100/post/htmx-partial-refresh 2026-08-03T10:00:00Z HTMX 与局部刷新:让老页面焕发新生

现代 SPA 很强大,但博客这类内容站其实只需要一点点交互——HTMX 是恰到好处的答案。

核心思路

服务端返回 HTML 片段,HTMX 负责局部替换,不需要任何构建工具

<form hx-post="/comment" hx-target="#comments" hx-swap="afterbegin">
  <input name="content" required>
  <button>提交评论</button>
</form>

优点

  1. 零构建:一个 <script> 就引入,没有打包器、没有框架版本地狱。
  2. 服务端掌控:所有校验、防注入逻辑留在 Rust 侧。
  3. 渐进增强:没有 JS 也能完整使用,只是不丝滑。

配合 Alpine.js

局部交互(折叠、主题切换、表单状态)交给 Alpine,两者分工明确:

  • HTMX:网络层交互
  • Alpine:界面状态

这套组合让博客的前端代码总量控制在几百行以内。

]]>
七月读书记 http://localhost:9100/post/july-reading-notes 2026-07-30T10:00:00Z 七月读书记

七月读了四本书,记录一下。

《置身事内》

关于中国政府与经济发展的通识读物。地方政府行为是理解中国经济的一把钥匙,这本书讲得清楚、克制。

《纳瓦尔宝典》

  • 财富 = 专长 × 杠杆
  • 用判断力赚钱,而不是时间
  • 把自己产品化

金句摘抄:

如果在一个艰难的决定上意见不统一,你就应该选择短期内更痛苦的道路。

《编程珠玑》

经典中的经典,第 2 章的二分查找变体至今仍值得反复琢磨。算法题刷多了容易忘本,回到珠玑能找回初心。

《小王子》

“真正重要的东西,用眼睛是看不见的。” 成年后重读,每一页都是新的。

]]>
SQLite 中文全文搜索:FTS5 trigram 实战 http://localhost:9100/post/sqlite-fts5-trigram 2026-07-18T10:00:00Z SQLite 中文全文搜索:FTS5 trigram 实战

SQLite 的 FTS5 提供 trigram 分词器:按连续三字符切分,天然适配中文子串匹配,不依赖词表与外部进程。

建索引

CREATE VIRTUAL TABLE posts_fts USING fts5(
    title, content_md,
    content='posts', content_rowid='id',
    tokenize='trigram'
);

外部内容表(external content)不重复存储正文,通过触发器与 posts 同步。

踩坑记录

  1. 三字起检:trigram 至少需要三个字符,搜索单字或双字没有索引命中,需要回退 LIKE 查询。
  2. 排序用 bm25:bm25(posts_fts) 分数越小相关度越高(与旧版 MySQL 的 MATCH ... AGAINST 方向相反)。
  3. 查询关键词在拼入前先清洗,去除引号、星号等检索语法字符,避免报错。

代码实现

if can_use_fulltext(&keyword) {
    // FTS5 MATCH + bm25 排序
} else {
    // 单字/双字词:LIKE 兜底
}

中文搜索没有银弹,结合业务量选择合适的召回策略即可。

]]>
Axum 0.8 路由与中间件速览 http://localhost:9100/post/axum-0-8-routing-middleware 2026-07-02T10:00:00Z Axum 0.8 路由与中间件速览

Axum 0.8 的路径参数语法从 :param 改为 {param}。

基础路由

Router::new()
    .route("/", get(index))
    .route("/post/{slug}", get(post))
    .route("/admin", get(admin).layer(auth_layer))

提取器组合

State、Query、Path、Session 可以任意组合,按声明顺序提取。

async fn handler(
    State(state): State<AppState>,
    Query(q): Query<SearchQuery>,
    Path(slug): Path<String>,
) -> Result<Html<String>, AppError> {
    Ok(Html(format!("slug={slug}")))
}

中间件顺序

layer 是逆序叠加:后加的 layer 更靠近内核。静态资源、压缩、Session 各就各位。

  • 第一层:Trace
  • 第二层:Compression
  • 第三层:Session
  • 内核:路由
]]>
用 Rust 写一个博客:技术选型与架构 http://localhost:9100/post/rust-axum-blog 2026-06-20T10:00:00Z 用 Rust 写一个博客:技术选型与架构

个人博客是检验一个 Web 框架的好实验场:功能完整、边界清晰、可以随时推到重来。

为什么选 Rust

性能与内存安全是首要理由。单进程处理静态渲染的博客毫无压力,但编译期的类型检查能帮你少写一半的调试代码。

技术栈一览

层次选择
Web 框架Axum
数据库SQLite(FTS5 trigram 全文索引)
模板Askama(编译期类型安全)
前端Tailwind + Alpine + HTMX

核心设计

预渲染策略

文章保存时一次性渲染 Markdown 并存储 HTML,前台请求零渲染开销:

let rendered = markdown::render(&md);
let summary = markdown::summary(&md, 200);

TOC 自动生成

标题支持 {#锚点} 显式指定,也可以自动生成,目录树在详情页侧栏展示。

下一步

  • 全文搜索
  • 评论系统
  • 后台管理

欢迎在留言板与我交流。

]]>