三层架构
灵动桌面框架的 Rust 后端(src-tauri/src/)以「Command → Service → Database」三层为主干,models、state、error 作为各层共用的支撑模块,托盘、远程网关等能力独立成模块,确保代码职责清晰、易于维护。
架构图
┌─────────────────────────────────────────┐
│ 前端 invoke('set_config', {...}) │
└──────────────────┬──────────────────────┘
↓
┌──────────────────────────────────────────┐
│ Layer 1: Commands(命令层) │
│ ● 接收 IPC 请求、注入 State │
│ ● 调用 Service 层、转换错误类型 │
└──────────────────┬───────────────────────┘
↓
┌──────────────────────────────────────────┐
│ Layer 2: Services(业务层) │
│ ● 参数校验、业务规则(如“不存在”判定) │
│ ● 调用 Database 层 │
└──────────────────┬───────────────────────┘
↓
┌──────────────────────────────────────────┐
│ Layer 3: Database(数据层) │
│ ● SQL 操作、Schema 迁移 │
└──────────────────────────────────────────┘
目录与职责
| 层级 / 模块 | 位置 | 职责 | 错误类型 |
|---|---|---|---|
| Command | src-tauri/src/commands/ |
IPC 入口(system.rs、config.rs),从 tauri::State 取出数据库后转发给 Service |
CommandError |
| Service | src-tauri/src/services/ |
业务逻辑(config.rs);另含主窗口显示器记忆 main_window_placement.rs |
AppError |
| Database | src-tauri/src/database/ |
mod.rs 封装 Database(Mutex<Connection>)与配置 DAO,schema.rs 负责版本迁移 |
AppError |
| Models | src-tauri/src/models/mod.rs |
前后端共用的数据结构:AppConfig、SystemInfo |
- |
| State | src-tauri/src/state.rs |
全局状态 AppState { db },在 setup 中 app.manage(...) 注册 |
- |
| Error | src-tauri/src/error.rs |
AppError(内部)与 CommandError(前端可见)及其转换 |
- |
| 入口 | src-tauri/src/lib.rs |
注册插件、初始化数据库 / 托盘 / 窗口、注册 Command、处理窗口事件 | - |
不属于三层主干、但同样在 lib.rs 中声明的模块:
| 模块 | 位置 | 说明 |
|---|---|---|
| 系统托盘 | src-tauri/src/tray.rs |
托盘图标与「显示窗口 / 退出」菜单 |
| 通用工具 | src-tauri/src/shared/ |
与业务无关的工具函数(如 time_utils) |
| 远程网关 | src-tauri/src/remote/ |
面向移动端伴侣的 HTTP 网关骨架,默认不启动 |
调用流程示例
前端: configApi.set('theme', 'dark') // src/lib/api/config.ts
→ invoke('set_config', { key, value })
→ Command 层: commands::config::set_config // 取 state.db,转发
→ Service 层: ConfigService::set // 校验 key 非空
→ Database 层: Database::set_config // INSERT ... ON CONFLICT(key) DO UPDATE
启动流程
lib.rs 的 run() 按以下顺序组装应用:
- 注册插件:
single-instance(必须第一个,禁止多开,二次启动时唤回已有窗口)、opener、store、log、updater、process setup:在app_data_dir下打开数据库(开发环境dev-app.db,生产环境app.db)并执行迁移 →app.manage(AppState::new(db))→ 初始化托盘 → 把主窗口摆回上次所在的显示器后再显示 → 开发模式给窗口标题加[DEV]后缀invoke_handler注册 Commandon_window_event:主窗口事件交给main_window_placement记录位置;点击关闭按钮时隐藏到托盘而不是退出
开发规范
- Command 层不写业务逻辑,只做 State 注入、转发与错误转换
- 参数校验与业务规则放在 Service 层(如
ConfigService::set拒绝空键) - Service 层不写 SQL,只调用 Database 方法
- Database 层不写业务逻辑,只做数据访问
- 禁止跨层调用(Command 直接调 Database)