# UUID 长度问题分析报告

> Date: 2026-04-28 20:15
> Issue: 16-char vs 32-char UUID formats

---

## 问题发现

### 实际数据统计

```sql
SELECT DISTINCT LENGTH(uuid) as len, COUNT(*) FROM dev.videos GROUP BY LENGTH(uuid);

-- Result:
len | count 
-----+-------
  16 |    20    -- 旧格式 (SHA256[0:16])
  32 |  7479    -- 新格式 (SHA256[0:32])
```

### 384b0ff44aaaa1f14cb2cd63b3fea966 状态

| 字段 | 值 |
|------|-----|
| **uuid** | 384b0ff44aaaa1f14cb2cd63b3fea966 (16 字符) |
| **birth_registration** | NULL |
| **file_name** | Old_Time_Movie_Show_-_Charade_1963.HD.mov |

---

## UUID 格式对比

| 格式 | 长度 | 生成函数 | 用途 |
|------|------|----------|------|
| **旧格式** | 16 | `compute_uuid()` | 早期视频注册 |
| **新格式** | 32 | `compute_birth_uuid()` | Birth UUID (隐私保护) |

### 生成逻辑

**旧格式 (16 字符)**:

```rust
// src/core/storage/uuid.rs:6-11
pub fn compute_uuid(user_path: &str, filename: &str) -> String {
    let key = format!("{}/{}", user_path, filename);
    let hash = Sha256::digest(key.as_bytes());
    hex::encode(hash)[0..16].to_string()  // 只取前 16 字符
}
```

**新格式 (32 字符)**:

```rust
// src/core/storage/uuid.rs:82-91
pub fn compute_birth_uuid(
    mac_address: &str,
    timestamp: &str,
    username: &str,
    filename: &str,
) -> String {
    let key = format!("{}|{}|{}|{}", mac_address, timestamp, username, filename);
    let hash = Sha256::digest(key.as_bytes());
    hex::encode(hash)[0..32].to_string()  // 取前 32 字符
}
```

---

## 差异分析

### 隐私保护

| 特性 | 旧格式 | 新格式 |
|------|--------|--------|
| **包含元素** | Path + Filename | MAC + Time + User + Filename |
| **隐私风险** | 路径暴露 | 全部哈希化 |
| **唯一性** | 相对路径依赖 | MAC + Time 确保唯一 |
| **不可变性** | 迁移会改变 | 记录原始注册信息 |

### 代码判断逻辑

```rust
// src/core/storage/uuid.rs:94-96
pub fn is_birth_uuid(uuid: &str) -> bool {
    uuid.len() == 32 && !uuid.contains('_')
}
```

---

## 数据库列定义

| 表 | 列 | 类型 | 可容纳 |
|-----|-----|------|--------|
| **videos** | uuid | VARCHAR(32) | ✅ 16 + 32 |
| **face_detections** | file_uuid | VARCHAR(255) | ✅ 16 + 32 |
| **chunks** | uuid | VARCHAR(32) | ✅ 16 + 32 |
| **jobs** | asset_uuid | VARCHAR(32) | ✅ 16 + 32 |

---

## 影响范围

### 受影响的表

```sql
-- 查询使用 16 字符 UUID 的表
SELECT 'face_detections' as table_name, COUNT(*) 
FROM dev.face_detections WHERE LENGTH(file_uuid) = 16;

SELECT 'chunks' as table_name, COUNT(*) 
FROM dev.chunks WHERE LENGTH(uuid) = 16;

SELECT 'jobs' as table_name, COUNT(*) 
FROM dev.jobs WHERE LENGTH(asset_uuid) = 16;
```

### 依赖关系

```
videos.uuid (16-char)
  ↓
face_detections.file_uuid (16-char) -- FK
chunks.uuid (16-char)
jobs.asset_uuid (16-char) -- FK
```

---

## 解决方案

### 方案 A: 保留兼容模式（推荐）

**优点**:
- 不破坏现有数据
- 无需大量迁移
- VARCHAR(32) 可容纳两种格式

**缺点**:
- 代码需要处理两种格式
- `is_birth_uuid()` 返回 false (16-char)
- 部分功能受限（如 Birth UUID 查询）

**实施**:
1. 保持现有数据不变
2. 新视频使用 32 字符格式
3. 代码兼容两种格式

### 方案 B: 强制迁移（破坏性）

**优点**:
- 统一格式
- 全部启用 Birth UUID 功能
- 代码简化

**缺点**:
- 破坏性更改
- 需要更新所有关联表
- 外键关系需重建

**实施步骤**:

```sql
-- 1. 生成新 UUID
UPDATE dev.videos 
SET uuid = (
    SELECT compute_birth_uuid(...)
)
WHERE LENGTH(uuid) = 16;

-- 2. 更新关联表
UPDATE dev.face_detections 
SET file_uuid = (
    SELECT new_uuid FROM videos WHERE old_uuid = file_uuid
)
WHERE LENGTH(file_uuid) = 16;

-- 3. 更新 chunks
UPDATE dev.chunks 
SET uuid = (
    SELECT new_uuid FROM videos WHERE old_uuid = uuid
)
WHERE LENGTH(uuid) = 16;
```

---

## 建议

### 当前阶段：兼容模式

**理由**:
1. 384b0ff44aaaa1f14cb2cd63b3fea966 已有大量关联数据：
   - face_detections: 78 条
   - chunks: 5683 条
   - jobs: 2 条

2. 迁移风险高：
   - 外键关系复杂
   - 可能破坏现有功能

3. VARCHAR(32) 已足够：
   - 可容纳两种格式
   - 无需修改列定义

### 长期规划：渐进迁移

1. **Phase 1**: 保持兼容
2. **Phase 2**: 新注册使用 32 字符
3. **Phase 3**: 逐步迁移旧数据（可选）

---

## 判断函数使用

```rust
// 检查是否是 Birth UUID
if is_birth_uuid(&uuid) {
    // 启用 Birth UUID 相关功能
} else {
    // 使用旧格式逻辑
}
```

### 受影响的功能

| 功能 | 依赖 | 影响 |
|------|------|------|
| **Birth UUID 查询** | `is_birth_uuid()` | 16-char 无法查询 |
| **跨设备同步** | Birth UUID | 16-char 不支持 |
| **隐私保护** | Birth UUID | 16-char 不受保护 |

---

## 代码检查清单

### 需要兼容的代码

| 文件 | 行 | 需检查 |
|------|-----|--------|
| `uuid.rs:95` | is_birth_uuid() | ✅ 已检查 |
| `uuid.rs:263` | 测试用例 | ✅ 已验证 |
| `postgres_db.rs:620` | videos 表 | ✅ VARCHAR(32) |
| `rule1_ingest.rs:9` | execute_rule1() | ✅ 使用 file_uuid 参数 |
| `face_recognition.rs` | face_detections | ✅ VARCHAR(255) |

### 无需修改的代码

- 所有参数类型使用 `&str` 或 `String`
- VARCHAR 定义已足够
- 外键关系正常

---

## 结论

### 问题根源

384b0ff44aaaa1f14cb2cd63b3fea966 使用旧格式 UUID (16 字符)，原因是：
1. 早期注册（未启用 Birth UUID）
2. `compute_uuid()` 只取 SHA256 前 16 字符
3. birth_registration = NULL

### 当前状态

- **videos 表**: VARCHAR(32) ✅ 可容纳两种格式
- **代码逻辑**: `is_birth_uuid()` 区分两种格式 ✅
- **外键关系**: 正常 ✅

### 建议

**当前**: 保持兼容模式，不强制迁移
**未来**: 新视频使用 32 字符 Birth UUID

---

## 相关文件

| 文件 | 说明 |
|------|------|
| `src/core/storage/uuid.rs` | UUID 生成逻辑 |
| `migrations/019_add_birth_registration.sql` | Birth UUID 表结构 |
| `migrations/025_rename_video_uuid_to_file_uuid.sql` | 列重命名迁移 |
