# 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<FileNodeData>
验证点：
  ├── node_id正确
  ├── label正确
  ├── node_type正确（folder/file）
  └── file_size正确（NULL for folder）
```

**query_children验证**:
```
测试：query_children("root_id")
预期：返回 Vec<FileNodeData>
验证点：
  ├── 数量正确（801 folders + 11857 files）
  ├── 所有子节点 parent_id正确
  └── 子节点类型正确
```

**read_file验证**:
```
测试：read_file("file_node_id")
预期：返回 Option<Vec<u8>>
验证点：
  ├── 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<Connection>可锁定 ✅

Step 2: 节点查询测试
├── query_node("root_id")
├── 返回 Option<FileNodeData>
├── 检查 node_id, label, node_type
└── 验证数据正确 ✅

Step 3: 子节点查询测试
├── query_children("root_id")
├── 返回 Vec<FileNodeData>
├── 检查数量（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::<String>());
        }
    }
    
    #[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（完整验证指南）