import 'dart:async'; import 'dart:collection'; import 'dart:convert'; import 'dart:io'; import 'package:path_provider/path_provider.dart'; import 'package:uuid/uuid.dart'; import '../app/api/core/api_core_paths.dart'; import '../app/constants/configs.dart'; import '../app/constants/enums.dart'; import '../app/data/sp/ix_sp.dart'; import 'log/logger.dart'; /// API请求统计记录 class ApiStatRecord { final String id; final String domain; final String path; final bool success; final int errorCode; final String errorMessage; final int apiRequestTime; final int apiResponseTime; ApiStatRecord({ required this.id, required this.domain, required this.path, required this.success, required this.errorCode, required this.errorMessage, required this.apiRequestTime, required this.apiResponseTime, }); /// 从JSON创建 factory ApiStatRecord.fromJson(Map json) { return ApiStatRecord( id: json['id'] ?? '', domain: json['domain'] ?? '', path: json['path'] ?? '', success: json['success'] ?? false, errorCode: json['errorCode'] ?? 0, errorMessage: json['errorMessage'] ?? '', apiRequestTime: json['apiRequestTime'] ?? 0, apiResponseTime: json['apiResponseTime'] ?? 0, ); } /// 转换为JSON Map toJson() { return { 'id': id, 'domain': domain, 'path': path, 'success': success, 'errorCode': errorCode, 'errorMessage': errorMessage, 'apiRequestTime': apiRequestTime, 'apiResponseTime': apiResponseTime, }; } @override String toString() { return jsonEncode(toJson()); } } /// API统计管理器 /// /// 功能: /// - 记录 API 请求的成功/失败统计 /// - 最大保存 100 条记录(FIFO) /// - 支持文件持久化存储 /// - 批量写入优化(每 10 条) class ApiStatistics { // 单例模式 static final ApiStatistics _instance = ApiStatistics._internal(); factory ApiStatistics() => _instance; ApiStatistics._internal(); static ApiStatistics get instance => _instance; static const String TAG = 'ApiStatistics'; // 最大保存记录数 static const int maxRecords = 100; // 批量写入阈值 static const int _batchWriteThreshold = 10; // 文件名 static const String _fileName = 'api_statistics.json'; // 使用Queue来保持FIFO顺序 final Queue _records = Queue(); // UUID生成器 final Uuid _uuid = const Uuid(); // 操作锁,防止在遍历时修改 bool _isOperating = false; // 是否已初始化 bool _isInitialized = false; // 未保存的记录数 int _unsavedCount = 0; // 是否正在写入文件 bool _isWriting = false; // 文件路径缓存 String? _filePath; // 禁用的日志模块缓存 Set _disabledModules = {}; /// 初始化(需要在 App 启动时调用) Future initialize() async { if (_isInitialized) return; try { // 获取文件路径 _filePath = await _getFilePath(); // 从文件加载数据 await _loadFromFile(); _isInitialized = true; log(TAG, 'ApiStatistics initialized, loaded ${_records.length} records'); } catch (e) { log(TAG, 'ApiStatistics initialize error: $e'); } } /// 更新禁用模块列表(在 launch 数据更新后调用) void updateDisabledModules() { try { final launch = IXSP.getLaunch(); _disabledModules = (launch?.appConfig?.disabledLogModules ?? []).toSet(); log(TAG, 'ApiStatistics disabled modules updated: $_disabledModules'); } catch (e) { log(TAG, 'ApiStatistics updateDisabledModules error: $e'); } } /// 检查模块是否被禁用 bool _isModuleDisabled(String path) { var moduleName = LogModule.NM_ApiOtherLog.name; if (path == ApiCorePaths.launch) { moduleName = LogModule.NM_ApiLaunchLog.name; } else if (path == ApiCorePaths.getDispatchInfo) { moduleName = LogModule.NM_ApiRouterLog.name; } return _disabledModules.contains(moduleName); } /// 获取文件路径 Future _getFilePath() async { if (_filePath != null) return _filePath!; final directory = await getApplicationDocumentsDirectory(); _filePath = '${directory.path}/$_fileName'; return _filePath!; } /// 从文件加载数据 Future _loadFromFile() async { try { final filePath = await _getFilePath(); final file = File(filePath); if (!await file.exists()) { log(TAG, 'ApiStatistics file not exists, skip loading'); return; } final content = await file.readAsString(); if (content.isEmpty) return; final List jsonList = jsonDecode(content); _records.clear(); for (final json in jsonList) { if (json is Map) { _records.addLast(ApiStatRecord.fromJson(json)); } } // 确保不超过最大数量 while (_records.length > maxRecords) { _records.removeFirst(); } log(TAG, 'ApiStatistics loaded ${_records.length} records from file'); } catch (e) { log(TAG, 'ApiStatistics load error: $e'); } } /// 保存到文件 Future _saveToFile() async { if (_isWriting) return; if (_unsavedCount == 0) return; _isWriting = true; try { // 更新禁用模块列表 updateDisabledModules(); final filePath = await _getFilePath(); final file = File(filePath); // 过滤掉被禁用模块的记录 _filterDisabledRecords(); // 获取记录副本 final records = getRecordsJson(); // 写入文件 await file.writeAsString(jsonEncode(records)); _unsavedCount = 0; log(TAG, 'ApiStatistics saved ${records.length} records to file'); } catch (e) { log(TAG, 'ApiStatistics save error: $e'); } finally { _isWriting = false; } } /// 过滤掉被禁用模块的记录 void _filterDisabledRecords() { if (_disabledModules.isEmpty) return; final toRemove = []; for (final record in _records) { if (_isModuleDisabled(record.path)) { toRemove.add(record); } } for (final record in toRemove) { _records.remove(record); } if (toRemove.isNotEmpty) { log( TAG, 'ApiStatistics filtered out ${toRemove.length} disabled module records', ); } } /// 检查是否需要保存 void _checkAndSave() { if (_unsavedCount >= _batchWriteThreshold) { _saveToFile(); } } /// 添加一条成功记录 void addSuccess({ required String domain, required String path, required int apiRequestTime, required int apiResponseTime, int errorCode = 200, }) { _addRecord( domain: domain, path: path, success: true, errorCode: errorCode, errorMessage: '', apiRequestTime: apiRequestTime, apiResponseTime: apiResponseTime, ); } /// 添加一条失败记录 void addFailure({ required String domain, required String path, required int apiRequestTime, required int apiResponseTime, required int errorCode, required String errorMessage, }) { _addRecord( domain: domain, path: path, success: false, errorCode: errorCode, errorMessage: errorMessage, apiRequestTime: apiRequestTime, apiResponseTime: apiResponseTime, ); } /// 内部添加记录方法(同步,线程安全) void _addRecord({ required String domain, required String path, required bool success, required int errorCode, required String errorMessage, required int apiRequestTime, required int apiResponseTime, }) { // 如果正在进行其他操作,延迟添加 if (_isOperating) { scheduleMicrotask( () => _addRecord( domain: domain, path: path, success: success, errorCode: errorCode, errorMessage: errorMessage, apiRequestTime: apiRequestTime, apiResponseTime: apiResponseTime, ), ); return; } final record = ApiStatRecord( id: _uuid.v4(), domain: domain, path: path, success: success, errorCode: errorCode, errorMessage: errorMessage, apiRequestTime: apiRequestTime, apiResponseTime: apiResponseTime, ); _records.addLast(record); _unsavedCount++; // 超出最大数量时,移除最早的记录 while (_records.length > maxRecords) { _records.removeFirst(); } // 检查是否需要保存 _checkAndSave(); } /// 获取所有记录(返回副本,避免并发修改问题) List getRecords() { _isOperating = true; try { return List.from(_records); } finally { _isOperating = false; } } /// 获取所有记录的JSON列表(返回副本) List> getRecordsJson() { _isOperating = true; try { return _records.map((r) => r.toJson()).toList(); } finally { _isOperating = false; } } /// 获取所有记录的JSON字符串 String getRecordsJsonString({bool pretty = false}) { final json = getRecordsJson(); if (pretty) { return const JsonEncoder.withIndent(' ').convert(json); } return jsonEncode(json); } /// 获取成功记录数 int get successCount { final records = getRecords(); return records.where((r) => r.success).length; } /// 获取失败记录数 int get failureCount { final records = getRecords(); return records.where((r) => !r.success).length; } /// 获取总记录数 int get totalCount => _records.length; /// 获取成功率 double get successRate { final total = totalCount; if (total == 0) return 0.0; return successCount / total; } /// 获取平均响应时间(毫秒) double get averageResponseTime { final records = getRecords(); if (records.isEmpty) return 0.0; final totalTime = records.fold(0, (sum, r) => sum + r.apiResponseTime); return totalTime / records.length; } /// 清空所有记录 Future clear() async { _isOperating = true; try { _records.clear(); _unsavedCount = 0; // 清空文件 await _saveToFile(); } finally { _isOperating = false; } } /// 移除已上传的记录,保留上传过程中新增的记录 Future _removeUploadedRecords( List uploadedRecords, ) async { _isOperating = true; try { // 获取已上传记录的 ID 集合 final uploadedIds = uploadedRecords.map((r) => r.id).toSet(); // 移除已上传的记录 _records.removeWhere((r) => uploadedIds.contains(r.id)); _unsavedCount = _records.length; // 保存剩余记录到文件(包括上传过程中新增的记录) await _forceSaveToFile(); } finally { _isOperating = false; } } /// 强制保存到文件(忽略 _unsavedCount 检查) Future _forceSaveToFile() async { if (_isWriting) return; _isWriting = true; try { final filePath = await _getFilePath(); final file = File(filePath); final records = _records.map((r) => r.toJson()).toList(); await file.writeAsString(jsonEncode(records)); _unsavedCount = 0; log(TAG, 'ApiStatistics force saved ${records.length} records to file'); } catch (e) { log(TAG, 'ApiStatistics force save error: $e'); } finally { _isWriting = false; } } /// 获取最近N条记录(返回副本) List getRecentRecords(int count) { final records = getRecords(); if (count >= records.length) { return records; } return records.sublist(records.length - count); } /// 获取统计摘要 Map getSummary() { final records = getRecords(); final total = records.length; final success = records.where((r) => r.success).length; final failure = total - success; final rate = total > 0 ? success / total : 0.0; final avgTime = total > 0 ? records.fold(0, (sum, r) => sum + r.apiResponseTime) / total : 0.0; return { 'totalCount': total, 'successCount': success, 'failureCount': failure, 'successRate': '${(rate * 100).toStringAsFixed(2)}%', 'averageResponseTime': '${avgTime.toStringAsFixed(2)}ms', }; } /// 强制保存到文件(App 进入后台或退出时调用) Future flush() async { await _saveToFile(); } /// 释放资源(App 退出时调用) Future dispose() async { await _saveToFile(); _isInitialized = false; } /// App 进入后台时调用(不阻塞,fire-and-forget) void onAppPaused() { // 异步保存,不等待结果 _saveToFile(); } /// App 进入前台时调用(不阻塞,fire-and-forget) Future>> onAppResumed() async { await _loadFromFile(); return await _uploadAndClear(); } /// 上传统计数据并清空 Future>> _uploadAndClear() async { try { // 先更新禁用模块列表 updateDisabledModules(); final records = getRecords(); if (records.isEmpty) return []; // 检查模块是否禁用 final isLaunchDisabled = _disabledModules.contains( LogModule.NM_ApiLaunchLog.name, ); final isRouterDisabled = _disabledModules.contains( LogModule.NM_ApiRouterLog.name, ); final isOtherDisabled = _disabledModules.contains( LogModule.NM_ApiOtherLog.name, ); // 过滤掉禁用模块的记录 final enabledRecords = records.where((record) { if (record.path == ApiCorePaths.launch) { return !isLaunchDisabled; } else if (record.path == ApiCorePaths.getDispatchInfo) { return !isRouterDisabled; } else { return !isOtherDisabled; } }).toList(); // 移除已上传的记录,保留上传过程中新增的记录(如上传接口本身的记录) await _removeUploadedRecords(records); log( TAG, 'ApiStatistics uploaded ${enabledRecords.length}/${records.length} records ' '(launch disabled: $isLaunchDisabled, router disabled: $isRouterDisabled)', ); // 合并上传(一次接口调用) if (enabledRecords.isNotEmpty) { final logs = _formatLogsForUpload(enabledRecords); log(TAG, 'ApiStatistics upload ${logs.length} records'); return logs; } return []; } catch (e) { log(TAG, 'ApiStatistics upload error: $e'); return []; } } /// 格式化日志数据用于上传 List> _formatLogsForUpload(List records) { return records.map((record) { var moduleName = LogModule.NM_ApiOtherLog.name; if (record.path == ApiCorePaths.launch) { moduleName = LogModule.NM_ApiLaunchLog.name; } else if (record.path == ApiCorePaths.getDispatchInfo) { moduleName = LogModule.NM_ApiRouterLog.name; } return { 'id': record.id, 'time': record.apiRequestTime, 'level': LogLevel.info.name, 'module': moduleName, 'category': Configs.productCode, 'fields': record.toJson(), }; }).toList(); } }