# Portal Face API 实现报告

> Date: 2026-04-28 21:25
> Status: ✅ 完成

---

## 实现内容

### 新增 API

| API | 方法 | 说明 |
|-----|------|------|
| `/api/v1/faces/candidates` | GET | 列出未绑定 faces |
| `/api/v1/identities/:id/faces` | GET | 列出 identity 的 faces |

---

## API 1: /api/v1/faces/candidates

### 功能

查询 `face_detections` 表中未绑定的 faces（`identity_id IS NULL`）

### Query 参数

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `file_uuid` | String | null | 过滤特定文件 |
| `min_confidence` | Float | 0.5 | 最小置信度 |
| `page` | Int | 1 | 页码 |
| `page_size` | Int | 15 | 每页数量（最大 100） |
| `limit` | Int | null | 总数量限制 |

### Response 结构

```json
{
  "candidates": [
    {
      "id": 11,
      "face_id": null,
      "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
      "frame_number": 1798,
      "confidence": 0.916,
      "bbox": {"x":945,"y":113,"width":179,"height":263},
      "attributes": {
        "age": 35,
        "gender": "male",
        "pose": {"yaw":3.23,"roll":-3.76,"pitch":-6.64}
      }
    }
  ],
  "total": 78,
  "page": 1,
  "page_size": 10
}
```

### 测试验证

```bash
curl "http://localhost:3003/api/v1/faces/candidates?min_confidence=0.5&page_size=10" \
  -H "X-API-Key: muser_test_001"

# Response: 78 candidates ✅
```

---

## API 2: /api/v1/identities/:id/faces

### 功能

查询绑定到特定 identity 的 faces（`identity_id = $id`）

### Path 参数

| 参数 | 类型 | 说明 |
|------|------|------|
| `identity_id` | Int | Identity ID |

### Query 参数

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `page` | Int | 1 | 页码 |
| `page_size` | Int | 100 | 每页数量（最大 1000） |

### Response 结构

```json
{
  "identity_id": 22,
  "faces": [
    {
      "id": 11,
      "face_id": "face_100",
      "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
      "frame_number": 100,
      "confidence": 0.92,
      "bbox": {...},
      "attributes": {...}
    }
  ],
  "total": 5
}
```

### 测试验证

```bash
curl "http://localhost:3003/api/v1/identities/22/faces?page_size=5" \
  -H "X-API-Key: muser_test_001"

# Response: {"identity_id":22,"faces":[],"total":0} ✅
# (当前 identity 22 无绑定 faces)
```

---

## 代码变更

### 文件: `src/api/identities.rs`

**修改内容**:

1. **新增路由定义** (line 53-55):
```rust
.route("/api/v1/faces/candidates", get(list_face_candidates))
.route("/api/v1/identities/:identity_id/faces", get(get_identity_faces))
```

1. **新增数据结构** (line 411-465):
```rust
#[derive(Debug, Deserialize)]
pub struct FaceCandidatesQuery {...}

#[derive(Debug, Serialize)]
pub struct FaceCandidate {...}

#[derive(Debug, Serialize)]
pub struct FaceCandidatesResponse {...}

#[derive(Debug, Deserialize)]
pub struct IdentityFacesQuery {...}

#[derive(Debug, Serialize)]
pub struct IdentityFace {...}

#[derive(Debug, Serialize)]
pub struct IdentityFacesResponse {...}
```

1. **新增 handler 函数** (line 467-592):
```rust
async fn list_face_candidates(...) {...}
async fn get_identity_faces(...) {...}
```

---

## 数据验证

### 测试 UUID: 384b0ff44aaaa1f14cb2cd63b3fea966

| 数据 | 数量 | 来源 |
|------|------|------|
| **face_detections (candidates)** | 78 | ✅ API 返回 |
| **face_detections (bound)** | 0 | ✅ 所有未绑定 |
| **identities** | 15 | ✅ identities 表 |

### 数据完整性

- ✅ bbox 字段正确（JSON）
- ✅ attributes 字段正确（age, gender, pose）
- ✅ confidence 排序正确（DESC）
- ✅ 分页参数正确（page, page_size）

---

## 编译验证

```bash
cargo check --lib  # ✅ Passed
cargo build --release --bin momentry_playground  # ✅ Passed (36s)
```

---

## 后续工作

### 已完成

- ✅ `/api/v1/faces/candidates` API
- ✅ `/api/v1/identities/:id/faces` API
- ✅ 编译验证
- ✅ API 测试

### 待实现（前端）

- 🔧 FaceCandidates.vue（显示 candidates）
- 🔧 IdentityDetailView.vue（添加 Faces tab）
- 🔧 RegisterIdentityModal.vue（注册流程）

---

## Portal 集成建议

### 前端调用示例

**Candidates 页面**:
```javascript
// Fetch candidates
const response = await fetch(
  'http://localhost:3003/api/v1/faces/candidates?min_confidence=0.8&page_size=20',
  { headers: { 'X-API-Key': apiKey } }
);

const data = await response.json();
// data.candidates: Face 数组
// data.total: 总数量
```

**Identity Faces 列表**:
```javascript
// Fetch identity faces
const response = await fetch(
  `http://localhost:3003/api/v1/identities/${identityId}/faces`,
  { headers: { 'X-API-Key': apiKey } }
);

const data = await response.json();
// data.faces: 绑定的 Face 数组
// data.total: 总数量
```

---

## 性能优化

### SQL 查询

**candidates API**:
```sql
SELECT id, face_id, file_uuid, frame_number, confidence, bbox, attributes
FROM face_detections
WHERE identity_id IS NULL AND confidence >= $1
ORDER BY confidence DESC
LIMIT $2 OFFSET $3
```

**identity faces API**:
```sql
SELECT id, face_id, file_uuid, frame_number, confidence, bbox, attributes
FROM face_detections
WHERE identity_id = $1
ORDER BY confidence DESC
LIMIT $2 OFFSET $3
```

**优化建议**:
- 添加索引：`CREATE INDEX idx_face_detections_candidates ON face_detections(confidence DESC) WHERE identity_id IS NULL;`
- 添加索引：`CREATE INDEX idx_face_detections_identity ON face_detections(identity_id, confidence DESC);`

---

## API Key

测试环境 API Key: `muser_test_001`

---

## 文件清单

| 文件 | 说明 |
|------|------|
| `src/api/identities.rs` | API 实现 |
| `docs_v1.0/PORTAL_FACE_API_IMPLEMENTATION.md` | 实现报告 |
| `docs_v1.0/PORTAL_FACE_DEMO_PLAN.md` | 演示计划 |

---

## 总结

**实现时间**: 约 15 分钟

**验证结果**:
- ✅ 编译通过
- ✅ API 功能正常
- ✅ 数据结构正确
- ✅ 分页功能正常

**下一步**: 前端 UI 实现（预计 3-4 小时）
