# 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<u64>,
    pub project_id: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    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<u64>,
    pub project_id: String,
}

/// 更新素材请求
#[derive(Debug, Deserialize)]
pub struct UpdateMaterialRequest {
    pub name: Option<String>,
    pub material_type: Option<MaterialType>,
}
```

### 模型设计原则
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<Option<Material>>;
    async fn get_all(&self) -> Result<Vec<Material>>;
    async fn get_by_project_id(&self, project_id: &str) -> Result<Vec<Material>>;
    async fn update(&self, material: &Material) -> Result<()>;
    async fn delete(&self, id: &str) -> Result<()>;
    async fn search(&self, query: &MaterialQueryParams) -> Result<Vec<Material>>;
}

/// 素材仓库实现
/// 遵循 Tauri 开发规范的仓库模式
pub struct MaterialRepository {
    database: Arc<Database>,
}

impl MaterialRepository {
    pub fn new(database: Arc<Database>) -> 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<Option<Material>> {
        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<Material> {
        // 业务验证
        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<Option<Material>> {
        repository.get_by_id(id).await
            .map_err(|e| anyhow!("获取素材详情失败: {}", e))
    }
    
    /// 更新素材
    pub async fn update_material(
        repository: &MaterialRepository,
        id: &str,
        request: UpdateMaterialRequest,
    ) -> Result<Material> {
        // 获取现有素材
        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<Material, String> {
    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<Vec<Material>, 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<Option<Material>, 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<Material, String> {
    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<Material> {
    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<BusinessError> 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<Database> {
        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<ConnectionPool>,
}

impl Database {
    pub async fn get_connection(&self) -> Result<PooledConnection> {
        self.pool.get().await
            .map_err(|e| anyhow!("获取数据库连接失败: {}", e))
    }
    
    // 读写分离
    pub async fn get_read_connection(&self) -> Result<PooledConnection> {
        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<F>(
    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<Material> {
    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<Mutex<HashMap<String, Vec<Duration>>>>,
}

impl PerformanceMonitor {
    pub fn record_operation<F, R>(&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最佳实践
- [ ] 输入验证是否完整
- [ ] 是否存在安全漏洞
