소스 검색

更新Hive本地缓存,修改DIO的拦截器

liukai 6 달 전
부모
커밋
c396c493ff

+ 0 - 2
packages/cpt_job_sg/lib/modules/applied_staff_detail/staff_detail_widget.dart

@@ -4,9 +4,7 @@ import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 import 'package:plugin_basic/basic_export.dart';
-import 'package:plugin_platform/engine/directory/directory_util.dart';
 import 'package:plugin_platform/engine/image/image_preview.dart';
-import 'package:shared/utils/log_utils.dart';
 import 'package:shared/utils/util.dart';
 import 'package:widgets/ext/ex_widget.dart';
 import 'package:widgets/my_load_image.dart';

+ 11 - 0
packages/cs_plugin_platform/lib/engine/cache/cache_item.dart

@@ -0,0 +1,11 @@
+/*
+ * Hive 缓存管理的对象,包含过期时间
+ */
+class CacheItem<T> {
+  final T value;
+  final int? expiryTime;
+
+  CacheItem(this.value, [this.expiryTime]);
+
+  bool get isExpired => expiryTime != null && DateTime.now().millisecondsSinceEpoch > expiryTime!;
+}

+ 26 - 0
packages/cs_plugin_platform/lib/engine/cache/cache_item_adapter.dart

@@ -0,0 +1,26 @@
+import 'package:hive/hive.dart';
+import 'package:hive_flutter/hive_flutter.dart';
+import 'cache_item.dart';
+
+
+class CacheItemAdapter<T> extends TypeAdapter<CacheItem<T>> {
+  @override
+  final typeId = 0; // 确保这个ID是唯一的
+
+  @override
+  CacheItem<T> read(BinaryReader reader) {
+    final value = reader.read() as T;
+    final hasExpiry = reader.readBool();
+    final expiryTime = hasExpiry ? reader.readInt() : null;
+    return CacheItem<T>(value, expiryTime);
+  }
+
+  @override
+  void write(BinaryWriter writer, CacheItem<T> obj) {
+    writer.write(obj.value);
+    writer.writeBool(obj.expiryTime != null);
+    if (obj.expiryTime != null) {
+      writer.writeInt(obj.expiryTime!);
+    }
+  }
+}

+ 0 - 344
packages/cs_plugin_platform/lib/engine/cache/file_cache_manager.dart

@@ -1,344 +0,0 @@
-import 'dart:convert';
-import 'dart:io';
-import 'package:shared/utils/encrypt_util.dart';
-import 'package:shared/utils/log_utils.dart';
-import 'package:synchronized/synchronized.dart';
-
-import 'package:path/path.dart' as path;
-
-import '../directory/directory_util.dart';
-
-/*
-
-  // 存Json数据 - 写入key 与 json 即可,可以选择加入过期时间
-  await fileCache.putJsonByKey(
-    'hutu',
-    {
-    "name": "hutu",
-    "age": 35,
-    },
-    expiration: const Duration(hours: 1));
-
-
-  // 取Json数据 - 根据key就能取到,如果没有或者过期则为null
-  final json = await fileCache.getJsonByKey('hutu');
-
-
-   // 存储基本数据类型
-    await fileCache.putValueByKey('county', '凤凰');
-    await fileCache.putValueByKey('age', 18);
-
-   // 取出基本数据类型
-   final county = await fileCache.getValueByKey<String>('county');
-   Log.d('获取county:$county');
-
-   final age = await fileCache.getValueByKey<int>('age');
-   Log.d('获取age:$age');
-
- */
-class FileCacheManager {
-  static const maxSizeInBytes = 500 * 1024 * 1024; // 最大限制 500M 文件缓存大小
-  // static const maxSizeInBytes = 271360; // 最大限制 500M 文件缓存大小
-  static const String _fileCategory = "app_file_cache";
-  static final Lock _lock = Lock();
-
-  //单例
-  FileCacheManager._internal();
-
-  static final FileCacheManager _singleton = FileCacheManager._internal();
-
-  factory FileCacheManager() => _singleton;
-
-  String? cachePath;
-
-  // 初始化 - 获取到缓存路径
-  Future _init() async {
-    cachePath = DirectoryUtil.getTempPath(category: _fileCategory);
-    if (cachePath != null) {
-      //尝试异步创建自定义的文件夹
-      await DirectoryUtil.createTempDir(category: _fileCategory);
-    } else {
-      throw Exception('DirectoryUtil 无法获取到Cache文件夹,可能 DirectoryUtil 没有初始化,请检查!');
-    }
-  }
-
-  /// 移除指定的 Key 对应的缓存文件
-  Future<void> removeByKey(String key) async {
-    if (cachePath == null) {
-      await _init();
-    }
-
-    String path = '$cachePath/$key';
-    final file = File(path);
-    if (await file.exists()) {
-      await file.delete();
-    }
-  }
-
-  /// 移除全部的 Key 对应的缓存文件
-  Future<void> removeAllKey() async {
-    if (cachePath == null) {
-      await _init();
-    }
-
-    Directory cacheDir = Directory(cachePath!);
-    if (await cacheDir.exists()) {
-      List<File> cacheFiles = cacheDir.listSync().whereType<File>().toList();
-      if (cacheFiles.isNotEmpty) {
-        for (File file in cacheFiles) {
-          if (await file.exists()) {
-            await file.delete();
-          }
-        }
-      }
-    }
-  }
-
-  // 读取文件的全部Json数据
-  Future<Map<String, dynamic>?> _readAllJsonFromFile(String key) async {
-    String path = '$cachePath/$key';
-    final file = File(path);
-    if (await file.exists()) {
-      String jsonString = await file.readAsString();
-      final jsonData = jsonDecode(jsonString) as Map<String, dynamic>;
-      return jsonData;
-    }
-    return null;
-  }
-
-  // ============================= Json(对象) 类型 =========================================
-
-  /// 添加Json数据到本地文件缓存
-  Future<void> putJsonByKey(String key, Map<String, dynamic> jsonData, {Duration? expiration}) async {
-    // 加锁
-    await _lock.synchronized(() async {
-      if (cachePath == null) {
-        await _init();
-      } else {
-        Directory cacheDir = Directory(cachePath ?? '');
-        if (!await cacheDir.exists()) {
-          await DirectoryUtil.createTempDir(category: _fileCategory);
-        }
-      }
-
-      //加密Key
-      key = EncryptUtil.encodeMd5(key);
-
-      final file = File('$cachePath/$key');
-      Map<String, dynamic> existingData = {};
-
-      //获取到已经存在的 Json
-      if (await file.exists()) {
-        final jsonString = await file.readAsString();
-        existingData = jsonDecode(jsonString) as Map<String, dynamic>;
-      }
-
-      //存入现有的 key - value 缓存
-      existingData[key] = {
-        'data': jsonData,
-        'timestamp': DateTime.now().millisecondsSinceEpoch,
-        'expiration': expiration?.inMilliseconds,
-      };
-
-      //转化为新的Json文本
-      String newJsonString = jsonEncode(existingData);
-
-      //检查限制的总大小并写入到文件
-      checkAndWriteFile(file, newJsonString);
-    });
-  }
-
-  /// 从本都缓存文件读取Json数据
-  Future<Map<String, dynamic>?> getJsonByKey(String key) async {
-    if (cachePath == null) {
-      await _init();
-    } else {
-      Directory cacheDir = Directory(cachePath ?? '');
-      if (!await cacheDir.exists()) {
-        await DirectoryUtil.createTempDir(category: _fileCategory);
-      }
-    }
-
-    //加密Key
-    key = EncryptUtil.encodeMd5(key);
-
-    final jsonData = await _readAllJsonFromFile(key);
-    if (jsonData == null) {
-      return null;
-    }
-
-    //取出对应 key 的Json文本
-    final jsonEntry = jsonData[key] as Map<String, dynamic>?;
-    if (jsonEntry == null || _isExpired(jsonEntry)) {
-      return null;
-    }
-
-    //返回去除过期时间之后的真正数据
-    return jsonEntry['data'] as Map<String, dynamic>?;
-  }
-
-  // ============================= 基本数据类型 =========================================
-
-  /// 添加基本数据类型的数据到本地文件缓存
-  Future<void> putValueByKey<T>(String key, T value, {Duration? expiration}) async {
-    // 加锁
-    await _lock.synchronized(() async {
-      if (cachePath == null) {
-        await _init();
-      } else {
-        Directory cacheDir = Directory(cachePath ?? '');
-        if (!await cacheDir.exists()) {
-          await DirectoryUtil.createTempDir(category: _fileCategory);
-        }
-      }
-
-      //加密Key
-      key = EncryptUtil.encodeMd5(key);
-
-      final file = File('$cachePath/$key');
-      Map<String, dynamic> existingData = {};
-
-      //获取到已经存在的 Json
-      if (await file.exists()) {
-        final jsonString = await file.readAsString();
-        existingData = jsonDecode(jsonString) as Map<String, dynamic>;
-      }
-
-      //存入现有的 key - value 缓存
-      existingData[key] = {
-        'data': value,
-        'timestamp': DateTime.now().millisecondsSinceEpoch,
-        'expiration': expiration?.inMilliseconds,
-      };
-
-      //转化为新的Json文本写入到文件
-      String newJsonString = jsonEncode(existingData);
-
-      //检查限制的总大小并写入到文件
-      checkAndWriteFile(file, newJsonString);
-    });
-  }
-
-  /// 从本都缓存文件读取基本数据类型数据
-  Future<T?> getValueByKey<T>(String key) async {
-    if (cachePath == null) {
-      await _init();
-    } else {
-      Directory cacheDir = Directory(cachePath ?? '');
-      if (!await cacheDir.exists()) {
-        await DirectoryUtil.createTempDir(category: _fileCategory);
-      }
-    }
-
-    //加密Key
-    key = EncryptUtil.encodeMd5(key);
-
-    final jsonData = await _readAllJsonFromFile(key);
-    if (jsonData == null) {
-      return null;
-    }
-
-    //取出对应 key 的Json文本
-    final jsonEntry = jsonData[key] as Map<String, dynamic>?;
-    if (jsonEntry == null || _isExpired(jsonEntry)) {
-      return null;
-    }
-
-    //返回去除过期时间之后的真正数据
-    return jsonEntry['data'] as T?;
-  }
-
-  // 是否过期了
-  bool _isExpired(Map<String, dynamic> jsonEntry) {
-    final timestamp = jsonEntry['timestamp'] as int?;
-    final expiration = jsonEntry['expiration'] as int?;
-    if (timestamp != null && expiration != null) {
-      final currentTime = DateTime.now().millisecondsSinceEpoch;
-      return currentTime - timestamp > expiration;
-    }
-    return false;
-  }
-
-  // 检查是否超过最大限制,并写入文件
-  Future<void> checkAndWriteFile(File file, String jsonString) async {
-    Directory cacheDir = Directory(cachePath!);
-    List<File> cacheFiles = cacheDir.listSync().whereType<File>().toList();
-
-    if (cacheFiles.isNotEmpty) {
-      // 计算缓存文件夹的总大小
-      int totalSizeInBytes = 0;
-      for (File file in cacheFiles) {
-        totalSizeInBytes += await file.length();
-      }
-
-      Log.d("totalSizeInBytes $totalSizeInBytes  maxSizeInBytes:$maxSizeInBytes");
-
-      //如果总大小超过限制,依次删除文件直到满足条件
-      while (maxSizeInBytes > 0 && totalSizeInBytes > maxSizeInBytes) {
-        File? fileToDelete;
-        int oldestTimestamp = 0;
-
-        for (File file in cacheDir.listSync().whereType<File>().toList()) {
-          final key = path.basename(file.path);
-          //取出全部的 Json 文本与对象
-          final jsonString = await file.readAsString();
-          final jsonData = jsonDecode(jsonString) as Map<String, dynamic>?;
-          if (jsonData == null) {
-            continue;
-          }
-
-          //取出对应 key 的 Json 对象
-          final jsonEntry = jsonData[key] as Map<String, dynamic>?;
-          if (jsonEntry == null || _isExpired(jsonEntry)) {
-            fileToDelete = file;
-            Log.d("FileCacheManager 找到过期的文件$file");
-            break;
-
-          } else {
-
-            // 最进最少操作的时间戳
-              final timestamp = (await file.lastModified()).millisecondsSinceEpoch;
-              if (oldestTimestamp == 0) {
-                oldestTimestamp = timestamp;
-                fileToDelete = file;
-              }
-
-              if (timestamp < oldestTimestamp) {
-                oldestTimestamp = timestamp;
-                fileToDelete = file;
-              }
-          }
-        }
-
-        //遍历文件结束之后需要删除处理的逻辑
-        Log.d("FileCacheManager 需要删除的文件:$fileToDelete");
-        if (fileToDelete != null && await fileToDelete.exists()) {
-          await fileToDelete.delete();
-          break;
-        } else {
-          break;
-        }
-
-      }  //结束While循环
-
-      Log.d("写入的路径:$file");
-      //最后写入文件
-      await file.writeAsString(jsonString);
-    } else {
-      // 如果是空文件夹,直接写即可
-      await file.writeAsString(jsonString);
-    }
-  }
-
-
-  // 根据文件路径计算文件大小
-  Future<int> _calculateSize(File file) async {
-    if (await file.exists()) {
-      return await file.length();
-    }
-    return 0;
-  }
-}
-
-//可以用 fileCache 直接使用更方便
-var fileCache = FileCacheManager();

+ 257 - 0
packages/cs_plugin_platform/lib/engine/cache/local_cache_manager.dart

@@ -0,0 +1,257 @@
+import 'dart:typed_data';
+import 'package:hive/hive.dart';
+import 'package:hive_flutter/hive_flutter.dart';
+import 'package:crypto/crypto.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:shared/utils/log_utils.dart';
+import 'dart:convert';
+import 'package:path/path.dart' as path;
+import 'package:synchronized/synchronized.dart';
+
+import 'cache_item.dart';
+import 'cache_item_adapter.dart';
+
+enum CacheBoxType {
+  httpCache,
+  appCache,
+  logCache,
+  encryptedCache,
+  // 可以根据需要添加更多类型
+}
+
+class LocalCacheManager {
+  static final LocalCacheManager _instance = LocalCacheManager._internal();
+
+  factory LocalCacheManager() {
+    return _instance;
+  }
+
+  LocalCacheManager._internal();
+
+  final Map<CacheBoxType, Box> _boxes = {};
+  bool _isInitialized = false;
+  late HiveCipher _cipher;
+
+  Future<void> init({String? encryptionKey}) async {
+    if (_isInitialized) return;
+
+    await Hive.initFlutter('hive_boxes');
+
+    Hive.registerAdapter(CacheItemAdapter<dynamic>()); //默认注册缓存的对象
+
+    // 获取应用文档目录
+    final appDocumentDir = await getApplicationDocumentsDirectory();
+    final hiveBoxesPath = path.join(appDocumentDir.path, 'hive_boxes');
+
+    _collection = await BoxCollection.open(
+      _collectionName,
+      {'app_cache', 'http_data', 'log', 'encrypt'},
+      path: hiveBoxesPath,
+    );
+
+    if (encryptionKey != null) {
+      final key = Uint8List.fromList(sha256.convert(utf8.encode(encryptionKey)).bytes);
+      _cipher = HiveAesCipher(key);
+    }
+
+    _isInitialized = true;
+    Log.d("LocalCacheManager 初始化完成");
+  }
+
+  Future<Box> _getBox(CacheBoxType boxType) async {
+    if (!_isInitialized) {
+      throw StateError('LocalCacheManager not initialized. Call init() first.');
+    }
+
+    if (!_boxes.containsKey(boxType)) {
+      final box = await Hive.openBox(
+        boxType.toString(),
+        encryptionCipher: boxType == CacheBoxType.encryptedCache ? _cipher : null,
+      );
+      _boxes[boxType] = box;
+    }
+    return _boxes[boxType]!;
+  }
+
+  Future<void> put<T>(
+    String key,
+    T value, {
+    CacheBoxType boxType = CacheBoxType.appCache,
+    Duration? expiry,
+  }) async {
+    final box = await _getBox(boxType);
+    final expiryTime = expiry != null ? DateTime.now().add(expiry).millisecondsSinceEpoch : null;
+    final cacheItem = CacheItem<T>(value, expiryTime);
+    await box.put(key, cacheItem);
+  }
+
+  Future<T?> get<T>(
+    String key, {
+    CacheBoxType boxType = CacheBoxType.appCache,
+  }) async {
+    final box = await _getBox(boxType);
+    final cacheItem = box.get(key) as CacheItem<T>?;
+    if (cacheItem == null) return null;
+    if (cacheItem.isExpired) {
+      await delete(key, boxType: boxType);
+      return null;
+    }
+    return cacheItem.value;
+  }
+
+  Future<void> delete(String key, {CacheBoxType boxType = CacheBoxType.appCache}) async {
+    final box = await _getBox(boxType);
+    await box.delete(key);
+  }
+
+  Future<int> add(dynamic value, {CacheBoxType boxType = CacheBoxType.appCache}) async {
+    final box = await _getBox(boxType);
+    return box.add(value);
+  }
+
+  Future<List<dynamic>> getAllValues({CacheBoxType boxType = CacheBoxType.appCache}) async {
+    final box = await _getBox(boxType);
+    return box.values.toList();
+  }
+
+  Future<void> clear({CacheBoxType boxType = CacheBoxType.appCache}) async {
+    final box = await _getBox(boxType);
+    await box.clear();
+  }
+
+  Future<void> closeBox(CacheBoxType boxType) async {
+    if (_boxes.containsKey(boxType)) {
+      await _boxes[boxType]!.close();
+      _boxes.remove(boxType);
+    }
+  }
+
+  Future<void> closeAllBoxes() async {
+    for (var boxType in _boxes.keys.toList()) {
+      await closeBox(boxType);
+    }
+  }
+
+  static const String _collectionName = 'my_app_collection';
+  BoxCollection? _collection;
+  final _initLock = Lock();
+
+  // 改进的 _getCollection 方法
+  Future<BoxCollection> _getCollection() async {
+    if (_collection != null) {
+      return _collection!;
+    }
+
+    return _initLock.synchronized(() async {
+      if (_collection != null) {
+        return _collection!;
+      }
+
+      _collection = await BoxCollection.open(
+        _collectionName,
+        {'app_cache', 'user_data', 'settings'},
+        path: 'path/to/your/hive/boxes',
+      );
+
+      return _collection!;
+    });
+  }
+
+  // 根据CacheBoxType获取对应的box名称
+  String _getBoxName(CacheBoxType type) {
+    switch (type) {
+      case CacheBoxType.appCache:
+        return 'app_cache';
+      case CacheBoxType.httpCache:
+        return 'http_data';
+      case CacheBoxType.logCache:
+        return 'log';
+      case CacheBoxType.encryptedCache:
+        return 'encrypt';
+      default:
+        throw ArgumentError('Unsupported CacheBoxType: $type');
+    }
+  }
+
+  // 事务方法
+  Future<void> performTransaction(
+    Future<void> Function() transaction, {
+    List<CacheBoxType> boxTypes = const [CacheBoxType.appCache],
+    bool readOnly = false,
+  }) async {
+    final collection = await _getCollection();
+    final boxNames = boxTypes.map((type) => _getBoxName(type)).toList();
+
+    await collection.transaction(
+      () async {
+        await transaction();
+      },
+      boxNames: boxNames,
+      readOnly: readOnly,
+    );
+  }
+
+  // 批量操作方法
+  Future<void> putAll(Map<String, dynamic> entries, {CacheBoxType boxType = CacheBoxType.appCache}) async {
+    final box = await _getBox(boxType);
+    await box.putAll(entries);
+  }
+
+  // 获取所有键
+  Future<List<String>> getAllKeys({CacheBoxType boxType = CacheBoxType.appCache}) async {
+    final box = await _getBox(boxType);
+    return box.keys.cast<String>().toList();
+  }
+
+  // 检查键是否存在
+  Future<bool> containsKey(String key, {CacheBoxType boxType = CacheBoxType.appCache}) async {
+    final box = await _getBox(boxType);
+    return box.containsKey(key);
+  }
+
+  // 获取缓存大小
+  Future<int> getSize({CacheBoxType boxType = CacheBoxType.appCache}) async {
+    final box = await _getBox(boxType);
+    return box.length;
+  }
+
+  // 监听缓存变化
+  void listenToChanges(void Function(BoxEvent) onChanged, {CacheBoxType boxType = CacheBoxType.appCache}) async {
+    final box = await _getBox(boxType);
+    box.watch().listen(onChanged);
+  }
+
+  //获取当前Key的有效事件
+  Future<Duration?> getTimeToLive(
+    String key, {
+    CacheBoxType boxType = CacheBoxType.appCache,
+  }) async {
+    final box = await _getBox(boxType);
+    final cacheItem = box.get(key) as CacheItem?;
+    if (cacheItem == null || cacheItem.expiryTime == null) return null;
+    final expiryTime = DateTime.fromMillisecondsSinceEpoch(cacheItem.expiryTime!);
+    final now = DateTime.now();
+    if (now.isAfter(expiryTime)) {
+      await delete(key, boxType: boxType);
+      return null;
+    }
+    return expiryTime.difference(now);
+  }
+
+  //更新当前Key的有效时间
+  Future<void> updateExpiry(
+    String key,
+    Duration newExpiry, {
+    CacheBoxType boxType = CacheBoxType.appCache,
+  }) async {
+    final box = await _getBox(boxType);
+    final cacheItem = box.get(key) as CacheItem?;
+    if (cacheItem == null) return;
+    final newExpiryTime = DateTime.now().add(newExpiry).millisecondsSinceEpoch;
+    final updatedCacheItem = CacheItem(cacheItem.value, newExpiryTime);
+    await box.put(key, updatedCacheItem);
+  }
+}
+
+//可以用 LocalCacheManager 直接使用更方便
+var localCache = LocalCacheManager();

+ 12 - 6
packages/cs_plugin_platform/lib/http/dio/interceptor_cache_controller.dart

@@ -1,9 +1,9 @@
 import 'package:dio/dio.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:plugin_platform/engine/cache/local_cache_manager.dart';
 import 'package:shared/utils/log_utils.dart';
-import '../../engine/cache/file_cache_manager.dart';
 import '../http_provider.dart';
 
-
 /*
  * Http的缓存策略与处理
  */
@@ -17,7 +17,7 @@ class CacheControlInterceptor extends Interceptor {
     if (cacheControlName == CacheControl.onlyCache.name) {
       final key = options.uri.toString();
       //直接返回缓存
-      final json = await FileCacheManager().getJsonByKey(key);
+      final json = await localCache.get(key, boxType: CacheBoxType.httpCache);
       if (json != null) {
         handler.resolve(Response(
           statusCode: 200,
@@ -37,7 +37,7 @@ class CacheControlInterceptor extends Interceptor {
       //有缓存用缓存,没缓存用网络请求的数据并存入缓存
     } else if (cacheControlName == CacheControl.cacheFirstOrNetworkPut.name) {
       final key = options.uri.toString();
-      final json = await FileCacheManager().getJsonByKey(key);
+      final json = await localCache.get(key, boxType: CacheBoxType.httpCache);
       if (json != null) {
         handler.resolve(Response(
           statusCode: 200,
@@ -91,11 +91,17 @@ class CacheControlInterceptor extends Interceptor {
           duration = Duration(milliseconds: int.parse(cacheExpiration));
         }
 
+        final isShowLoadingDialog = requestHeaders['is_show_loading_dialog'] != null && requestHeaders['is_show_loading_dialog'] == 'true';
+        if (isShowLoadingDialog) {
+          SmartDialog.dismiss(status: SmartStatus.loading);
+        }
+
         //直接存入Json数据到本地File
-        fileCache.putJsonByKey(
+        localCache.put(
           cacheKey ?? 'unknow',
           jsonMap,
-          expiration: duration,
+          boxType: CacheBoxType.httpCache,
+          expiry: duration,
         );
       }
     }

+ 5 - 1
packages/cs_plugin_platform/pubspec.yaml

@@ -59,11 +59,15 @@ dependencies:
 
   # 图片预览(单图与多图) https://github.com/xia-weiyang/image_preview
   image_preview: ^1.2.4
-  
+
   # SD卡管理,缓存管理
   path_provider: 2.1.4
   synchronized: ^3.1.0+1
 
+  # Hive本地存储
+  hive: ^2.2.3
+  hive_flutter: ^1.1.0
+
   # Flutter 常用工具类库 https://github.com/Sky24n/flustars
   # SP-KV存储,一般存储基本数据类型,由各平台各自实现
   shared_preferences: 2.3.1