// 同步引擎 — Web 平台实现(无 Isar 持久化) // // Web 平台上 Isar 不可用,操作队列仅保存在内存中。 // 核心同步逻辑与原生版一致,仅持久化部分为空实现。 import 'dart:async'; import 'dart:convert'; import 'dart:collection'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/foundation.dart'; import '../remote/api_client.dart'; /// 同步操作类型 enum SyncOperationType { create('POST'), update('PUT'), delete('DELETE'); const SyncOperationType(this.httpMethod); final String httpMethod; } /// 同步状态 enum SyncStatus { idle, syncing, paused, error, } /// 待同步操作 class PendingOperation { final String id; final SyncOperationType type; final String endpoint; final Map data; final int version; final DateTime createdAt; final int retryCount; static const int maxRetryCount = 5; const PendingOperation({ required this.id, required this.type, required this.endpoint, required this.data, required this.version, required this.createdAt, this.retryCount = 0, }); PendingOperation copyWith({ String? id, SyncOperationType? type, String? endpoint, Map? data, int? version, DateTime? createdAt, int? retryCount, }) => PendingOperation( id: id ?? this.id, type: type ?? this.type, endpoint: endpoint ?? this.endpoint, data: data ?? this.data, version: version ?? this.version, createdAt: createdAt ?? this.createdAt, retryCount: retryCount ?? this.retryCount, ); bool get isExhausted => retryCount >= maxRetryCount; } /// 同步引擎 — Web 版(内存队列,无持久化) class SyncEngine { final ApiClient _apiClient; final Queue _pendingQueue = Queue(); StreamSubscription>? _connectivitySub; SyncStatus _status = SyncStatus.idle; String? _lastError; SyncEngine({required ApiClient apiClient}) : _apiClient = apiClient; SyncStatus get status => _status; String? get lastError => _lastError; int get pendingCount => _pendingQueue.length; bool get isSyncing => _status == SyncStatus.syncing; /// 入队待同步操作 — 私密日记(is_private=true)不入队 void enqueue(PendingOperation operation) { // 防御性检查:私密日记仅保存在本地,不上传后端 final isPrivate = operation.data['is_private'] as bool? ?? false; if (isPrivate) { debugPrint('SyncEngine.enqueue: 跳过私密日记 ${operation.id}'); return; } _pendingQueue.add(operation); if (_status == SyncStatus.idle) { _status = SyncStatus.paused; } } void enqueueAll(List operations) { for (final op in operations) { _pendingQueue.add(op); } if (_status == SyncStatus.idle && _pendingQueue.isNotEmpty) { _status = SyncStatus.paused; } } Future trySync() async { if (_status == SyncStatus.syncing) return; if (_pendingQueue.isEmpty) { _status = SyncStatus.idle; return; } final connectivity = Connectivity(); final result = await connectivity.checkConnectivity(); final isOnline = result.any((r) => r != ConnectivityResult.none); if (!isOnline) { _status = SyncStatus.paused; _lastError = '网络不可用'; return; } _status = SyncStatus.syncing; _lastError = null; while (_pendingQueue.isNotEmpty) { final operation = _pendingQueue.removeFirst(); try { await _executeOperation(operation); } on OfflineException { _pendingQueue.addFirst(operation); _status = SyncStatus.paused; _lastError = '同步中断:网络不可用'; return; } catch (e) { debugPrint('SyncEngine.trySync 操作失败: $e'); final retried = operation.copyWith(retryCount: operation.retryCount + 1); if (retried.isExhausted) { _lastError = '操作同步失败(已耗尽重试次数): ${operation.endpoint}'; continue; } _pendingQueue.addFirst(retried); _status = SyncStatus.error; _lastError = '同步失败: $e'; return; } } _status = SyncStatus.idle; _lastError = null; } Future _executeOperation(PendingOperation operation) async { switch (operation.type) { case SyncOperationType.create: await _apiClient.post(operation.endpoint, data: operation.data); case SyncOperationType.update: await _apiClient.put(operation.endpoint, data: operation.data); case SyncOperationType.delete: await _apiClient.delete(operation.endpoint); } } void clear() { _pendingQueue.clear(); _status = SyncStatus.idle; _lastError = null; } List get snapshot => _pendingQueue.toList(); /// Web 平台:持久化为空操作(队列仅保存在内存中) Future persistPendingQueue() async {} /// Web 平台:恢复队列为空操作(无持久化数据) Future restorePendingQueue() async {} void startAutoSync() { _connectivitySub = Connectivity().onConnectivityChanged.listen((result) { final isOnline = result.any((r) => r != ConnectivityResult.none); if (isOnline && _pendingQueue.isNotEmpty && _status != SyncStatus.syncing) { debugPrint('SyncEngine: 网络恢复,开始同步 ${_pendingQueue.length} 个操作'); trySync(); } }); } void dispose() { _connectivitySub?.cancel(); _connectivitySub = null; } String _encodeJson(Map data) { try { return jsonEncode(data); } catch (_) { return '{}'; } } Map _decodeJson(String json) { try { return jsonDecode(json) as Map; } catch (_) { return {}; } } }