- 集成 react-syntax-highlighter 实现代码高亮显示 - 新增 code-highlighter UI 组件和 syntax-helpers 工具 - 添加 HTML/XML 格式化相关 Rust 依赖(minify-html、markup_fmt 等) - 在开发指南中整合 Rust-TS 跨语言命名规范 - 移除冗余的 Tauri_Naming_Conventions.md 文档 - 更新 Claude Code 配置添加工具命令权限
26 KiB
26 KiB
Tauri 应用开发指南
目录
架构概述
三层架构
┌─────────────────────────────────────────────────┐
│ Command 层 │
│ 职责:参数验证、调用 Service、结果转换 │
│ 文件:src/commands/*.rs │
├─────────────────────────────────────────────────┤
│ Service 层 │
│ 职责:业务逻辑、流程编排、状态管理 │
│ 文件:src/services/*.rs │
├─────────────────────────────────────────────────┤
│ Utils 层 │
│ 职责:纯算法、无状态工具函数 │
│ 文件:src/utils/*.rs │
└─────────────────────────────────────────────────┘
依赖原则
- 单向依赖:上层依赖下层,下层不依赖上层
- 依赖链:Command → Service → Utils
- Utils 完全独立:只依赖标准库和 models
- Service 可以直接调用 Windows API
添加新功能的完整流程
步骤 1:需求分析
在开始编码前,明确以下内容:
- 功能描述:这个功能做什么?
- 用户交互:用户如何触发这个功能?
- 输入输出:需要什么参数?返回什么结果?
- 错误场景:哪些情况下会出错?如何处理?
步骤 2:设计数据模型(如需要)
如果新功能需要新的数据结构,在 models/ 中定义:
// 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/ 中实现:
// 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 层)
// 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<NewFeatureResult> {
// 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<NewFeatureResult> {
// 使用 tokio 或其他异步运行时
Ok(self.execute(&config)?)
}
}
规范:
- ✅ 使用 struct 命名空间(如
NewFeatureService) - ✅ 所有方法返回
AppResult<T> - ✅ 参数验证放在 Service 层
- ✅ 可以直接调用 Windows API
- ✅ 添加详细的文档注释
- ✅ 包含同步和异步两个版本(如需要)
步骤 5:创建 Tauri 命令(Command 层)
// 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<NewFeatureResult, String> {
// 创建服务实例
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<NewFeatureResult, String> {
let service = NewFeatureService;
service
.execute_async(config)
.await
.map_err(|e| e.to_string())
}
规范:
- ✅ 使用
#[tauri::command]宏 - ✅ 返回类型为
Result<T, String> - ✅ 使用
.map_err(|e| e.to_string())转换错误 - ✅ 包含前端调用示例
- ✅ 参数使用结构体,便于扩展
- ✅ 添加详细的文档注释
步骤 6:注册模块
更新各模块的 mod.rs 和 lib.rs:
// 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 中添加:
// 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 类型定义:
// 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<NewFeatureResult>;
execute_new_feature_async: (config: NewFeatureConfig) => Promise<NewFeatureResult>;
};
步骤 9:编写测试
// 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:验证和测试
# 1. 检查代码编译
cargo check
# 2. 运行测试
cargo test
# 3. 构建应用
pnpm build
# 4. 运行应用
pnpm tauri dev
# 5. 测试功能
# - 在前端界面测试新功能
# - 检查控制台输出
# - 验证错误处理
各层开发规范
Models 层规范
职责:定义数据结构,不包含业务逻辑
规范清单:
- ✅ 使用
serde的Serialize和Deserialize - ✅ 实现
Debug和Clone - ✅ 所有字段必须有文档注释
- ✅ 使用
new()方法构造复杂对象 - ✅ 字段类型优先使用基本类型
- ✅ 集中定义在一个模块中
示例:
/// 用户信息
#[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 操作
- ✅ 包含单元测试
- ✅ 使用清晰的函数命名
示例:
/// 计算两点之间的距离
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<T> - ✅ 参数验证在 Service 层进行
- ✅ 可以直接调用 Windows API
- ✅ 可以调用 Utils 层函数
- ✅ 提供同步和异步版本(如需要)
- ✅ 使用详细的中文档注释
示例:
pub struct FileService;
impl FileService {
pub fn read_and_process(&self, path: &str) -> AppResult<String> {
// 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<T, String> - ✅ 参数使用结构体(便于扩展)
- ✅ 简洁的适配器代码
- ✅ 包含前端调用示例
- ✅ 错误转换为字符串
示例:
#[tauri::command]
pub fn process_file(path: String) -> Result<String, String> {
let service = FileService;
service
.read_and_process(&path)
.map_err(|e| e.to_string())
}
代码示例
完整示例:添加一个屏幕截图功能
1. 定义数据模型
// 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
// 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<ScreenshotResult> {
// 验证配置
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<ScreenshotResult> {
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. 创建命令
// 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<ScreenshotResult, String> {
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<ScreenshotResult, String> {
ScreenshotService::capture_region(x, y, width, height, config)
.map_err(|e| e.to_string())
}
最佳实践
1. 命名规范
1.1 Rust 内部命名规范
| 类型 | 命名风格 | 示例 |
|---|---|---|
| 模块 | 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 |
1.2 跨语言命名规范(Rust ↔ TypeScript)
核心原则:
- ✅ 各自遵循语言规范:Rust 用 snake_case,前端用 camelCase
- ✅ 通过 serde 自动转换:使用
#[serde(rename_all = "camelCase")] - ✅ 类型名称保持一致:两端都用 PascalCase
Rust 端实现:
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] // 自动转换
pub struct SessionConfig {
pub host: String, // Rust: snake_case → JSON: "host"
pub port: u16,
pub user_name: String, // Rust: snake_case → JSON: "userName"
pub private_key_path: String, // Rust: snake_case → JSON: "privateKeyPath"
pub auth_method: AuthMethod,
}
前端类型定义:
export interface SessionConfig {
host: string; // camelCase
port: number;
userName: string; // camelCase
privateKeyPath: string; // camelCase
authMethod: AuthMethod;
}
字段命名对照表:
| Rust 端 (snake_case) | 前端 (camelCase) | 说明 |
|---|---|---|
user_name |
userName |
用户名 |
private_key_path |
privateKeyPath |
私钥路径 |
auth_method |
authMethod |
认证方法 |
connection_id |
connectionId |
连接 ID |
terminal_type |
terminalType |
终端类型 |
keep_alive_interval |
keepAliveInterval |
保活间隔 |
strict_host_key_checking |
strictHostKeyChecking |
主机密钥检查 |
注意事项:
- ✅ 所有与前端交互的 struct 都必须添加
#[serde(rename_all = "camelCase")] - ✅ Enum 变体命名也使用
#[serde(rename_all = "camelCase")] - ✅ 类型名称(如
SessionConfig)在两端保持一致 - ❌ 不要在 Rust 端直接使用 camelCase 命名字段
- ❌ 不要在前端使用 snake_case 命名属性
完整示例:
// Rust 端
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SessionConfig {
pub id: Option<String>,
pub name: String,
pub host: String,
pub port: u16,
pub username: String,
#[serde(default = "default_keep_alive_interval")]
pub keepAliveInterval: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub enum AuthMethod {
Password { password: String },
PublicKey { privateKeyPath: String, passphrase: Option<String> },
}
// 前端
export interface SessionConfig {
id?: string;
name: string;
host: string;
port: number;
username: string;
keepAliveInterval?: number;
}
export type AuthMethod =
| { Password: { password: string } }
| { PublicKey: { privateKeyPath: string; passphrase?: string } };
2. 文档注释规范
/// 简短描述(一句话)
///
/// 详细描述(可以多行,说明功能、用途、注意事项)
///
/// # 参数
///
/// * `param1` - 参数说明
/// * `param2` - 参数说明
///
/// # 返回
///
/// 返回值说明
///
/// # 错误
///
/// - 错误类型1:错误场景
/// - 错误类型2:错误场景
///
/// # 示例
///
/// ```
/// let result = function_name(args);
/// assert!(result.is_ok());
/// ```
pub fn function_name(param1: Type1, param2: Type2) -> AppResult<ReturnType> {
// 实现
}
3. 错误处理规范
// ✅ 推荐:使用具体的错误类型
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. 参数验证规范
// ✅ 在 Service 层验证参数
impl SomeService {
pub fn execute(&self, input: &str) -> AppResult<Output> {
// 参数验证
if input.is_empty() {
return Err(AppError::InvalidData("输入不能为空".to_string()));
}
if input.len() > 1000 {
return Err(AppError::InvalidData("输入过长".to_string()));
}
// 业务逻辑
// ...
}
}
5. 异步处理规范
// ✅ 提供 sync 和 async 两个版本
impl SomeService {
// 同步版本
pub fn execute(&self, input: &str) -> AppResult<Output> {
// 直接调用
}
// 异步版本
pub async fn execute_async(&self, input: String) -> AppResult<Output> {
// 使用 async/await
tokio::time::sleep(Duration::from_secs(1)).await;
self.execute(&input)
}
}
6. 代码组织规范
// ✅ 推荐:按功能分组
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() { }
}
测试规范
单元测试
#[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());
}
}
集成测试
// 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);
}
运行测试
# 运行所有测试
cargo test
# 运行特定测试
cargo test test_color_conversion
# 运行测试并显示输出
cargo test -- --nocapture
# 运行测试并生成文档
cargo test --doc
常见问题
Q: 如何添加新的错误类型?
A: 在 error.rs 中添加新的枚举变体,并在 Display 实现中添加处理:
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:
// 使用 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 命令:
// Service
pub async fn fetch_data(&self) -> AppResult<Data> {
// 异步操作
}
// Command
#[tauri::command]
pub async fn get_data() -> Result<Data, String> {
service.fetch_data().await.map_err(|e| e.to_string())
}
检查清单
在提交代码前,确保:
- 代码符合 Rust 格式规范(
cargo fmt --check) - 代码通过 clippy 检查(
cargo clippy) - 所有测试通过(
cargo test) - 编译成功(
cargo check) - 所有公开 API 都有文档注释
- 文档注释包含示例代码
- 错误消息使用中文
- 遵循依赖原则(上层依赖下层)
- 新增类型已导出(如需要)
- 命令已在
lib.rs中注册