|
|
|
|
@ -1,5 +1,8 @@
|
|
|
|
|
import 'dart:convert'; |
|
|
|
|
import 'package:dio/dio.dart'; |
|
|
|
|
import 'package:get/get.dart'; |
|
|
|
|
import 'package:problem_check_system/app/core/domain/entities/sync_status.dart'; |
|
|
|
|
import 'package:problem_check_system/app/core/repositories/image_repository.dart'; |
|
|
|
|
import 'package:problem_check_system/app/core/services/network_status_service.dart'; |
|
|
|
|
import 'package:problem_check_system/app/features/problem/data/datasources/problem_local_data_source.dart'; |
|
|
|
|
import 'package:problem_check_system/app/features/problem/data/datasources/problem_remote_data_source.dart'; |
|
|
|
|
@ -18,11 +21,13 @@ class ProblemRepositoryImpl implements ProblemRepository {
|
|
|
|
|
final ProblemLocalDataSource localDataSource; |
|
|
|
|
final ProblemRemoteDataSource remoteDataSource; |
|
|
|
|
final NetworkStatusService networkStatusService; |
|
|
|
|
final ImageRepository imageRepository; |
|
|
|
|
|
|
|
|
|
ProblemRepositoryImpl({ |
|
|
|
|
required this.localDataSource, |
|
|
|
|
required this.remoteDataSource, |
|
|
|
|
required this.networkStatusService, |
|
|
|
|
required this.imageRepository, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
@ -127,13 +132,72 @@ class ProblemRepositoryImpl implements ProblemRepository {
|
|
|
|
|
return problem; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// @override |
|
|
|
|
// Future<ProblemEntity> syncProblemToServer(ProblemEntity problem) async { |
|
|
|
|
// // 将 Entity 转换为 DTO,准备与服务器通信 |
|
|
|
|
// final problemDto = ProblemDto.fromEntity(problem); |
|
|
|
|
// ProblemDto syncedDto; |
|
|
|
|
|
|
|
|
|
// switch (problem.syncStatus) { |
|
|
|
|
// case SyncStatus.pendingCreate: |
|
|
|
|
// syncedDto = await remoteDataSource.createProblem(problemDto); |
|
|
|
|
// break; |
|
|
|
|
// case SyncStatus.pendingUpdate: |
|
|
|
|
// syncedDto = await remoteDataSource.updateProblem(problemDto); |
|
|
|
|
// break; |
|
|
|
|
// case SyncStatus.pendingDelete: |
|
|
|
|
// // 先从远程删除 |
|
|
|
|
// await remoteDataSource.deleteProblem(problemDto.id); |
|
|
|
|
// // 远程删除成功后,必须从本地也删除 |
|
|
|
|
// await localDataSource.deleteProblem(problem.id); |
|
|
|
|
// // 返回一个更新了状态的实体,表示操作完成 |
|
|
|
|
// return problem.copyWith(syncStatus: SyncStatus.synced); |
|
|
|
|
|
|
|
|
|
// case SyncStatus.synced: |
|
|
|
|
// case SyncStatus.untracked: |
|
|
|
|
// return problem; |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
// // 将从服务器返回的最新 DTO 转换回 Entity |
|
|
|
|
// // 这个 syncedEntity 现在包含了服务器生成的ID、时间戳等信息 |
|
|
|
|
// final syncedEntity = syncedDto.toEntity(); |
|
|
|
|
|
|
|
|
|
// // 将这个最新的、已同步的 Entity 转换为 Model,并更新回本地数据库 |
|
|
|
|
// // 这是确保本地状态与服务器一致的核心步骤! |
|
|
|
|
// final modelToSave = ProblemModel.fromEntity(syncedEntity); |
|
|
|
|
// await localDataSource.updateProblem(modelToSave.toMap()); |
|
|
|
|
|
|
|
|
|
// // 返回最终从服务器同步回来的、纯净的 Entity |
|
|
|
|
// return syncedEntity; |
|
|
|
|
// } |
|
|
|
|
@override |
|
|
|
|
Future<ProblemEntity> syncProblemToServer(ProblemEntity problem) async { |
|
|
|
|
// 2. 在方法开始时创建一个 CancelToken |
|
|
|
|
// 这个 token 将用于本次同步操作中的所有图片上传 |
|
|
|
|
final cancelToken = CancelToken(); |
|
|
|
|
|
|
|
|
|
ProblemEntity problemToSend = problem; |
|
|
|
|
|
|
|
|
|
// 仅在需要创建或更新时才处理图片上传 |
|
|
|
|
if (problem.syncStatus == SyncStatus.pendingCreate || |
|
|
|
|
problem.syncStatus == SyncStatus.pendingUpdate) { |
|
|
|
|
// 3. 将 CancelToken 传递给辅助方法 |
|
|
|
|
final processedImageUrls = await _uploadLocalImagesAndGetRemoteUrls( |
|
|
|
|
problem.imageUrls, |
|
|
|
|
cancelToken: cancelToken, // 传递 token |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// 使用包含了远程URL的新实体进行后续操作 |
|
|
|
|
problemToSend = problem.copyWith(imageUrls: processedImageUrls); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ---- 后续逻辑与你原来的一致,只是使用 problemToSend ---- |
|
|
|
|
|
|
|
|
|
// 将 Entity 转换为 DTO,准备与服务器通信 |
|
|
|
|
final problemDto = ProblemDto.fromEntity(problem); |
|
|
|
|
final problemDto = ProblemDto.fromEntity(problemToSend); |
|
|
|
|
ProblemDto syncedDto; |
|
|
|
|
|
|
|
|
|
switch (problem.syncStatus) { |
|
|
|
|
switch (problemToSend.syncStatus) { |
|
|
|
|
case SyncStatus.pendingCreate: |
|
|
|
|
syncedDto = await remoteDataSource.createProblem(problemDto); |
|
|
|
|
break; |
|
|
|
|
@ -141,11 +205,9 @@ class ProblemRepositoryImpl implements ProblemRepository {
|
|
|
|
|
syncedDto = await remoteDataSource.updateProblem(problemDto); |
|
|
|
|
break; |
|
|
|
|
case SyncStatus.pendingDelete: |
|
|
|
|
// 先从远程删除 |
|
|
|
|
await remoteDataSource.deleteProblem(problemDto.id); |
|
|
|
|
// 远程删除成功后,必须从本地也删除 |
|
|
|
|
await localDataSource.deleteProblem(problem.id); |
|
|
|
|
// 返回一个更新了状态的实体,表示操作完成 |
|
|
|
|
await imageRepository.deleteProblemImages(problem.id); |
|
|
|
|
return problem.copyWith(syncStatus: SyncStatus.synced); |
|
|
|
|
|
|
|
|
|
case SyncStatus.synced: |
|
|
|
|
@ -153,17 +215,47 @@ class ProblemRepositoryImpl implements ProblemRepository {
|
|
|
|
|
return problem; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 将从服务器返回的最新 DTO 转换回 Entity |
|
|
|
|
// 这个 syncedEntity 现在包含了服务器生成的ID、时间戳等信息 |
|
|
|
|
final syncedEntity = syncedDto.toEntity(); |
|
|
|
|
|
|
|
|
|
// 将这个最新的、已同步的 Entity 转换为 Model,并更新回本地数据库 |
|
|
|
|
// 这是确保本地状态与服务器一致的核心步骤! |
|
|
|
|
final modelToSave = ProblemModel.fromEntity(syncedEntity); |
|
|
|
|
final entityToSaveLocally = syncedDto.toEntity().copyWith( |
|
|
|
|
imageUrls: problem.imageUrls, |
|
|
|
|
); |
|
|
|
|
final modelToSave = ProblemModel.fromEntity(entityToSaveLocally); |
|
|
|
|
await localDataSource.updateProblem(modelToSave.toMap()); |
|
|
|
|
|
|
|
|
|
// 返回最终从服务器同步回来的、纯净的 Entity |
|
|
|
|
return syncedEntity; |
|
|
|
|
return entityToSaveLocally; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 辅助方法:处理图片URL列表,上传本地图片并返回包含远程URL的新列表 |
|
|
|
|
Future<List<String>> _uploadLocalImagesAndGetRemoteUrls( |
|
|
|
|
List<String> urls, { |
|
|
|
|
required CancelToken cancelToken, // 接收 token |
|
|
|
|
}) async { |
|
|
|
|
// 创建所有上传任务的 Future 列表 |
|
|
|
|
final uploadFutures = urls.map((url) async { |
|
|
|
|
// 判断 URL 是否是本地文件路径 (简单判断,!url.startsWith('http') 更通用) |
|
|
|
|
if (!url.startsWith('http')) { |
|
|
|
|
try { |
|
|
|
|
Get.log('准备上传本地图片: $url'); |
|
|
|
|
// 将 CancelToken 传递给 imageRepository 的 uploadImage 方法 |
|
|
|
|
final remoteUrl = await imageRepository.uploadImage( |
|
|
|
|
url, |
|
|
|
|
cancelToken: cancelToken, |
|
|
|
|
); |
|
|
|
|
Get.log('图片上传成功: $url -> $remoteUrl'); |
|
|
|
|
return remoteUrl; |
|
|
|
|
} catch (e) { |
|
|
|
|
Get.log('图片上传失败: $url, 错误: $e'); |
|
|
|
|
// 如果上传失败,建议抛出异常,中断整个同步过程 |
|
|
|
|
// 因为数据不完整可能会导致后续问题 |
|
|
|
|
throw Exception('图片上传失败: $url'); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
// 已经是远程 URL,直接返回,无需上传 |
|
|
|
|
return url; |
|
|
|
|
} |
|
|
|
|
}).toList(); |
|
|
|
|
|
|
|
|
|
// 并行等待所有任务完成 |
|
|
|
|
return Future.wait(uploadFutures); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 同步服务器数据 |
|
|
|
|
@ -223,7 +315,44 @@ class ProblemRepositoryImpl implements ProblemRepository {
|
|
|
|
|
|
|
|
|
|
// 4. 将无冲突的新数据直接写入本地数据库 |
|
|
|
|
if (newProblems.isNotEmpty) { |
|
|
|
|
List<Map<String, dynamic>> problemMaps = newProblems |
|
|
|
|
final List<ProblemEntity> problemsWithLocalImages = []; |
|
|
|
|
|
|
|
|
|
for (final problem in newProblems) { |
|
|
|
|
// 创建一个新的列表来存储下载后的本地图片路径 |
|
|
|
|
final List<String> localImagePaths = []; |
|
|
|
|
|
|
|
|
|
// 遍历实体中的每一个远程图片 URL |
|
|
|
|
for (final remoteUrl in problem.imageUrls) { |
|
|
|
|
try { |
|
|
|
|
Get.log('准备下载图片: $remoteUrl for problem ${problem.id}'); |
|
|
|
|
|
|
|
|
|
// 调用 ImageRepository 下载图片,它会返回本地文件路径 |
|
|
|
|
final localPath = await imageRepository.downloadImage( |
|
|
|
|
remoteUrl, |
|
|
|
|
problem.id, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// 将获取到的本地路径添加到新列表中 |
|
|
|
|
localImagePaths.add(localPath); |
|
|
|
|
Get.log('图片下载并替换成功: $remoteUrl -> $localPath'); |
|
|
|
|
} catch (e) { |
|
|
|
|
// 如果下载失败,可以选择记录日志,然后跳过这张图片 |
|
|
|
|
// 这样,失败的图片就不会被添加到本地路径列表中 |
|
|
|
|
Get.log('图片下载失败: $remoteUrl, 错误: $e'); |
|
|
|
|
// 在这种情况下,我们不把任何路径添加到 localImagePaths 中, |
|
|
|
|
// 或者你也可以选择保留原始的 remoteUrl,但这可能会导致后续逻辑复杂化。 |
|
|
|
|
// 目前的建议是:下载失败就直接跳过。 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 使用 copyWith 方法创建一个新的实体, |
|
|
|
|
// 用包含本地路径的列表替换掉旧的远程 URL 列表 |
|
|
|
|
final updatedProblem = problem.copyWith(imageUrls: localImagePaths); |
|
|
|
|
|
|
|
|
|
// 将更新后的实体添加到最终要存入数据库的列表中 |
|
|
|
|
problemsWithLocalImages.add(updatedProblem); |
|
|
|
|
} |
|
|
|
|
List<Map<String, dynamic>> problemMaps = problemsWithLocalImages |
|
|
|
|
.map((entity) => ProblemModel.fromEntity(entity).toMap()) |
|
|
|
|
.toList(); |
|
|
|
|
await localDataSource.cacheProblems(problemMaps); |
|
|
|
|
|