UNPKG

delta-sync

Version:

A lightweight framework for bi-directional database synchronization with automatic version tracking and conflict resolution.

258 lines (257 loc) 12 kB
// core/syncManager.ts // 同步管理器,提供轻量级的同步API,处理本地和远程数据的双向同步 export class SyncManager { constructor(localCoordinator, cloudCoordinator) { this.isSyncing = false; this.localCoordinator = localCoordinator; this.cloudCoordinator = cloudCoordinator; } async pushChanges(limit = 100) { if (this.isSyncing) { console.log("同步已在进行中,跳过本次推送"); return { success: false }; } try { const localVersion = await this.localCoordinator.getCurrentVersion(); const cloudVersion = await this.cloudCoordinator.getLatestVersion(); console.log(`版本信息: - 本地版本: ${localVersion} - 云端版本: ${cloudVersion}`); if (cloudVersion > localVersion) { console.warn("请先拉取最新的云端版本,才能进行推送"); return { success: false }; } this.isSyncing = true; console.log("开始推送变更..."); // 1. 获取本地待推送的附件变更 const attachmentChanges = await this.localCoordinator.getPendingAttachmentChanges(localVersion, limit); console.log(`待推送的附件变更数量: ${attachmentChanges.length}`); // 2. 处理附件变更 if (attachmentChanges.length > 0) { console.log("=== 开始处理附件变更 ==="); console.log(`附件变更详情:`, attachmentChanges.map(change => ({ id: change.id, version: change._version, type: change.type }))); // 2.1 处理文件传输 const result = await this.processAttachmentChanges('push', attachmentChanges); console.log(`附件处理结果: - 成功数量: ${result.processed} - 失败数量: ${result.failed} - 成功ID: ${result.attachmentIds.processed.join(', ')} - 失败ID: ${result.attachmentIds.failed.join(', ')}`); // 2.2 处理附件变更记录 await this.localCoordinator.applyAttachmentChanges(attachmentChanges); console.log("本地附件变更记录已更新"); // 2.3 推送附件变更记录到云端 await this.cloudCoordinator.applyAttachmentChanges(attachmentChanges); console.log(`推送 ${attachmentChanges.length} 个附件变更记录到云端`); console.log("=== 附件变更处理完成 ==="); } // 3. 处理数据变更 const pendingChanges = await this.localCoordinator.getPendingChanges(localVersion, limit); console.log(`待推送的数据变更数量: ${pendingChanges.length}`); if (pendingChanges.length > 0) { console.log("=== 开始处理数据变更 ==="); const response = await this.cloudCoordinator.applyChanges(pendingChanges); if (!response.success) { console.error("推送数据变更失败:", response.error); return { success: false, changes: pendingChanges }; } if (response.success && response.version) { await this.localCoordinator.updateCurrentVersion(response.version); console.log(`已更新本地版本到: ${response.version}`); return { success: true, changes: response.changes || pendingChanges, version: response.version }; } } console.log("推送变更完成"); return { success: true, changes: [], version: localVersion }; } catch (error) { console.error('推送变更时发生错误:', error); return { success: false }; } finally { this.isSyncing = false; } } async pullChanges() { if (this.isSyncing) { console.log("同步已在进行中,跳过本次拉取"); return { success: false }; } try { this.isSyncing = true; console.log("开始拉取变更..."); const localVersion = await this.localCoordinator.getCurrentVersion(); const cloudVersion = await this.cloudCoordinator.getLatestVersion(); if (cloudVersion === 0 || cloudVersion <= localVersion) { console.log("云端没有更新,无需拉取变更"); return { success: true, version: localVersion }; } console.log(`当前本地版本: ${localVersion}, 云端最新版本: ${cloudVersion}`); // 1. 拉取附件变更 const attachmentChanges = await this.cloudCoordinator.getAttachmentChanges(localVersion); console.log(`获取到 ${attachmentChanges.length}个附件变更`); if (attachmentChanges.length > 0) { console.log(`附件变更详情:`, attachmentChanges.map(change => ({ id: change.id, version: change._version, type: change.type }))); const result = await this.processAttachmentChanges('pull', attachmentChanges); console.log(`附件处理结果: - 成功数量: ${result.processed} - 失败数量: ${result.failed} `); await this.localCoordinator.applyAttachmentChanges(attachmentChanges); } const response = await this.cloudCoordinator.getPendingChanges(localVersion); if (!response.success || !response.changes) { console.error("拉取数据变更失败:", response.error); return { success: false }; } console.log(`获取到 ${response.changes.length} 个数据变更`); if (response.changes.length > 0) { await this.localCoordinator.applyDataChange(response.changes); const syncedVersion = Math.max(...response.changes.map(change => change._version || 0), ...attachmentChanges.map(change => change._version || 0)); await this.localCoordinator.updateCurrentVersion(syncedVersion); console.log(`已应用数据变更,最新版本: ${response.version}`); return { success: true, changes: response.changes, version: cloudVersion }; } console.log("拉取变更完成"); return { success: true, changes: [], version: cloudVersion }; } catch (error) { console.error('拉取变更时发生错误:', error); return { success: false }; } finally { this.isSyncing = false; } } async processAttachmentChanges(direction, attachmentChanges) { // 结果统计 const result = { processed: 0, failed: 0, attachmentIds: { processed: [], failed: [] } }; // 如果没有变更,直接返回 if (attachmentChanges.length === 0) { return result; } // 源和目标适配器 const sourceAdapter = direction === 'push' ? this.localCoordinator.localAdapter : this.cloudCoordinator.cloudAdapter; const targetAdapter = direction === 'push' ? this.cloudCoordinator.cloudAdapter : this.localCoordinator.localAdapter; try { // 1. 批量处理删除操作 const deleteIds = attachmentChanges .filter(change => change.type === 'delete') .map(change => change.id); if (deleteIds.length > 0) { const deleteResult = await targetAdapter.deleteFiles(deleteIds) .catch(err => { console.error(`批量删除${direction === 'push' ? '云端' : '本地'}附件失败:`, err); return { deleted: [], failed: deleteIds }; }); result.processed += deleteResult.deleted.length; result.failed += deleteResult.failed.length; result.attachmentIds.processed.push(...deleteResult.deleted); result.attachmentIds.failed.push(...deleteResult.failed); } // 2. 批量处理上传/下载操作 const transferIds = attachmentChanges .filter(change => change.type === 'put') .map(change => change.id); if (transferIds.length > 0) { // 读取源文件 const filesMap = await sourceAdapter.readFiles(transferIds) .catch(err => { console.error(`批量读取${direction === 'push' ? '本地' : '云端'}附件失败:`, err); result.failed += transferIds.length; result.attachmentIds.failed.push(...transferIds); return new Map(); }); // 准备传输文件 const fileItems = []; const readFailedIds = []; for (const id of transferIds) { const content = filesMap.get(id); if (content) { fileItems.push({ fileId: id, content }); } else { readFailedIds.push(id); } } // 记录读取失败的文件 if (readFailedIds.length > 0) { result.failed += readFailedIds.length; result.attachmentIds.failed.push(...readFailedIds); } // 写入目标 if (fileItems.length > 0) { const savedAttachments = await targetAdapter.saveFiles(fileItems) .catch(err => { console.error(`批量保存${direction === 'push' ? '云端' : '本地'}附件失败:`, err); result.failed += fileItems.length; result.attachmentIds.failed.push(...fileItems.map(item => item.fileId)); return []; }); // 记录保存结果 const savedIds = savedAttachments.map(att => att.id); result.processed += savedIds.length; result.attachmentIds.processed.push(...savedIds); // 标记保存失败的文件 const failedToSaveIds = fileItems .map(item => item.fileId) .filter(id => !savedIds.includes(id)); if (failedToSaveIds.length > 0) { result.failed += failedToSaveIds.length; result.attachmentIds.failed.push(...failedToSaveIds); } } } return result; } catch (error) { console.error(`处理附件变更时发生意外错误:`, error); return { processed: 0, failed: attachmentChanges.length, attachmentIds: { processed: [], failed: attachmentChanges.map(change => change.id) } }; } } }