Skip to content

同步机制设计

爱生活 的同步系统采用可插拔 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)

字段类型说明
idstringUUID,队列唯一标识
tableNamestring数据表名
recordIdstring被变更的记录 ID
operationenumcreate / update / delete
dataany变更的完整数据载荷
syncStatusenumWAIT_SYNC / SYNCED / CONFLICT
retryCountnumber重试次数
lastErrorstring?最近一次同步失败的错误信息
moduleTypeModuleType所属模块(用于选择性同步)
createdAtnumber入队时间戳

2.2 同步状态 (SyncStatus)

定义于 /src/types/index.ts,所有业务实体均包含此字段:

状态含义
LOCAL_ONLY仅存在本地,从未同步
WAIT_SYNC等待同步(已入队)
SYNCED已同步
CONFLICT冲突(本地和远程同时修改)

状态流转:

创建实体 → LOCAL_ONLY → 入队列 → WAIT_SYNC → 上传成功 → SYNCED
                                                    → 失败重试 → WAIT_SYNC
                                                    → 冲突检测 → CONFLICT

2.3 乐观锁 (version)

所有业务实体继承 BaseEntity,包含 version: number 字段:

字段说明
version版本号,每次修改 +1
deleted软删除标记

冲突检测规则:上传时比对本地 version 与远程 version,不一致则产生 CONFLICT。

2.4 同步配置 (SyncConfig)

字段类型说明
idstring配置 ID(固定为 default
providerstring当前 Provider 名称
autoSyncboolean是否自动同步
syncIntervalnumber自动同步间隔(分钟),默认 30
modulesSyncModules同步模块开关(7 个模块)
lastSyncAtnumber?上次同步时间戳
lastSyncResultSyncResult?上次同步结果
providerConfigRecordProvider 特有配置(URL/Token 等)

2.5 同步模块 (SyncModules)

模块字段说明
taskboolean任务
calendarboolean日程
habitboolean习惯
knowledgeboolean知识库(笔记/日记)
financeboolean财务
healthboolean健康
inventoryboolean资产

3. SyncManager 核心引擎

/src/sync/sync-manager.ts — 同步系统的核心,全局单例。

3.1 Provider 注册与切换

typescript
class SyncManager {
  private providers: Map<string, SyncProvider>
  private currentProvider: SyncProvider  // 默认 LocalSyncProvider

  registerProvider(provider: SyncProvider)  // 注册新 Provider
  setProvider(name: string)                 // 切换并认证
  getCurrentProvider(): SyncProvider        // 获取当前
}
  • 注册:通过 registerProvider() 将 Provider 实例加入 providers Map
  • 切换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 默认配置

参数默认值说明
providerlocal默认本地 Provider(不执行云同步)
autoSyncfalse默认关闭自动同步
syncInterval30 min自动同步间隔
modules全部开启7 个模块默认参与同步

4. SyncProvider 接口

/src/sync/types.ts — 所有 Provider 必须实现的标准接口。

4.1 接口定义

typescript
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

字段类型说明
successboolean是否成功
uploadednumber上传条数
downloadednumber下载条数
conflictsnumber冲突条数
errorsstring[]错误信息列表
syncedAtnumber同步完成时间戳

5. 已实现的 Provider

5.1 LocalSyncProvider

/src/sync/providers/local-provider.ts — 默认 Provider,所有操作为空实现。

方法行为
upload()不执行上传,返回空成功结果
download()不执行下载,返回空数组
sync()返回成功(uploaded=0, downloaded=0, conflicts=0)
authenticate()始终返回 true
typelocal

作用:作为默认占位实现,确保在未配置云同步时系统正常运行。所有同步逻辑在 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 服务器

配置项

字段说明
URLWebDAV 服务器地址
用户名Basic Auth 用户名
密码Basic Auth 密码

5.3 GitHub Gist 同步

实现于 /src/pages/sync/index.vue,利用 Gist API 作为轻量级存储后端:

上传PATCH /gists/{gistId} — 将加密后的 JSON 数据更新到指定 Gist 下载GET /gists/{gistId} — 拉取 Gist 内容并恢复

配置项

字段说明
TokenGitHub Personal Access Token(需 gist scope)
Gist ID目标 Gist 的唯一标识

5.4 当前实现说明

WebDAV 和 Gist 的同步逻辑当前直接写在页面代码中,尚未封装为标准 SyncProvider 实现。后续规划将其迁移为独立的 Provider 类(WebDAVProviderGistProvider),注册到 SyncManager 以享受统一的队列管理和冲突检测。

6. 同步策略

6.1 全量同步 (当前实现)

应用于 WebDAV 和 Gist 同步:

上传:遍历所有表 → 读取全部记录 → 序列化为 JSON → PUT 到远端
下载:GET 远端文件 → 解析 JSON → 逐表清空 → bulkPut 写入

优点:实现简单,不依赖复杂的增量逻辑 缺点:数据量大时传输效率低,适合个人数据量较小的场景

6.2 增量同步 (接口预留)

SyncProvider.download(since) 接口已预留 since 时间戳参数:

download(since?: number): Promise<SyncQueueItem[]>

接入方式:

  1. 远端服务按 since 返回该时间之后变更的记录
  2. Provider 将远端变更转换为 SyncQueueItem[] 格式返回
  3. 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.tssyncTaskToCalendar(task)

操作同步行为
创建任务(有 dueDate)自动在日历中创建对应事件
完成任务日历事件标题加 emoji 前缀、颜色变绿
删除任务 / 移除截止日期自动删除关联的日历事件

7.2 习惯 → 目标同步

/src/stores/habit.store.tssyncGoalProgress(habit, record)

操作同步行为
习惯打卡自动更新关联目标的 current 计数
达到目标值自动标记 completed

8. AI 与同步集成

AI Agent 执行数据操作时自动设置同步追踪字段:

typescript
// 所有 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

  1. src/sync/providers/ 下创建新文件,如 icloud-provider.ts
  2. 实现 SyncProvider 接口的 5 个必需方法
  3. SyncManager 初始化时注册:
typescript
syncManager.registerProvider(new ICloudProvider())

11.2 接口约定

约定说明
upload 幂等相同数据多次上传结果一致
download 增量支持 since 时间戳过滤
authenticate 无副作用仅验证,不修改远端数据
disconnect 清理清除本地缓存的凭据和临时数据
错误传播网络错误、认证失败抛出明确异常,由 SyncManager 统一处理重试