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/network_status_service.dart';
import 'package:problem_check_system/app/core/services/http_provider.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/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.dart';
import 'package:problem_check_system/app/core/repositories/image_repository_impl.dart'; import 'package:problem_check_system/app/core/repositories/image_repository_impl.dart';
import 'package:problem_check_system/app/core/services/upgrader_service.dart'; import 'package:problem_check_system/app/core/services/upgrader_service.dart';
@ -29,7 +28,6 @@ class InitialBinding implements Bindings {
} }
void _registerRepositories() { void _registerRepositories() {
Get.lazyPut<FileRepository>(() => FileRepository());
Get.lazyPut<ImageRepository>( Get.lazyPut<ImageRepository>(
() => ImageRepositoryImpl(httpProvider: Get.find<HttpProvider>()), () => 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 // image_repository.dart
import 'package:dio/dio.dart';
abstract class ImageRepository { abstract class ImageRepository {
Future<String> downloadImage(String imageUrl, String problemId); 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<bool> isImageDownloaded(String imageUrl, String problemId);
Future<String?> getLocalImagePath(String imageUrl, String problemId); Future<String?> getLocalImagePath(String imageUrl, String problemId);
Future<void> deleteProblemImages(String problemId); Future<void> deleteProblemImages(String problemId);

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

@ -1,11 +1,15 @@
// image_repository_impl.dart // image_repository_impl.dart
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart'; 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_provider/path_provider.dart';
import 'package:path/path.dart' as path; 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/services/http_provider.dart';
import 'package:problem_check_system/app/core/repositories/image_repository.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 { class ImageRepositoryImpl extends GetxService implements ImageRepository {
final HttpProvider httpProvider; 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 @override
Future<bool> isImageDownloaded(String imageUrl, String problemId) async { Future<bool> isImageDownloaded(String imageUrl, String problemId) async {
try { try {

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

@ -11,6 +11,7 @@ class NavigationBinding extends Bindings {
() => NavigationController( () => NavigationController(
networkStatusService: Get.find<NetworkStatusService>(), networkStatusService: Get.find<NetworkStatusService>(),
enterpriseListController: Get.find<EnterpriseListController>(), 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/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/home/pages/home_page.dart';
import 'package:problem_check_system/app/features/my/views/my_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'; import 'package:problem_check_system/app/features/problem/presentation/pages/problem_list_page.dart';
class NavigationController extends GetxController { class NavigationController extends GetxController {
@ -19,9 +20,11 @@ class NavigationController extends GetxController {
final fabUploadPosition = Offset(0, 0).obs; final fabUploadPosition = Offset(0, 0).obs;
final NetworkStatusService networkStatusService; final NetworkStatusService networkStatusService;
final EnterpriseListController enterpriseListController; final EnterpriseListController enterpriseListController;
final ProblemListController problemListController;
NavigationController({ NavigationController({
required this.networkStatusService, required this.networkStatusService,
required this.enterpriseListController, required this.enterpriseListController,
required this.problemListController,
}); });
/// get /// get
@ -108,8 +111,10 @@ class NavigationController extends GetxController {
} }
break; break;
case 2: // case 2: //
Get.log("当前在问题页面,准备跳转到问题上传页..."); final result = await Get.toNamed(AppRoutes.problemUpload);
Get.toNamed(AppRoutes.problemUpload); if (result == true) {
problemListController.search();
}
break; break;
default: default:
final result = await Get.toNamed(AppRoutes.enterpriseUpload); 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 '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/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/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_local_data_source.dart';
import 'package:problem_check_system/app/features/problem/data/datasources/problem_remote_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 ProblemLocalDataSource localDataSource;
final ProblemRemoteDataSource remoteDataSource; final ProblemRemoteDataSource remoteDataSource;
final NetworkStatusService networkStatusService; final NetworkStatusService networkStatusService;
final ImageRepository imageRepository;
ProblemRepositoryImpl({ ProblemRepositoryImpl({
required this.localDataSource, required this.localDataSource,
required this.remoteDataSource, required this.remoteDataSource,
required this.networkStatusService, required this.networkStatusService,
required this.imageRepository,
}); });
@override @override
@ -127,13 +132,72 @@ class ProblemRepositoryImpl implements ProblemRepository {
return problem; 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 @override
Future<ProblemEntity> syncProblemToServer(ProblemEntity problem) async { 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 // Entity DTO
final problemDto = ProblemDto.fromEntity(problem); final problemDto = ProblemDto.fromEntity(problemToSend);
ProblemDto syncedDto; ProblemDto syncedDto;
switch (problem.syncStatus) { switch (problemToSend.syncStatus) {
case SyncStatus.pendingCreate: case SyncStatus.pendingCreate:
syncedDto = await remoteDataSource.createProblem(problemDto); syncedDto = await remoteDataSource.createProblem(problemDto);
break; break;
@ -141,11 +205,9 @@ class ProblemRepositoryImpl implements ProblemRepository {
syncedDto = await remoteDataSource.updateProblem(problemDto); syncedDto = await remoteDataSource.updateProblem(problemDto);
break; break;
case SyncStatus.pendingDelete: case SyncStatus.pendingDelete:
//
await remoteDataSource.deleteProblem(problemDto.id); await remoteDataSource.deleteProblem(problemDto.id);
//
await localDataSource.deleteProblem(problem.id); await localDataSource.deleteProblem(problem.id);
// await imageRepository.deleteProblemImages(problem.id);
return problem.copyWith(syncStatus: SyncStatus.synced); return problem.copyWith(syncStatus: SyncStatus.synced);
case SyncStatus.synced: case SyncStatus.synced:
@ -153,17 +215,47 @@ class ProblemRepositoryImpl implements ProblemRepository {
return problem; return problem;
} }
// DTO Entity final entityToSaveLocally = syncedDto.toEntity().copyWith(
// syncedEntity ID imageUrls: problem.imageUrls,
final syncedEntity = syncedDto.toEntity(); );
final modelToSave = ProblemModel.fromEntity(entityToSaveLocally);
// Entity Model
//
final modelToSave = ProblemModel.fromEntity(syncedEntity);
await localDataSource.updateProblem(modelToSave.toMap()); await localDataSource.updateProblem(modelToSave.toMap());
// Entity return entityToSaveLocally;
return syncedEntity; }
/// 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. // 4.
if (newProblems.isNotEmpty) { 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()) .map((entity) => ProblemModel.fromEntity(entity).toMap())
.toList(); .toList();
await localDataSource.cacheProblems(problemMaps); 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:get/get.dart';
import 'package:problem_check_system/app/core/bindings/base_bindings.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/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/enterprise/domain/usecases/get_enterprises_usecase.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_local_data_source.dart';
@ -29,11 +31,15 @@ class ProblemListBinding extends BaseBindings {
@override @override
void register3Repositories() { void register3Repositories() {
Get.lazyPut<ImageRepository>(
() => ImageRepositoryImpl(httpProvider: Get.find()),
);
Get.lazyPut<ProblemRepository>( Get.lazyPut<ProblemRepository>(
() => ProblemRepositoryImpl( () => ProblemRepositoryImpl(
localDataSource: Get.find(), localDataSource: Get.find(),
remoteDataSource: Get.find(), remoteDataSource: Get.find(),
networkStatusService: 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:get/get.dart';
import 'package:problem_check_system/app/core/bindings/base_bindings.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_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/datasources/enterprise_remote_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.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 @override
void register3Repositories() { void register3Repositories() {
Get.lazyPut<ImageRepository>(
() => ImageRepositoryImpl(httpProvider: Get.find()),
);
Get.lazyPut<ProblemRepository>( Get.lazyPut<ProblemRepository>(
() => ProblemRepositoryImpl( () => ProblemRepositoryImpl(
localDataSource: Get.find(), localDataSource: Get.find(),
remoteDataSource: Get.find(), remoteDataSource: Get.find(),
networkStatusService: Get.find(), networkStatusService: Get.find(),
imageRepository: Get.find(),
), ),
); );
Get.lazyPut<EnterpriseRepository>( 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/resolve_conflict_usecase.dart';
import 'package:problem_check_system/app/features/problem/domain/usecases/sync_problems_usecase.dart'; import 'package:problem_check_system/app/features/problem/domain/usecases/sync_problems_usecase.dart';
// TODO
class ProblemListController extends GetxController { class ProblemListController extends GetxController {
final GetAllProblemsUsecase getAllProblemsUsecase; final GetAllProblemsUsecase getAllProblemsUsecase;
final GetEnterprisesUsecase getEnterprisesUsecase; 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( return Column(
children: [ children: [
Expanded( Expanded(

Loading…
Cancel
Save