04e467e433
后端: - 微信回调 db.commit 失败仍返回 SUCCESS,避免无限重试 - recharge() 加 order_id 幂等保护,防重复充值 - time_expire 使用北京时间(UTC+8),修复时区 bug - 充值档位后端配置化(points-config.yaml + /recharge-options API) - 代码审查 20 项修复(认证加固、扣费顺序、错误响应、状态同步等) 前端: - 充值弹窗:自动轮询 + 【我已支付】手动兜底 - 二维码倒计时显示,过期后遮罩 + 刷新按钮 - 充值档位从后端动态加载 - 去掉 select/qrcode 弹窗标题,金额红色突出显示 - 全项目命名统一(视频生成/压制成片/配音合成/声音复刻等) - Modal 关闭按钮独立于 title 显示
9.6 KiB
9.6 KiB
美家卡智影 - 数据架构设计
1. 架构目标
- 离线优先:无网络时也能正常使用
- 云端同步:多设备间数据同步
- 多媒体本地存储:大文件存本地目录,小数据存云端
- 自动冲突解决:简化用户操作
2. 数据分层
┌─────────────────────────────────────────────────────────────────┐
│ 云端 (PostgreSQL) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ users │ │ projects │ │ avatars │ ... │
│ │ (用户账号) │ │ (项目元数据) │ │ (形象元数据) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ 小数据、结构数据、需要同步的数据 │
└─────────────────────────────────────────────────────────────────┘
↑↓ 同步
┌─────────────────────────────────────────────────────────────────┐
│ 本地 (Tauri + 浏览器) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ SQLite / 文件系统 (~/Documents/Meijiaka/) │ │
│ │ ├─ projects/ # 项目数据 │ │
│ │ │ └─ {project_id}/ │ │
│ │ │ ├─ project.json # 项目配置 │ │
│ │ │ ├─ segments/ # 分镜数据 │ │
│ │ │ ├─ videos/ # 生成的视频 │ │
│ │ │ └─ covers/ # 封面图片 │ │
│ │ ├─ avatars/ # 克隆形象 │ │
│ │ │ └─ {avatar_id}/ │ │
│ │ │ ├─ avatar.json # 形象配置 │ │
│ │ │ └─ video.mp4 # 形象视频 │ │
│ │ └─ temp/ # 临时文件 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ SWR Cache (内存) │ │
│ │ - API 响应缓存 │ │
│ │ - 乐观更新队列 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
3. 数据分类
3.1 云端优先数据(小数据,结构化)
| 数据类型 | 存储位置 | 同步策略 | 说明 |
|---|---|---|---|
| 用户信息 | 云端 | 实时 | 账号、设置偏好 |
| 项目列表 | 云端 | 实时 | 项目ID、标题、状态、更新时间 |
| 形象元数据 | 云端 | 实时 | 形象ID、名称、云端视频URL |
| 成本统计 | 云端 | 实时 | API 调用记录、费用 |
| 脚本内容 | 云端+本地 | 双向同步 | 分镜文案、场景描述 |
3.2 本地优先数据(大文件,多媒体)
| 数据类型 | 存储位置 | 同步策略 | 说明 |
|---|---|---|---|
| 形象视频 | 本地 | 按需上传 | 克隆形象的原视频文件 |
| 生成视频 | 本地 | 可选上传 | AI生成的分镜视频 |
| 封面图片 | 本地 | 可选上传 | 项目封面 |
| 合成视频 | 本地 | 可选上传 | 最终成片 |
| 临时文件 | 本地 | 不同步 | 处理过程中的缓存 |
4. 同步策略
4.1 自动同步(实时)
// 云端优先数据 - 自动同步
- 用户修改脚本 → 立即保存到云端
- 切换形象 → 立即同步到云端
- 项目状态变更 → 实时更新
4.2 手动同步(用户触发)
// 大文件 - 手动同步
- 上传形象视频到云端备份
- 下载云端形象到本地
- 分享项目(打包上传)
4.3 离线队列
// 离线时操作进入队列
interface OfflineQueue {
id: string;
type: 'create' | 'update' | 'delete';
entity: 'project' | 'segment' | 'avatar';
data: any;
timestamp: number;
retryCount: number;
}
5. 冲突解决
5.1 简单策略:最后写入优先
// 基于 updatedAt 时间戳
const resolveConflict = (local: Data, remote: Data) => {
return local.updatedAt > remote.updatedAt ? local : remote;
};
5.2 字段级合并(可选)
// 不同字段分别决定
const mergeConflict = (local: Project, remote: Project) => {
return {
...remote,
segments: local.updatedAt > remote.updatedAt
? local.segments
: remote.segments,
coverPath: local.coverUpdatedAt > remote.coverUpdatedAt
? local.coverPath
: remote.coverPath,
};
};
6. 存储实现
6.1 本地目录结构
~/Documents/Meijiaka/ # 用户文档目录
├── config.json # 全局配置
├── cache/ # 缓存目录
│ └── swr/ # SWR 缓存
├── projects/ # 项目数据
│ └── {project_id}/
│ ├── meta.json # 项目元数据
│ ├── segments.json # 分镜数据
│ ├── assets/ # 资源文件
│ │ ├── videos/ # 生成视频
│ │ ├── covers/ # 封面图片
│ │ └── temp/ # 临时文件
│ └── exports/ # 导出文件
├── avatars/ # 克隆形象
│ └── {avatar_id}/
│ ├── meta.json # 形象配置
│ └── source.mp4 # 原视频
└── voices/ # 克隆音色
└── {voice_id}/
└── sample.mp3
6.2 数据存储接口
// 统一存储接口
interface StorageAdapter {
// 项目数据
saveProject(project: Project): Promise<void>;
loadProject(projectId: string): Promise<Project | null>;
deleteProject(projectId: string): Promise<void>;
listProjects(): Promise<ProjectSummary[]>;
// 形象数据
saveAvatar(avatar: Avatar): Promise<void>;
loadAvatar(avatarId: string): Promise<Avatar | null>;
deleteAvatar(avatarId: string): Promise<void>;
listAvatars(): Promise<AvatarSummary[]>;
// 多媒体文件
saveMedia(projectId: string, file: File, type: MediaType): Promise<string>;
loadMedia(path: string): Promise<Blob>;
deleteMedia(path: string): Promise<void>;
}
7. 状态管理
7.1 分层状态
// 1. 服务端状态 (SWR)
const { data: projects } = useSWR('/api/projects', fetcher);
// 2. 本地持久化状态 (Zustand + Storage)
const projectStore = useProjectStore(); // 自动持久化到本地文件
// 3. 临时 UI 状态 (React State)
const [selectedTab, setSelectedTab] = useState('script');
7.2 同步 hook
// 自动同步 hook
function useSyncProject(projectId: string) {
const { data: remoteProject } = useSWR(`/api/projects/${projectId}`);
const localProject = useProjectStore(state => state.project);
const sync = useProjectStore(state => state.sync);
useEffect(() => {
if (remoteProject && localProject) {
sync(remoteProject, localProject);
}
}, [remoteProject, localProject]);
}
8. 迁移路径
Phase 1: 本地文件存储(当前)
- 项目数据存 Tauri 本地
- 形象数据存 localStorage
Phase 2: 云端同步(下一步)
- 后端新增 project/avatar 表
- 实现双向同步逻辑
- 离线队列
Phase 3: 多媒体管理
- 本地目录结构
- 文件导入/导出
- 云端备份(可选)
9. 关键决策
Q1: 是否需要本地 SQLite?
建议:Phase 1 不需要,JSON 文件足够简单。Phase 2 如需复杂查询再引入。
Q2: 如何处理大文件同步?
建议:不上传大文件,只在本地存储。用户需要跨设备时,提供"导出/导入"功能。
Q3: 离线支持到什么程度?
建议:
- 脚本编辑:完全离线
- 视频生成:需要联网(AI 服务)
- 压制成片:可离线(本地 FFmpeg)