feat(app): 统一同步协议 — SyncModels + ApiClient.sync + SyncEngine.tryBatchSync
Flutter ↔ Rust 同步协议对齐: - 新增 sync_models.dart: SyncReq/SyncResp/SyncChange/ConflictInfo 与 Rust dto.rs 一一对应 (CreateJournal/UpdateJournal/DeleteJournal) - ApiClient.sync(): 调用 POST /diary/sync 批量同步端点 - SyncEngine.tryBatchSync(): PendingOperation → SyncChange 批量提交 成功清空队列,冲突保留待用户处理 保留原有逐个同步 trySync() 作为降级方案 后端 509/509 测试通过, Flutter analyze 0 error
This commit is contained in:
@@ -22,6 +22,7 @@ import 'package:isar/isar.dart';
|
||||
|
||||
import '../local/isar_database_native.dart';
|
||||
import '../local/collections/pending_operation_collection.dart';
|
||||
import '../models/sync_models.dart';
|
||||
import '../remote/api_client.dart';
|
||||
|
||||
/// 同步操作类型
|
||||
@@ -277,6 +278,79 @@ class SyncEngine {
|
||||
await persistPendingQueue();
|
||||
}
|
||||
|
||||
/// 批量同步 — 使用 POST /diary/sync 端点一次性提交所有变更
|
||||
///
|
||||
/// 将队列中的 PendingOperation 转换为 SyncChange 列表,
|
||||
/// 调用 Rust sync_handler 批量处理,获取服务端变更和冲突。
|
||||
/// 成功后清空队列;失败时保留队列供重试。
|
||||
Future<SyncResp?> tryBatchSync({DateTime? lastSyncTime}) async {
|
||||
if (_status == SyncStatus.syncing) return null;
|
||||
if (_pendingQueue.isEmpty) {
|
||||
_status = SyncStatus.idle;
|
||||
return null;
|
||||
}
|
||||
|
||||
_status = SyncStatus.syncing;
|
||||
_lastError = null;
|
||||
|
||||
try {
|
||||
// 转换: PendingOperation → SyncChange
|
||||
final changes = _pendingQueue.map(_operationToSyncChange).toList();
|
||||
|
||||
final req = SyncReq(
|
||||
lastSyncTime: lastSyncTime,
|
||||
changes: changes,
|
||||
);
|
||||
|
||||
final resp = await _apiClient.sync(req);
|
||||
|
||||
// 处理冲突 — 将冲突的操作保留在队列中
|
||||
if (resp.conflicts.isNotEmpty) {
|
||||
final conflictIds = resp.conflicts.map((c) => c.journalId).toSet();
|
||||
// 移除已成功同步的非冲突操作,保留冲突操作
|
||||
_pendingQueue.removeWhere(
|
||||
(op) => !conflictIds.contains(op.id),
|
||||
);
|
||||
_lastError = '${resp.conflicts.length} 个操作存在版本冲突';
|
||||
} else {
|
||||
// 全部成功,清空队列
|
||||
_pendingQueue.clear();
|
||||
}
|
||||
|
||||
_status = _pendingQueue.isEmpty ? SyncStatus.idle : SyncStatus.paused;
|
||||
await persistPendingQueue();
|
||||
|
||||
return resp;
|
||||
} on OfflineException {
|
||||
_status = SyncStatus.paused;
|
||||
_lastError = '同步中断:网络不可用';
|
||||
return null;
|
||||
} catch (e) {
|
||||
_status = SyncStatus.error;
|
||||
_lastError = '批量同步失败: $e';
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// PendingOperation → SyncChange 转换
|
||||
SyncChange _operationToSyncChange(PendingOperation op) {
|
||||
switch (op.type) {
|
||||
case SyncOperationType.create:
|
||||
return SyncChangeCreateJournal(data: op.data);
|
||||
case SyncOperationType.update:
|
||||
return SyncChangeUpdateJournal(
|
||||
id: op.id,
|
||||
version: op.version,
|
||||
data: op.data,
|
||||
);
|
||||
case SyncOperationType.delete:
|
||||
return SyncChangeDeleteJournal(
|
||||
id: op.id,
|
||||
version: op.version,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 执行单个同步操作
|
||||
Future<void> _executeOperation(PendingOperation operation) async {
|
||||
switch (operation.type) {
|
||||
|
||||
Reference in New Issue
Block a user