8 Commits 73ee7cd1b4 ... b63d5fb068

Tác giả SHA1 Thông báo Ngày
  James b63d5fb068 Merge branch 'update' 9 tháng trước cách đây
  James 43f294d1d9 backup 9 tháng trước cách đây
  James 327de887e1 数据库修改,增加 sembast 数据库 9 tháng trước cách đây
  James 1da03d1481 update 9 tháng trước cách đây
  James 1bd0ceee1e update 10 tháng trước cách đây
  James 18f9e395f8 update 10 tháng trước cách đây
  James c443006e21 update 11 tháng trước cách đây
  James 9179299b28 backup 11 tháng trước cách đây

+ 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