Browse Source

feat : 同步服务器问题到本地

dev
徐振升 5 hours ago
parent
commit
b7f3cd8e10
  1. 67
      README.md
  2. 4
      lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart
  3. 27
      lib/app/features/problem/data/datasources/problem_local_data_source.dart
  4. 74
      lib/app/features/problem/data/repositories/problem_repository_impl.dart
  5. 7
      lib/app/features/problem/domain/repositories/problem_repository.dart
  6. 11
      lib/app/features/problem/domain/usecases/sync_problems_usecase.dart
  7. 1
      lib/app/features/problem/presentation/bindings/problem_list_binding.dart
  8. 1
      lib/app/features/problem/presentation/bindings/problem_upload_binding.dart

67
README.md

@ -76,3 +76,70 @@ SQFlite: 本地数据库,用于离线数据存储。
Image Picker: 用于从图库或相机选择图片。
HTTP/Dio: 网络请求库。推荐使用 Dio,因为它提供了更强大的拦截器、表单数据处理和错误处理功能。
TODO//
/// 定义了一个通用的 CRUD (Create, Read, Update, Delete) 仓库必须具备的核心能力
abstract class ICrudRepository<T> {
Future<T?> getById(String id);
Future<List<T>> getAll();
Future<T> add(T entity);
Future<T> update(T entity);
Future<void> delete(String id);
Future<T> syncToServer(T entity);
}
新增
import 'package:problem_check_system/app/core/domain/repositories/i_crud_repository.dart';
class AddEntityUseCase<T> {
final ICrudRepository<T> repository;
AddEntityUseCase({required this.repository});
Future<T> call(T entity) {
return repository.add(entity);
}
}
更新
import 'package:problem_check_system/app/core/domain/repositories/i_crud_repository.dart';
class UpdateEntityUseCase<T> {
final ICrudRepository<T> repository;
UpdateEntityUseCase({required this.repository});
Future<T> call(T entity) {
return repository.update(entity);
}
}
删除
import 'package:problem_check_system/app/core/domain/repositories/i_crud_repository.dart';
class DeleteEntityUseCase<T> {
final ICrudRepository<T> repository;
DeleteEntityUseCase({required this.repository});
Future<void> call(String id) {
return repository.delete(id);
}
}
根据id获取
import 'package:problem_check_system/app/core/domain/repositories/i_crud_repository.dart';
class GetEntityByIdUseCase<T> {
final ICrudRepository<T> repository;
GetEntityByIdUseCase({required this.repository});
Future<T?> call(String id) {
return repository.getById(id);
}
}

4
lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart

@ -5,9 +5,7 @@ import 'package:problem_check_system/app/features/enterprise/data/datasources/en
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_dto.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/core/domain/entities/data_conflict.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
import 'package:problem_check_system/app/core/domain/entities/sync_result.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
import 'package:uuid/uuid.dart';
@ -181,7 +179,7 @@ class EnterpriseRepositoryImpl implements EnterpriseRepository {
}
// 5.
return SyncResult(
return EnterpriseSyncResult(
newItemsFromServer: newEnterprises.length,
conflicts: conflicts,
);

27
lib/app/features/problem/data/datasources/problem_local_data_source.dart

@ -1,5 +1,7 @@
import 'package:problem_check_system/app/core/domain/entities/sync_status.dart';
import 'package:problem_check_system/app/core/services/database_service.dart';
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_filter_params.dart';
import 'package:sqflite/sqflite.dart'; // DatabaseService
@ -35,6 +37,8 @@ abstract class ProblemLocalDataSource {
///
/// [id] -
Future<void> deleteProblem(String id);
Future<void> cacheProblems(List<Map<String, dynamic>> newProblems);
}
// IProblemLocalDataSource
@ -157,4 +161,27 @@ class ProblemLocalDataSourceImpl implements ProblemLocalDataSource {
whereArgs: [problemMap['id']],
);
}
@override
Future<void> cacheProblems(List<Map<String, dynamic>> problemMaps) async {
if (problemMaps.isEmpty) {
return; //
}
final db = await _databaseService.database;
// 1. Batch
final batch = db.batch();
// 2. insert batch
for (final problem in problemMaps) {
batch.insert(
_tableName,
problem,
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// 3. batch
await batch.commit(noResult: true);
}
}

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

@ -1,8 +1,10 @@
import 'dart:convert';
import 'package:problem_check_system/app/core/domain/entities/sync_status.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';
import 'package:problem_check_system/app/features/problem/data/model/problem_dto.dart';
import 'package:problem_check_system/app/features/problem/data/model/problem_filter_dto.dart';
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_filter_params.dart';
@ -15,10 +17,12 @@ import 'package:problem_check_system/app/features/problem/domain/repositories/pr
class ProblemRepositoryImpl implements ProblemRepository {
final ProblemLocalDataSource localDataSource;
final ProblemRemoteDataSource remoteDataSource;
final NetworkStatusService networkStatusService;
ProblemRepositoryImpl({
required this.localDataSource,
required this.remoteDataSource,
required this.networkStatusService,
});
@override
@ -161,4 +165,74 @@ class ProblemRepositoryImpl implements ProblemRepository {
// Entity
return syncedEntity;
}
///
@override
Future<ProblemSyncResult> syncWithServer() async {
if (!networkStatusService.isOnline.value) {
// 线
return const ProblemSyncResult();
}
//
final remoteProblemsDto = await remoteDataSource.getProblems(
ProblemFilterDto(),
);
final localProblemsModel = await localDataSource.getAllProblems(
ProblemFilterParams(),
);
final remoteProblems = remoteProblemsDto
.map((dto) => dto.toEntity())
.toList();
final localProblems = localProblemsModel.map(
(map) => ProblemModel.fromMap(map),
);
// 2. 使 Map
final localProblemMap = {for (var e in localProblems) e.id: e.toEntity()};
final List<ProblemEntity> newProblems = [];
final List<ProblemConflict> conflicts = [];
// 3.
for (final remoteProblem in remoteProblems) {
final localProblem = localProblemMap[remoteProblem.id];
if (localProblem == null) {
//
newProblems.add(remoteProblem);
} else {
//
final remoteMillis =
remoteProblem.lastModifiedTime.millisecondsSinceEpoch;
final localMillis =
localProblem.lastModifiedTime.millisecondsSinceEpoch;
if (remoteMillis != localMillis) {
//
conflicts.add(
ProblemConflict(
localVersion: localProblem,
serverVersion: remoteProblem,
),
);
}
//
}
}
// 4.
if (newProblems.isNotEmpty) {
List<Map<String, dynamic>> problemMaps = newProblems
.map((entity) => ProblemModel.fromEntity(entity).toMap())
.toList();
await localDataSource.cacheProblems(problemMaps);
}
// 5.
return ProblemSyncResult(
newItemsFromServer: newProblems.length,
conflicts: conflicts,
);
}
}

7
lib/app/features/problem/domain/repositories/problem_repository.dart

@ -1,7 +1,12 @@
import 'package:problem_check_system/app/core/domain/entities/data_conflict.dart';
import 'package:problem_check_system/app/core/domain/entities/sync_result.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_filter_params.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_list_item_entity.dart';
typedef ProblemSyncResult = SyncResult<ProblemEntity>;
typedef ProblemConflict = DataConflict<ProblemEntity>;
/// Problem
///
abstract class ProblemRepository {
@ -14,4 +19,6 @@ abstract class ProblemRepository {
Future<void> deleteProblem(String id);
//
Future<ProblemEntity> syncProblemToServer(ProblemEntity problem);
Future<ProblemSyncResult> syncWithServer();
}

11
lib/app/features/problem/domain/usecases/sync_problems_usecase.dart

@ -0,0 +1,11 @@
import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart';
class SyncProblemsUsecase {
final ProblemRepository repository;
SyncProblemsUsecase({required this.repository});
Future<ProblemSyncResult> call() async {
return repository.syncWithServer();
}
}

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

@ -30,6 +30,7 @@ class ProblemListBinding extends BaseBindings {
() => ProblemRepositoryImpl(
localDataSource: Get.find(),
remoteDataSource: Get.find(),
networkStatusService: Get.find(),
),
);
}

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

@ -38,6 +38,7 @@ class ProblemUploadBinding extends BaseBindings {
() => ProblemRepositoryImpl(
localDataSource: Get.find(),
remoteDataSource: Get.find(),
networkStatusService: Get.find(),
),
);
Get.lazyPut<EnterpriseRepository>(

Loading…
Cancel
Save