# MixVideo 数据库设计规范

## 数据库技术栈

### 核心技术
- **SQLite 3.40+**: 嵌入式数据库
- **WAL模式**: 提高并发性能
- **外键约束**: 保证数据一致性
- **连接池**: 支持高并发访问
- **读写分离**: 优化查询性能

### 数据库配置
```sql
-- 启用WAL模式
PRAGMA journal_mode = WAL;

-- 启用外键约束
PRAGMA foreign_keys = ON;

-- 设置同步模式
PRAGMA synchronous = NORMAL;

-- 设置缓存大小
PRAGMA cache_size = 10000;

-- 设置超时时间
PRAGMA busy_timeout = 5000;

-- 启用安全删除
PRAGMA secure_delete = ON;

-- 设置临时存储
PRAGMA temp_store = MEMORY;
```

## 表设计规范

### 命名规范
- **表名**: snake_case复数形式 (如 `materials`, `projects`)
- **字段名**: snake_case (如 `created_at`, `project_id`)
- **索引名**: `idx_表名_字段名` (如 `idx_materials_project_id`)
- **外键名**: `fk_表名_引用表名` (如 `fk_materials_projects`)

### 字段设计规范
```sql
-- 主键设计
id TEXT PRIMARY KEY NOT NULL,  -- 使用UUID字符串

-- 时间戳字段
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),

-- 软删除字段
is_active INTEGER NOT NULL DEFAULT 1,

-- 外键字段
project_id TEXT NOT NULL,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,

-- 枚举字段（使用TEXT存储JSON）
material_type TEXT NOT NULL CHECK (material_type IN ('video', 'audio', 'image')),

-- 可选字段
description TEXT,
duration INTEGER,  -- 可为NULL

-- 文件路径字段
path TEXT NOT NULL,
size INTEGER NOT NULL CHECK (size > 0),
```

## 核心表结构

### 1. 项目表 (projects)
```sql
CREATE TABLE projects (
    id TEXT PRIMARY KEY NOT NULL,
    name TEXT NOT NULL,
    description TEXT,
    path TEXT NOT NULL UNIQUE,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    is_active INTEGER NOT NULL DEFAULT 1
);

-- 索引
CREATE INDEX idx_projects_name ON projects(name);
CREATE INDEX idx_projects_path ON projects(path);
CREATE INDEX idx_projects_created_at ON projects(created_at);
```

### 2. 素材表 (materials)
```sql
CREATE TABLE materials (
    id TEXT PRIMARY KEY NOT NULL,
    name TEXT NOT NULL,
    path TEXT NOT NULL,
    material_type TEXT NOT NULL CHECK (material_type IN ('video', 'audio', 'image')),
    size INTEGER NOT NULL CHECK (size > 0),
    duration INTEGER,  -- 视频/音频时长(秒)
    width INTEGER,     -- 图片/视频宽度
    height INTEGER,    -- 图片/视频高度
    fps REAL,          -- 视频帧率
    project_id TEXT NOT NULL,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    is_active INTEGER NOT NULL DEFAULT 1,
    
    FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
);

-- 索引
CREATE INDEX idx_materials_project_id ON materials(project_id);
CREATE INDEX idx_materials_type ON materials(material_type);
CREATE INDEX idx_materials_name ON materials(name);
CREATE INDEX idx_materials_created_at ON materials(created_at);
CREATE UNIQUE INDEX idx_materials_path_project ON materials(path, project_id);
```

### 3. 模特表 (models)
```sql
CREATE TABLE models (
    id TEXT PRIMARY KEY NOT NULL,
    name TEXT NOT NULL,
    stage_name TEXT,
    gender TEXT NOT NULL CHECK (gender IN ('male', 'female', 'other')),
    age INTEGER CHECK (age > 0 AND age < 150),
    height INTEGER CHECK (height > 0 AND height < 300),  -- 厘米
    weight INTEGER CHECK (weight > 0 AND weight < 500),  -- 公斤
    measurements TEXT,  -- JSON格式存储三围
    description TEXT,
    tags TEXT,  -- JSON数组格式存储标签
    avatar_path TEXT,
    contact_info TEXT,  -- JSON格式存储联系信息
    social_media TEXT,  -- JSON格式存储社交媒体
    status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'archived')),
    rating REAL CHECK (rating >= 1.0 AND rating <= 5.0),
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    is_active INTEGER NOT NULL DEFAULT 1
);

-- 索引
CREATE INDEX idx_models_name ON models(name);
CREATE INDEX idx_models_gender ON models(gender);
CREATE INDEX idx_models_status ON models(status);
CREATE INDEX idx_models_rating ON models(rating);
```

### 4. 模特照片表 (model_photos)
```sql
CREATE TABLE model_photos (
    id TEXT PRIMARY KEY NOT NULL,
    model_id TEXT NOT NULL,
    photo_type TEXT NOT NULL CHECK (photo_type IN ('portrait', 'full_body', 'casual', 'formal', 'swimwear', 'other')),
    path TEXT NOT NULL,
    description TEXT,
    is_primary INTEGER NOT NULL DEFAULT 0,  -- 是否为主要照片
    sort_order INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    is_active INTEGER NOT NULL DEFAULT 1,
    
    FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE
);

-- 索引
CREATE INDEX idx_model_photos_model_id ON model_photos(model_id);
CREATE INDEX idx_model_photos_type ON model_photos(photo_type);
CREATE INDEX idx_model_photos_primary ON model_photos(is_primary);
CREATE UNIQUE INDEX idx_model_photos_path ON model_photos(path);
```

### 5. 模板表 (templates)
```sql
CREATE TABLE templates (
    id TEXT PRIMARY KEY NOT NULL,
    name TEXT NOT NULL,
    description TEXT,
    canvas_config TEXT NOT NULL,  -- JSON格式存储画布配置
    duration INTEGER NOT NULL,    -- 模板总时长(微秒)
    fps REAL NOT NULL,
    materials TEXT,  -- JSON格式存储模板素材
    tracks TEXT,     -- JSON格式存储轨道信息
    import_status TEXT NOT NULL DEFAULT 'pending' CHECK (import_status IN ('pending', 'processing', 'completed', 'failed')),
    source_file_path TEXT,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    is_active INTEGER NOT NULL DEFAULT 1
);

-- 索引
CREATE INDEX idx_templates_name ON templates(name);
CREATE INDEX idx_templates_status ON templates(import_status);
CREATE INDEX idx_templates_created_at ON templates(created_at);
```

### 6. AI分类记录表 (video_classifications)
```sql
CREATE TABLE video_classifications (
    id TEXT PRIMARY KEY NOT NULL,
    material_id TEXT NOT NULL,
    classification_result TEXT NOT NULL,  -- JSON格式存储分类结果
    confidence_score REAL,
    processing_time INTEGER,  -- 处理时间(毫秒)
    ai_model_version TEXT,
    status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
    error_message TEXT,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    
    FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE CASCADE
);

-- 索引
CREATE INDEX idx_video_classifications_material_id ON video_classifications(material_id);
CREATE INDEX idx_video_classifications_status ON video_classifications(status);
CREATE INDEX idx_video_classifications_created_at ON video_classifications(created_at);
```

### 7. 穿搭图片表 (outfit_images)
```sql
CREATE TABLE outfit_images (
    id TEXT PRIMARY KEY NOT NULL,
    model_id TEXT NOT NULL,
    image_path TEXT NOT NULL,
    prompt TEXT,
    negative_prompt TEXT,
    generation_params TEXT,  -- JSON格式存储生成参数
    is_favorite INTEGER NOT NULL DEFAULT 0,
    tags TEXT,  -- JSON数组格式存储标签
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    is_active INTEGER NOT NULL DEFAULT 1,
    
    FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE
);

-- 索引
CREATE INDEX idx_outfit_images_model_id ON outfit_images(model_id);
CREATE INDEX idx_outfit_images_favorite ON outfit_images(is_favorite);
CREATE INDEX idx_outfit_images_created_at ON outfit_images(created_at);
CREATE UNIQUE INDEX idx_outfit_images_path ON outfit_images(image_path);
```

### 8. 对话记录表 (conversations)
```sql
CREATE TABLE conversations (
    id TEXT PRIMARY KEY NOT NULL,
    session_id TEXT NOT NULL,
    role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
    content TEXT NOT NULL,
    metadata TEXT,  -- JSON格式存储元数据
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    is_active INTEGER NOT NULL DEFAULT 1
);

-- 索引
CREATE INDEX idx_conversations_session_id ON conversations(session_id);
CREATE INDEX idx_conversations_role ON conversations(role);
CREATE INDEX idx_conversations_created_at ON conversations(created_at);
```

## 数据库迁移规范

### 迁移文件结构
```
migrations/
├── 001_initial_schema.sql
├── 002_add_model_photos.sql
├── 003_add_video_classifications.sql
├── 004_add_outfit_images.sql
└── 005_add_conversations.sql
```

### 迁移脚本模板
```sql
-- Migration: 001_initial_schema.sql
-- Description: 创建初始数据库结构
-- Date: 2024-01-01

BEGIN TRANSACTION;

-- 创建项目表
CREATE TABLE projects (
    -- 表结构定义
);

-- 创建索引
CREATE INDEX idx_projects_name ON projects(name);

-- 插入初始数据（如果需要）
INSERT INTO projects (id, name, path) VALUES 
('default', 'Default Project', '/default/path');

COMMIT;
```

### 迁移管理
```rust
pub struct DatabaseMigration {
    version: u32,
    description: String,
    sql: String,
}

impl Database {
    pub async fn migrate(&self) -> Result<()> {
        // 创建迁移记录表
        self.create_migration_table().await?;
        
        // 获取当前版本
        let current_version = self.get_current_version().await?;
        
        // 执行待处理的迁移
        let migrations = self.get_pending_migrations(current_version)?;
        
        for migration in migrations {
            self.execute_migration(&migration).await?;
        }
        
        Ok(())
    }
}
```

## 查询优化规范

### 索引策略
```sql
-- 单列索引
CREATE INDEX idx_materials_project_id ON materials(project_id);

-- 复合索引
CREATE INDEX idx_materials_project_type ON materials(project_id, material_type);

-- 唯一索引
CREATE UNIQUE INDEX idx_projects_path ON projects(path);

-- 部分索引
CREATE INDEX idx_active_materials ON materials(project_id) WHERE is_active = 1;
```

### 查询优化
```sql
-- ✅ 使用索引的查询
SELECT * FROM materials 
WHERE project_id = ? AND material_type = 'video'
ORDER BY created_at DESC
LIMIT 20;

-- ✅ 使用EXISTS而不是IN
SELECT * FROM projects p
WHERE EXISTS (
    SELECT 1 FROM materials m 
    WHERE m.project_id = p.id AND m.is_active = 1
);

-- ✅ 使用适当的JOIN
SELECT p.name, COUNT(m.id) as material_count
FROM projects p
LEFT JOIN materials m ON p.id = m.project_id AND m.is_active = 1
WHERE p.is_active = 1
GROUP BY p.id, p.name;
```

### 分页查询
```sql
-- 基于OFFSET的分页（适用于小数据集）
SELECT * FROM materials
WHERE project_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?;

-- 基于游标的分页（适用于大数据集）
SELECT * FROM materials
WHERE project_id = ? AND created_at < ?
ORDER BY created_at DESC
LIMIT ?;
```

## 数据完整性规范

### 外键约束
```sql
-- 级联删除
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE

-- 限制删除
FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE RESTRICT

-- 设置NULL
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
```

### 检查约束
```sql
-- 数值范围检查
CHECK (age > 0 AND age < 150)

-- 枚举值检查
CHECK (status IN ('active', 'inactive', 'archived'))

-- 字符串长度检查
CHECK (length(name) > 0 AND length(name) <= 255)
```

### 触发器
```sql
-- 自动更新时间戳
CREATE TRIGGER update_materials_timestamp
AFTER UPDATE ON materials
FOR EACH ROW
BEGIN
    UPDATE materials SET updated_at = datetime('now') WHERE id = NEW.id;
END;

-- 软删除触发器
CREATE TRIGGER soft_delete_materials
INSTEAD OF DELETE ON materials
FOR EACH ROW
BEGIN
    UPDATE materials SET is_active = 0, updated_at = datetime('now') WHERE id = OLD.id;
END;
```

## 备份和恢复规范

### 备份策略
```rust
pub struct DatabaseBackup {
    source_path: PathBuf,
    backup_dir: PathBuf,
}

impl DatabaseBackup {
    pub async fn create_backup(&self) -> Result<PathBuf> {
        let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
        let backup_name = format!("mixvideo_backup_{}.db", timestamp);
        let backup_path = self.backup_dir.join(backup_name);
        
        // 使用SQLite的备份API
        let source = Connection::open(&self.source_path)?;
        let backup = Connection::open(&backup_path)?;
        
        let backup_handle = rusqlite::backup::Backup::new(&source, &backup)?;
        backup_handle.run_to_completion(5, Duration::from_millis(250), None)?;
        
        Ok(backup_path)
    }
    
    pub async fn restore_backup(&self, backup_path: &Path) -> Result<()> {
        // 验证备份文件
        self.validate_backup(backup_path)?;
        
        // 创建当前数据库的备份
        let current_backup = self.create_backup().await?;
        
        // 恢复数据库
        std::fs::copy(backup_path, &self.source_path)?;
        
        // 验证恢复的数据库
        self.validate_database().await?;
        
        Ok(())
    }
}
```

### 数据验证
```sql
-- 检查数据完整性
PRAGMA integrity_check;

-- 检查外键约束
PRAGMA foreign_key_check;

-- 分析数据库统计信息
ANALYZE;
```

## 性能监控规范

### 查询性能分析
```sql
-- 启用查询计划分析
EXPLAIN QUERY PLAN SELECT * FROM materials WHERE project_id = ?;

-- 查看表统计信息
SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'index';

-- 检查表大小
SELECT 
    name,
    COUNT(*) as row_count,
    SUM(length(sql)) as size_estimate
FROM sqlite_master 
WHERE type = 'table'
GROUP BY name;
```

### 性能监控
```rust
pub struct DatabaseMetrics {
    query_times: HashMap<String, Vec<Duration>>,
    connection_pool_stats: ConnectionPoolStats,
}

impl DatabaseMetrics {
    pub fn record_query_time(&mut self, query: &str, duration: Duration) {
        self.query_times
            .entry(query.to_string())
            .or_insert_with(Vec::new)
            .push(duration);
    }
    
    pub fn get_slow_queries(&self, threshold: Duration) -> Vec<(String, Duration)> {
        self.query_times
            .iter()
            .filter_map(|(query, times)| {
                let avg_time = times.iter().sum::<Duration>() / times.len() as u32;
                if avg_time > threshold {
                    Some((query.clone(), avg_time))
                } else {
                    None
                }
            })
            .collect()
    }
}
```

## 安全规范

### 数据加密
```rust
// 敏感数据加密存储
pub fn encrypt_sensitive_data(data: &str, key: &[u8]) -> Result<String> {
    // 使用AES加密
    let cipher = Aes256Gcm::new(key.into());
    let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
    let ciphertext = cipher.encrypt(&nonce, data.as_bytes())?;
    
    // 组合nonce和密文
    let mut result = nonce.to_vec();
    result.extend_from_slice(&ciphertext);
    
    Ok(base64::encode(result))
}
```

### 访问控制
```sql
-- 创建只读视图
CREATE VIEW materials_readonly AS
SELECT id, name, material_type, size, created_at
FROM materials
WHERE is_active = 1;

-- 限制敏感字段访问
CREATE VIEW models_public AS
SELECT id, name, stage_name, gender, description, tags, avatar_path, rating
FROM models
WHERE is_active = 1;
```

### 审计日志
```sql
CREATE TABLE audit_logs (
    id TEXT PRIMARY KEY NOT NULL,
    table_name TEXT NOT NULL,
    operation TEXT NOT NULL CHECK (operation IN ('INSERT', 'UPDATE', 'DELETE')),
    record_id TEXT NOT NULL,
    old_values TEXT,  -- JSON格式
    new_values TEXT,  -- JSON格式
    user_id TEXT,
    timestamp TEXT NOT NULL DEFAULT (datetime('now'))
);
```
