Tauri 2 分层架构设计
Tauri 2 分层架构设计
学习目标
- 理解 Tauri 2 应用的四层架构设计
- 掌握 130+ Tauri 命令的组织方式与模块拆分策略
- 了解 AppState 全局状态管理与插件集成模式
- 识别 Tauri 应用的生命周期管理关键点
前置知识
需要了解 Rust 的基本概念(struct、trait、async/await)和 Tauri 框架的定位。
项目实践
四层架构
CC Switch 的 Rust 后端严格遵循四层分离:
命令层(src-tauri/src/commands/)
- 职责:接收来自前端的 Tauri invoke 调用,参数验证,调用服务层,返回结果
- 文件组织:按功能域拆分(
provider.rs、proxy.rs、mcp.rs、skill.rs等 30+ 文件) - 注册方式:在
lib.rs的invoke_handler中统一注册(130+ 命令)
.invoke_handler(tauri::generate_handler![ commands::get_providers, commands::add_provider, commands::switch_provider, // ... 130+ commands])设计决策:命令层只做参数接收和结果转发,不包含业务逻辑。这使得服务层可以被命令层和测试代码复用。
服务层(src-tauri/src/services/)
- 职责:业务逻辑实现(Provider CRUD、代理启动/停止、MCP 同步等)
- 结构:每个服务是一个
Clone的 struct,内部持有Arc<Database>或其他依赖 - 并发:通过
Arc<Database>共享数据库连接,避免多个服务实例创建多个连接
pub struct ProxyService { db: Arc<Database>, server: Arc<RwLock<Option<ProxyServer>>>, app_handle: Arc<RwLock<Option<tauri::AppHandle>>>, switch_locks: SwitchLockManager,}数据访问层(src-tauri/src/database/dao/)
- 职责:SQLite SQL 语句封装,不涉及业务逻辑
- 组织:按表拆分(
providers.rs、mcp.rs、prompts.rs、skills.rs、settings.rs) - 连接安全:通过
lock_conn!宏获取 Mutex 保护,panic 时转换为AppError而非 crash
数据库(src-tauri/src/database/mod.rs)
Databasestruct 封装Mutex<Connection>init()负责文件打开、PRAGMA 设置、表创建、Schema 迁移memory()创建内存数据库用于测试
全局状态管理
CC Switch 使用 Tauri 的 app.manage() 注册全局状态:
// 核心状态app.manage(app_state); // AppState { db, proxy_service }app.manage(SkillServiceState(Arc::new(skill_service)));app.manage(CopilotAuthState(Arc::new(RwLock::new(auth_manager))));app.manage(CodexOAuthState(Arc::new(RwLock::new(oauth_manager))));命令层通过 tauri::State 获取这些单例:
#[tauri::command]async fn get_providers( state: tauri::State<'_, AppState>, app_type: String,) -> Result<HashMap<String, Provider>, String> { state.db.get_all_providers(&app_type)}为什么用 tauri::State 而非全局 static:Tauri 的状态管理保证每个状态只有一个实例,且在应用退出时自动清理。全局 static 需要手动管理生命周期,且在 Tauri 的多窗口场景下可能出现不一致。
插件集成
CC Switch 集成了多个 Tauri 插件:
| 插件 | 用途 |
|---|---|
tauri-plugin-log | 日志输出(单文件 + stdout) |
tauri-plugin-dialog | 文件选择对话框 |
tauri-plugin-store | 持久化键值存储(配置目录覆盖) |
tauri-plugin-updater | 自动更新 |
tauri-plugin-deep-link | Deep Link URL 处理 |
tauri-plugin-window-state | 窗口位置/大小持久化 |
tauri-plugin-single-instance | 防止多实例运行 |
tauri-plugin-process | 进程管理 |
tauri-plugin-opener | 打开外部链接/文件 |
插件注册顺序很重要:Store 插件必须在日志插件之前注册,因为日志目录可能由 Store 覆盖的配置决定。
应用生命周期
关键生命周期钩子:
setup()— 应用初始化,创建数据库、State、托盘、注册 deep-linkon_window_event— 窗口关闭时决定最小化到托盘还是真正退出ExitRequested— 用户退出时清理代理、保存窗口状态Reopen(macOS)— Dock 图标点击时恢复主窗口
问题与规避
陷阱 1:130+ 命令的注册维护困难
问题:所有命令在 lib.rs 的一个 generate_handler! 宏中注册,新增命令容易忘记注册。
CC Switch 的解决方案:
- 命令按功能域组织在
commands/子目录中,每个子模块有mod.rs导出 - 部分命令通过
commands::*通配符导入,减少手动维护 - 编译时如果命令函数存在但未注册,不会报错,所以代码审查时需要关注
陷阱 2:Mutex 死锁
问题:多个服务同时操作数据库,如果获取锁的顺序不一致,可能死锁。
CC Switch 的解决方案:
- 所有数据库操作通过同一个
Databasestruct 的lock_conn!宏获取锁 - 服务层不直接获取底层
Connection的锁 SwitchLockManager为 per-app 操作提供独立的 Mutex,避免代理切换与配置写入竞争
陷阱 3:Rust 1.85 版本要求
问题:CC Switch 要求 Rust 1.85+(通过 rust-toolchain.toml 指定),开发者可能使用旧版本。
规避:CI 中通过 rust-toolchain.toml 自动安装正确版本,本地开发通过 rustup 自动同步。
设计取舍
为什么选择 Tauri 2 而非 Electron?
| 维度 | Tauri 2 | Electron |
|---|---|---|
| 安装包大小 | ~15MB(Rust 编译 + 系统 WebView) | ~150MB(捆绑 Chromium) |
| 内存占用 | ~50MB 基础 | ~200MB 基础 |
| 安全性 | Rust 内存安全 + 系统 WebView 沙箱 | Chromium 沙箱 |
| 开发体验 | 需要 Rust 知识,前端部分相同 | 纯 JS/TS,生态更成熟 |
| WebView 差异 | 依赖系统 WebView(macOS=WebKit, Windows=WebView2, Linux=WebKitGTK) | 统一 Chromium |
对于 CC Switch 这种配置管理工具(不需要复杂的渲染特性),Tauri 2 的小体积和低内存是明显优势。
参考来源
- Tauri 2 文档. https://tauri.app/
- Tauri Plugin 列表. https://tauri.app/plugin/
- Rust Edition 2021 文档. https://doc.rust-lang.org/edition-guide/rust-2021/