# file_uuid 設計理念與規格 > Version: 1.0 | Date: 2026-04-30 > Architecture: Birth Identity Model (戶籍制度模型) --- ## 1. 核心概念 系統將每個媒體檔案視為一個「自然人」,擁有一個**終身不變的身份證字號** (`file_uuid`)。 | 戶籍概念 | 系統對應 | 說明 | | :--- | :--- | :--- | | **身分證字號** | `file_uuid` | 檔案的終身唯一標識,出生後永不變更 | | **出生登記** | 首次 `register` | 檔案首次被系統納管,觸發分析處理 (ASR, Face, etc.) | | **戶籍地** | `file_path` | 檔案當前存放位置,可隨搬家而變更 | | **主管單位** | `MAC Address` | 核發身份的伺服器/機器,確保跨機器的管轄獨立 | | **居住證申請時間** | `registration_time` | 檔案在該管轄單位登記的時間戳記 | --- ## 2. file_uuid 生成公式 ```text file_uuid = SHA256( MAC_Address | Birthday | Canonical_Path | Filename )[0:32] ``` ### 設計原則 | 原則 | 說明 | | :--- | :--- | | **唯一性** | 同一台機器上,相同路徑與檔名只會產生一個 UUID | | **穩定性** | **生日 (Birthday)** 是身份錨點。如果檔案在原地重新註冊,系統會找回原始生日,確保 UUID 不變 | | **管轄獨立** | 不同機器的 MAC 不同,確保跨伺服器身份獨立 | | **路徑綁定** | **Canonical Path** 參與計算。檔案移動到新路徑會產生新 UUID(視為新環境下的註冊) | | **隱私保護** | 所有元素經 Hash 處理,無法反推出原始資訊 | ### 關鍵元素 | 元素 | 說明 | | :--- | :--- | | `Birthday` | 首次註冊的時間戳記。系統會透過檔名查詢資料庫,找回原始生日,確保身份連續 | | `Canonical Path` | 檔案的絕對路徑。確保位置的唯一性 | | `Filename` | 檔案名稱 | --- ## 3. 生命週期 ### 3.1 出生 (Birth / 首次納管) 當檔案首次被系統發現並執行 `register` 時: ``` 1. 取得本机 MAC Address 2. 讀取 Filename 3. 查詢資料庫:是否有同檔名 (Filename) 的紀錄? ├─ 有紀錄 → 取出其 registration_time 作為「生日 (Birthday)」 └─ 無紀錄 → 使用 NOW() 作為「生日 (Birthday)」 4. 計算 file_uuid = SHA256(MAC | Birthday | Canonical_Path | Filename)[0:32] 5. 檢查 DB 是否已存在該 UUID ├─ 已存在 → 拒絕重複登記 (已有出生紀錄) └─ 不存在 → 建立新生紀錄 6. 記錄 registration_time (居住證申請時間) ``` **出生後**:`file_uuid` 即成為該檔案的終身身份,不可更改。 ### 3.2 搬家 (Move / 路徑變更) 當檔案從 `/data/demo/` 移動到 `/archive/2024/` 時: ``` 1. 檔案路徑變更 (Canonical Path 改變) 2. 系統以新 Path 計算 UUID → 產生新 UUID 3. 查詢 DB → 找不到該 UUID (視為新身份) 4. 但若檔名相同,會查詢到舊的「生日 (Birthday)」 5. 執行動作: ├─ 建立新紀錄 (新 UUID,新路徑) ├─ 使用原始的 Birthday (保持血緣關係) └─ 可選擇是否繼承舊紀錄的分析結果 ``` **關鍵邏輯**: - 路徑改變 = 新環境 = 新 UUID - 但透過 **Birthday 查詢機制**,系統知道這是同一個「人」搬到了新家 ### 3.3 跨機器遷移 (Cross-Machine) 當檔案從 Server-A 複製到 Server-B 時: ``` Server-A (MAC: aa:bb:cc:dd:ee:ff): file_uuid = SHA256("aa:bb:cc:dd:ee:ff|Birthday|/path|video.mp4") → "abc123..." Server-B (MAC: 11:22:33:44:55:66): file_uuid = SHA256("11:22:33:44:55:66|Birthday|/path|video.mp4") → "def456..." ``` - **結果**:兩台伺服器各自擁有獨立管轄權 - **意義**:各管各的戶口,互不干擾 --- ## 4. 資料庫欄位定義 ### videos 表 | 欄位 | 類型 | 說明 | 範例 | | :--- | :--- | :--- | :--- | | `file_uuid` | VARCHAR(32) | **身分證字號** (不可變) | `384b0ff44aaaa1f1...` | | `file_path` | TEXT | **戶籍地址** (可變) | `/data/demo/video.mp4` | | `file_name` | VARCHAR(255) | 原始檔名 | `video.mp4` | | `registration_time` | TIMESTAMPTZ | **居住證申請時間** | `2026-04-30T02:00:00+08` | | `birth_registration` | JSONB | 出生登記詳情 | 見下方結構 | ### birth_registration JSONB 結構 ```json { "registration_source": { "mac_address": "ba:f5:ee:bc:45:78", "original_path": "/Users/accusys/momentry/var/sftpgo/data/demo", "original_filename": "Old_Time_Movie_Show_-_Charade_1963.HD.mov", "timestamp": "2026-04-29T02:25:14+08:00" } } ``` --- ## 5. 代碼實作 ### 5.1 UUID 計算 (`src/core/storage/uuid.rs`) ```rust pub fn compute_birth_uuid( mac_address: &str, birthday: &str, path: &str, filename: &str, ) -> String { let key = format!("{}|{}|{}|{}", mac_address, birthday, path, filename); let hash = Sha256::digest(key.as_bytes()); hex::encode(hash)[0..32].to_string() } ``` ### 5.2 註冊流程 (`src/api/server.rs`) ```rust // 1. 取得 MAC、路徑與檔名 let mac_address = get_mac_address(); let canonical_path = path.canonicalize()...; let filename = path.file_name()...; // 2. 查詢生日 (Identity Anchor) // 以檔名查詢 DB,若有紀錄則使用原始生日,否則使用 NOW() let birthday = db.find_birthday_by_filename(&filename).await.unwrap_or(now()); // 3. 計算穩定身份 let file_uuid = compute_birth_uuid(&mac_address, &birthday, &canonical_path, &filename); // 4. 檢查是否已出生 if let Some(existing) = db.get_video_by_uuid(&file_uuid).await? { if existing.registration_time.is_some() { return Ok(already_exists_response); } } // 5. 新生登記 + 觸發分析 db.register_video(&record).await?; ``` --- ## 6. 情境對照表 | 情境 | file_uuid | file_path | Birthday | 觸發分析? | 說明 | | :--- | :--- | :--- | :--- | :--- | :--- | | **首次註冊** | 新生成 | 記錄當前路徑 | NOW() | ✅ 是 | 出生登記,全面納管 | | **同一檔案再次註冊** | 相同 | 不變 | 原始 | ❌ 否 | 已有戶籍,拒絕重複 | | **檔案移動到同機另一目錄** | **不同** | 新路徑 | 原始 | ✅ 是 | 新位置視為新環境 | | **檔案複製到另一台伺服器** | 不同 | 記錄新路徑 | ✅ 是 | 新管轄區,獨立登記 | | **檔名變更** | 不同 | 記錄新路徑 | ✅ 是 | 視為不同身份 | | **檔案刪除後重新加入** | 相同 | 記錄新路徑 | ⚠️ 視情況 | 若 DB 紀錄仍存在,可恢復關聯 | --- ## 7. 設計優勢 1. **身份錨點**:透過 Birthday 機制,即使路徑改變,系統仍能識別檔案的歷史血緣 2. **路徑綁定**:UUID 包含 Canonical Path,確保每個位置的檔案都有獨立身份,避免混淆 3. **管轄清晰**:MAC Address 確保每台伺服器的數據獨立 4. **可追溯性**:`birth_registration` 記錄原始出處與 Birthday,便於審計 5. **防止重複**:系統以 UUID 為準,同一位置同一檔案絕不會重複登記 --- ## 8. 相關文件 | 文件 | 說明 | | :--- | :--- | | `src/core/storage/uuid.rs` | UUID 生成實作 | | `src/api/server.rs` | 註冊端點與流程 | | `src/core/ingestion.rs` | Watcher 自動 ingestion 邏輯 | | `docs_v1.0/UUID_LENGTH_ISSUE.md` | 舊版 UUID 長度問題分析 | | `docs_v1.0/UUID_CLEANUP_PLAN.md` | 歷史數據清理方案 |