# MixVideo 后端开发规范 (Rust/Tauri) ## 技术栈规范 ### 核心技术 - **Tauri 2.0**: 跨平台桌面应用框架 - **Rust 1.70+**: 系统编程语言 - **SQLite**: 嵌入式数据库 (WAL模式) - **Tokio**: 异步运行时 - **Serde**: 序列化/反序列化 - **anyhow/thiserror**: 错误处理 ### 依赖管理 ```toml [workspace.dependencies] # Tauri 相关 tauri = { version = "2.0", features = ["api-all"] } tauri-build = { version = "2.0", features = [] } # 异步和并发 tokio = { version = "1.0", features = ["full"] } futures = "0.3" async-trait = "0.1" # 序列化 serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" # 错误处理 anyhow = "1.0" thiserror = "1.0" # 数据库 rusqlite = { version = "0.29", features = ["bundled", "chrono"] } chrono = { version = "0.4", features = ["serde"] } # 日志 tracing = "0.1" tracing-subscriber = "0.3" ``` ## 四层架构设计 ### 1. 基础设施层 (Infrastructure) ```rust // src/infrastructure/mod.rs pub mod database; pub mod logging; pub mod file_system; pub mod gemini_service; pub mod ffmpeg; pub mod event_bus; pub mod performance; // 基础设施层职责: // - 数据库连接管理 // - 外部服务集成 // - 文件系统操作 // - 日志和监控 // - 事件总线 ``` ### 2. 数据访问层 (Data) ```rust // src/data/mod.rs pub mod models; pub mod repositories; // 数据访问层职责: // - 数据模型定义 // - 数据库操作封装 // - 查询优化 // - 事务管理 ``` ### 3. 业务逻辑层 (Business) ```rust // src/business/mod.rs pub mod services; pub mod errors; // 业务逻辑层职责: // - 核心业务逻辑 // - 业务规则验证 // - 工作流编排 // - 领域模型 ``` ### 4. 表现层 (Presentation) ```rust // src/presentation/mod.rs pub mod commands; // 表现层职责: // - Tauri命令定义 // - 参数验证 // - 响应格式化 // - 错误转换 ``` ## 代码组织规范 ### 模块结构 ```rust // lib.rs - 库入口 extern crate lazy_static; // 四层架构模块 pub mod infrastructure; pub mod data; pub mod business; pub mod presentation; pub mod services; // 应用配置 pub mod app_state; pub mod config; use app_state::AppState; use presentation::commands; use tauri::Manager; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_dialog::init()) .manage(AppState::new()) .invoke_handler(tauri::generate_handler![ // 注册所有命令 commands::project_commands::create_project, commands::material_commands::get_all_materials, // ... 其他命令 ]) .setup(|app| { // 应用初始化逻辑 Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` ### 文件命名规范 - **模块文件**: snake_case (如 `material_service.rs`) - **结构体**: PascalCase (如 `MaterialRepository`) - **函数**: snake_case (如 `create_material`) - **常量**: SCREAMING_SNAKE_CASE (如 `DEFAULT_PAGE_SIZE`) ## 数据模型规范 ### 实体模型设计 ```rust use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; /// 素材实体模型 /// 遵循 Tauri 开发规范的数据模型设计原则 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Material { pub id: String, pub name: String, pub path: String, pub material_type: MaterialType, pub size: u64, pub duration: Option, pub project_id: String, pub created_at: DateTime, pub updated_at: DateTime, pub is_active: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum MaterialType { Video, Audio, Image, } impl Material { /// 创建新的素材实例 pub fn new(request: CreateMaterialRequest) -> Self { let now = Utc::now(); Self { id: uuid::Uuid::new_v4().to_string(), name: request.name, path: request.path, material_type: request.material_type, size: request.size, duration: request.duration, project_id: request.project_id, created_at: now, updated_at: now, is_active: true, } } /// 验证素材数据 pub fn validate(&self) -> Result<(), String> { if self.name.trim().is_empty() { return Err("素材名称不能为空".to_string()); } if self.path.trim().is_empty() { return Err("素材路径不能为空".to_string()); } Ok(()) } /// 更新素材信息 pub fn update(&mut self, request: UpdateMaterialRequest) { if let Some(name) = request.name { self.name = name; } if let Some(material_type) = request.material_type { self.material_type = material_type; } self.updated_at = Utc::now(); } } /// 创建素材请求 #[derive(Debug, Deserialize)] pub struct CreateMaterialRequest { pub name: String, pub path: String, pub material_type: MaterialType, pub size: u64, pub duration: Option, pub project_id: String, } /// 更新素材请求 #[derive(Debug, Deserialize)] pub struct UpdateMaterialRequest { pub name: Option, pub material_type: Option, } ``` ### 模型设计原则 1. **不可变性**: 优先使用不可变字段 2. **验证**: 内置数据验证方法 3. **序列化**: 支持JSON序列化 4. **文档**: 详细的文档注释 5. **测试**: 为模型编写单元测试 ## 仓库模式规范 ### 仓库接口设计 ```rust use async_trait::async_trait; use anyhow::Result; /// 素材仓库接口 /// 定义素材数据访问的抽象接口 #[async_trait] pub trait MaterialRepositoryTrait { async fn create(&self, material: &Material) -> Result<()>; async fn get_by_id(&self, id: &str) -> Result>; async fn get_all(&self) -> Result>; async fn get_by_project_id(&self, project_id: &str) -> Result>; async fn update(&self, material: &Material) -> Result<()>; async fn delete(&self, id: &str) -> Result<()>; async fn search(&self, query: &MaterialQueryParams) -> Result>; } /// 素材仓库实现 /// 遵循 Tauri 开发规范的仓库模式 pub struct MaterialRepository { database: Arc, } impl MaterialRepository { pub fn new(database: Arc) -> Self { Self { database } } } #[async_trait] impl MaterialRepositoryTrait for MaterialRepository { async fn create(&self, material: &Material) -> Result<()> { let conn = self.database.get_connection().await?; conn.execute( r#" INSERT INTO materials ( id, name, path, material_type, size, duration, project_id, created_at, updated_at, is_active ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) "#, params![ material.id, material.name, material.path, serde_json::to_string(&material.material_type)?, material.size, material.duration, material.project_id, material.created_at.to_rfc3339(), material.updated_at.to_rfc3339(), material.is_active, ], )?; Ok(()) } async fn get_by_id(&self, id: &str) -> Result> { let conn = self.database.get_read_connection().await?; let mut stmt = conn.prepare( r#" SELECT id, name, path, material_type, size, duration, project_id, created_at, updated_at, is_active FROM materials WHERE id = ?1 AND is_active = 1 "# )?; let material = stmt.query_row(params![id], |row| { Ok(Material { id: row.get(0)?, name: row.get(1)?, path: row.get(2)?, material_type: serde_json::from_str(&row.get::<_, String>(3)?) .map_err(|e| rusqlite::Error::InvalidColumnType(3, "material_type".to_string(), rusqlite::types::Type::Text))?, size: row.get(4)?, duration: row.get(5)?, project_id: row.get(6)?, created_at: DateTime::parse_from_rfc3339(&row.get::<_, String>(7)?) .map_err(|e| rusqlite::Error::InvalidColumnType(7, "created_at".to_string(), rusqlite::types::Type::Text))? .with_timezone(&Utc), updated_at: DateTime::parse_from_rfc3339(&row.get::<_, String>(8)?) .map_err(|e| rusqlite::Error::InvalidColumnType(8, "updated_at".to_string(), rusqlite::types::Type::Text))? .with_timezone(&Utc), is_active: row.get(9)?, }) }).optional()?; Ok(material) } } ``` ### 仓库设计原则 1. **接口分离**: 定义清晰的仓库接口 2. **异步操作**: 所有数据库操作都是异步的 3. **错误处理**: 统一的错误处理机制 4. **连接管理**: 合理使用数据库连接 5. **查询优化**: 优化SQL查询性能 ## 业务服务规范 ### 服务层设计 ```rust use anyhow::{anyhow, Result}; use crate::data::repositories::material_repository::MaterialRepository; use crate::data::models::material::{Material, CreateMaterialRequest, UpdateMaterialRequest}; /// 素材管理服务 /// 遵循 Tauri 开发规范的业务服务设计 pub struct MaterialService; impl MaterialService { /// 创建素材 pub async fn create_material( repository: &MaterialRepository, request: CreateMaterialRequest, ) -> Result { // 业务验证 if request.name.trim().is_empty() { return Err(anyhow!("素材名称不能为空")); } if !std::path::Path::new(&request.path).exists() { return Err(anyhow!("素材文件不存在")); } // 创建素材实例 let material = Material::new(request); // 验证素材数据 material.validate() .map_err(|e| anyhow!("素材验证失败: {}", e))?; // 保存到数据库 repository.create(&material).await .map_err(|e| anyhow!("创建素材失败: {}", e))?; Ok(material) } /// 获取素材详情 pub async fn get_material_by_id( repository: &MaterialRepository, id: &str, ) -> Result> { repository.get_by_id(id).await .map_err(|e| anyhow!("获取素材详情失败: {}", e)) } /// 更新素材 pub async fn update_material( repository: &MaterialRepository, id: &str, request: UpdateMaterialRequest, ) -> Result { // 获取现有素材 let mut material = repository.get_by_id(id).await? .ok_or_else(|| anyhow!("素材不存在"))?; // 更新素材信息 material.update(request); // 验证更新后的数据 material.validate() .map_err(|e| anyhow!("素材验证失败: {}", e))?; // 保存更新 repository.update(&material).await .map_err(|e| anyhow!("更新素材失败: {}", e))?; Ok(material) } /// 删除素材 pub async fn delete_material( repository: &MaterialRepository, id: &str, ) -> Result<()> { // 检查素材是否存在 let material = repository.get_by_id(id).await? .ok_or_else(|| anyhow!("素材不存在"))?; // 执行删除 repository.delete(id).await .map_err(|e| anyhow!("删除素材失败: {}", e))?; Ok(()) } } ``` ### 服务设计原则 1. **业务封装**: 封装复杂的业务逻辑 2. **验证**: 完整的业务规则验证 3. **事务**: 合理使用数据库事务 4. **错误处理**: 详细的错误信息 5. **测试**: 完整的单元测试覆盖 ## Tauri命令规范 ### 命令定义 ```rust use tauri::{command, State}; use crate::app_state::AppState; use crate::business::services::material_service::MaterialService; use crate::data::models::material::{Material, CreateMaterialRequest, UpdateMaterialRequest}; /// 创建素材命令 /// 遵循 Tauri 开发规范的命令设计模式 #[command] pub async fn create_material( state: State<'_, AppState>, request: CreateMaterialRequest, ) -> Result { let repository_guard = state.get_material_repository() .map_err(|e| format!("获取素材仓库失败: {}", e))?; let repository = repository_guard.as_ref() .ok_or("素材仓库未初始化")?; MaterialService::create_material(repository, request) .await .map_err(|e| e.to_string()) } /// 获取所有素材命令 #[command] pub async fn get_all_materials( state: State<'_, AppState>, ) -> Result, String> { let repository_guard = state.get_material_repository() .map_err(|e| format!("获取素材仓库失败: {}", e))?; let repository = repository_guard.as_ref() .ok_or("素材仓库未初始化")?; repository.get_all() .await .map_err(|e| e.to_string()) } /// 根据ID获取素材命令 #[command] pub async fn get_material_by_id( state: State<'_, AppState>, id: String, ) -> Result, String> { let repository_guard = state.get_material_repository() .map_err(|e| format!("获取素材仓库失败: {}", e))?; let repository = repository_guard.as_ref() .ok_or("素材仓库未初始化")?; MaterialService::get_material_by_id(repository, &id) .await .map_err(|e| e.to_string()) } /// 更新素材命令 #[command] pub async fn update_material( state: State<'_, AppState>, id: String, request: UpdateMaterialRequest, ) -> Result { let repository_guard = state.get_material_repository() .map_err(|e| format!("获取素材仓库失败: {}", e))?; let repository = repository_guard.as_ref() .ok_or("素材仓库未初始化")?; MaterialService::update_material(repository, &id, request) .await .map_err(|e| e.to_string()) } /// 删除素材命令 #[command] pub async fn delete_material( state: State<'_, AppState>, id: String, ) -> Result<(), String> { let repository_guard = state.get_material_repository() .map_err(|e| format!("获取素材仓库失败: {}", e))?; let repository = repository_guard.as_ref() .ok_or("素材仓库未初始化")?; MaterialService::delete_material(repository, &id) .await .map_err(|e| e.to_string()) } ``` ### 命令设计原则 1. **参数验证**: 严格验证输入参数 2. **错误转换**: 将内部错误转换为用户友好的消息 3. **状态管理**: 正确使用Tauri状态管理 4. **异步处理**: 所有命令都是异步的 5. **文档**: 详细的命令文档 ## 错误处理规范 ### 错误类型定义 ```rust use thiserror::Error; /// 业务错误类型 #[derive(Error, Debug)] pub enum BusinessError { #[error("验证错误: {message}")] ValidationError { message: String }, #[error("资源不存在: {resource}")] NotFound { resource: String }, #[error("权限不足: {operation}")] PermissionDenied { operation: String }, #[error("业务规则冲突: {rule}")] BusinessRuleViolation { rule: String }, #[error("外部服务错误: {service} - {message}")] ExternalServiceError { service: String, message: String }, } /// 数据库错误类型 #[derive(Error, Debug)] pub enum DatabaseError { #[error("连接错误: {0}")] ConnectionError(#[from] rusqlite::Error), #[error("事务错误: {message}")] TransactionError { message: String }, #[error("查询错误: {query}")] QueryError { query: String }, #[error("数据完整性错误: {constraint}")] IntegrityError { constraint: String }, } ``` ### 错误处理模式 ```rust use anyhow::{Context, Result}; /// 带上下文的错误处理 pub async fn process_material(id: &str) -> Result { let material = repository.get_by_id(id) .await .with_context(|| format!("获取素材失败: {}", id))? .ok_or_else(|| anyhow!("素材不存在: {}", id))?; validate_material(&material) .with_context(|| "素材验证失败")?; Ok(material) } /// 错误转换 impl From for String { fn from(error: BusinessError) -> Self { match error { BusinessError::ValidationError { message } => { format!("输入验证失败: {}", message) } BusinessError::NotFound { resource } => { format!("找不到资源: {}", resource) } _ => error.to_string(), } } } ``` ## 测试规范 ### 单元测试模板 ```rust #[cfg(test)] mod tests { use super::*; use crate::infrastructure::database::Database; use std::sync::Arc; async fn setup_test_database() -> Arc { let db = Database::new_in_memory().unwrap(); db.migrate().await.unwrap(); Arc::new(db) } #[tokio::test] async fn test_create_material_success() { // Arrange let db = setup_test_database().await; let repository = MaterialRepository::new(db); let request = CreateMaterialRequest { name: "Test Material".to_string(), path: "/test/path".to_string(), material_type: MaterialType::Video, size: 1024, duration: Some(60), project_id: "project-1".to_string(), }; // Act let result = MaterialService::create_material(&repository, request).await; // Assert assert!(result.is_ok()); let material = result.unwrap(); assert_eq!(material.name, "Test Material"); assert_eq!(material.size, 1024); } #[tokio::test] async fn test_create_material_validation_error() { // Arrange let db = setup_test_database().await; let repository = MaterialRepository::new(db); let request = CreateMaterialRequest { name: "".to_string(), // 空名称应该失败 path: "/test/path".to_string(), material_type: MaterialType::Video, size: 1024, duration: Some(60), project_id: "project-1".to_string(), }; // Act let result = MaterialService::create_material(&repository, request).await; // Assert assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("名称不能为空")); } } ``` ### 集成测试 ```rust #[cfg(test)] mod integration_tests { use super::*; use crate::app_state::AppState; use tauri::test::{mock_app, MockRuntime}; #[tokio::test] async fn test_material_crud_workflow() { // 设置测试应用 let app = mock_app(); let state = AppState::new(); // 测试创建 let create_request = CreateMaterialRequest { name: "Integration Test Material".to_string(), path: "/test/integration".to_string(), material_type: MaterialType::Video, size: 2048, duration: Some(120), project_id: "integration-project".to_string(), }; let created = create_material(state.clone(), create_request).await.unwrap(); // 测试读取 let retrieved = get_material_by_id(state.clone(), created.id.clone()).await.unwrap(); assert!(retrieved.is_some()); // 测试更新 let update_request = UpdateMaterialRequest { name: Some("Updated Material".to_string()), material_type: None, }; let updated = update_material(state.clone(), created.id.clone(), update_request).await.unwrap(); assert_eq!(updated.name, "Updated Material"); // 测试删除 delete_material(state.clone(), created.id).await.unwrap(); } } ``` ## 性能优化规范 ### 数据库优化 ```rust // 使用连接池 pub struct Database { pool: Arc, } impl Database { pub async fn get_connection(&self) -> Result { self.pool.get().await .map_err(|e| anyhow!("获取数据库连接失败: {}", e)) } // 读写分离 pub async fn get_read_connection(&self) -> Result { self.pool.get_read_only().await .map_err(|e| anyhow!("获取只读连接失败: {}", e)) } } // 批量操作优化 impl MaterialRepository { pub async fn batch_create(&self, materials: &[Material]) -> Result<()> { let conn = self.database.get_connection().await?; let tx = conn.transaction()?; for material in materials { tx.execute( "INSERT INTO materials (...) VALUES (...)", // 参数 )?; } tx.commit()?; Ok(()) } } ``` ### 内存优化 ```rust // 使用流式处理大量数据 pub async fn process_large_dataset( repository: &MaterialRepository, processor: F, ) -> Result<()> where F: Fn(&Material) -> Result<()>, { let mut offset = 0; const BATCH_SIZE: usize = 100; loop { let materials = repository.get_batch(offset, BATCH_SIZE).await?; if materials.is_empty() { break; } for material in materials { processor(&material)?; } offset += BATCH_SIZE; } Ok(()) } ``` ## 日志和监控规范 ### 结构化日志 ```rust use tracing::{info, warn, error, debug, instrument}; #[instrument(skip(repository))] pub async fn create_material( repository: &MaterialRepository, request: CreateMaterialRequest, ) -> Result { info!( material_name = %request.name, project_id = %request.project_id, "开始创建素材" ); let material = Material::new(request); match repository.create(&material).await { Ok(_) => { info!( material_id = %material.id, material_name = %material.name, "素材创建成功" ); Ok(material) } Err(e) => { error!( error = %e, material_name = %material.name, "素材创建失败" ); Err(e) } } } ``` ### 性能监控 ```rust use std::time::Instant; pub struct PerformanceMonitor { metrics: Arc>>>, } impl PerformanceMonitor { pub fn record_operation(&self, operation: &str, f: F) -> R where F: FnOnce() -> R, { let start = Instant::now(); let result = f(); let duration = start.elapsed(); let mut metrics = self.metrics.lock().unwrap(); metrics.entry(operation.to_string()) .or_insert_with(Vec::new) .push(duration); result } } ``` ## 安全规范 ### 输入验证 ```rust use validator::{Validate, ValidationError}; #[derive(Debug, Deserialize, Validate)] pub struct CreateMaterialRequest { #[validate(length(min = 1, max = 255, message = "名称长度必须在1-255字符之间"))] pub name: String, #[validate(custom = "validate_file_path")] pub path: String, #[validate(range(min = 1, message = "文件大小必须大于0"))] pub size: u64, } fn validate_file_path(path: &str) -> Result<(), ValidationError> { if !std::path::Path::new(path).exists() { return Err(ValidationError::new("文件不存在")); } // 防止路径遍历攻击 if path.contains("..") { return Err(ValidationError::new("非法路径")); } Ok(()) } ``` ### SQL注入防护 ```rust // ✅ 使用参数化查询 conn.execute( "SELECT * FROM materials WHERE name = ?1 AND project_id = ?2", params![name, project_id], )?; // ❌ 避免字符串拼接 // let sql = format!("SELECT * FROM materials WHERE name = '{}'", name); ``` ## 代码质量规范 ### Clippy配置 ```toml # Cargo.toml [lints.clippy] all = "warn" pedantic = "warn" nursery = "warn" cargo = "warn" # 允许的lint too_many_arguments = "allow" module_name_repetitions = "allow" ``` ### 代码审查清单 - [ ] 是否遵循四层架构设计 - [ ] 错误处理是否完整 - [ ] 是否有适当的日志记录 - [ ] 数据库操作是否优化 - [ ] 是否有相应的测试覆盖 - [ ] 文档注释是否完整 - [ ] 是否符合Rust最佳实践 - [ ] 输入验证是否完整 - [ ] 是否存在安全漏洞