CRUD 操作
灵动桌面框架的数据库 CRUD 操作封装在 Database 层(src-tauri/src/database/mod.rs),以内置的 app_config 表为例。
所有方法都先拿锁再操作;锁中毒时转成 AppError::Custom,不使用 unwrap():
let conn = self.conn.lock().map_err(|e| AppError::Custom(e.to_string()))?;
查询所有
impl Database {
/// 获取所有配置(排除已软删除的)
pub fn get_all_config(&self) -> Result<Vec<AppConfig>, AppError> {
let conn = self.conn.lock().map_err(|e| AppError::Custom(e.to_string()))?;
let mut stmt = conn.prepare(
"SELECT key, value FROM app_config WHERE deleted_at IS NULL ORDER BY key",
)?;
let configs = stmt
.query_map([], |row| {
Ok(AppConfig {
key: row.get(0)?,
value: row.get(1)?,
})
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(configs)
}
}
AppConfig 定义在 src-tauri/src/models/mod.rs,只有 key 和 value 两个字段。
查询单条
/// 获取单个配置(排除已软删除的)
pub fn get_config(&self, key: &str) -> Result<Option<String>, AppError> {
let conn = self.conn.lock().map_err(|e| AppError::Custom(e.to_string()))?;
let mut stmt = conn.prepare(
"SELECT value FROM app_config WHERE key = ?1 AND deleted_at IS NULL",
)?;
let result = stmt
.query_row([key], |row| row.get::<_, String>(0))
.ok();
Ok(result)
}
查不到时返回 None,由 Service 层转成 AppError::NotFound(前端收到错误码 NOT_FOUND)。
新增/更新(Upsert)
/// 设置配置(upsert)
pub fn set_config(&self, key: &str, value: &str) -> Result<(), AppError> {
let conn = self.conn.lock().map_err(|e| AppError::Custom(e.to_string()))?;
conn.execute(
"INSERT INTO app_config (key, value, updated_at)
VALUES (?1, ?2, datetime('now', 'localtime'))
ON CONFLICT(key) DO UPDATE SET
value = excluded.value,
updated_at = excluded.updated_at",
[key, value],
)?;
Ok(())
}
删除(软删除)
默认的删除是软删除:只写入 deleted_at,数据仍保留在库中。
/// 软删除配置(设置 deleted_at 而非物理删除)
pub fn delete_config(&self, key: &str) -> Result<bool, AppError> {
let conn = self.conn.lock().map_err(|e| AppError::Custom(e.to_string()))?;
let affected = conn.execute(
"UPDATE app_config SET deleted_at = datetime('now', 'localtime') WHERE key = ?1 AND deleted_at IS NULL",
[key],
)?;
Ok(affected > 0)
}
返回值表示是否真的删掉了一条;Service 层在返回 false 时报 NOT_FOUND。
此外 Database 层还提供了软删除三件套的另外两个方法,默认没有接成 Command,业务需要时再自行暴露:
| 方法 | SQL | 说明 |
|---|---|---|
hard_delete_config(key) | DELETE FROM app_config WHERE key = ?1 | 物理删除,不可恢复 |
restore_config(key) | UPDATE app_config SET deleted_at = NULL WHERE key = ?1 AND deleted_at IS NOT NULL | 恢复已软删除的配置 |
TIP
软删除后再对同一个 key 调用 set_config,会走 ON CONFLICT 分支更新 value,但不会清空 deleted_at——这条配置依然处于已删除状态。需要"删了再加回来"时,先调用 restore_config。
完整调用链
前端: configApi.delete('theme') // src/lib/api/config.ts
→ invoke("delete_config", { key })
→ Command: commands::config::delete_config(state, key)
→ Service: ConfigService::delete(&state.db, &key)
→ Database: delete_config(&key)
→ SQL: UPDATE app_config SET deleted_at = ... WHERE key = 'theme' AND deleted_at IS NULL
前端 configApi 提供 getAll / get / set / delete 四个方法,分别对应 get_all_config / get_config / set_config / delete_config 四个 Command。
参数绑定
安全
始终使用参数化查询(?1, ?2),禁止字符串拼接 SQL,防止 SQL 注入。
// ✅ 正确:参数绑定
conn.execute("DELETE FROM t WHERE key = ?1", [key])?;
// ❌ 错误:字符串拼接
conn.execute(&format!("DELETE FROM t WHERE key = '{}'", key), [])?;