ca4a0b1303
- 用户数据隔离:所有用户数据按 users/{user_id}/ 隔离,Rust IPC 命令自治读取 auth.json
- 安全加固:delete_local_product/rename_local_product/export_product 增加前缀校验
- 移除音调(pitch)功能:从 VoiceSynthesis、projectStore、types 等完全移除
- 动态视频分辨率:根据素材最小高度自动选择 720p/1080p,9:16 比例强校验
- ASS 字幕按目标分辨率等比例缩放(720p 和 1080p 视觉一致)
- Canvas 预览支持参数化 playResY,预览与压制效果一致
- 配音合成增加台词字数校验弹窗(语速>1.0时要求更多字)
- BGM 默认音量从 25% 调至 15%
- 素材选择提示文案更新(9:16 比例,5-60秒)
- 视频校验从严格 1080x1920 改为 9:16 比例判断
- 背景图片弹窗宽度从 440px 放大到 560px
6.2 KiB
6.2 KiB
用户数据隔离改造方案
背景
当前所有本地数据存储在全局路径下,切换账号后 A 用户的数据对 B 用户可见。涉及隐私(音色素材)和商业数据(项目文件),需要按 user_id 隔离。
目标
- 按登录用户的
user_id隔离本地数据 - 未登录时使用匿名沙盒或禁止写入
- 兼容旧数据,首次启动自动迁移
改动范围
1. Rust 层 — 路径改造(src/storage/paths.rs)
新增用户隔离根目录,用户相关数据全部迁移:
{app_local_data_dir}/
users/
{user_id}/
voices.json ← 音色素材库
cover_avatars.json ← 封面头像库
projects/ ← 项目目录
config.json ← 用户级配置(可选)
bgm_cache/ ← 全局共享(缓存无隐私问题)
config.json ← 全局配置(窗口状态、偏好设置等)
temp/ ← 临时文件(定时清理)
需要修改的函数:
| 当前路径 | 改造后 |
|---|---|
{data}/voices.json |
{data}/users/{user_id}/voices.json |
{data}/cover_avatars.json |
{data}/users/{user_id}/cover_avatars.json |
{data}/cover_avatars/ |
{data}/users/{user_id}/cover_avatars/ |
{data}/projects/ |
{data}/users/{user_id}/projects/ |
{data}/config.json |
保持全局(或拆分为全局+用户级) |
{data}/bgm_cache/ |
保持全局 |
{config}/auth.json |
保持全局(只存当前登录态) |
新增:
get_current_user_id() -> Option<String>:从全局状态或调用方获取当前 user_idget_user_data_dir(user_id: &str):返回用户隔离目录
2. Rust 层 — 存储命令改造
受影响的命令文件:
src/commands/voice.rs— load/save/delete voice materialssrc/commands/project.rs— 项目 CRUDsrc/commands/avatar.rs— 封面头像src/commands/config.rs— 配置(决定全局 or 用户级)
改造方式:
方式 A(推荐):命令函数签名增加 user_id: String 参数
#[tauri::command]
pub async fn load_voice_materials(user_id: String) -> ApiResponse<Vec<VoiceMaterial>> {
let path = get_user_voices_json_path(&user_id)?;
// ...
}
方式 B:命令内部从 AppHandle 全局状态读取当前 user_id
let user_id = app.state::<CurrentUser>().id.clone();
推荐方式 A,前端调用时传入 user.id,减少 Rust 层对全局状态的依赖,更明确。
3. 前端 — AuthStore 改造(src/store/authStore.ts)
新增:
- 登录成功后,写入一个全局标记
CURRENT_USER_ID = user.id - 登出/被踢时,清除该标记
- 所有调用 Rust 存储命令的地方,带上
userId参数
受影响的 Store:
voiceStore.ts—loadVoiceMaterials()、addVoiceMaterial()等需要传userIdprojectStore.ts— 项目操作需要传userIdavatarStore.ts— 封面头像操作需要传userId
4. 前端 — API 调用改造
所有 invoke('xxx', { ... }) 调用用户相关命令时,增加 userId:
// 改造前
await invoke('load_voice_materials')
// 改造后
const userId = useAuthStore.getState().user?.id;
await invoke('load_voice_materials', { userId })
5. 数据迁移策略
检测时机: 应用启动时,init_app_data_dir 之后
迁移逻辑(Rust):
pub fn migrate_legacy_data(data_dir: &Path) -> Result<(), StorageError> {
// 1. 检查是否存在旧数据(全局 voices.json / projects / cover_avatars.json)
let legacy_voices = data_dir.join("voices.json");
if !legacy_voices.exists() {
return Ok(()); // 无旧数据,跳过
}
// 2. 读取 auth.json 获取当前登录的 user_id
let auth_path = get_auth_state_path(app)?;
let auth: Option<AuthState> = read_json(&auth_path)?;
let user_id = match auth.and_then(|a| a.user).map(|u| u.id) {
Some(id) => id,
None => {
// 未登录但有旧数据:移到 anonymous/ 目录,提示用户登录后迁移
return move_to_anonymous(data_dir);
}
};
// 3. 创建用户目录并迁移
let user_dir = data_dir.join("users").join(&user_id);
ensure_dir(&user_dir)?;
// 迁移 voices.json
if legacy_voices.exists() {
fs::rename(&legacy_voices, user_dir.join("voices.json"))?;
}
// 迁移 cover_avatars.json + cover_avatars/
let legacy_avatars = data_dir.join("cover_avatars.json");
if legacy_avatars.exists() {
fs::rename(&legacy_avatars, user_dir.join("cover_avatars.json"))?;
}
let legacy_avatars_dir = data_dir.join("cover_avatars");
if legacy_avatars_dir.exists() {
fs::rename(&legacy_avatars_dir, user_dir.join("cover_avatars"))?;
}
// 迁移 projects/
let legacy_projects = data_dir.join("projects");
if legacy_projects.exists() {
fs::rename(&legacy_projects, user_dir.join("projects"))?;
}
// 4. 写迁移标记,避免重复迁移
let flag = data_dir.join(".migration_v1_done");
fs::write(&flag, "done")?;
Ok(())
}
迁移顺序:
- 启动应用
- 初始化
app_local_data_dir - 检测
.migration_v1_done标记,不存在则执行迁移 - 迁移完成后继续正常启动
工作量评估
| 模块 | 文件数 | 预估工作量 |
|---|---|---|
| paths.rs 改造 | 1 | 2h |
| Rust 存储命令加 user_id 参数 | ~8 个文件 | 4h |
| lib.rs 注册新签名 | 1 | 0.5h |
| 前端 Store 改造 | ~4 个文件 | 3h |
| 数据迁移逻辑 | 1 个新文件 | 3h |
| 测试验证 | — | 2h |
| 总计 | ~14.5h |
风险点
- 多设备登录同一账号:数据只在本地隔离,不同设备间数据不互通。如果需要跨设备同步,需要后端支持。
- 匿名数据:未登录时产生的数据(理论上目前不存在,因为功能都需要登录),需要决定是禁止未登录操作还是存到
anonymous/目录。 - 回滚:迁移是单向的(移动文件),回滚需要手动恢复。建议迁移前做备份副本而非直接 move。
建议实施时机
- v1.8.0 或 v1.9.0 版本中实施
- 最好在素材库数据量还不大的早期做,迁移成本低