From 80452886675d77c4395b995be216c01b6183e435 Mon Sep 17 00:00:00 2001 From: Warren Date: Mon, 18 May 2026 16:22:05 +0800 Subject: [PATCH] =?UTF-8?q?FSKit=E7=AE=80=E5=8C=96=E7=89=88=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=AA=8C=E8=AF=81=E6=8C=87=E5=8D=97=EF=BC=9A=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E4=B8=8E=E6=84=8F=E4=B9=89=E8=AF=A6=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 核心内容: 1. 数据结构说明(file_nodes表) 2. 字段意义详解(node_id/label/parent_id/aliases_json/file_size) 3. 4种验证方法(query_node/query_children/read_file/statfs) 4. 验证步骤流程(6步完整流程) 5. 数据意义解析(技术+业务层面) 6. 创建验证测试代码(5个warren_tests) 关键发现: - node_id:32字符UUID,确定性生成 - parent_id:NULL为根节点,有值为子节点 - aliases_json.path:文件实际路径(重要!) - 数据规模:12659 nodes(801 folders + 11857 files) 下一步: cargo test --lib fskit::warren_tests --- docs/FSKIT_SIMPLE_DATA_VALIDATION_GUIDE.md | 917 +++++++++++++++++++++ 1 file changed, 917 insertions(+) create mode 100644 docs/FSKIT_SIMPLE_DATA_VALIDATION_GUIDE.md diff --git a/docs/FSKIT_SIMPLE_DATA_VALIDATION_GUIDE.md b/docs/FSKIT_SIMPLE_DATA_VALIDATION_GUIDE.md new file mode 100644 index 0000000..f05cf67 --- /dev/null +++ b/docs/FSKIT_SIMPLE_DATA_VALIDATION_GUIDE.md @@ -0,0 +1,917 @@ +# 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(完整验证指南) \ No newline at end of file