三层架构
灵动桌面框架的 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)