# FSKit 简化版数据验证指南 **日期**: 2026-05-18 **目标**: 验证 SQLite backend 数据正确性与完整性 --- ## 1. 数据结构说明 ### file_nodes 表结构 ```sql CREATE TABLE file_nodes ( node_id TEXT PRIMARY KEY, -- 节点唯一标识(UUID) label TEXT NOT NULL, -- 文件/文件夹名称 aliases_json TEXT, -- JSON格式的扩展信息 sha256 TEXT, -- SHA256 hash值 file_uuid TEXT, -- 文件UUID(可选) file_size INTEGER, -- 文件大小(字节) registered_at INTEGER, -- 注册时间(timestamp) parent_id TEXT, -- 父节点ID(NULL为根节点) children_json TEXT, -- 子节点列表(JSON) node_type TEXT NOT NULL, -- 节点类型:folder/file icon TEXT, -- 图标名称(可选) color TEXT, -- 颜色(可选) bg_color TEXT, -- 背景颜色(可选) created_at INTEGER, -- 创建时间 updated_at INTEGER, -- 更新时间 sort_order INTEGER DEFAULT 0 -- 排序顺序 ); ``` --- ## 2. 数据字段含义详解 ### node_id(节点唯一标识) **格式**: 32字符UUID **示例**: `8b1ede3cd6970f02fa85b8e34b682caf` **生成方式**: ```rust // src/scan.rs 中的生成逻辑 SHA256(path | filename | mac_address | mtime) .chars() .take(32) ``` **特性**: - ✅ 确定性(同一文件 = 同一UUID) - ✅ 唯一性(不同文件 = 不同UUID) - ✅ 支持增量导入(无需外部API) **用途**: - SQLite primary key - FSVolume query_node(node_id) - Finder 文件识别 --- ### label(文件/文件夹名称) **格式**: 文本字符串 **示例**: ``` "Test_Plan_ME5.docx" "Marketing" "Videos" "demo.mp4" ``` **特性**: - ✅ 用户可见名称 - ✅ 支持中文/英文/数字 - ✅ 包含文件扩展名 **用途**: - Finder 显示名称 - FSVolume enumerate_directory 输出 - 搜索与排序依据 --- ### node_type(节点类型) **格式**: 枚举值 **取值**: - `folder` - 资料夹节点 - `file` - 文件节点 **统计**(warren.sqlite): ``` folder: 801 个 file: 11857 个 total: 12659 个 ``` **用途**: - 决定 FSItem 类型(FSItemType::Directory / FSItemType::File) - 决定是否可枚举子节点 - 决定是否可读取文件内容 --- ### parent_id(父节点ID) **格式**: 32字符UUID 或 NULL **特性**: - NULL = 根节点(root folder) - 有值 = 子节点 **层级关系**: ``` root_id (NULL) ├── node_id_1 (parent_id = root_id) │ ├── node_id_2 (parent_id = node_id_1) │ └── node_id_3 (parent_id = node_id_1) └── node_id_4 (parent_id = root_id) ├── node_id_5 (parent_id = node_id_4) └── node_id_6 (parent_id = node_id_4) ``` **用途**: - 构建文件树结构 - query_children(parent_id) 查询 - 目录枚举逻辑 --- ### aliases_json(扩展信息) **格式**: JSON字符串 **结构**: ```json { "path": "/Users/accusys/momentry/var/sftpgo/data/warren/Test_Plan_ME5.docx", "alias_zh_tw": "测试计划", "alias_en": "Test Plan", "alias_ja": "テスト計画" } ``` **关键字段**: - `path` - 文件在磁盘上的实际路径(重要!) **用途**: - read_file(node_id) 读取文件内容 - 文件路径解析 - 多语言别名支持 --- ### file_size(文件大小) **格式**: 整数(字节) **示例**: ``` 1024 - 1KB 1048576 - 1MB 26214400 - 25MB ``` **特性**: - folder节点: NULL - file节点: 实际文件大小 **用途**: - FSVolume get_attributes 输出 - Finder 文件大小显示 - statfs 总大小统计 --- ### sha256(文件哈希) **格式**: 64字符十六进制字符串 **示例**: `355a063b697a812742fae2a021cdda5c355a063b697a812742fae2a021cdda5c` **生成方式**: ```rust // src/scan.rs hash 计算 use sha2::{Sha256, Digest}; let mut hasher = Sha256::new(); hasher.update(file_content); let hash = hasher.finalize(); ``` **用途**: - 文件完整性验证 - 重复文件检测 - 版本控制依据 --- ### created_at / updated_at(时间戳) **格式**: Unix timestamp(秒) **示例**: ``` 1715788800 - 2024-05-15 08:00:00 UTC 1744876800 - 2025-05-18 12:00:00 UTC ``` **用途**: - FSVolume get_attributes 输出 - Finder 创建/修改时间显示 - 文件排序依据 --- ## 3. 数据验证方法 ### 方法1:query_node(节点查询) **验证目标**: 确认节点存在且数据正确 **测试代码**: ```rust #[test] fn test_query_warren_root() { let fs = MarkBaseFS::new("warren", "data/users/warren.sqlite"); // 查询根节点 let root_id = "8b1ede3cd6970f02fa85b8e34b682caf"; let root = fs.query_node(root_id); assert!(root.is_some()); let root_node = root.unwrap(); assert_eq!(root_node.node_type, "folder"); assert!(root_node.label.contains("Home")); } #[test] fn test_query_warren_file() { let fs = MarkBaseFS::new("warren", "data/users/warren.sqlite"); // 查询文件节点 let file_id = "test_file_node_id"; let file = fs.query_node(file_id); assert!(file.is_some()); let file_node = file.unwrap(); assert_eq!(file_node.node_type, "file"); assert!(file_node.file_size.is_some()); assert!(file_node.file_size.unwrap() > 0); } ``` **预期结果**: - ✅ 根节点存在 - ✅ node_type正确 - ✅ label包含用户ID --- ### 方法2:query_children(子节点查询) **验证目标**: 确认层级关系正确 **测试代码**: ```rust #[test] fn test_query_warren_children() { let fs = MarkBaseFS::new("warren", "data/users/warren.sqlite"); // 查询根节点的子节点 let root_id = "root_node_id"; let children = fs.query_children(root_id); // 预期:801 folders + 11857 files assert!(children.len() > 1000); // 检查子节点类型 let folders = children.iter().filter(|c| c.node_type == "folder").count(); let files = children.iter().filter(|c| c.node_type == "file").count(); println!("Folders: {}, Files: {}", folders, files); assert!(folders > 0); assert!(files > folders); } ``` **预期结果**: - ✅ 子节点数量正确(>1000) - ✅ folders + files = 总节点数 - ✅ parent_id正确关联 --- ### 方法3:read_file(文件读取) **验证目标**: 确认 aliases.json.path 可读取 **测试代码**: ```rust #[test] fn test_read_warren_file() { let fs = MarkBaseFS::new("warren", "data/users/warren.sqlite"); // 选择一个小文件测试 let file_id = "small_text_file_node_id"; let content = fs.read_file(file_id); assert!(content.is_some()); let data = content.unwrap(); assert!(data.len() > 0); // 检查文件内容 let text = String::from_utf8(data).unwrap(); println!("File content: {}", text); } #[test] fn test_read_warren_large_file() { let fs = MarkBaseFS::new("warren", "data/users/warren.sqlite"); // 测试大文件(视频/图片) let video_id = "video_file_node_id"; let content = fs.read_file(video_id); assert!(content.is_some()); let data = content.unwrap(); assert!(data.len() > 1_000_000); // > 1MB } ``` **预期结果**: - ✅ 文件内容可读取 - ✅ 文件大小正确 - ✅ 文件路径解析成功 --- ### 方法4:statfs(统计验证) **验证目标**: 确认总体统计正确 **测试代码**: ```rust #[test] fn test_warren_statfs() { let conn = Connection::open("data/users/warren.sqlite").unwrap(); let volume = MarkBaseVolume::new(conn, "warren".to_string()); let (total_nodes, total_size) = volume.statfs(); println!("Total nodes: {}", total_nodes); println!("Total size: {} bytes ({:.2} GB)", total_size, total_size as f64 / 1_073_741_824.0 ); assert_eq!(total_nodes, 12659); assert!(total_size > 0); } ``` **预期结果**: - ✅ total_nodes = 12659 - ✅ total_size > 0 - ✅ 统计数据准确 --- ## 4. 数据验证步骤 ### Step 1:准备测试环境 **前提条件**: - ✅ warren.sqlite 存在(12MB) - ✅ MarkBaseFS struct 编译成功 - ✅ SQLite connection 可用 **检查数据库**: ```bash # 确认数据库存在 ls -lh data/users/warren.sqlite # 检查节点总数 sqlite3 data/users/warren.sqlite "SELECT COUNT(*) FROM file_nodes" # 检查节点类型分布 sqlite3 data/users/warren.sqlite " SELECT node_type, COUNT(*) FROM file_nodes GROUP BY node_type " ``` --- ### Step 2:执行基础验证 **测试命令**: ```bash cargo test --lib fskit::filesystem::test_query_warren_root cargo test --lib fskit::filesystem::test_query_warren_children cargo test --lib fskit::filesystem::test_read_warren_file cargo test --lib fskit::volume::test_warren_statfs ``` **验证点**: 1. ✅ query_node 返回正确节点 2. ✅ query_children 返回正确数量 3. ✅ read_file 返回正确内容 4. ✅ statfs 返回正确统计 --- ### Step 3:深度验证 **检查数据完整性**: ```bash # 检查所有节点都有 parent_id 关联 sqlite3 data/users/warren.sqlite " SELECT COUNT(*) FROM file_nodes WHERE parent_id IS NULL AND node_type = 'folder' " # 预期:至少有1个根节点(root folder) ``` **检查 aliases.json 完整性**: ```bash # 检查所有文件节点都有 path sqlite3 data/users/warren.sqlite " SELECT COUNT(*) FROM file_nodes WHERE node_type = 'file' AND aliases_json IS NOT NULL AND aliases_json LIKE '%path%' " # 预期:接近 11857(所有文件节点) ``` **检查文件大小一致性**: ```bash # 检查 file_size 与实际文件大小匹配 sqlite3 data/users/warren.sqlite " SELECT node_id, label, file_size, aliases_json FROM file_nodes WHERE node_type = 'file' AND file_size IS NOT NULL LIMIT 5 " ``` --- ## 5. 数据意义详解 ### 节点ID(node_id)的意义 **技术意义**: - SQLite primary key - 文件系统唯一标识 - 支持快速查询(索引) **业务意义**: - 用户文件追踪 - 版本控制依据 - 增量导入识别 **示例数据**: ``` node_id: 8b1ede3cd6970f02fa85b8e34b682caf 含义: Home文件夹的唯一标识 用途: - query_node(node_id) → 查询节点详情 - query_children(node_id) → 查询子节点 - Finder 显示文件树 ``` --- ### 标签(label)的意义 **技术意义**: - 用户可见名称 - 文件系统显示名称 - 搜索索引依据 **业务意义**: - 用户文件命名 - 多语言支持(aliases) - 业务分类依据 **示例数据**: ``` label: Test_Plan_ME5.docx 含义: 文件名称 + 扩展名 用途: - Finder 显示名称 - 文件搜索 - 业务文档识别 ``` --- ### 父节点ID(parent_id)的意义 **技术意义**: - 文件树结构构建 - 层级关系维护 - 递归查询依据 **业务意义**: - 文件组织结构 - 用户文件夹层次 - 业务分类层级 **示例数据**: ``` parent_id: 8b1ede3cd6970f02fa85b8e34b682caf 含义: 该节点的父文件夹 用途: - query_children(parent_id) → 枚举子节点 - 构建文件树 - 层级导航 ``` --- ### 别名JSON(aliases_json)的意义 **技术意义**: - 文件路径解析 - 多语言支持 - 扩展信息存储 **业务意义**: - 实际文件位置 - 用户访问路径 - 业务元数据 **示例数据**: ```json { "path": "/Users/accusys/momentry/var/sftpgo/data/warren/Test_Plan_ME5.docx", "alias_zh_tw": "测试计划" } ``` **path字段意义**: - 文件在磁盘上的实际位置 - read_file(node_id) 读取依据 - 文件访问路径 --- ### 文件大小(file_size)的意义 **技术意义**: - 文件大小统计 - 存储空间计算 - 传输进度依据 **业务意义**: - 用户文件大小 - 存储容量规划 - 业务文件规模 **示例数据**: ``` file_size: 26214400 (25MB) 含义: 文件占用空间 用途: - statfs 统计总大小 - Finder 显示文件大小 - AJA System Test 性能计算 ``` --- ## 6. 验证结果预期 ### 基础验证预期 **query_node验证**: ``` 测试:query_node("root_id") 预期:返回 Option 验证点: ├── node_id正确 ├── label正确 ├── node_type正确(folder/file) └── file_size正确(NULL for folder) ``` **query_children验证**: ``` 测试:query_children("root_id") 预期:返回 Vec 验证点: ├── 数量正确(801 folders + 11857 files) ├── 所有子节点 parent_id正确 └── 子节点类型正确 ``` **read_file验证**: ``` 测试:read_file("file_node_id") 预期:返回 Option> 验证点: ├── aliases_json.path存在 ├── 文件可读取 ├── 内容大小正确 └── 内容可解码(文本文件) ``` **statfs验证**: ``` 测试:statfs() 预期:返回 (total_nodes, total_size) 验证点: ├── total_nodes = 12659 ├── total_size > 0 └── 统计数据准确 ``` --- ### 深度验证预期 **数据一致性验证**: ``` 测试:所有节点都有 parent_id 预期:至少有1个 root folder 验证:parent_id IS NULL 节点数量 >= 1 测试:所有文件都有 aliases_json 预期:11857 file节点都有 path 验证:aliases_json LIKE '%path%' 数量 = 11857 测试:文件大小与实际匹配 预期:file_size = std::fs::metadata(path).len() 验证:随机抽样10个文件验证 ``` --- ## 7. 数据验证流程图 ``` 数据验证流程: Step 1: 基础连接测试 ├── MarkBaseFS::new("warren", "warren.sqlite") ├── SQLite connection成功 └── Mutex可锁定 ✅ Step 2: 节点查询测试 ├── query_node("root_id") ├── 返回 Option ├── 检查 node_id, label, node_type └── 验证数据正确 ✅ Step 3: 子节点查询测试 ├── query_children("root_id") ├── 返回 Vec ├── 检查数量(801 + 11857) └── 验证层级关系 ✅ Step 4: 文件读取测试 ├── read_file("file_node_id") ├── 解析 aliases_json.path ├── std::fs::read(path) └── 验证文件内容 ✅ Step 5: 统计验证测试 ├── statfs() ├── 检查 total_nodes(12659) ├── 检查 total_size └── 验证统计数据 ✅ Step 6: 深度一致性测试 ├── 检查 parent_id 关联 ├── 检查 aliases_json 完整性 ├── 检查 file_size 一致性 └── 验证数据完整 ✅ ``` --- ## 8. 创建验证测试 **添加测试代码**: ```rust // src/fskit/filesystem.rs #[cfg(test)] mod warren_tests { use super::*; #[test] fn test_warren_database_connection() { let fs = MarkBaseFS::new("warren", "data/users/warren.sqlite"); assert_eq!(fs.user_id, "warren"); // 测试 SQLite connection 可用 let conn = fs.sqlite.lock().unwrap(); let count: i64 = conn.query_row( "SELECT COUNT(*) FROM file_nodes", [], |row| row.get(0) ).unwrap(); assert_eq!(count, 12659); } #[test] fn test_warren_query_root() { let fs = MarkBaseFS::new("warren", "data/users/warren.sqlite"); // 查询根节点 let conn = fs.sqlite.lock().unwrap(); let root_id: String = conn.query_row( "SELECT node_id FROM file_nodes WHERE parent_id IS NULL LIMIT 1", [], |row| row.get(0) ).unwrap(); let root = fs.query_node(&root_id); assert!(root.is_some()); let root_node = root.unwrap(); assert_eq!(root_node.node_type, "folder"); } #[test] fn test_warren_query_children() { let fs = MarkBaseFS::new("warren", "data/users/warren.sqlite"); // 查询根节点ID let conn = fs.sqlite.lock().unwrap(); let root_id: String = conn.query_row( "SELECT node_id FROM file_nodes WHERE parent_id IS NULL LIMIT 1", [], |row| row.get(0) ).unwrap(); let children = fs.query_children(&root_id); // 预期:至少有子节点 assert!(children.len() > 0); // 检查类型分布 let folders = children.iter().filter(|c| c.node_type == "folder").count(); let files = children.iter().filter(|c| c.node_type == "file").count(); println!("Root children: {} folders, {} files", folders, files); assert!(folders > 0); assert!(files > 0); } #[test] fn test_warren_read_text_file() { let fs = MarkBaseFS::new("warren", "data/users/warren.sqlite"); // 查找一个小文本文件 let conn = fs.sqlite.lock().unwrap(); let (node_id, aliases_json): (String, String) = conn.query_row( "SELECT node_id, aliases_json FROM file_nodes WHERE node_type = 'file' AND aliases_json IS NOT NULL AND file_size < 1000 LIMIT 1", [], |row| Ok((row.get(0)?, row.get(1)?)) ).unwrap(); // 解析 aliases_json let aliases: serde_json::Value = serde_json::from_str(&aliases_json).unwrap(); let path = aliases["path"].as_str().unwrap(); // 检查路径存在 assert!(std::path::Path::new(path).exists()); // 读取文件 let content = fs.read_file(&node_id); assert!(content.is_some()); let data = content.unwrap(); assert!(data.len() > 0); // 尝试解码为文本 if let Ok(text) = String::from_utf8(data) { println!("File content preview: {}", text.chars().take(100).collect::()); } } #[test] fn test_warren_statfs() { use crate::fskit::volume::MarkBaseVolume; let conn = Connection::open("data/users/warren.sqlite").unwrap(); let volume = MarkBaseVolume::new(conn, "warren".to_string()); let (total_nodes, total_size) = volume.statfs(); println!("Total nodes: {}", total_nodes); println!("Total size: {} bytes ({:.2} GB)", total_size, total_size as f64 / 1_073_741_824.0 ); assert_eq!(total_nodes, 12659); assert!(total_size > 0); } } ``` --- ## 9. 执行验证测试 **运行命令**: ```bash cargo test --lib fskit::warren_tests # 预期输出: running 5 tests test warren_database_connection ... ok test warren_query_root ... ok test warren_query_children ... ok test warren_read_text_file ... ok test warren_statfs ... ok test result: ok. 5 passed; 0 failed ``` --- ## 10. 数据验证意义总结 ### 技术层面意义 **验证SQLite backend正确性**: - ✅ query_node:节点查询逻辑正确 - ✅ query_children:层级关系正确 - ✅ read_file:文件路径解析正确 - ✅ statfs:统计计算正确 **验证数据完整性**: - ✅ 所有节点有 parent_id - ✅ 所有文件有 aliases_json - ✅ 文件大小与实际匹配 --- ### 业务层面意义 **验证用户数据完整性**: - ✅ 12659 nodes全部可访问 - ✅ 文件树结构正确 - ✅ 用户文件可读取 **验证系统可靠性**: - ✅ SQLite backend稳定 - ✅ 数据查询正确 - ✅ 文件访问成功 --- ## 总结 **数据验证核心**: 1. ✅ 数据结构正确(node_id, label, parent_id) 2. ✅ 层级关系完整(root → children) 3. ✅ 文件路径可用(aliases_json.path) 4. ✅ 统计数据准确(12659 nodes) **下一步行动**: ```bash # 立即执行验证测试 cargo test --lib fskit::warren_tests # 查看数据详情 sqlite3 data/users/warren.sqlite " SELECT node_id, label, node_type, file_size FROM file_nodes WHERE parent_id IS NULL LIMIT 5 " # 验证文件可读取 sqlite3 data/users/warren.sqlite " SELECT label, aliases_json FROM file_nodes WHERE node_type = 'file' AND file_size < 1000 LIMIT 1 " ``` --- **文档完成时间**: 2026-05-18 16:50 **版本**: 1.0(完整验证指南)