Browse Source

feat : 问题中的上传图片与下载图片

dev
徐振升 2 months ago
parent
commit
08dae3b11a
  1. 2
      lib/app/core/bindings/initial_binding.dart
  2. 65
      lib/app/core/repositories/file_repository.dart
  3. 7
      lib/app/core/repositories/image_repository.dart
  4. 61
      lib/app/core/repositories/image_repository_impl.dart
  5. 1
      lib/app/features/navigation/presentation/bindings/navigation_binding.dart
  6. 9
      lib/app/features/navigation/presentation/controllers/navigation_controller.dart
  7. 159
      lib/app/features/problem/data/repositories/problem_repository_impl.dart
  8. 6
      lib/app/features/problem/presentation/bindings/problem_list_binding.dart
  9. 6
      lib/app/features/problem/presentation/bindings/problem_upload_binding.dart
  10. 2
      lib/app/features/problem/presentation/controllers/problem_list_controller.dart
  11. 5
      lib/app/features/problem/presentation/pages/problem_form_page.dart

2
lib/app/core/bindings/initial_binding.dart

@ -5,7 +5,6 @@ import 'package:problem_check_system/app/core/services/database_service.dart';
import 'package:problem_check_system/app/core/services/network_status_service.dart';
import 'package:problem_check_system/app/core/services/http_provider.dart';
import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
import 'package:problem_check_system/app/core/repositories/file_repository.dart';
import 'package:problem_check_system/app/core/repositories/image_repository.dart';
import 'package:problem_check_system/app/core/repositories/image_repository_impl.dart';
import 'package:problem_check_system/app/core/services/upgrader_service.dart';
@ -29,7 +28,6 @@ class InitialBinding implements Bindings {
}
void _registerRepositories() {
Get.lazyPut<FileRepository>(() => FileRepository());
Get.lazyPut<ImageRepository>(
() => ImageRepositoryImpl(httpProvider: Get.find<HttpProvider>()),
);

65
lib/app/core/repositories/file_repository.dart

@ -1,65 +0,0 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; // kDebugMode debugPrint
import 'package:get/get.dart' hide FormData, MultipartFile;
import 'package:path/path.dart' as p;
import 'package:problem_check_system/app/core/extensions/http_response_extension.dart';
import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart';
import 'package:problem_check_system/app/core/services/http_provider.dart';
class FileRepository extends GetxService {
final HttpProvider _httpProvider = Get.find<HttpProvider>();
/// @param imageFilePath
/// @param cancelToken
/// @param onSendProgress
/// @return URL
Future<String> uploadImage(
String imageFilePath, {
required CancelToken cancelToken,
ProgressCallback? onSendProgress,
}) async {
try {
// 1. FormData multipart/form-data
final formData = FormData.fromMap({
// 'file':
'file': await MultipartFile.fromFile(
imageFilePath,
filename: p.basename(imageFilePath),
),
});
// 2. 使 HttpProvider post
final response = await _httpProvider.post(
ApiEndpoints.postUploadFile,
data: formData,
cancelToken: cancelToken, // post
onSendProgress: onSendProgress, // post
);
// --- () ---
if (kDebugMode) {
debugPrint('服务器返回的状态码: ${response.statusCode}');
debugPrint('服务器返回的原始数据: ${response.data}');
}
// 3. URL
if (response.isSuccess) {
final Map<String, dynamic> data = response.data;
// URL 'url'
String imageUrl =
"${ApiEndpoints.baseUrl}${ApiEndpoints.postUploadFile}/${data['objectName']}";
return imageUrl;
} else {
throw Exception('上传失败,状态码: ${response.statusCode}');
}
} on DioException catch (e) {
Get.log('图片上传发生未知错误: $e');
throw Exception('图片上传失败: ${e.message}');
} catch (e) {
Get.log('图片上传发生未知错误: $e');
throw Exception('图片上传发生未知错误: $e');
}
}
}

7
lib/app/core/repositories/image_repository.dart

@ -1,6 +1,13 @@
// image_repository.dart
import 'package:dio/dio.dart';
abstract class ImageRepository {
Future<String> downloadImage(String imageUrl, String problemId);
Future<String> uploadImage(
String imageFilePath, {
required CancelToken cancelToken,
ProgressCallback? onSendProgress,
});
Future<bool> isImageDownloaded(String imageUrl, String problemId);
Future<String?> getLocalImagePath(String imageUrl, String problemId);
Future<void> deleteProblemImages(String problemId);

61
lib/app/core/repositories/image_repository_impl.dart

@ -1,11 +1,15 @@
// image_repository_impl.dart
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart' hide FormData, MultipartFile;
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import 'package:problem_check_system/app/core/extensions/http_response_extension.dart';
import 'package:problem_check_system/app/core/services/http_provider.dart';
import 'package:problem_check_system/app/core/repositories/image_repository.dart';
import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart';
class ImageRepositoryImpl extends GetxService implements ImageRepository {
final HttpProvider httpProvider;
@ -70,6 +74,61 @@ class ImageRepositoryImpl extends GetxService implements ImageRepository {
}
}
/// @param imageFilePath
/// @param cancelToken
/// @param onSendProgress
/// @return URL
@override
Future<String> uploadImage(
String imageFilePath, {
required CancelToken cancelToken,
ProgressCallback? onSendProgress,
}) async {
try {
// 1. FormData multipart/form-data
final formData = FormData.fromMap({
// 'file':
'file': await MultipartFile.fromFile(
imageFilePath,
filename: p.basename(imageFilePath),
),
});
// 2. 使 HttpProvider post
final response = await httpProvider.post(
ApiEndpoints.postUploadFile,
data: formData,
cancelToken: cancelToken, // post
onSendProgress: onSendProgress, // post
);
// --- () ---
if (kDebugMode) {
debugPrint('服务器返回的状态码: ${response.statusCode}');
debugPrint('服务器返回的原始数据: ${response.data}');
}
// 3. URL
if (response.isSuccess) {
final Map<String, dynamic> data = response.data;
// URL 'url'
String imageUrl =
"${ApiEndpoints.baseUrl}${ApiEndpoints.postUploadFile}/${data['objectName']}";
return imageUrl;
} else {
throw Exception('上传失败,状态码: ${response.statusCode}');
}
} on DioException catch (e) {
Get.log('图片上传发生未知错误: $e');
throw Exception('图片上传失败: ${e.message}');
} catch (e) {
Get.log('图片上传发生未知错误: $e');
throw Exception('图片上传发生未知错误: $e');
}
}
@override
Future<bool> isImageDownloaded(String imageUrl, String problemId) async {
try {

1
lib/app/features/navigation/presentation/bindings/navigation_binding.dart

@ -11,6 +11,7 @@ class NavigationBinding extends Bindings {
() => NavigationController(
networkStatusService: Get.find<NetworkStatusService>(),
enterpriseListController: Get.find<EnterpriseListController>(),
problemListController: Get.find(),
),
);
}

9
lib/app/features/navigation/presentation/controllers/navigation_controller.dart

@ -7,6 +7,7 @@ import 'package:problem_check_system/app/features/enterprise/presentation/contro
import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_list_page.dart';
import 'package:problem_check_system/app/features/home/pages/home_page.dart';
import 'package:problem_check_system/app/features/my/views/my_page.dart';
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_list_controller.dart';
import 'package:problem_check_system/app/features/problem/presentation/pages/problem_list_page.dart';
class NavigationController extends GetxController {
@ -19,9 +20,11 @@ class NavigationController extends GetxController {
final fabUploadPosition = Offset(0, 0).obs;
final NetworkStatusService networkStatusService;
final EnterpriseListController enterpriseListController;
final ProblemListController problemListController;
NavigationController({
required this.networkStatusService,
required this.enterpriseListController,
required this.problemListController,
});
/// get
@ -108,8 +111,10 @@ class NavigationController extends GetxController {
}
break;
case 2: //
Get.log("当前在问题页面,准备跳转到问题上传页...");
Get.toNamed(AppRoutes.problemUpload);
final result = await Get.toNamed(AppRoutes.problemUpload);
if (result == true) {
problemListController.search();
}
break;
default:
final result = await Get.toNamed(AppRoutes.enterpriseUpload);

159
lib/app/features/problem/data/repositories/problem_repository_impl.dart

@ -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);

6
lib/app/features/problem/presentation/bindings/problem_list_binding.dart

@ -1,5 +1,7 @@
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/bindings/base_bindings.dart';
import 'package:problem_check_system/app/core/repositories/image_repository.dart';
import 'package:problem_check_system/app/core/repositories/image_repository_impl.dart';
import 'package:problem_check_system/app/core/services/database_service.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/get_enterprises_usecase.dart';
import 'package:problem_check_system/app/features/problem/data/datasources/problem_local_data_source.dart';
@ -29,11 +31,15 @@ class ProblemListBinding extends BaseBindings {
@override
void register3Repositories() {
Get.lazyPut<ImageRepository>(
() => ImageRepositoryImpl(httpProvider: Get.find()),
);
Get.lazyPut<ProblemRepository>(
() => ProblemRepositoryImpl(
localDataSource: Get.find(),
remoteDataSource: Get.find(),
networkStatusService: Get.find(),
imageRepository: Get.find(),
),
);
}

6
lib/app/features/problem/presentation/bindings/problem_upload_binding.dart

@ -1,5 +1,7 @@
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/bindings/base_bindings.dart';
import 'package:problem_check_system/app/core/repositories/image_repository.dart';
import 'package:problem_check_system/app/core/repositories/image_repository_impl.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart';
@ -34,11 +36,15 @@ class ProblemUploadBinding extends BaseBindings {
@override
void register3Repositories() {
Get.lazyPut<ImageRepository>(
() => ImageRepositoryImpl(httpProvider: Get.find()),
);
Get.lazyPut<ProblemRepository>(
() => ProblemRepositoryImpl(
localDataSource: Get.find(),
remoteDataSource: Get.find(),
networkStatusService: Get.find(),
imageRepository: Get.find(),
),
);
Get.lazyPut<EnterpriseRepository>(

2
lib/app/features/problem/presentation/controllers/problem_list_controller.dart

@ -15,8 +15,6 @@ import 'package:problem_check_system/app/features/problem/domain/usecases/get_al
import 'package:problem_check_system/app/features/problem/domain/usecases/resolve_conflict_usecase.dart';
import 'package:problem_check_system/app/features/problem/domain/usecases/sync_problems_usecase.dart';
// TODO
class ProblemListController extends GetxController {
final GetAllProblemsUsecase getAllProblemsUsecase;
final GetEnterprisesUsecase getEnterprisesUsecase;

5
lib/app/features/problem/presentation/pages/problem_form_page.dart

@ -34,11 +34,6 @@ class ProblemFormPage extends GetView<ProblemFormController> {
),
);
}
// 3.
if (controller.enterpriseList.isEmpty) {
return const Center(child: Text('没有可用的企业'));
}
return Column(
children: [
Expanded(

Loading…
Cancel
Save