| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- import 'dart:async';
- import 'dart:collection';
- import 'dart:convert';
- import 'dart:io';
- import 'package:get/get.dart';
- 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/controllers/api_controller.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<String, dynamic> 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<String, dynamic> 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<ApiStatRecord> _records = Queue<ApiStatRecord>();
- // UUID生成器
- final Uuid _uuid = const Uuid();
- // 操作锁,防止在遍历时修改
- bool _isOperating = false;
- // 是否已初始化
- bool _isInitialized = false;
- // 未保存的记录数
- int _unsavedCount = 0;
- // 是否正在写入文件
- bool _isWriting = false;
- // 文件路径缓存
- String? _filePath;
- // 禁用的日志模块缓存
- Set<String> _disabledModules = {};
- /// 初始化(需要在 App 启动时调用)
- Future<void> 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<String> _getFilePath() async {
- if (_filePath != null) return _filePath!;
- final directory = await getApplicationDocumentsDirectory();
- _filePath = '${directory.path}/$_fileName';
- return _filePath!;
- }
- /// 从文件加载数据
- Future<void> _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<dynamic> jsonList = jsonDecode(content);
- _records.clear();
- for (final json in jsonList) {
- if (json is Map<String, dynamic>) {
- _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<void> _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 = <ApiStatRecord>[];
- 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<ApiStatRecord> getRecords() {
- _isOperating = true;
- try {
- return List<ApiStatRecord>.from(_records);
- } finally {
- _isOperating = false;
- }
- }
- /// 获取所有记录的JSON列表(返回副本)
- List<Map<String, dynamic>> 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<int>(0, (sum, r) => sum + r.apiResponseTime);
- return totalTime / records.length;
- }
- /// 清空所有记录
- Future<void> clear() async {
- _isOperating = true;
- try {
- _records.clear();
- _unsavedCount = 0;
- // 清空文件
- await _saveToFile();
- } finally {
- _isOperating = false;
- }
- }
- /// 移除已上传的记录,保留上传过程中新增的记录
- Future<void> _removeUploadedRecords(
- List<ApiStatRecord> 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<void> _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<ApiStatRecord> getRecentRecords(int count) {
- final records = getRecords();
- if (count >= records.length) {
- return records;
- }
- return records.sublist(records.length - count);
- }
- /// 获取统计摘要
- Map<String, dynamic> 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<int>(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<void> flush() async {
- await _saveToFile();
- }
- /// 释放资源(App 退出时调用)
- Future<void> dispose() async {
- await _saveToFile();
- _isInitialized = false;
- }
- /// App 进入后台时调用
- Future<void> onAppPaused() async {
- await _saveToFile();
- }
- /// App 进入前台时调用
- Future<void> onAppResumed() async {
- await _loadFromFile();
- await _uploadAndClear();
- }
- /// 上传统计数据并清空
- Future<void> _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 enabledRecords = records.where((record) {
- if (record.path == ApiCorePaths.launch) {
- return !isLaunchDisabled;
- } else {
- return !isRouterDisabled;
- }
- }).toList();
- // 合并上传(一次接口调用)
- if (enabledRecords.isNotEmpty) {
- final apiController = Get.find<ApiController>();
- final logs = _formatLogsForUpload(enabledRecords);
- await apiController.uploadApiStatisticsLog(logs);
- }
- // 移除已上传的记录,保留上传过程中新增的记录(如上传接口本身的记录)
- await _removeUploadedRecords(records);
- log(
- TAG,
- 'ApiStatistics uploaded ${enabledRecords.length}/${records.length} records '
- '(launch disabled: $isLaunchDisabled, router disabled: $isRouterDisabled)',
- );
- } catch (e) {
- log(TAG, 'ApiStatistics upload error: $e');
- }
- }
- /// 格式化日志数据用于上传
- List<Map<String, dynamic>> _formatLogsForUpload(List<ApiStatRecord> 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();
- }
- }
|