第十三章 数据持久化:从文件到 SQLite#
"桌面应用最怕的不是没有云,而是离线时什么都做不了。"
Hive 的第一条产品原则是本地优先:用户打开应用、编辑笔记、搜索历史、查看最近消息,都不应该依赖网络。本章从文件存储讲起,最终落到 SQLite + Rust service 的持久化架构。
mindmap
root((数据持久化))
本地优先
离线可用
快速启动
用户信任
文件系统
Markdown 导出
附件
配置
SQLite
表结构
索引
migration
Hive 模型
notes
tags
sync queue
13.1 本地优先的分层模型#
flowchart TB
UI["Vue UI"] --> API["IPC API"]
API --> Service["Rust Service"]
Service --> Repo["Repository"]
Repo --> SQLite["SQLite"]
Repo --> FS["附件文件目录"]
Service --> Sync["同步队列"]
这套模型把“存储在哪里”和“业务怎样表达”分开。UI 不关心 SQLite,Service 不关心按钮,Repository 不关心窗口。
13.2 文件存储适合什么#
Markdown 笔记天然适合文件存储。它可读、可 diff、容易导入导出。配置、草稿、导出结果也可以放在应用数据目录。
use tauri::Manager;
#[tauri::command]
async fn save_markdown(
app: tauri::AppHandle,
name: String,
content: String,
) -> Result<(), String> {
let dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
std::fs::write(dir.join(name), content).map_err(|e| e.to_string())?;
Ok(())
}
文件存储的问题也明显:按标签查询、全文搜索、排序分页、冲突合并都会变复杂。Hive 可以把正文导出为 Markdown,但主数据模型仍应进入数据库。
13.3 SQLite 的位置#
SQLite 是桌面应用的默认好选择:零服务、事务可靠、查询能力强、备份容易。它不是“轻量玩具数据库”,而是非常成熟的嵌入式数据库。
CREATE TABLE notes (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
deleted_at TEXT
);
CREATE TABLE note_tags (
note_id TEXT NOT NULL,
tag TEXT NOT NULL,
PRIMARY KEY (note_id, tag)
);
13.4 使用 sqlx 建模#
sqlx 提供异步数据库访问、连接池、查询映射。Tauri Core 中可以把连接池作为 managed state。
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};
pub struct AppState {
pub db: SqlitePool,
}
pub async fn open_db(path: &str) -> Result<SqlitePool, sqlx::Error> {
SqlitePoolOptions::new()
.max_connections(5)
.connect(path)
.await
}
创建笔记:
#[derive(serde::Serialize, sqlx::FromRow)]
pub struct Note {
pub id: String,
pub title: String,
pub content: String,
pub created_at: String,
pub updated_at: String,
}
pub async fn insert_note(db: &SqlitePool, note: &Note) -> Result<(), sqlx::Error> {
sqlx::query(
"INSERT INTO notes (id, title, content, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)",
)
.bind(¬e.id)
.bind(¬e.title)
.bind(¬e.content)
.bind(¬e.created_at)
.bind(¬e.updated_at)
.execute(db)
.await?;
Ok(())
}
13.5 Migration:让数据结构可演进#
桌面应用一旦发布,用户本地就有各种历史版本数据库。迁移脚本必须随应用一起发布。
src-tauri/migrations/
├── 0001_create_notes.sql
├── 0002_add_sync_state.sql
└── 0003_create_attachments.sql
sqlx::migrate!("./migrations").run(&pool).await?;
迁移原则:
只向前迁移,不指望用户手动回滚。
大表变更分批执行,避免启动时长时间卡住。
每次迁移都要能重复验证:旧库升级、新库初始化都要测试。
13.6 Hive 的数据模型#
erDiagram
NOTES ||--o{ NOTE_TAGS : has
NOTES ||--o{ ATTACHMENTS : owns
NOTES ||--o{ SYNC_ITEMS : records
NOTES {
text id PK
text title
text content
text created_at
text updated_at
text deleted_at
}
NOTE_TAGS {
text note_id FK
text tag
}
ATTACHMENTS {
text id PK
text note_id FK
text path
text mime
}
SYNC_ITEMS {
text id PK
text entity_id
text operation
text status
}
Hive 使用软删除支持撤销与同步冲突处理。附件只在数据库保存元数据,真实文件放在 app data 目录,避免数据库膨胀。
13.7 小结#
文件适合人类可读的导入导出,SQLite 适合应用内部的查询和一致性。Hive 的持久化层采用 SQLite 保存结构化数据,文件系统保存附件,并通过 migration 支撑长期升级。
下一章我们让 Hive 连接云端 API,并设计离线队列。