## Context TTMusic 的歌单数据存储在两个本地 RDB 数据库中: - `PlaylistStore.db`:包含 `playlistTable`(歌单信息)和 `playlistSongTable`(歌单-歌曲关联) - `mediaDB.db`:包含 `mediaTable`(歌曲元数据),歌单通过 `songFilePath` 关联到歌曲 当前没有任何备份机制。应用已有完善的远程网盘集成(`RemoteDriveManager` 支持 WebDAV、SMB、百度网盘等 13 种协议),以及基于服务器 API(`pay.ss5.xyz`)的 VIP 会员体系。VIP 状态通过 `PreferencesUtil.getBooleanSync('hasActiveSubscription')` 判断。 ## Goals / Non-Goals **Goals:** - 用户可以将全部歌单数据导出为单个 JSON 文件到本地存储 - 用户可以从 JSON 备份文件导入/恢复歌单数据 - 用户可以将备份文件上传到已配置的 WebDAV 账户 - 用户可以从 WebDAV 账户下载并恢复备份 - VIP 用户可以启用自动备份到服务器 - 在设置页面提供统一的备份管理入口 **Non-Goals:** - 不备份歌曲音频文件本身,仅备份歌单结构和歌曲元数据 - 不支持增量备份,每次备份为全量快照 - 不支持实时多设备同步(仅手动或定时备份+恢复) - 不支持非 WebDAV 类型的远程网盘备份(SMB/FTP/百度等留待后续扩展) - 不修改现有数据库表结构 ## Decisions ### D1: 备份文件格式 — JSON **选择**: 使用单个 JSON 文件作为备份格式 **备选方案**: - SQLite DB 文件直接拷贝:简单但不可读、版本兼容性差 - Protocol Buffers:性能好但增加依赖、不可人工编辑 - JSON:可读、无额外依赖、易于调试和版本迁移 **理由**: JSON 格式与项目现有的数据交换模式一致(ConfigManager、API 响应均为 JSON),ArkTS 原生支持 `JSON.stringify`/`JSON.parse`,无需引入新依赖。 **格式结构**: ```json { "version": 1, "exportTime": "2026-02-08T12:00:00.000Z", "appVersion": "1.0.0", "playlists": [ { "id": "...", "name": "...", "coverPath": "...", "description": "...", "createTime": "...", "updateTime": "...", "songCount": 10, "sortOrder": 0, "songs": [ { "songFilePath": "...", "addTime": "...", "sortOrder": 0 } ] } ] } ``` ### D2: 导入合并策略 — 按名称匹配 + 用户选择 **选择**: 导入时按歌单名称检测冲突,让用户选择「覆盖」「跳过」或「重命名」 **备选方案**: - 按 ID 匹配:ID 是 `Date.now() + random`,不同设备几乎不会冲突,但无法识别逻辑重复 - 全量覆盖:简单但会丢失用户修改 - 按名称匹配 + 用户选择:最灵活 **理由**: 歌单名称对用户有实际意义,同名歌单大概率是同一歌单的不同版本。提供选择权避免意外数据丢失。 ### D3: WebDAV 备份路径 — 固定子目录 **选择**: 备份文件存放在 WebDAV 账户的 `/TTMusic/backups/` 固定目录下 **理由**: 使用固定路径避免用户需要手动选择目录,同时与用户的音乐文件隔离。文件名格式为 `playlist_backup_YYYYMMDD_HHmmss.json`,便于识别和管理。 ### D4: 服务器自动备份 — 复用已有 API 体系 **选择**: 通过 `pay.ss5.xyz` 服务器 API 上传/下载备份数据,需要 `userToken` 认证 **API 设计**: - `POST /backup/playlist/upload` — 上传备份 JSON(需 token + VIP) - `GET /backup/playlist/download` — 下载最新备份(需 token + VIP) - `GET /backup/playlist/list` — 获取备份历史列表(需 token + VIP) **理由**: 复用已有的用户认证体系(`userToken`)和服务器架构,与现有 VIP 功能(会员信息、支付等)保持一致。 ### D5: 自动备份触发时机 — 歌单变更后延迟上传 **选择**: 在歌单增删改操作后设置 5 分钟延迟窗口,窗口内多次变更合并为一次上传 **备选方案**: - 即时上传:频繁操作时产生大量请求 - 固定间隔轮询:浪费资源且实时性差 - 延迟合并:兼顾实时性和效率 **理由**: 用户整理歌单时通常连续操作(添加多首歌、创建多个歌单),延迟合并避免频繁网络请求。使用 `setTimeout` + 标志位实现防抖。 ### D6: 核心模块设计 — PlaylistBackupManager **选择**: 新建 `PlaylistBackupManager` 类统一管理所有备份相关逻辑 **模块职责**: - `exportToJson()`: 从 PlaylistTable 读取全部歌单数据,序列化为 JSON - `importFromJson()`: 解析 JSON,校验格式版本,写入 PlaylistTable - `saveToLocal()`: 使用 `DocumentViewPicker` 让用户选择保存位置 - `loadFromLocal()`: 使用 `DocumentViewPicker` 让用户选择备份文件 - `uploadToWebDav()`: 通过 `RemoteDriveManager` 的 `rcpSocket` 上传到 WebDAV - `downloadFromWebDav()`: 从 WebDAV 下载备份文件 - `uploadToServer()`: VIP 用户上传到服务器 API - `downloadFromServer()`: VIP 用户从服务器下载 - `scheduleAutoBackup()`: 管理自动备份的防抖定时器 **理由**: 集中管理避免备份逻辑散落在多个文件中,便于测试和维护。 ## Risks / Trade-offs **[风险] 大量歌单导出的内存占用** → 分批序列化歌单数据,避免一次性将所有歌单加载到内存中。对于超过 100 个歌单的场景进行分页查询。 **[风险] WebDAV 服务器兼容性** → 不同 WebDAV 服务器对大文件上传行为不一致。备份文件通常较小(几十 KB 到几 MB),风险可控。使用现有 `rcpSocket.uploadFile` 方法,已验证兼容性。 **[风险] 备份文件版本不兼容** → JSON 格式包含 `version` 字段。导入时检查版本号,不兼容的版本给出明确提示。当前版本为 1,预留升级空间。 **[风险] 自动备份消耗流量** → 仅 VIP 用户启用,且使用防抖合并。备份数据为纯文本 JSON,体积较小。设置页面提供开关让用户控制。 **[风险] 服务器 API 需要后端配合** → 服务器端 API 需要新增三个端点。可以先实现本地导出/导入 + WebDAV 备份,服务器自动备份作为第二阶段上线。 **[取舍] 不备份歌曲音频文件** → 降低了备份完整性,但大幅减少备份体积和时间。歌曲文件可以从原始来源重新获取(本地扫描或 WebDAV)。 **[取舍] 不支持增量备份** → 实现简单,但每次备份为全量。鉴于歌单数据量通常较小(几十个歌单,几千首歌曲关联),全量备份的开销完全可接受。 ## Open Questions - Q1: 服务器自动备份的 API 端点是否需要额外的签名验证机制(类似 `deleteAccount` 中的签名方式)? - Q2: 备份文件中是否需要包含部分歌曲元数据(如歌名、艺术家)以便在目标设备没有对应歌曲文件时仍能显示歌单内容? - Q3: WebDAV 备份是否应该支持自动定时备份(不仅限于服务器备份),还是仅支持手动触发?