Files
meijiaka-zy/docs/migrate-avatars-to-local.md
T

244 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 迁移方案:废弃云端 `mjk_avatars` 表,数字人元数据全量迁移到本地存储
> **状态:方案已调整(2026-04-17** — 原方案中提到的 Celery 架构已完全移除,形象克隆现由 `app/scheduler/handlers/avatar_handler.py`Async Engine Scheduler)统一调度。云端仍保留 `avatars` 表作为形象克隆的持久化记录。
## 方案目标
| 目标 | 说明 |
|------|------|
| ✅ 贯彻设计理念 | 真正做到**轻量云 + 全本地业务数据**,云端只记日志不存业务数据 |
| ✅ 统一接口日志 | 所有接口请求统一记录到 `mjk_interface_request_logs`,按接口统计积分消耗 |
| ✅ 简化后端代码 | 删除大量 CRUD、状态管理、定时任务代码,后端更干净 |
| ✅ 用户掌控数据 | 所有数字人元数据存在用户本地,云端只记克隆请求的消耗积分 |
---
## 存储结构变化
### 变化前(现状)
```
云端 PostgreSQL: mjk_avatars
└─ 存储所有数字人元数据 (name/voice_id/element_id/status 等)
前端本地:
└─ 只做缓存,从云端同步
```
### 变化后(目标)
```
云端 PostgreSQL:
├─ mjk_interface_request_logs ← 只记:avatar_clone 请求 + 消耗积分 + 状态
└─ mjk_avatars ← 废弃,不再写入新数据(存量可保留可删除)
用户本地磁盘:
└─ ~/Documents/Meijiaka/avatars/{avatar_id}/
├─ meta.json ← 完整数字人元数据(JSON)
└─ source.mp4 ← 原始上传视频
```
---
## 本地存储结构定义
### 目录结构
```
~/Documents/Meijiaka/
└── avatars/
└── {avatar_id}/ # avatar_id = avt_{16位随机hex}
├── meta.json # 元数据(JSON 格式)
└── source.mp4 # 原始上传视频
```
### `meta.json` 结构
```json
{
"id": "avt_xxxxxxxxxxxxxxxx",
"name": "我的数字人",
"voiceId": "klingai-voice-id-string",
"elementId": 12345678,
"voiceTaskId": "kling-task-id-string",
"elementTaskId": "kling-task-id-string",
"videoUrl": "https://domain.com/path/to/source.mp4",
"trialUrl": "https://domain.com/path/to/trial.wav",
"status": "succeed",
"failReason": null,
"createdAt": "2026-04-16T10:00:00.000Z",
"updatedAt": "2026-04-16T10:05:00.000Z"
}
```
---
## 代码改动清单
### 后端 Python
| 操作 | 文件 | 改动说明 |
|------|------|----------|
| 🆕 新增 | `app/models/interface_request_logs.py` | SQLAlchemy 模型 `InterfaceRequestLogs` |
| 🆕 新增 | `app/crud/interface_request_logs.py` | CRUDcreate / update |
| ✏️ 修改 | `app/models/__init__.py` | 删除 `Avatar` 导入,新增 `InterfaceRequestLogs` |
| ✏️ 修改 | `app/api/v1/avatar.py` | 完全重写<br>• 保留:`POST /clone` / `GET /tasks/{id}` / `GET /clone/stream` / `POST /tasks/{id}/retry` / `DELETE /{id}` <br>• 删除:`GET /library` / `PATCH /{id}` / `/health` |
| ✏️ 修改 | `app/scheduler/handlers/avatar_handler.py` | 精简:删除所有对 `mjk_avatars` 读写,只记接口日志,进度放 Redis Registry |
| ❌ 删除 | `app/models/avatar.py` | 模型废弃,删除 |
| ❌ 删除 | `app/crud/avatar.py` | CRUD 废弃,删除 |
| ❌ 删除 | `app/tasks/avatar_clone.py` | 逻辑已合并到 avatar_handler,删除 |
### Rust Tauri`tauri-app/src-tauri/src/persistence.rs`
新增以下 IPC 命令:
```rust
/// 列出所有本地数字人(按创建时间倒序)
#[tauri::command]
pub fn list_avatars(app: AppHandle) -> Result<Vec<AvatarMeta>, String>;
/// 保存数字人元数据
#[tauri::command]
pub fn save_avatar(app: AppHandle, avatar_id: String, meta: AvatarMeta) -> Result<(), String>;
/// 获取单个数字人元数据
#[tauri::command]
pub fn get_avatar(app: AppHandle, avatar_id: String) -> Result<Option<AvatarMeta>, String>;
/// 删除数字人(删除整个本地目录)
#[tauri::command]
pub fn delete_avatar(app: AppHandle, avatar_id: String) -> Result<(), String>;
/// 更新数字人名称
#[tauri::command]
pub fn update_avatar_name(app: AppHandle, avatar_id: String, name: String) -> Result<(), String>;
```
`lib.rs` 注册新命令。
### 前端 TypeScript
| 模块 | 改动 |
|------|------|
| **Avatar 列表** | 原:`GET /avatar/library` 从后端获取 → 现在:调用 Tauri IPC 从本地读取 |
| **创建克隆** | 流程变化:<br>1. 前端生成 `avatar_id`<br>2. `POST /avatar/clone` → 获取 `task_id`<br>3. 前端创建本地目录 + 写入初始 `meta.json` (`status=pending`)<br>4. SSE 监听进度<br>5. 完成后 → 前端把 `voice_id`/`element_id` 写入本地 `meta.json`<br>6. 完成 |
| **删除 Avatar** | 流程变化:<br>1. 前端调用 `DELETE /avatar/{avatar_id}`(后端负责删除 Kling 远程资源)<br>2. 后端记删除日志到接口日志<br>3. 前端调用 IPC 删除本地目录 |
| **重命名 Avatar** | 原:调用后端 PATCH → 现在:前端直接修改本地 `meta.json`,无需请求后端 |
| **选择数字人生成视频** | 用法不变:从本地读取 `voice_id`/`element_id` → 传给后端视频生成接口 |
---
## 工作流对比
### 改动前(当前)
```
用户提交克隆
→ POST /clone → 后端写 mjk_avatars (status=pending) → 派发任务
→ Async Engine Scheduler (avatar_handler) 每一步都更新 `avatars` 表
→ 前端 SSE 轮询读 `avatars` 表拿进度
→ 完成后 Scheduler 更新 status=succeed 写入 voice_id/element_id
→ 前端从 `avatars` 表读结果 → 缓存到本地
→ 列表从 `avatars` 表读取
```
### 改动后(目标)
```
用户提交克隆
→ 前端生成 avatar_id → 创建本地 meta.json (status=pending)
→ POST /clone → 后端:
1. 在 mjk_interface_request_logs 插入记录
interface_type=avatar_clone, status=pending, started_at=now, cost_credits=X
2. 注册到 Async Engine Scheduler (Redis Registry)
3. 返回 {task_id, avatar_id}
→ Async Engine Scheduler (avatar_handler) 执行:
1. 调用 Kling 创建音色 → 轮询 → 获取 voice_id
2. 调用 Kling 创建主体 → 轮询 → 获取 element_id
3. 更新 Redis Registry 状态为 completed,写入结果
4. 更新接口日志: status=success/failed, finished_at=now
→ 前端 SSE 从 TaskCache 获取结果
→ 完成后前端将 voice_id/element_id 写入本地 meta.json
→ 列表展示直接从本地读取,不请求后端
```
---
## 接口日志记录规则
`mjk_interface_request_logs``avatar_clone` 的记录:
| 时机 | 操作 | 字段值 |
|------|------|--------|
| 刚收到请求 | 插入新记录 | `interface_type=avatar_clone`, `status=pending`, `started_at=NOW`, `cost_credits` = 克隆一次所需积分 |
| 任务完成成功 | 更新记录 | `status=success`, `finished_at=NOW` |
| 任务失败 | 更新记录 | `status=failed`, `finished_at=NOW`, `error_message=错误原因` |
> 积分在请求创建时即扣除,因为无论成功失败,KlingAI 开始处理后会计费。
---
## 存量数据迁移策略
### 渐进迁移(对用户友好)
1. **保留云端表**`mjk_avatars` 保留不删除,存量数据继续存在
2. **前端自动迁移**:用户首次打开形象库时:
- 前端检查:如果后端有数据但本地没有 → 提示用户"将云端数字人同步到本地"
- 用户确认后,前端逐个拉取数据写入本地
- 同步完成后,后续只使用本地数据
3. **下线旧表**:稳定运行一段时间后,可在维护窗口物理删除 `mjk_avatars`
### 回滚方案
- 迁移过程中如果出问题,随时切回原逻辑(表保留,代码只需恢复删除部分)
---
## 优缺点总结
| 优点 | 说明 |
|------|------|
| ✅ 完全符合需求 | 云端只存接口请求记录和消耗积分,不存用户业务数据 |
| ✅ 云端存储成本极低 | 只有接口日志,每条几KB,用户增长成本可控 |
| ✅ 后端代码大幅简化 | 删除了整个 Avatar CRUD、状态机管理、定时任务恢复,代码减少约 300 行 |
| ✅ 用户完全掌控数据 | 所有数字人元数据存储在用户本地磁盘 |
| ✅ 形象库展示更快 | 本地读取文件比查询数据库快很多 |
| ✅ 兼容存量数据 | 渐进迁移,可回滚 |
| 缺点 | 说明 | 应对 |
|------|------|------|
| 用户换电脑需要迁移 | 用户需要自行迁移数据,或重新克隆 | 后续可增加导出/导入功能解决 |
| 本地硬盘损坏数据丢失 | 这是"全本地"设计的必然结果 | 符合项目初始"轻量云+全本地"设计理念,用户自担数据安全 |
---
## 执行步骤(按顺序)
1. **数据库**
- 生成 Alembic 迁移:所有表重命名加 `mjk_` 前缀 + 新建 `mjk_interface_request_logs`
- 修改所有 Python 模型中的 `__tablename__`
2. **后端代码**
- 新建 `interface_request_logs` 模型和 CRUD
- 重写 `app/api/v1/avatar.py`
- 精简 `app/tasks/avatar_tasks.py`
- 删除废弃文件
3. **Rust Tauri**
-`persistence.rs` 新增 avatar 相关 IPC 命令
-`lib.rs` 注册命令
4. **前端代码**
- 修改形象库:从本地读取
- 修改创建流程:完成后写入本地
- 修改删除流程:删除云端 Kling 资源后删除本地
- 修改重命名:直接本地修改
5. **测试验证**
- 创建克隆 → 检查本地文件生成 → 检查接口日志写入
- 列表展示 → 删除 → 重命名 全流程测试
---
## 相关文档
- [数据库设计规范](./database-design.md) - 完整的数据库命名规范和表结构
- [视频生成流程](./video-generation-flow.md) - 完整视频生成流程说明
---
*版本:v1.0*
*创建日期:2026-04-16*