Przeglądaj źródła

Merge branch 'update'

* update:
  backup
  数据库修改,增加 sembast 数据库
  update
  update
  update
  update
  backup

# Conflicts:
#	pubspec.lock
James 7 miesięcy temu
rodzic
commit
b63d5fb068

+ 2 - 1
lib/appfx.dart

@@ -5,7 +5,8 @@ export 'util/fileUtils.dart';
 export 'util/logger.dart';
 export 'util/riverpodUtils.dart';
 
-export 'store/dbMgrBase.dart';
+export 'store/objbxDbMgrBase.dart';
+export 'store/semDbMgrBase.dart';
 export 'store/sharedStore.dart';
 
 export 'theme/appTheme.dart';

+ 156 - 0
lib/network/httpClientWrap.dart

@@ -0,0 +1,156 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+
+enum HttpClientStatus {
+  idle,
+  connecting,
+  connected,
+  disconnected,
+  error,
+}
+
+typedef HttpClientStatusHandler = void Function(HttpClientStatus status);
+
+class HttpClientWrap {
+  final Duration retryInterval;
+  final Map<String, String>? defaultHeaders;
+
+  HttpClient? _httpClient;
+  bool _disposed = false;
+
+  HttpClientStatus _status = HttpClientStatus.idle;
+  HttpClientStatus get status => _status;
+
+  final List<HttpClientStatusHandler> _statusHandlers = [];
+
+  HttpClientWrap({
+    this.retryInterval = const Duration(seconds: 5),
+    this.defaultHeaders,
+  });
+
+  /// 发送 GET 请求
+  Future<dynamic> get(String url, {Map<String, String>? headers, dynamic body}) async {
+    return _sendRequest(url, 'GET', headers: headers, body: body);
+  }
+
+  /// 发送 POST 请求
+  Future<dynamic> post(String url, {Map<String, String>? headers, dynamic body}) async {
+    return _sendRequest(url, 'POST', headers: headers, body: body);
+  }
+
+  /// 发送 PUT 请求
+  Future<dynamic> put(String url, {Map<String, String>? headers, dynamic body}) async {
+    return _sendRequest(url, 'PUT', headers: headers, body: body);
+  }
+
+  /// 发送 DELETE 请求
+  Future<dynamic> delete(String url, {Map<String, String>? headers}) async {
+    return _sendRequest(url, 'DELETE', headers: headers);
+  }
+
+  Future<dynamic> _sendRequest(
+      String url,
+      String method, {
+        Map<String, String>? headers,
+        dynamic body,
+      }) async {
+    if (_disposed) {
+      throw StateError('HTTP Client has been disposed');
+    }
+
+    _updateStatus(HttpClientStatus.connecting);
+    _httpClient ??= HttpClient();
+
+    try {
+
+      late final Uri uri;
+      if ((method == 'GET') && (body != null)) {
+        uri = Uri.parse(url).replace(
+          queryParameters: body,
+        );
+      } else {
+        uri = Uri.parse(url);
+      }
+
+      final request = await _httpClient!.openUrl(method, uri);
+
+      // 设置请求头
+      final mergedHeaders = {...?defaultHeaders, ...?headers};
+      mergedHeaders.forEach((key, value) {
+        request.headers.set(key, value);
+      });
+
+      // 添加请求体(如果是POST/PUT)
+      if (body != null && (method == 'POST' || method == 'PUT')) {
+        if (body is Map || body is List) {
+          request.write(jsonEncode(body));
+        } else {
+          request.write(body.toString());
+        }
+      }
+
+      final response = await request.close();
+      _updateStatus(HttpClientStatus.connected);
+
+      // 处理响应
+      final responseBody = await response.transform(utf8.decoder).join();
+      dynamic decodedResponse;
+
+      try {
+        decodedResponse = jsonDecode(responseBody);
+      } catch (e) {
+        decodedResponse = responseBody;
+      }
+
+      // Log.d('get response: $decodedResponse');
+
+      _updateStatus(HttpClientStatus.idle);
+      return decodedResponse; // 直接返回响应数据
+
+    } catch (e) {
+      _handleError(e);
+      _scheduleRetry();
+      rethrow; // 抛出异常以便调用方捕获
+    }
+  }
+
+  /// 添加状态监听器
+  void addStatusListener(HttpClientStatusHandler handler) {
+    _statusHandlers.add(handler);
+  }
+
+  /// 移除状态监听器
+  void removeStatusListener(HttpClientStatusHandler handler) {
+    _statusHandlers.remove(handler);
+  }
+
+  /// 释放资源
+  Future<void> dispose() async {
+    _disposed = true;
+    _httpClient?.close();
+    _httpClient = null;
+    _statusHandlers.clear();
+    _updateStatus(HttpClientStatus.disconnected);
+  }
+
+  void _handleError(dynamic error) {
+    _updateStatus(HttpClientStatus.error);
+  }
+
+  void _scheduleRetry() {
+    if (_disposed) return;
+
+    Timer(retryInterval, () {
+      _updateStatus(HttpClientStatus.idle);
+    });
+  }
+
+  void _updateStatus(HttpClientStatus status) {
+    _status = status;
+    for (final handler in _statusHandlers) {
+      handler(status);
+    }
+  }
+}

+ 124 - 0
lib/network/httpServerWrap.dart

@@ -0,0 +1,124 @@
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:convert';
+// import '../util/logger.dart';
+
+
+enum HttpServerStatus {
+  starting,
+  started,
+  stopping,
+  stopped,
+}
+
+typedef HttpRequestHandler = Future<void> Function(HttpRequest request);
+typedef HttpServerStatusHandler = void Function(HttpServerStatus status);
+
+
+class HttpServerWrap {
+
+  HttpServer? _server;
+
+  HttpServerStatus _status = HttpServerStatus.stopped;
+  HttpServerStatus get status => _status;
+
+  String? _serverAddress;
+  String? get serverAddress => _serverAddress;
+
+  // 用于向 UI 推送事件
+  final StreamController<String> _eventController = StreamController.broadcast();
+  Stream<String> get events => _eventController.stream;
+
+  // // 保存所有活跃的客户端连接(可选)
+  // final List<WebSocket> _activeSockets = [];
+  // 请求处理器
+  HttpRequestHandler? _requestHandler;
+
+  HttpServerWrap();
+
+
+  // 启动服务器
+  Future<void> start({int port = 8080, HttpRequestHandler? requestHandler}) async {
+    if (_status == HttpServerStatus.starting ||
+        _status == HttpServerStatus.started ||
+        _status == HttpServerStatus.stopping) {
+      return;
+    }
+    _requestHandler = requestHandler;
+    _updateStatus(HttpServerStatus.starting);
+
+    try {
+      _server = await HttpServer.bind(InternetAddress.anyIPv4, port);
+      _serverAddress = 'http://${_server?.address.host}:$port';
+      _updateStatus(HttpServerStatus.started);
+      // Log.d('🚀 HTTP 服务器已启动: $_serverAddress');
+
+      await for (HttpRequest request in _server!) {
+        _handleRequest(request);
+      }
+    } catch (error) {
+      // Log.e('$error');
+      _updateStatus(HttpServerStatus.stopped, message: '$error');
+    }
+  }
+
+  Future<void> _handleRequest(HttpRequest request) async {
+    try {
+      final clientInfo = '🔌 请求来自: ${request.connectionInfo?.remoteAddress} ${request.method} ${request.uri}';
+      _eventController.add(clientInfo);
+
+      if (_requestHandler != null) {
+        await _requestHandler!(request);
+      } else {
+        // 默认响应
+        request.response
+          ..statusCode = HttpStatus.ok
+          ..headers.contentType = ContentType.json
+          ..write(jsonEncode({'message': 'Hello from test server'}))
+          ..close();
+      }
+    } catch (e) {
+      request.response
+        ..statusCode = HttpStatus.internalServerError
+        ..write('Error processing request: $e')
+        ..close();
+    }
+  }
+
+
+  // 停止服务器
+  Future<void> stop() async {
+    _updateStatus(HttpServerStatus.stopping);
+    await _server?.close();
+    _updateStatus(HttpServerStatus.stopped);
+    _server = null;
+    _serverAddress = null;
+    // Log.d('🛑 WebSocket 服务器已停止');
+  }
+
+  void _updateStatus(HttpServerStatus status, {String? message}) {
+    _status = status;
+    String statusStr = '';
+    switch (status) {
+      case HttpServerStatus.starting:
+        statusStr = '正在启动...';
+        break;
+      case HttpServerStatus.started:
+        statusStr = '🚀 服务器已启动,监听端口: $_serverAddress';
+        break;
+      case HttpServerStatus.stopping:
+        statusStr = '正在关闭...';
+        break;
+      case HttpServerStatus.stopped:
+        statusStr = '🛑 服务器已关闭';
+        if (message != null) {
+          statusStr += ': $message';
+        }
+        break;
+    }
+    _eventController.add(statusStr);
+  }
+
+
+}

+ 170 - 0
lib/network/websocketClientWrap.dart

@@ -0,0 +1,170 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+
+enum WebSocketStatus {
+  connecting,
+  connected,
+  disconnected,
+  error,
+}
+
+
+typedef WebSocketMessageHandler = void Function(dynamic message);
+typedef WebSocketStatusHandler = void Function(WebSocketStatus status);
+
+class WebsocketClientWrap {
+  final String url;
+  final Duration reconnectInterval;
+  final Map<String, String>? headers;
+
+  WebSocket? _webSocket;
+  StreamSubscription? _subscription;
+  Timer? _reconnectTimer;
+  bool _disposed = false;
+
+  WebSocketStatus _status = WebSocketStatus.disconnected;
+  WebSocketStatus get status => _status;
+
+  final List<WebSocketMessageHandler> _messageHandlers = [];
+  final List<WebSocketStatusHandler> _statusHandlers = [];
+
+  WebsocketClientWrap({
+    required this.url,
+    this.reconnectInterval = const Duration(seconds: 5),
+    this.headers,
+  });
+
+  /// 连接 WebSocket
+  Future<void> connect() async {
+    if (_disposed) {
+      throw StateError('WebSocket has been disposed');
+    }
+
+    if (_status == WebSocketStatus.connected ||
+        _status == WebSocketStatus.connecting) {
+      return;
+    }
+
+    _updateStatus(WebSocketStatus.connecting);
+
+    try {
+      _webSocket = await WebSocket.connect(url, headers: headers);
+      _updateStatus(WebSocketStatus.connected);
+
+      _subscription = _webSocket!.listen(
+        _handleMessage,
+        onError: _handleError,
+        onDone: _handleDone,
+      );
+
+      // 取消重连定时器
+      _reconnectTimer?.cancel();
+      _reconnectTimer = null;
+    } catch (e) {
+      _handleError(e);
+      _scheduleReconnect();
+    }
+  }
+
+  /// 断开连接
+  Future<void> disconnect() async {
+    _reconnectTimer?.cancel();
+    _reconnectTimer = null;
+
+    await _subscription?.cancel();
+    _subscription = null;
+
+    await _webSocket?.close();
+    _webSocket = null;
+
+    _updateStatus(WebSocketStatus.disconnected);
+  }
+
+  /// 发送消息
+  void send(dynamic message) {
+    if (_status != WebSocketStatus.connected || _webSocket == null) {
+      throw StateError('WebSocket is not connected');
+    }
+
+    if (message is String) {
+      _webSocket!.add(message);
+    } else if (message is Map || message is List) {
+      _webSocket!.add(jsonEncode(message));
+    } else {
+      _webSocket!.add(message.toString());
+    }
+  }
+
+  /// 添加消息监听器
+  void addMessageListener(WebSocketMessageHandler handler) {
+    _messageHandlers.add(handler);
+  }
+
+  /// 移除消息监听器
+  void removeMessageListener(WebSocketMessageHandler handler) {
+    _messageHandlers.remove(handler);
+  }
+
+  /// 添加状态监听器
+  void addStatusListener(WebSocketStatusHandler handler) {
+    _statusHandlers.add(handler);
+  }
+
+  /// 移除状态监听器
+  void removeStatusListener(WebSocketStatusHandler handler) {
+    _statusHandlers.remove(handler);
+  }
+
+  /// 释放资源
+  Future<void> dispose() async {
+    _disposed = true;
+    await disconnect();
+    _messageHandlers.clear();
+    _statusHandlers.clear();
+  }
+
+  void _handleMessage(dynamic message) {
+    try {
+      // 尝试解析 JSON 消息
+      final decoded = jsonDecode(message);
+      for (final handler in _messageHandlers) {
+        handler(decoded);
+      }
+    } catch (e) {
+      // 如果不是 JSON,直接传递原始消息
+      for (final handler in _messageHandlers) {
+        handler(message);
+      }
+    }
+  }
+
+  void _handleError(dynamic error) {
+    _updateStatus(WebSocketStatus.error);
+    _scheduleReconnect();
+  }
+
+  void _handleDone() {
+    if (_status != WebSocketStatus.disconnected && !_disposed) {
+      _updateStatus(WebSocketStatus.disconnected);
+      _scheduleReconnect();
+    }
+  }
+
+  void _scheduleReconnect() {
+    if (_disposed || _reconnectTimer != null) return;
+
+    _reconnectTimer = Timer(reconnectInterval, () {
+      _reconnectTimer = null;
+      connect();
+    });
+  }
+
+  void _updateStatus(WebSocketStatus status) {
+    _status = status;
+    for (final handler in _statusHandlers) {
+      handler(status);
+    }
+  }
+}

+ 115 - 0
lib/network/websocketServerWrap.dart

@@ -0,0 +1,115 @@
+
+import 'dart:async';
+import 'dart:io';
+// import '../util/logger.dart';
+
+
+enum WebsocketServerStatus {
+  starting,
+  started,
+  stopping,
+  stopped,
+}
+
+typedef WebSocketMessageHandler = void Function(dynamic message);
+typedef WebSocketStatusHandler = void Function(WebsocketServerStatus status);
+
+
+class WebsocketServerWrap {
+
+  HttpServer? _server;
+
+  WebsocketServerStatus _status = WebsocketServerStatus.stopped;
+  WebsocketServerStatus get status => _status;
+
+  String? _serverAddress;
+  String? get serverAddress => _serverAddress;
+
+  // 用于向 UI 推送事件
+  final StreamController<String> _eventController = StreamController.broadcast();
+  Stream<String> get events => _eventController.stream;
+
+  // 保存所有活跃的客户端连接(可选)
+  final List<WebSocket> _activeSockets = [];
+
+  WebsocketServerWrap();
+
+
+  // 启动服务器
+  Future<void> start({int port = 8080}) async {
+    if (_status == WebsocketServerStatus.starting ||
+        _status == WebsocketServerStatus.started ||
+        _status == WebsocketServerStatus.stopping) {
+      return;
+    }
+    _updateStatus(WebsocketServerStatus.starting);
+
+    try {
+      _server = await HttpServer.bind(InternetAddress.anyIPv4, port);
+      _serverAddress = 'ws://${_server?.address.host}:$port';
+      _updateStatus(WebsocketServerStatus.started);
+      // Log.d('🚀 WebSocket 服务器已启动: ws://${_server?.address.host}:$port');
+
+      await for (HttpRequest request in _server!) {
+        if (WebSocketTransformer.isUpgradeRequest(request)) {
+          final socket = await WebSocketTransformer.upgrade(request);
+          _handleNewConnection(socket, request);
+        }
+      }
+    } catch (error) {
+      // Log.e('$error');
+    }
+  }
+
+
+  void _handleNewConnection(WebSocket socket, HttpRequest request) {
+    final clientInfo = '🔌 客户端连接: ${request.connectionInfo?.remoteAddress}';
+    _eventController.add(clientInfo);
+    _activeSockets.add(socket); // 记录活跃连接
+
+    socket.listen((message) {
+      final msgLog = '📩 ${request.connectionInfo?.remoteAddress}: $message';
+      _eventController.add(msgLog);
+    },
+      onDone: () {
+        _eventController.add('❌ 客户端断开: ${request.connectionInfo?.remoteAddress}');
+        _activeSockets.remove(socket); // 移除断开连接
+      },
+    );
+  }
+
+
+  // 停止服务器
+  Future<void> stop() async {
+    _updateStatus(WebsocketServerStatus.stopping);
+    await _server?.close();
+    // _eventController.add('🛑 服务器已停止');
+    _activeSockets.clear(); // 清空所有连接
+    _updateStatus(WebsocketServerStatus.stopped);
+    _server = null;
+    _serverAddress = null;
+    // Log.d('🛑 WebSocket 服务器已停止');
+  }
+
+  void _updateStatus(WebsocketServerStatus status) {
+    _status = status;
+    String statusStr = '';
+    switch (status) {
+      case WebsocketServerStatus.starting:
+        statusStr = '正在启动...';
+        break;
+      case WebsocketServerStatus.started:
+        statusStr = '🚀 服务器已启动,监听端口: $_serverAddress';
+        break;
+      case WebsocketServerStatus.stopping:
+        statusStr = '正在关闭...';
+        break;
+      case WebsocketServerStatus.stopped:
+        statusStr = '🛑 服务器已关闭';
+        break;
+    }
+    _eventController.add(statusStr);
+  }
+
+
+}

+ 0 - 25
lib/store/appStoreBase.dart

@@ -1,25 +0,0 @@
-
-// import 'package:path/path.dart' as p;
-// import 'package:path_provider/path_provider.dart';
-import 'package:objectbox/objectbox.dart';
-
-
-class AppStoreBase {
-  /// The Store of this app.
-  late final Store store;
-
-  // AppStoreBase._create(this.store) {
-  //   // Add any additional setup code, e.g. build queries.
-  //
-  //   // new box instance
-  // }
-  //
-  // /// Create an instance of ObjectBox to use throughout the app.
-  // static Future<AppStoreBase> create() async {
-  //   final docsDir = await getApplicationDocumentsDirectory();
-  //   // Future<Store> openStore() {...} is defined in the generated objectbox.g.dart
-  //   final store = await openStore(directory: p.join(docsDir.path, "obx"));
-  //   return AppStoreBase._create(store);
-  // }
-
-}

+ 2 - 2
lib/store/dbMgrBase.dart → lib/store/objbxDbMgrBase.dart

@@ -2,14 +2,14 @@
 import 'package:objectbox/objectbox.dart';
 
 
-abstract class dbMgrBase<T> {
+abstract class ObjbxDbMgrBase<T> {
 
   /// The Store of this app.
   late final Store store;
 
   late final Box<T> objectBox;
 
-  dbMgrBase(this.store) {
+  ObjbxDbMgrBase(this.store) {
     objectBox = Box(store);
   }
 

+ 56 - 0
lib/store/semDbMgrBase.dart

@@ -0,0 +1,56 @@
+
+import 'package:sembast/sembast_io.dart';
+import 'dart:async';
+
+
+abstract class SemDbMgrBase<T> {
+
+  final Database database;
+  abstract final String repositoryName;
+
+  SemDbMgrBase(this.database);
+
+  // 获取存储
+  StoreRef<String, Map<String, dynamic>> get storeRef {
+    return StoreRef<String, Map<String, dynamic>>(repositoryName);
+  }
+
+  // 转换对象为Map
+  Map<String, dynamic> toMap(T item);
+
+  // 从Map创建对象
+  T fromMap(Map<String, dynamic> map);
+
+  // 添加或更新记录
+  Future<String> addOrUpdate(T item, {String? id}) async {
+    final itemMap = toMap(item);
+    final recordId = id ?? DateTime.now().millisecondsSinceEpoch.toString();
+
+    await storeRef.record(recordId).put(database, itemMap);
+    return recordId;
+  }
+
+  // 获取所有记录
+  Future<List<T>> getAll() async {
+    final records = await storeRef.find(database);
+    return records.map((record) => fromMap(record.value)).toList();
+  }
+
+  // 根据ID获取记录
+  Future<T?> getById(String id) async {
+    final record = await storeRef.record(id).get(database);
+    return record != null ? fromMap(record) : null;
+  }
+
+  // 删除记录
+  Future<void> delete(String id) async {
+    await storeRef.record(id).delete(database);
+  }
+
+  // 清空存储
+  Future<void> clear() async {
+    await storeRef.delete(database);
+  }
+}
+
+

+ 11 - 0
lib/store/sharedStore.dart

@@ -61,6 +61,17 @@ class SharedStore {
   }
 
 
+  // 删
+  Future<void> remove(String key) async {
+    SharedPreferences prefs = await sharedStorage;
+    await prefs.remove(key);
+  }
+  // 查
+  Future<bool> hasKey(String key) async {
+    SharedPreferences prefs = await sharedStorage;
+    return prefs.containsKey(key);
+  }
+
 }
 
 

+ 156 - 0
lib/util/base64Utils.dart

@@ -0,0 +1,156 @@
+
+import 'dart:convert';
+import 'dart:typed_data';
+import 'package:convert/convert.dart' show hex;
+
+
+
+/// Uint8List <--> Base64
+String bytesToBase64(Uint8List data, {bool urlSafe = false}) {
+  try {
+    if (data.isEmpty) {
+      throw ArgumentError('Input data cannot be empty');
+    }
+
+    final base64Str = base64Encode(data);
+
+    if (urlSafe) {
+      return base64Str
+          .replaceAll('+', '-')
+          .replaceAll('/', '_')
+          .replaceAll('=', '');
+    }
+
+    return base64Str;
+  } on ArgumentError catch (e) {
+    rethrow;
+  } catch (e) {
+    throw Exception('Failed to encode Uint8List to Base64: ${e.toString()}');
+  }
+}
+
+Uint8List base64ToBytes(String base64Str, {bool? urlSafe}) {
+  try {
+    if (base64Str.isEmpty) {
+      throw ArgumentError('Base64 string cannot be empty');
+    }
+    // 如果未指定 urlSafe,自动检测格式
+    final isUrlSafe = urlSafe ??
+        (base64Str.contains('-') || base64Str.contains('_'));
+
+    String normalizedStr = base64Str;
+
+    if (isUrlSafe) {
+      // 添加必要的填充字符
+      final padding = '=' * ((4 - base64Str.length % 4) % 4);
+      normalizedStr = base64Str
+          .replaceAll('-', '+')
+          .replaceAll('_', '/') + padding;
+    }
+
+    // 验证 Base64 字符串格式
+    if (!RegExp(r'^[a-zA-Z0-9+/]*={0,2}$').hasMatch(normalizedStr)) {
+      throw FormatException('Invalid Base64 string format');
+    }
+
+    return base64Decode(normalizedStr);
+  } on ArgumentError catch (e) {
+    rethrow;
+  } on FormatException catch (e) {
+    rethrow;
+  } catch (e) {
+    throw Exception('Failed to decode Base64 to Uint8List: ${e.toString()}');
+  }
+}
+
+
+
+/// 将十六进制字符串直接转换为 Base64
+///
+/// [hexStr] 十六进制字符串
+/// [urlSafe] 是否生成URL安全的Base64
+///
+/// 返回 Base64 字符串或抛出异常
+String hexToBase64(String hexStr, {bool urlSafe = false}) {
+  try {
+    if (hexStr.isEmpty) {
+      throw ArgumentError('Hex string cannot be empty');
+    }
+
+    final normalizedHex = hexStr.startsWith('0x')
+        ? hexStr.substring(2)
+        : hexStr;
+
+    if (!RegExp(r'^[0-9a-fA-F]+$').hasMatch(normalizedHex)) {
+      throw FormatException('Invalid hex string format');
+    }
+
+    final bytes = hex.decode(normalizedHex.padLeft(
+        normalizedHex.length + normalizedHex.length % 2,
+        '0'
+    ));
+
+    final base64 = base64Encode(bytes);
+    return urlSafe
+        ? base64.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '')
+        : base64;
+  } on ArgumentError catch (e) {
+    rethrow;
+  } on FormatException catch (e) {
+    rethrow;
+  } catch (e) {
+    throw Exception('Failed to convert hex to Base64: ${e.toString()}');
+  }
+}
+
+
+/// 将 Base64 字符串转换为十六进制字符串
+///
+/// [base64Str] 输入的 Base64 字符串
+/// [urlSafe] 输入是否是 URL 安全的 Base64 编码
+/// [withPrefix] 是否添加 "0x" 前缀
+///
+/// 返回十六进制字符串或抛出异常
+String base64ToHex(String base64Str, {
+  bool urlSafe = false,
+  bool withPrefix = false,
+}) {
+  try {
+    if (base64Str.isEmpty) {
+      throw ArgumentError('Base64 string cannot be empty');
+    }
+
+    String normalizedStr = base64Str;
+
+    // 处理 URL 安全的 Base64
+    if (urlSafe) {
+      // 添加必要的填充字符
+      final padding = '=' * ((4 - base64Str.length % 4) % 4);
+      normalizedStr = base64Str
+          .replaceAll('-', '+')
+          .replaceAll('_', '/') + padding;
+    }
+
+    // 验证 Base64 字符串格式
+    if (!RegExp(r'^[a-zA-Z0-9+/]*={0,2}$').hasMatch(normalizedStr)) {
+      throw FormatException('Invalid Base64 string format');
+    }
+
+    // 解码为字节数组
+    final bytes = base64Decode(normalizedStr);
+
+    // 转换为十六进制
+    final hexString = bytes.map((byte) {
+      return byte.toRadixString(16).padLeft(2, '0');
+    }).join('');
+
+    return withPrefix ? '0x$hexString' : hexString;
+  } on ArgumentError catch (e) {
+    rethrow;
+  } on FormatException catch (e) {
+    rethrow;
+  } catch (e) {
+    throw Exception('Failed to convert Base64 to hex: ${e.toString()}');
+  }
+}
+

+ 55 - 1
lib/util/hexUtils.dart

@@ -1,7 +1,8 @@
 import 'dart:convert' show utf8;
 import 'dart:typed_data';
-
 import 'package:convert/convert.dart' show hex;
+import "package:pointycastle/digests/keccak.dart";
+
 
 bool isHexPrefixed(String str) {
   ArgumentError.checkNotNull(str);
@@ -221,3 +222,56 @@ String dec2hex(String decString) {
 
 
 
+
+
+/// ----- to Hash -----
+/// 注意:Hash256 和 Keccak256 其实不一样,
+/// 但由于最早接触 Ethereum 相关代码,其中 Hash 使用的是 Keccak256,
+/// 造成App其他地方的 Hash 也都沿用了 Keccak256。
+/// 历史原因,不做修改。[2023-12-15 James.Zhang]
+
+String toHash(String anyString) {
+  return toKeccak256(anyString);
+}
+
+String toKeccak256(String anyString, [int bitLength = 256]) {
+  var keccak = KeccakDigest(bitLength);
+  keccak.update(
+    Uint8List.fromList(anyString.codeUnits),
+    0,
+    anyString.codeUnits.length,
+  );
+
+  var out = Uint8List(bitLength ~/ 8);
+  keccak.doFinal(out, 0);
+
+  return _uint8ListToHex(out);
+}
+
+/// Character `0`.
+const int _zero = 0x30;
+/// Character `a`.
+const int _a = 0x61;
+
+String _uint8ListToHex(List<int> bytes) {
+  final buffer = Uint8List(bytes.length * 2);
+
+  int bufferIndex = 0;
+  for (var i = 0; i < bytes.length; i++) {
+    var byte = bytes[i];
+
+    // The bitwise arithmetic here is equivalent to `byte ~/ 16` and `byte % 16`
+    // for valid byte values, but is easier for dart2js to optimize given that
+    // it can't prove that [byte] will always be positive.
+    buffer[bufferIndex++] = _codeUnitForDigit((byte & 0xF0) >> 4);
+    buffer[bufferIndex++] = _codeUnitForDigit(byte & 0x0F);
+  }
+
+  return String.fromCharCodes(buffer);
+}
+
+/// Returns the ASCII/Unicode code unit corresponding to the hexadecimal digit
+/// [digit].
+int _codeUnitForDigit(int digit) =>
+    digit < 10 ? digit + _zero : digit + _a - 10;
+

+ 9 - 0
lib/util/logger.dart

@@ -1,3 +1,5 @@
+
+import 'dart:convert';
 import 'package:logger/logger.dart';
 
 class Log {
@@ -34,4 +36,11 @@ class Log {
   static void f(dynamic message) {
     _logger.f(message);
   }
+
+  // json
+  static void json(Map json) {
+    const encoder = JsonEncoder.withIndent('  '); // 2空格缩进
+    final prettyString = encoder.convert(json);
+    print(prettyString);
+  }
 }

+ 3 - 1
pubspec.yaml

@@ -14,8 +14,9 @@ dependencies:
   crypto: ^3.0.6
   flutter_riverpod: ^2.4.9
   riverpod_annotation: ^2.3.3
-  objectbox: ^2.4.0
+  objectbox: ^4.1.0
   objectbox_flutter_libs: any
+  sembast: ^3.8.5
   logger: ^2.0.2+1
   bot_toast: ^4.1.3
   path: ^1.8.3
@@ -23,6 +24,7 @@ dependencies:
   cross_file: ^0.3.3+8
   file_selector: ^1.0.3
   shared_preferences: ^2.2.2
+  pointycastle: ^3.9.1
 
 dev_dependencies:
   build_runner: ^2.4.8