# 数据库设计 本文档介绍 Flash Send 的 SQLite 数据库设计。 ## 概述 Flash Send 使用 SQLite 作为本地数据库,存储: - 聊天消息记录 - 文件传输记录 - 已知设备信息 - 应用设置 **数据库文件位置**: `{app_data_dir}/flash_send.db` ## 表结构 ### chat_messages 存储聊天消息。 ```sql CREATE TABLE IF NOT EXISTS chat_messages ( id TEXT PRIMARY KEY, -- 消息唯一 ID (UUID) from_device TEXT NOT NULL, -- 发送方设备 ID to_device TEXT NOT NULL, -- 接收方设备 ID content TEXT NOT NULL, -- 消息内容 message_type TEXT NOT NULL, -- 消息类型 (text/image/file/system) timestamp INTEGER NOT NULL, -- 时间戳 (毫秒) is_read INTEGER DEFAULT 0 -- 是否已读 (0/1) ); -- 索引:按设备和时间查询 CREATE INDEX IF NOT EXISTS idx_messages_devices ON chat_messages(from_device, to_device); CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON chat_messages(timestamp); ``` **字段说明**: | 字段 | 类型 | 说明 | |------|------|------| | id | TEXT | 消息 UUID | | from_device | TEXT | 发送方设备 ID | | to_device | TEXT | 接收方设备 ID | | content | TEXT | 消息内容 | | message_type | TEXT | text/image/file/system | | timestamp | INTEGER | Unix 时间戳(毫秒) | | is_read | INTEGER | 0=未读, 1=已读 | --- ### file_transfers 存储文件传输记录。 ```sql CREATE TABLE IF NOT EXISTS file_transfers ( file_id TEXT PRIMARY KEY, -- 传输唯一 ID (UUID) name TEXT NOT NULL, -- 文件名 size INTEGER NOT NULL, -- 文件大小 (字节) progress REAL DEFAULT 0, -- 传输进度 (0.0-1.0) status TEXT NOT NULL, -- 传输状态 (JSON) mime_type TEXT, -- MIME 类型 from_device TEXT NOT NULL, -- 发送方设备 ID to_device TEXT NOT NULL, -- 接收方设备 ID local_path TEXT, -- 本地文件路径 created_at INTEGER NOT NULL, -- 创建时间 completed_at INTEGER, -- 完成时间 transferred_bytes INTEGER DEFAULT 0 -- 已传输字节数 ); -- 索引:按设备查询 CREATE INDEX IF NOT EXISTS idx_transfers_devices ON file_transfers(from_device, to_device); CREATE INDEX IF NOT EXISTS idx_transfers_created ON file_transfers(created_at); ``` **字段说明**: | 字段 | 类型 | 说明 | |------|------|------| | file_id | TEXT | 传输 UUID | | name | TEXT | 文件名 | | size | INTEGER | 文件大小(字节) | | progress | REAL | 进度 0.0-1.0 | | status | TEXT | JSON: "pending"/"transferring"/"completed"/"failed"/"cancelled" | | mime_type | TEXT | MIME 类型 | | from_device | TEXT | 发送方设备 ID | | to_device | TEXT | 接收方设备 ID | | local_path | TEXT | 本地存储路径 | | created_at | INTEGER | 创建时间戳 | | completed_at | INTEGER | 完成时间戳 | | transferred_bytes | INTEGER | 已传输字节数 | **status 字段格式**: ```json "\"pending\"" "\"transferring\"" "\"completed\"" "\"failed\"" "\"cancelled\"" ``` --- ### known_devices 存储已知设备信息。 ```sql CREATE TABLE IF NOT EXISTS known_devices ( device_id TEXT PRIMARY KEY, -- 设备唯一 ID device_name TEXT NOT NULL, -- 设备名称 ip TEXT, -- 最后已知 IP ws_port INTEGER, -- WebSocket 端口 http_port INTEGER, -- HTTP 端口 last_seen INTEGER -- 最后在线时间 ); ``` **字段说明**: | 字段 | 类型 | 说明 | |------|------|------| | device_id | TEXT | 设备 UUID | | device_name | TEXT | 显示名称 | | ip | TEXT | IP 地址 | | ws_port | INTEGER | WebSocket 端口 | | http_port | INTEGER | HTTP 端口 | | last_seen | INTEGER | 最后在线时间戳 | --- ### app_settings 存储应用设置。 ```sql CREATE TABLE IF NOT EXISTS app_settings ( key TEXT PRIMARY KEY, -- 设置键名 value TEXT -- 设置值 ); ``` **常用设置键**: | 键名 | 说明 | 示例值 | |------|------|--------| | theme | 主题模式 | system/light/dark | | device_name | 设备名称 | My Computer | | download_dir | 下载目录 | C:\Downloads | | language | 语言 | zh-CN | --- ## 数据操作 ### 消息操作 #### 保存消息 ```rust pub fn save_message(message: &ChatMessage) -> Result<()> { conn.execute( "INSERT INTO chat_messages (id, from_device, to_device, content, message_type, timestamp, is_read) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", params![ message.id, message.from_device, message.to_device, message.content, message.message_type.to_string(), message.timestamp, message.is_read as i32, ], )?; Ok(()) } ``` #### 获取聊天历史 ```rust pub fn get_chat_history( device_id: &str, local_device_id: &str, limit: usize, offset: usize, ) -> Result> { let mut stmt = conn.prepare( "SELECT id, from_device, to_device, content, message_type, timestamp, is_read FROM chat_messages WHERE (from_device = ?1 AND to_device = ?2) OR (from_device = ?2 AND to_device = ?1) ORDER BY timestamp DESC LIMIT ?3 OFFSET ?4" )?; // ... } ``` #### 删除聊天历史 ```rust pub fn delete_chat_history(device_id: &str, local_device_id: &str) -> Result<()> { conn.execute( "DELETE FROM chat_messages WHERE (from_device = ?1 AND to_device = ?2) OR (from_device = ?2 AND to_device = ?1)", params![device_id, local_device_id], )?; Ok(()) } ``` --- ### 传输操作 #### 保存传输记录 ```rust pub fn save_file_transfer(transfer: &FileTransfer) -> Result<()> { conn.execute( "INSERT INTO file_transfers (file_id, name, size, progress, status, mime_type, from_device, to_device, local_path, created_at, completed_at, transferred_bytes) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12) ON CONFLICT(file_id) DO UPDATE SET progress = ?4, status = ?5, local_path = ?9, completed_at = ?11, transferred_bytes = ?12", params![...], )?; Ok(()) } ``` #### 更新传输进度 ```rust pub fn update_transfer_progress( file_id: &str, progress: f64, transferred_bytes: u64, status: &str, ) -> Result<()> { let completed_at = if status == "completed" || status == "failed" { Some(chrono::Utc::now().timestamp()) } else { None }; conn.execute( "UPDATE file_transfers SET progress = ?1, transferred_bytes = ?2, status = ?3, completed_at = ?4 WHERE file_id = ?5", params![progress, transferred_bytes, status, completed_at, file_id], )?; Ok(()) } ``` #### 获取传输历史 ```rust pub fn get_transfer_history( device_id: &str, local_device_id: &str, limit: usize, ) -> Result> { let mut stmt = conn.prepare( "SELECT file_id, name, size, progress, status, mime_type, from_device, to_device, local_path, created_at, completed_at, transferred_bytes FROM file_transfers WHERE (from_device = ?1 AND to_device = ?2) OR (from_device = ?2 AND to_device = ?1) ORDER BY created_at DESC LIMIT ?3" )?; // ... } ``` --- ### 设备操作 #### 保存已知设备 ```rust pub fn save_known_device(device: &DeviceInfo) -> Result<()> { conn.execute( "INSERT INTO known_devices (device_id, device_name, ip, ws_port, http_port, last_seen) VALUES (?1, ?2, ?3, ?4, ?5, ?6) ON CONFLICT(device_id) DO UPDATE SET device_name = ?2, ip = ?3, ws_port = ?4, http_port = ?5, last_seen = ?6", params![...], )?; Ok(()) } ``` --- ### 设置操作 #### 获取设置 ```rust pub fn get_setting(key: &str) -> Result> { let mut stmt = conn.prepare( "SELECT value FROM app_settings WHERE key = ?1" )?; stmt.query_row(params![key], |row| row.get(0)).optional() } ``` #### 保存设置 ```rust pub fn set_setting(key: &str, value: &str) -> Result<()> { conn.execute( "INSERT INTO app_settings (key, value) VALUES (?1, ?2) ON CONFLICT(key) DO UPDATE SET value = ?2", params![key, value], )?; Ok(()) } ``` --- ## 数据库迁移 ### 迁移机制 使用版本号管理数据库迁移: ```rust pub fn init_database(conn: &Connection) -> Result<()> { // 创建版本表 conn.execute( "CREATE TABLE IF NOT EXISTS schema_version (version INTEGER)", [], )?; let version: i32 = conn .query_row("SELECT version FROM schema_version", [], |row| row.get(0)) .unwrap_or(0); // 根据版本执行迁移 if version < 1 { // 创建初始表结构 create_initial_tables(conn)?; update_version(conn, 1)?; } if version < 2 { // 添加 transferred_bytes 字段 add_transferred_bytes_column(conn)?; update_version(conn, 2)?; } Ok(()) } ``` ### 迁移示例 添加 `transferred_bytes` 字段: ```rust fn add_transferred_bytes_column(conn: &Connection) -> Result<()> { // 检查字段是否存在 let columns: Vec = conn .prepare("PRAGMA table_info(file_transfers)")? .query_map([], |row| row.get::<_, String>(1))? .collect::, _>>()?; if !columns.contains(&"transferred_bytes".to_string()) { conn.execute( "ALTER TABLE file_transfers ADD COLUMN transferred_bytes INTEGER DEFAULT 0", [], )?; } Ok(()) } ``` --- ## 数据清理 ### 自动清理策略 - **消息**: 保留最近 1000 条/设备 - **传输记录**: 保留最近 100 条/设备 - **离线设备**: 30 天未见自动删除 ### 手动清理 ```rust // 清理指定设备的所有数据 pub fn cleanup_device_data(device_id: &str) -> Result<()> { conn.execute("DELETE FROM chat_messages WHERE from_device = ?1 OR to_device = ?1", [device_id])?; conn.execute("DELETE FROM file_transfers WHERE from_device = ?1 OR to_device = ?1", [device_id])?; conn.execute("DELETE FROM known_devices WHERE device_id = ?1", [device_id])?; Ok(()) } ``` --- ## 性能优化 ### 索引策略 - 按设备 ID 查询:`idx_messages_devices`, `idx_transfers_devices` - 按时间排序:`idx_messages_timestamp`, `idx_transfers_created` ### 连接池 使用全局单例连接,通过 `parking_lot::Mutex` 保护: ```rust static DB_CONN: OnceCell> = OnceCell::new(); pub fn conn() -> MutexGuard<'static, Connection> { DB_CONN.get().expect("Database not initialized").lock() } ``` ### 事务使用 批量操作使用事务提升性能: ```rust conn.execute("BEGIN TRANSACTION", [])?; for message in messages { save_message(&message)?; } conn.execute("COMMIT", [])?; ```