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
177 lines
4.5 KiB
Dart
177 lines
4.5 KiB
Dart
// 同步协议模型 — 与 Rust 端 SyncReq/SyncResp/SyncChange/ConflictInfo 一一对应
|
|
//
|
|
// 端点: POST /api/v1/diary/sync
|
|
// Rust DTO: crates/erp-diary/src/dto.rs (SyncReq, SyncResp, SyncChange, ConflictInfo)
|
|
|
|
/// 同步请求 — 与 Rust SyncReq 对应
|
|
///
|
|
/// ```rust
|
|
/// pub struct SyncReq {
|
|
/// pub last_sync_time: Option<DateTime<Utc>>,
|
|
/// pub changes: Vec<SyncChange>,
|
|
/// }
|
|
/// ```
|
|
class SyncReq {
|
|
final DateTime? lastSyncTime;
|
|
final List<SyncChange> changes;
|
|
|
|
const SyncReq({this.lastSyncTime, this.changes = const []});
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
if (lastSyncTime != null)
|
|
'last_sync_time': lastSyncTime!.toUtc().toIso8601String(),
|
|
'changes': changes.map((c) => c.toJson()).toList(),
|
|
};
|
|
}
|
|
|
|
/// 同步变更条目 — 与 Rust SyncChange 枚举对应
|
|
///
|
|
/// ```rust
|
|
/// pub enum SyncChange {
|
|
/// CreateJournal { data: serde_json::Value },
|
|
/// UpdateJournal { id: Uuid, version: i32, data: serde_json::Value },
|
|
/// DeleteJournal { id: Uuid, version: i32 },
|
|
/// }
|
|
/// ```
|
|
sealed class SyncChange {
|
|
const SyncChange();
|
|
|
|
Map<String, dynamic> toJson();
|
|
|
|
/// 从 JSON 反序列化
|
|
factory SyncChange.fromJson(Map<String, dynamic> json) {
|
|
if (json.containsKey('CreateJournal')) {
|
|
return SyncChangeCreateJournal(
|
|
data: json['CreateJournal']['data'] as Map<String, dynamic>,
|
|
);
|
|
}
|
|
if (json.containsKey('UpdateJournal')) {
|
|
final inner = json['UpdateJournal'] as Map<String, dynamic>;
|
|
return SyncChangeUpdateJournal(
|
|
id: inner['id'] as String,
|
|
version: inner['version'] as int,
|
|
data: inner['data'] as Map<String, dynamic>,
|
|
);
|
|
}
|
|
if (json.containsKey('DeleteJournal')) {
|
|
final inner = json['DeleteJournal'] as Map<String, dynamic>;
|
|
return SyncChangeDeleteJournal(
|
|
id: inner['id'] as String,
|
|
version: inner['version'] as int,
|
|
);
|
|
}
|
|
throw FormatException('Unknown SyncChange variant: $json');
|
|
}
|
|
}
|
|
|
|
/// 创建日记变更
|
|
class SyncChangeCreateJournal extends SyncChange {
|
|
final Map<String, dynamic> data;
|
|
|
|
const SyncChangeCreateJournal({required this.data});
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() => {
|
|
'CreateJournal': {'data': data},
|
|
};
|
|
}
|
|
|
|
/// 更新日记变更
|
|
class SyncChangeUpdateJournal extends SyncChange {
|
|
final String id;
|
|
final int version;
|
|
final Map<String, dynamic> data;
|
|
|
|
const SyncChangeUpdateJournal({
|
|
required this.id,
|
|
required this.version,
|
|
required this.data,
|
|
});
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() => {
|
|
'UpdateJournal': {
|
|
'id': id,
|
|
'version': version,
|
|
'data': data,
|
|
},
|
|
};
|
|
}
|
|
|
|
/// 删除日记变更
|
|
class SyncChangeDeleteJournal extends SyncChange {
|
|
final String id;
|
|
final int version;
|
|
|
|
const SyncChangeDeleteJournal({
|
|
required this.id,
|
|
required this.version,
|
|
});
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() => {
|
|
'DeleteJournal': {
|
|
'id': id,
|
|
'version': version,
|
|
},
|
|
};
|
|
}
|
|
|
|
/// 同步响应 — 与 Rust SyncResp 对应
|
|
///
|
|
/// ```rust
|
|
/// pub struct SyncResp {
|
|
/// pub server_changes: Vec<serde_json::Value>,
|
|
/// pub conflicts: Vec<ConflictInfo>,
|
|
/// pub sync_time: DateTime<Utc>,
|
|
/// }
|
|
/// ```
|
|
class SyncResp {
|
|
final List<Map<String, dynamic>> serverChanges;
|
|
final List<ConflictInfo> conflicts;
|
|
final DateTime syncTime;
|
|
|
|
const SyncResp({
|
|
required this.serverChanges,
|
|
required this.conflicts,
|
|
required this.syncTime,
|
|
});
|
|
|
|
factory SyncResp.fromJson(Map<String, dynamic> json) => SyncResp(
|
|
serverChanges: (json['server_changes'] as List)
|
|
.map((e) => Map<String, dynamic>.from(e as Map))
|
|
.toList(),
|
|
conflicts: (json['conflicts'] as List)
|
|
.map((e) => ConflictInfo.fromJson(Map<String, dynamic>.from(e as Map)))
|
|
.toList(),
|
|
syncTime: DateTime.parse(json['sync_time'] as String),
|
|
);
|
|
}
|
|
|
|
/// 冲突信息 — 与 Rust ConflictInfo 对应
|
|
///
|
|
/// ```rust
|
|
/// pub struct ConflictInfo {
|
|
/// pub journal_id: Uuid,
|
|
/// pub local_version: i32,
|
|
/// pub server_version: i32,
|
|
/// }
|
|
/// ```
|
|
class ConflictInfo {
|
|
final String journalId;
|
|
final int localVersion;
|
|
final int serverVersion;
|
|
|
|
const ConflictInfo({
|
|
required this.journalId,
|
|
required this.localVersion,
|
|
required this.serverVersion,
|
|
});
|
|
|
|
factory ConflictInfo.fromJson(Map<String, dynamic> json) => ConflictInfo(
|
|
journalId: json['journal_id'] as String,
|
|
localVersion: json['local_version'] as int,
|
|
serverVersion: json['server_version'] as int,
|
|
);
|
|
}
|