# Tauri 应用开发指南 ## 目录 - [架构概述](#架构概述) - [添加新功能的完整流程](#添加新功能的完整流程) - [各层开发规范](#各层开发规范) - [代码示例](#代码示例) - [最佳实践](#最佳实践) - [测试规范](#测试规范) --- ## 架构概述 ### 三层架构 ``` ┌─────────────────────────────────────────────────┐ │ Command 层 │ │ 职责:参数验证、调用 Service、结果转换 │ │ 文件:src/commands/*.rs │ ├─────────────────────────────────────────────────┤ │ Service 层 │ │ 职责:业务逻辑、流程编排、状态管理 │ │ 文件:src/services/*.rs │ ├─────────────────────────────────────────────────┤ │ Utils 层 │ │ 职责:纯算法、无状态工具函数 │ │ 文件:src/utils/*.rs │ └─────────────────────────────────────────────────┘ ``` ### 依赖原则 - **单向依赖**:上层依赖下层,下层不依赖上层 - **依赖链**:Command → Service → Utils - **Utils 完全独立**:只依赖标准库和 models - **Service 可以直接调用 Windows API** --- ## 添加新功能的完整流程 ### 步骤 1:需求分析 在开始编码前,明确以下内容: 1. **功能描述**:这个功能做什么? 2. **用户交互**:用户如何触发这个功能? 3. **输入输出**:需要什么参数?返回什么结果? 4. **错误场景**:哪些情况下会出错?如何处理? ### 步骤 2:设计数据模型(如需要) 如果新功能需要新的数据结构,在 `models/` 中定义: ```rust // src/models/new_feature.rs use serde::{Deserialize, Serialize}; /// 新功能的配置信息 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NewFeatureConfig { /// 配置项 1 pub option1: String, /// 配置项 2 pub option2: u32, } /// 新功能的输出结果 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NewFeatureResult { /// 状态码 pub status: u16, /// 结果数据 pub data: String, } ``` **规范**: - ✅ 使用中文注释 - ✅ 每个字段都要有注释说明含义 - ✅ 实现 `Debug`, `Clone`, `Serialize`, `Deserialize` - ✅ 结构体命名使用大驼峰(PascalCase) - ✅ 字段命名使用下划线命名(snake_case) ### 步骤 3:实现工具函数(如果需要纯算法) 如果有纯算法逻辑,在 `utils/` 中实现: ```rust // src/utils/new_algorithm.rs /// 新算法函数 /// /// 对输入数据进行处理 /// /// # 参数 /// /// * `input` - 输入数据 /// /// # 返回 /// /// 返回处理后的结果 /// /// # 示例 /// /// ``` /// use crate::utils::new_algorithm::process_data; /// /// let result = process_data(42); /// assert_eq!(result, 84); /// ``` pub fn process_data(input: u32) -> u32 { input * 2 } #[cfg(test)] mod tests { use super::*; #[test] fn test_process_data() { assert_eq!(process_data(2), 4); assert_eq!(process_data(0), 0); } } ``` **规范**: - ✅ 纯函数,无副作用 - ✅ 添加详细的文档注释 - ✅ 包含单元测试 - ✅ 不依赖任何外部状态 ### 步骤 4:实现业务逻辑(Service 层) ```rust // src/services/new_feature_service.rs //! 新功能服务 //! //! 提供新功能的核心业务逻辑 use crate::error::AppResult; use crate::models::new_feature::{NewFeatureConfig, NewFeatureResult}; /// 新功能服务 pub struct NewFeatureService; impl NewFeatureService { /// 执行新功能 /// /// 根据配置执行相应的操作 /// /// # 参数 /// /// * `config` - 功能配置 /// /// # 返回 /// /// 返回执行结果 /// /// # 错误 /// /// - 配置无效时返回 `AppError::InvalidColorData` pub fn execute(&self, config: &NewFeatureConfig) -> AppResult { // 1. 参数验证 if config.option1.is_empty() { return Err(AppError::InvalidColorData( "配置项 option1 不能为空".to_string() )); } // 2. 业务逻辑 let result = format!("处理: {} - {}", config.option1, config.option2); // 3. 返回结果 Ok(NewFeatureResult { status: 200, data: result, }) } /// 执行新功能(异步版本) /// /// 异步执行新功能 pub async fn execute_async(&self, config: NewFeatureConfig) -> AppResult { // 使用 tokio 或其他异步运行时 Ok(self.execute(&config)?) } } ``` **规范**: - ✅ 使用 struct 命名空间(如 `NewFeatureService`) - ✅ 所有方法返回 `AppResult` - ✅ 参数验证放在 Service 层 - ✅ 可以直接调用 Windows API - ✅ 添加详细的文档注释 - ✅ 包含同步和异步两个版本(如需要) ### 步骤 5:创建 Tauri 命令(Command 层) ```rust // src/commands/new_feature_commands.rs //! 新功能命令 //! //! 定义新功能相关的 Tauri 命令 use crate::models::new_feature::{NewFeatureConfig, NewFeatureResult}; use crate::services::new_feature_service::NewFeatureService; /// 执行新功能命令 /// /// Tauri 命令,用于从前端调用新功能 /// /// # 参数 /// /// * `config` - 功能配置 /// /// # 返回 /// /// 返回执行结果,包含状态码和数据 /// /// # 前端调用示例 /// /// ```typescript /// import { invoke } from '@tauri-apps/api/tauri'; /// /// const result = await invoke('execute_new_feature', { /// config: { /// option1: 'test', /// option2: 100 /// } /// }); /// console.log(result.status); // 200 /// console.log(result.data); // "处理: test - 100" /// ``` #[tauri::command] pub fn execute_new_feature(config: NewFeatureConfig) -> Result { // 创建服务实例 let service = NewFeatureService; // 调用服务层 service .execute(&config) .map_err(|e| e.to_string()) } /// 异步执行新功能命令 /// /// 异步版本的执行命令 /// /// # 参数 /// /// * `config` - 功能配置 /// /// # 返回 /// /// 返回执行结果 #[tauri::command] pub async fn execute_new_feature_async(config: NewFeatureConfig) -> Result { let service = NewFeatureService; service .execute_async(config) .await .map_err(|e| e.to_string()) } ``` **规范**: - ✅ 使用 `#[tauri::command]` 宏 - ✅ 返回类型为 `Result` - ✅ 使用 `.map_err(|e| e.to_string())` 转换错误 - ✅ 包含前端调用示例 - ✅ 参数使用结构体,便于扩展 - ✅ 添加详细的文档注释 ### 步骤 6:注册模块 更新各模块的 `mod.rs` 和 `lib.rs`: ```rust // src/models/mod.rs pub mod color; pub mod new_feature; // 新增 pub use color::{ColorInfo, RgbInfo, HslInfo}; pub use new_feature::{NewFeatureConfig, NewFeatureResult}; // 新增 // src/commands/mod.rs pub mod color_commands; pub mod window_commands; pub mod new_feature_commands; // 新增 // src/lib.rs .invoke_handler(tauri::generate_handler![ // ... 其他命令 commands::new_feature_commands::execute_new_feature, commands::new_feature_commands::execute_new_feature_async, ]) ``` **规范**: - ✅ 按字母顺序排列 - ✅ 导出常用类型 - ✅ 注册所有新命令 ### 步骤 7:错误处理扩展(如需要) 如果需要新的错误类型,在 `error.rs` 中添加: ```rust // src/error.rs #[derive(Debug)] pub enum AppError { // ... 现有错误类型 /// 新功能相关错误 NewFeatureFailed(String), } impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { // ... 现有错误 AppError::NewFeatureFailed(msg) => write!(f, "新功能执行失败: {}", msg), } } } ``` **规范**: - ✅ 错误类型使用中文描述 - ✅ 携带详细的错误信息(`String`) - ✅ 在 `Display` 实现中添加上下文 ### 步骤 8:更新前端类型定义 在 `src/types/` 或相关位置添加 TypeScript 类型定义: ```typescript // src/types/new-feature.ts /** * 新功能配置 */ export interface NewFeatureConfig { /** 配置项 1 */ option1: string; /** 配置项 2 */ option2: number; } /** * 新功能结果 */ export interface NewFeatureResult { /** 状态码 */ status: number; /** 结果数据 */ data: string; } // 导出 Tauri 命令类型 export type NewFeatureCommands = { execute_new_feature: (config: NewFeatureConfig) => Promise; execute_new_feature_async: (config: NewFeatureConfig) => Promise; }; ``` ### 步骤 9:编写测试 ```rust // src/services/new_feature_service.rs 中的测试 #[cfg(test)] mod tests { use super::*; #[test] fn test_execute_with_valid_config() { let service = NewFeatureService; let config = NewFeatureConfig { option1: "test".to_string(), option2: 100, }; let result = service.execute(&config).unwrap(); assert_eq!(result.status, 200); } #[test] fn test_execute_with_empty_option1() { let service = NewFeatureService; let config = NewFeatureConfig { option1: "".to_string(), option2: 100, }; assert!(service.execute(&config).is_err()); } } ``` **规范**: - ✅ 测试函数命名使用 `test_` 前缀 - ✅ 测试正常场景和错误场景 - ✅ 使用 `assert!` 和 `assert_eq!` 断言 - ✅ 运行 `cargo test` 验证 ### 步骤 10:验证和测试 ```bash # 1. 检查代码编译 cargo check # 2. 运行测试 cargo test # 3. 构建应用 pnpm build # 4. 运行应用 pnpm tauri dev # 5. 测试功能 # - 在前端界面测试新功能 # - 检查控制台输出 # - 验证错误处理 ``` --- ## 各层开发规范 ### Models 层规范 **职责**:定义数据结构,不包含业务逻辑 **规范清单**: - ✅ 使用 `serde` 的 `Serialize` 和 `Deserialize` - ✅ 实现 `Debug` 和 `Clone` - ✅ 所有字段必须有文档注释 - ✅ 使用 `new()` 方法构造复杂对象 - ✅ 字段类型优先使用基本类型 - ✅ 集中定义在一个模块中 **示例**: ```rust /// 用户信息 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserInfo { /// 用户 ID pub id: u32, /// 用户名 pub name: String, } impl UserInfo { /// 创建新用户 pub fn new(id: u32, name: String) -> Self { Self { id, name } } } ``` ### Utils 层规范 **职责**:提供纯函数算法,无副作用 **规范清单**: - ✅ 纯函数,不依赖外部状态 - ✅ 不使用 `unsafe` 代码 - ✅ 不执行 I/O 操作 - ✅ 包含单元测试 - ✅ 使用清晰的函数命名 **示例**: ```rust /// 计算两点之间的距离 pub fn calculate_distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 { let dx = x2 - x1; let dy = y2 - y1; (dx * dx + dy * dy).sqrt() } #[cfg(test)] mod tests { use super::*; #[test] fn test_calculate_distance() { let dist = calculate_distance(0.0, 0.0, 3.0, 4.0); assert_eq!(dist, 5.0); } } ``` ### Service 层规范 **职责**:业务逻辑实现和流程编排 **规范清单**: - ✅ 使用 struct 命名空间 - ✅ 所有方法返回 `AppResult` - ✅ 参数验证在 Service 层进行 - ✅ 可以直接调用 Windows API - ✅ 可以调用 Utils 层函数 - ✅ 提供同步和异步版本(如需要) - ✅ 使用详细的中文档注释 **示例**: ```rust pub struct FileService; impl FileService { pub fn read_and_process(&self, path: &str) -> AppResult { // 1. 参数验证 if path.is_empty() { return Err(AppError::InvalidData("路径不能为空".to_string())); } // 2. 调用 Windows API 或第三方库 let content = std::fs::read_to_string(path)?; // 3. 调用 Utils 层处理 let processed = utils::text::trim_whitespace(&content); Ok(processed.to_string()) } } ``` ### Command 层规范 **职责**:Tauri 命令处理,连接前端和 Service **规范清单**: - ✅ 使用 `#[tauri::command]` 宏 - ✅ 返回 `Result` - ✅ 参数使用结构体(便于扩展) - ✅ 简洁的适配器代码 - ✅ 包含前端调用示例 - ✅ 错误转换为字符串 **示例**: ```rust #[tauri::command] pub fn process_file(path: String) -> Result { let service = FileService; service .read_and_process(&path) .map_err(|e| e.to_string()) } ``` --- ## 代码示例 ### 完整示例:添加一个屏幕截图功能 #### 1. 定义数据模型 ```rust // src/models/screenshot.rs use serde::{Deserialize, Serialize}; /// 截图配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ScreenshotConfig { /// 是否包含光标 pub include_cursor: bool, /// JPEG 质量 (1-100) pub quality: u8, } /// 截图结果 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ScreenshotResult { /// 图片数据(Base64) pub data: String, /// 图片格式 pub format: String, /// 图片宽度 pub width: u32, /// 图片高度 pub height: u32, } ``` #### 2. 实现 Service ```rust // src/services/screenshot_service.rs //! 屏幕截图服务 //! //! 提供屏幕截图功能 use crate::error::{AppError, AppResult}; use crate::models::screenshot::{ScreenshotConfig, ScreenshotResult}; /// 截图服务 pub struct ScreenshotService; impl ScreenshotService { /// 截取屏幕 /// /// 根据配置截取屏幕画面 /// /// # 参数 /// /// * `config` - 截图配置 /// /// # 返回 /// /// 返回截图结果 pub fn capture(config: &ScreenshotConfig) -> AppResult { // 验证配置 if config.quality < 1 || config.quality > 100 { return Err(AppError::InvalidColorData( "图片质量必须在 1-100 之间".to_string() )); } // 调用 Windows API 或使用第三方库 // 例如:使用 screenshots-rs crate let screenshots = screenshots::Screen::all().map_err(|e| { AppError::InvalidData(format!("获取屏幕失败: {}", e)) })?; if let Some(screen) = screenshots.first() { let image = screen.capture().map_err(|e| { AppError::InvalidData(format!("截图失败: {}", e)) })?; // 转换为 Base64 let buffer = image.buffer(); let base64_data = base64::encode(buffer); Ok(ScreenshotResult { data: base64_data, format: "png".to_string(), width: image.width(), height: image.height(), }) } else { Err(AppError::InvalidData("未找到屏幕".to_string())) } } /// 截取指定区域 pub fn capture_region( x: u32, y: u32, width: u32, height: u32, config: &ScreenshotConfig, ) -> AppResult { if width == 0 || height == 0 { return Err(AppError::InvalidColorData( "宽度和高度必须大于 0".to_string() )); } // 调用 Windows API 实现区域截图 // ... Ok(ScreenshotResult { data: "base64_data".to_string(), format: "png".to_string(), width, height, }) } } ``` #### 3. 创建命令 ```rust // src/commands/screenshot_commands.rs //! 截图命令 use crate::models::screenshot::{ScreenshotConfig, ScreenshotResult}; use crate::services::screenshot_service::ScreenshotService; /// 截取屏幕命令 /// /// # 参数 /// /// * `config` - 截图配置 /// /// # 返回 /// /// 返回截图结果 /// /// # 前端调用示例 /// /// ```typescript /// import { invoke } from '@tauri-apps/api/tauri'; /// /// const result = await invoke('capture_screen', { /// config: { /// include_cursor: false, /// quality: 90 /// } /// }); /// console.log(result.data); // Base64 图片数据 /// ``` #[tauri::command] pub fn capture_screen(config: ScreenshotConfig) -> Result { ScreenshotService::capture(&config).map_err(|e| e.to_string()) } /// 截取区域命令 #[tauri::command] pub fn capture_region( x: u32, y: u32, width: u32, height: u32, config: ScreenshotConfig, ) -> Result { ScreenshotService::capture_region(x, y, width, height, config) .map_err(|e| e.to_string()) } ``` --- ## 最佳实践 ### 1. 命名规范 | 类型 | 命名风格 | 示例 | |------|----------|------| | 模块 | snake_case | `color_service.rs` | | 结构体 | PascalCase | `ColorInfo`, `NewFeatureService` | | 函数 | snake_case | `get_pixel_color`, `toggle_window` | | 常量 | SCREAMING_SNAKE_CASE | `MAX_RETRY_COUNT` | | Trait | PascalCase + 能力 | `ScreenAccessor`, `CursorController` | | 类型别名 | PascalCase + Type | `AppResult`, `JsonResult` | ### 2. 文档注释规范 ```rust /// 简短描述(一句话) /// /// 详细描述(可以多行,说明功能、用途、注意事项) /// /// # 参数 /// /// * `param1` - 参数说明 /// * `param2` - 参数说明 /// /// # 返回 /// /// 返回值说明 /// /// # 错误 /// /// - 错误类型1:错误场景 /// - 错误类型2:错误场景 /// /// # 示例 /// /// ``` /// let result = function_name(args); /// assert!(result.is_ok()); /// ``` pub fn function_name(param1: Type1, param2: Type2) -> AppResult { // 实现 } ``` ### 3. 错误处理规范 ```rust // ✅ 推荐:使用具体的错误类型 fn do_something() -> AppResult<()> { if condition { return Err(AppError::InvalidData("数据无效".to_string())); } Ok(()) } // ❌ 不推荐:直接返回 String fn do_something() -> Result<(), String> { if condition { return Err("错误".to_string()); } Ok(()) } ``` ### 4. 参数验证规范 ```rust // ✅ 在 Service 层验证参数 impl SomeService { pub fn execute(&self, input: &str) -> AppResult { // 参数验证 if input.is_empty() { return Err(AppError::InvalidData("输入不能为空".to_string())); } if input.len() > 1000 { return Err(AppError::InvalidData("输入过长".to_string())); } // 业务逻辑 // ... } } ``` ### 5. 异步处理规范 ```rust // ✅ 提供 sync 和 async 两个版本 impl SomeService { // 同步版本 pub fn execute(&self, input: &str) -> AppResult { // 直接调用 } // 异步版本 pub async fn execute_async(&self, input: String) -> AppResult { // 使用 async/await tokio::time::sleep(Duration::from_secs(1)).await; self.execute(&input) } } ``` ### 6. 代码组织规范 ```rust // ✅ 推荐:按功能分组 pub struct ColorService; impl ColorService { // 公共方法 pub fn new() -> Self { Self } pub fn do_work(&self) -> AppResult<()> { } // 私有辅助方法 fn validate(&self) -> AppResult<()> { } fn process(&self) -> AppResult<()> { } } // ✅ 使用 mod 组织相关功能 mod color_conversion { pub fn rgb_to_hsl() { } pub fn hsl_to_rgb() { } } ``` --- ## 测试规范 ### 单元测试 ```rust #[cfg(test)] mod tests { use super::*; #[test] fn test_normal_case() { // 测试正常场景 let result = function_under_test(normal_input); assert_eq!(result, expected); } #[test] fn test_edge_case() { // 测试边界条件 let result = function_under_test(edge_input); assert!(result.is_ok()); } #[test] fn test_error_case() { // 测试错误场景 let result = function_under_test(invalid_input); assert!(result.is_err()); } } ``` ### 集成测试 ```rust // tests/integration_test.rs #[test] fn test_full_workflow() { // 测试完整的业务流程 let service = MyService::new(); let config = MyConfig::default(); let result = service.execute(&config).unwrap(); assert_eq!(result.status, 200); } ``` ### 运行测试 ```bash # 运行所有测试 cargo test # 运行特定测试 cargo test test_color_conversion # 运行测试并显示输出 cargo test -- --nocapture # 运行测试并生成文档 cargo test --doc ``` --- ## 常见问题 ### Q: 如何添加新的错误类型? A: 在 `error.rs` 中添加新的枚举变体,并在 `Display` 实现中添加处理: ```rust pub enum AppError { // ... NewErrorType(String), } impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { // ... AppError::NewErrorType(msg) => write!(f, "新错误: {}", msg), } } } ``` ### Q: 如何调用 Windows API? A: 在 Service 层直接使用 Windows 相关的 crate 或调用 Windows API: ```rust // 使用 windows-rs crate use windows::Win32::UI::WindowsAndMessaging::GetCursorPos; impl CursorService { pub fn get_cursor_position(&self) -> AppResult<(i32, i32)> { let mut pos = POINT { x: 0, y: 0 }; unsafe { GetCursorPos(&mut pos)?; } Ok((pos.x, pos.y)) } } ``` ### Q: 如何处理异步操作? A: 在 Service 层提供 async 函数,Command 层使用 async 命令: ```rust // Service pub async fn fetch_data(&self) -> AppResult { // 异步操作 } // Command #[tauri::command] pub async fn get_data() -> Result { service.fetch_data().await.map_err(|e| e.to_string()) } ``` --- ## 检查清单 在提交代码前,确保: - [ ] 代码符合 Rust 格式规范(`cargo fmt --check`) - [ ] 代码通过 clippy 检查(`cargo clippy`) - [ ] 所有测试通过(`cargo test`) - [ ] 编译成功(`cargo check`) - [ ] 所有公开 API 都有文档注释 - [ ] 文档注释包含示例代码 - [ ] 错误消息使用中文 - [ ] 遵循依赖原则(上层依赖下层) - [ ] 新增类型已导出(如需要) - [ ] 命令已在 `lib.rs` 中注册 --- ## 参考资源 - [Rust 官方文档](https://doc.rust-lang.org/) - [Tauri 官方文档](https://tauri.app/) - [Rust API 指南](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html) - [Rust 命名规范](https://rust-lang.github.io/api-guidelines/naming.html)