同步机制设计
爱生活 的同步系统采用可插拔 Provider 架构,通过 SyncManager 统一管理变更追踪、同步队列和多云适配。所有同步数据经过 AES-256-GCM 透明加密,确保云端存储安全。
1. 同步架构总览
┌─────────────────────────────────────────────────────────────┐
│ UI 层 │
│ 同步设置页 (sync/index.vue) │ 数据管理 (data-management) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ SyncManager (单例) │
│ 变更追踪 │ 队列管理 │ Provider 切换 │ 同步执行 │
└───────────────────────────┬─────────────────────────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌──────────┐ ┌──────────────┐
│ LocalSyncProvider │ │ WebDAV │ │ GitHub Gist │
│ (默认/空操作) │ │ Provider │ │ Provider │
└──────────────────┘ └──────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ 数据层 (EncryptedStore) │
│ syncQueue │ syncConfig │ 业务数据表 (task/event/...) │
└─────────────────────────────────────────────────────────────┘设计原则:
- 可插拔 Provider:通过
SyncProvider接口统一抽象,新增云服务无需改动核心引擎 - 变更追踪:所有数据写操作自动入队,确保增量同步的基础
- 加密优先:同步数据在写入存储前已加密,云端仅存密文
- 冲突可检测:基于乐观锁版本号 (
version) 检测并发修改
2. 核心类型定义
/src/sync/types.ts — 同步系统的全部类型定义。
2.1 同步队列项 (SyncQueueItem)
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | UUID,队列唯一标识 |
tableName | string | 数据表名 |
recordId | string | 被变更的记录 ID |
operation | enum | create / update / delete |
data | any | 变更的完整数据载荷 |
syncStatus | enum | WAIT_SYNC / SYNCED / CONFLICT |
retryCount | number | 重试次数 |
lastError | string? | 最近一次同步失败的错误信息 |
moduleType | ModuleType | 所属模块(用于选择性同步) |
createdAt | number | 入队时间戳 |
2.2 同步状态 (SyncStatus)
定义于 /src/types/index.ts,所有业务实体均包含此字段:
| 状态 | 含义 |
|---|---|
LOCAL_ONLY | 仅存在本地,从未同步 |
WAIT_SYNC | 等待同步(已入队) |
SYNCED | 已同步 |
CONFLICT | 冲突(本地和远程同时修改) |
状态流转:
创建实体 → LOCAL_ONLY → 入队列 → WAIT_SYNC → 上传成功 → SYNCED
→ 失败重试 → WAIT_SYNC
→ 冲突检测 → CONFLICT2.3 乐观锁 (version)
所有业务实体继承 BaseEntity,包含 version: number 字段:
| 字段 | 说明 |
|---|---|
version | 版本号,每次修改 +1 |
deleted | 软删除标记 |
冲突检测规则:上传时比对本地 version 与远程 version,不一致则产生 CONFLICT。
2.4 同步配置 (SyncConfig)
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 配置 ID(固定为 default) |
provider | string | 当前 Provider 名称 |
autoSync | boolean | 是否自动同步 |
syncInterval | number | 自动同步间隔(分钟),默认 30 |
modules | SyncModules | 同步模块开关(7 个模块) |
lastSyncAt | number? | 上次同步时间戳 |
lastSyncResult | SyncResult? | 上次同步结果 |
providerConfig | Record | Provider 特有配置(URL/Token 等) |
2.5 同步模块 (SyncModules)
| 模块 | 字段 | 说明 |
|---|---|---|
| task | boolean | 任务 |
| calendar | boolean | 日程 |
| habit | boolean | 习惯 |
| knowledge | boolean | 知识库(笔记/日记) |
| finance | boolean | 财务 |
| health | boolean | 健康 |
| inventory | boolean | 资产 |
3. SyncManager 核心引擎
/src/sync/sync-manager.ts — 同步系统的核心,全局单例。
3.1 Provider 注册与切换
class SyncManager {
private providers: Map<string, SyncProvider>
private currentProvider: SyncProvider // 默认 LocalSyncProvider
registerProvider(provider: SyncProvider) // 注册新 Provider
setProvider(name: string) // 切换并认证
getCurrentProvider(): SyncProvider // 获取当前
}- 注册:通过
registerProvider()将 Provider 实例加入providersMap - 切换:
setProvider()切换当前 Provider 并调用authenticate()验证连接 - 内置:默认注册
LocalSyncProvider(空操作,本地不执行同步)
3.2 变更追踪
业务层写操作 (create/update/delete)
↓
markForSync(tableName, recordId, operation, data, moduleType)
↓
写入 syncQueue 表,状态 = WAIT_SYNC| 参数 | 说明 |
|---|---|
tableName | 数据表名,如 task |
recordId | 记录 ID |
operation | 操作类型 |
data | 完整 JSON 数据 |
moduleType | 所属模块,用于按模块选择性同步 |
AI Agent 在执行 Action 时通过 actions.ts 自动调用 markForSync(),确保 AI 创建的数据也进入同步队列。
3.3 同步执行
sync(modules?)
→ 筛选指定模块的待同步项
→ 构造上传载荷
→ currentProvider.sync(payload)
→ 更新队列状态 (SYNCED / CONFLICT)
→ 更新 lastSyncAt 和 lastSyncResult同步成功后,已完成的队列项状态更新为 SYNCED。后续通过 cleanSyncedQueue() 清理。
3.4 队列管理
| 方法 | 说明 |
|---|---|
getPendingQueue() | 获取所有 WAIT_SYNC 状态的队列项 |
cleanSyncedQueue() | 删除已完成的队列项(SYNCED 状态) |
getQueueByModule(moduleType) | 按模块筛选待同步项 |
3.5 默认配置
| 参数 | 默认值 | 说明 |
|---|---|---|
| provider | local | 默认本地 Provider(不执行云同步) |
| autoSync | false | 默认关闭自动同步 |
| syncInterval | 30 min | 自动同步间隔 |
| modules | 全部开启 | 7 个模块默认参与同步 |
4. SyncProvider 接口
/src/sync/types.ts — 所有 Provider 必须实现的标准接口。
4.1 接口定义
interface SyncProvider {
// 身份标识
name: string // Provider 名称(用于注册和切换)
type: 'local' | 'cloud' | 'self-hosted'
isAvailable: boolean // 当前环境是否可用
// 核心同步方法
upload(items: SyncQueueItem[]): Promise<SyncResult>
download(since?: number): Promise<SyncQueueItem[]>
sync(payload: SyncPayload): Promise<SyncResult>
// 连接管理
authenticate(): Promise<boolean>
disconnect(): Promise<void>
}4.2 方法约定
| 方法 | 职责 | 参数 |
|---|---|---|
upload | 推送本地变更到云端 | 待上传的队列项列表 |
download | 拉取云端变更到本地 | since 时间戳(增量拉取) |
sync | 全量同步(upload + download) | 同步载荷(含所有表数据) |
authenticate | 验证连接有效性 | 无 |
disconnect | 断开连接、清理凭据 | 无 |
4.3 SyncResult
| 字段 | 类型 | 说明 |
|---|---|---|
success | boolean | 是否成功 |
uploaded | number | 上传条数 |
downloaded | number | 下载条数 |
conflicts | number | 冲突条数 |
errors | string[] | 错误信息列表 |
syncedAt | number | 同步完成时间戳 |
5. 已实现的 Provider
5.1 LocalSyncProvider
/src/sync/providers/local-provider.ts — 默认 Provider,所有操作为空实现。
| 方法 | 行为 |
|---|---|
upload() | 不执行上传,返回空成功结果 |
download() | 不执行下载,返回空数组 |
sync() | 返回成功(uploaded=0, downloaded=0, conflicts=0) |
authenticate() | 始终返回 true |
type | local |
作用:作为默认占位实现,确保在未配置云同步时系统正常运行。所有同步逻辑在 Provider 层被短路,不会产生网络请求。
5.2 WebDAV 同步
实现于 /src/pages/sync/index.vue,基于 WebDAV 协议通过以下函数直接操作:
上传 (uploadToCloud):
读取所有表密文数据 → JSON.stringify → PUT 到 WebDAV 服务器下载 (downloadFromCloud):
GET WebDAV 文件 → JSON.parse → decrypt (如需) → writeRawBackup 写入各表连接方式:
| 平台 | 方式 |
|---|---|
| H5 (浏览器) | 通过 localhost:9399 代理转发(绕过 CORS 限制) |
| App | 直连 WebDAV 服务器 |
配置项:
| 字段 | 说明 |
|---|---|
| URL | WebDAV 服务器地址 |
| 用户名 | Basic Auth 用户名 |
| 密码 | Basic Auth 密码 |
5.3 GitHub Gist 同步
实现于 /src/pages/sync/index.vue,利用 Gist API 作为轻量级存储后端:
上传:PATCH /gists/{gistId} — 将加密后的 JSON 数据更新到指定 Gist 下载:GET /gists/{gistId} — 拉取 Gist 内容并恢复
配置项:
| 字段 | 说明 |
|---|---|
| Token | GitHub Personal Access Token(需 gist scope) |
| Gist ID | 目标 Gist 的唯一标识 |
5.4 当前实现说明
WebDAV 和 Gist 的同步逻辑当前直接写在页面代码中,尚未封装为标准 SyncProvider 实现。后续规划将其迁移为独立的 Provider 类(WebDAVProvider、GistProvider),注册到 SyncManager 以享受统一的队列管理和冲突检测。
6. 同步策略
6.1 全量同步 (当前实现)
应用于 WebDAV 和 Gist 同步:
上传:遍历所有表 → 读取全部记录 → 序列化为 JSON → PUT 到远端
下载:GET 远端文件 → 解析 JSON → 逐表清空 → bulkPut 写入优点:实现简单,不依赖复杂的增量逻辑 缺点:数据量大时传输效率低,适合个人数据量较小的场景
6.2 增量同步 (接口预留)
SyncProvider.download(since) 接口已预留 since 时间戳参数:
download(since?: number): Promise<SyncQueueItem[]>接入方式:
- 远端服务按
since返回该时间之后变更的记录 - Provider 将远端变更转换为
SyncQueueItem[]格式返回 - SyncManager 按队列项逐条应用到本地数据库
配合 markForSync() 的变更追踪,可实现真正的增量双向同步。当前 download(since) 仅在 LocalSyncProvider 中返回空数组,云 Provider 实现后即可启用。
6.3 冲突解决策略
乐观锁 (Optimistic Locking):
记录上传前:
local.version vs remote.version
├─ 相等 → 安全写入,version +1
└─ 不等 → 标记 CONFLICT,保留双版本通过 BaseEntity.version 字段实现。当前冲突检测逻辑已预留类型定义 (CONFLICT 状态),实际检测和合并 UI 待云 Provider 标准化后实现。
6.4 同步模块选择
用户可在同步设置页选择参与同步的模块。SyncManager 在执行同步时会过滤队列:
sync(['task', 'habit']) → 仅同步任务和习惯模块
sync() → 同步全部开启的模块7. 领域内同步
系统内在不同领域间也实现了数据同步,虽不通过网络,但体现了同步的一致性设计思想。
7.1 任务 → 日历同步
/src/stores/task.store.ts — syncTaskToCalendar(task):
| 操作 | 同步行为 |
|---|---|
| 创建任务(有 dueDate) | 自动在日历中创建对应事件 |
| 完成任务 | 日历事件标题加 emoji 前缀、颜色变绿 |
| 删除任务 / 移除截止日期 | 自动删除关联的日历事件 |
7.2 习惯 → 目标同步
/src/stores/habit.store.ts — syncGoalProgress(habit, record):
| 操作 | 同步行为 |
|---|---|
| 习惯打卡 | 自动更新关联目标的 current 计数 |
| 达到目标值 | 自动标记 completed |
8. AI 与同步集成
AI Agent 执行数据操作时自动设置同步追踪字段:
// 所有 AI 创建的实体均包含
{
syncStatus: 'LOCAL_ONLY',
version: 0,
deleted: false
}这确保 AI 创建的数据与用户手动创建的数据享有相同的同步能力。数据进入数据库后,由 markForSync() 将其加入同步队列。
9. 数据流全景
用户操作 / AI Agent
│
▼
Repository 写入业务数据
│ (syncStatus = LOCAL_ONLY, version = 0)
▼
markForSync() → syncQueue (WAIT_SYNC)
│
▼
SyncManager.sync()
│
├─ 筛选待同步项(按模块)
├─ 构造 SyncPayload
│
▼
currentProvider.sync(payload)
│
├─ upload: 本地变更 → 云端
├─ download: 云端变更 → 本地
│
▼
更新队列项状态 (SYNCED / CONFLICT)
│
▼
cleanSyncedQueue() → 删除已同步项10. 安全考量
同步数据的安全性由加密层保障:
| 环节 | 安全机制 |
|---|---|
| 本地存储 | AES-256-GCM 透明加密 |
| 传输过程 | HTTPS (WebDAV) / TLS (GitHub API) |
| 云端存储 | 仅存密文(服务端无法解密) |
| 凭据存储 | 通过 EncryptedStore 加密存储 Token/密码 |
| 数据恢复 | 下载后使用本地 ActiveKey 解密 |
即使云服务商被入侵或 Gist 被公开,没有用户密码就无法解密数据。
11. 扩展指南
11.1 实现新的 Provider
- 在
src/sync/providers/下创建新文件,如icloud-provider.ts - 实现
SyncProvider接口的 5 个必需方法 - 在
SyncManager初始化时注册:
syncManager.registerProvider(new ICloudProvider())11.2 接口约定
| 约定 | 说明 |
|---|---|
upload 幂等 | 相同数据多次上传结果一致 |
download 增量 | 支持 since 时间戳过滤 |
authenticate 无副作用 | 仅验证,不修改远端数据 |
disconnect 清理 | 清除本地缓存的凭据和临时数据 |
| 错误传播 | 网络错误、认证失败抛出明确异常,由 SyncManager 统一处理重试 |