From bd7b4659f19510353f0a384bfa9f3a1e6defebb5 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 15 Nov 2025 23:32:04 +0800 Subject: [PATCH] =?UTF-8?q?refactor(usage):=20=E9=87=8D=E5=91=BD=E5=90=8DT?= =?UTF-8?q?ranslationUsageService=E4=B8=BAUsageStatsService=E5=B9=B6?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=BB=9F=E8=AE=A1=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构翻译使用统计服务,将TranslationUsageService更名为更通用的UsageStatsService 优化统计逻辑,增加多维度统计和精确成本计算 添加月度统计汇总和历史数据清理功能 --- ..._service.dart => usage_stats_service.dart} | 126 +++++++++++------- .../controllers/translation_controller.dart | 14 +- .../translation_detail_controller.dart | 12 +- .../bindings/translation_stats_binding.dart | 8 +- .../translation_cost_controller.dart | 5 +- .../translation_stats_controller.dart | 16 +-- 6 files changed, 101 insertions(+), 80 deletions(-) rename lib/data/services/{translation_usage_service.dart => usage_stats_service.dart} (83%) diff --git a/lib/data/services/translation_usage_service.dart b/lib/data/services/usage_stats_service.dart similarity index 83% rename from lib/data/services/translation_usage_service.dart rename to lib/data/services/usage_stats_service.dart index 90f3db799..9a8653e7b 100644 --- a/lib/data/services/translation_usage_service.dart +++ b/lib/data/services/usage_stats_service.dart @@ -4,27 +4,28 @@ import 'package:get_storage/get_storage.dart'; import '../models/translation_usage_stats.dart'; import '../../core/utils/logger.dart'; -/// 翻译使用量统计服务 -class TranslationUsageService extends GetxService { - static const String _storageKey = 'translation_usage_stats'; +/// 使用量统计服务 +class UsageStatsService extends GetxService { + static const String _storageKey = 'usage_stats'; static const String _dailyStatsKey = 'daily_translation_stats'; static const String _costConfigKey = 'translation_cost_config'; - + final GetStorage _storage = GetStorage(); - + // 实时统计数据 final Rx realtimeStats = RealtimeUsageStats().obs; - + // 当日统计数据 - final Rx todayStats = TranslationUsageStats.empty('').obs; - + final Rx todayStats = + TranslationUsageStats.empty('').obs; + // 成本配置 final RxMap costConfig = {}.obs; - + // 预警阈值 final RxDouble dailyCostThreshold = 10.0.obs; // 日成本预警阈值 final RxDouble monthlyCostThreshold = 300.0.obs; // 月成本预警阈值 - + @override void onInit() { super.onInit(); @@ -72,11 +73,13 @@ class TranslationUsageService extends GetxService { void _loadTodayStats() { try { final today = DateTime.now(); - final todayKey = '${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}'; - + final todayKey = + '${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}'; + final statsData = _storage.read('${_dailyStatsKey}_$todayKey'); if (statsData != null) { - todayStats.value = TranslationUsageStats.fromJson(Map.from(statsData)); + todayStats.value = TranslationUsageStats.fromJson( + Map.from(statsData)); } else { todayStats.value = TranslationUsageStats.empty(getCurrentUserId()); } @@ -112,7 +115,8 @@ class TranslationUsageService extends GetxService { realtimeStats.value.translationCount++; realtimeStats.value.sourceTextCharacters += sourceText.length; realtimeStats.value.ttsTextCharacters += targetText.length; - realtimeStats.value.currentLanguagePair = '$sourceLanguage-$targetLanguage'; + realtimeStats.value.currentLanguagePair = + '$sourceLanguage-$targetLanguage'; realtimeStats.value.currentMode = mode; realtimeStats.refresh(); @@ -120,7 +124,8 @@ class TranslationUsageService extends GetxService { final languagePair = '$sourceLanguage→$targetLanguage'; // 更新语言对统计 - final newLanguagePairStats = Map.from(todayStats.value.languagePairStats); + final newLanguagePairStats = Map.from( + todayStats.value.languagePairStats); if (newLanguagePairStats.containsKey(languagePair)) { final existing = newLanguagePairStats[languagePair]!; newLanguagePairStats[languagePair] = LanguagePairStats( @@ -139,7 +144,8 @@ class TranslationUsageService extends GetxService { } // 更新模式统计 - final newModeStats = Map.from(todayStats.value.modeStats); + final newModeStats = + Map.from(todayStats.value.modeStats); if (newModeStats.containsKey(mode)) { final existing = newModeStats[mode]!; newModeStats[mode] = existing.copyWith( @@ -159,21 +165,26 @@ class TranslationUsageService extends GetxService { todayStats.value = todayStats.value.copyWith( translationApiCalls: todayStats.value.translationApiCalls + 1, - sourceTextCharacters: todayStats.value.sourceTextCharacters + sourceText.length, - ttsTextCharacters: todayStats.value.ttsTextCharacters + targetText.length, + sourceTextCharacters: + todayStats.value.sourceTextCharacters + sourceText.length, + ttsTextCharacters: + todayStats.value.ttsTextCharacters + targetText.length, languagePairStats: newLanguagePairStats, modeStats: newModeStats, estimatedCost: _calculateEstimatedCost(todayStats.value.copyWith( translationApiCalls: todayStats.value.translationApiCalls + 1, - sourceTextCharacters: todayStats.value.sourceTextCharacters + sourceText.length, - ttsTextCharacters: todayStats.value.ttsTextCharacters + targetText.length, + sourceTextCharacters: + todayStats.value.sourceTextCharacters + sourceText.length, + ttsTextCharacters: + todayStats.value.ttsTextCharacters + targetText.length, )), ); _saveTodayStats(); _checkCostThreshold(); - - Logger.info('记录翻译API调用: 源文本${sourceText.length}字符, 目标文本${targetText.length}字符'); + + Logger.info( + '记录翻译API调用: 源文本${sourceText.length}字符, 目标文本${targetText.length}字符'); } catch (e) { Logger.error('记录翻译API调用失败: $e'); } @@ -195,11 +206,13 @@ class TranslationUsageService extends GetxService { realtimeStats.refresh(); // 更新模式统计 - final newModeStats = Map.from(todayStats.value.modeStats); + final newModeStats = + Map.from(todayStats.value.modeStats); if (newModeStats.containsKey(mode)) { final existing = newModeStats[mode]!; newModeStats[mode] = existing.copyWith( - effectiveAudioDuration: existing.effectiveAudioDuration + effectiveDurationSeconds, + effectiveAudioDuration: + existing.effectiveAudioDuration + effectiveDurationSeconds, cost: existing.cost + _calculateAsrCost(effectiveDurationSeconds), ); } else { @@ -212,18 +225,22 @@ class TranslationUsageService extends GetxService { // 更新当日统计 todayStats.value = todayStats.value.copyWith( asrApiCalls: todayStats.value.asrApiCalls + 1, - effectiveAudioDuration: todayStats.value.effectiveAudioDuration + effectiveDurationSeconds, - totalSessionDuration: todayStats.value.totalSessionDuration + totalDurationSeconds, + effectiveAudioDuration: + todayStats.value.effectiveAudioDuration + effectiveDurationSeconds, + totalSessionDuration: + todayStats.value.totalSessionDuration + totalDurationSeconds, modeStats: newModeStats, estimatedCost: _calculateEstimatedCost(todayStats.value.copyWith( asrApiCalls: todayStats.value.asrApiCalls + 1, - effectiveAudioDuration: todayStats.value.effectiveAudioDuration + effectiveDurationSeconds, + effectiveAudioDuration: todayStats.value.effectiveAudioDuration + + effectiveDurationSeconds, )), ); _saveTodayStats(); - Logger.info('ASR统计: 有效语音${effectiveDurationSeconds}秒, 总时长${totalDurationSeconds}秒, 语言$language, 模式$mode'); + Logger.info( + 'ASR统计: 有效语音${effectiveDurationSeconds}秒, 总时长${totalDurationSeconds}秒, 语言$language, 模式$mode'); } catch (e) { Logger.error('记录ASR API调用失败: $e'); } @@ -244,7 +261,8 @@ class TranslationUsageService extends GetxService { todayStats.value = todayStats.value.copyWith( ttsApiCalls: todayStats.value.ttsApiCalls + 1, ttsTextCharacters: todayStats.value.ttsTextCharacters + text.length, - totalSessionDuration: todayStats.value.totalSessionDuration + durationSeconds, + totalSessionDuration: + todayStats.value.totalSessionDuration + durationSeconds, estimatedCost: _calculateEstimatedCost(todayStats.value.copyWith( ttsApiCalls: todayStats.value.ttsApiCalls + 1, ttsTextCharacters: todayStats.value.ttsTextCharacters + text.length, @@ -252,7 +270,7 @@ class TranslationUsageService extends GetxService { ); _saveTodayStats(); - + Logger.info('记录TTS API调用: 文本${text.length}字符, 时长${durationSeconds}秒'); } catch (e) { Logger.error('记录TTS API调用失败: $e'); @@ -271,7 +289,7 @@ class TranslationUsageService extends GetxService { ); _saveTodayStats(); - + Logger.info('记录AI摘要API调用'); } catch (e) { Logger.error('记录AI摘要API调用失败: $e'); @@ -281,12 +299,12 @@ class TranslationUsageService extends GetxService { /// 开始新会话 void startNewSession() { realtimeStats.value.reset(); - + // 更新当日会话数 todayStats.value = todayStats.value.copyWith( sessionCount: todayStats.value.sessionCount + 1, ); - + _saveTodayStats(); Logger.info('开始新的翻译会话'); } @@ -296,23 +314,28 @@ class TranslationUsageService extends GetxService { double cost = 0.0; // 翻译成本:基于源文本字符数 - cost += (stats.sourceTextCharacters / 1000) * (costConfig['translation_per_1k_chars'] ?? 0.05); + cost += (stats.sourceTextCharacters / 1000) * + (costConfig['translation_per_1k_chars'] ?? 0.05); // ASR成本:基于有效音频时长 - cost += (stats.effectiveAudioDuration / 60) * (costConfig['asr_per_minute'] ?? 0.02); + cost += (stats.effectiveAudioDuration / 60) * + (costConfig['asr_per_minute'] ?? 0.02); // TTS成本:基于TTS文本字符数 - cost += (stats.ttsTextCharacters / 1000) * (costConfig['tts_per_1k_chars'] ?? 0.03); + cost += (stats.ttsTextCharacters / 1000) * + (costConfig['tts_per_1k_chars'] ?? 0.03); // AI摘要成本(已废弃但保留兼容性) - cost += stats.aiSummaryApiCalls * (costConfig['ai_summary_per_request'] ?? 0.01); + cost += stats.aiSummaryApiCalls * + (costConfig['ai_summary_per_request'] ?? 0.01); return cost; } /// 计算翻译成本(单次调用) double _calculateTranslationCost(int sourceCharacters) { - return (sourceCharacters / 1000) * (costConfig['translation_per_1k_chars'] ?? 0.05); + return (sourceCharacters / 1000) * + (costConfig['translation_per_1k_chars'] ?? 0.05); } /// 计算ASR成本(单次调用) @@ -329,7 +352,8 @@ class TranslationUsageService extends GetxService { void _saveTodayStats() { try { final today = DateTime.now(); - final todayKey = '${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}'; + final todayKey = + '${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}'; _storage.write('${_dailyStatsKey}_$todayKey', todayStats.value.toJson()); } catch (e) { Logger.error('保存当日统计数据失败: $e'); @@ -339,7 +363,7 @@ class TranslationUsageService extends GetxService { /// 检查成本阈值 void _checkCostThreshold() { final todayCost = todayStats.value.estimatedCost; - + if (todayCost >= dailyCostThreshold.value) { _showCostWarning('今日翻译成本已达到预警阈值:¥${todayCost.toStringAsFixed(2)}'); } @@ -364,19 +388,20 @@ class TranslationUsageService extends GetxService { }) async { try { final List historyStats = []; - - for (DateTime date = startDate; - date.isBefore(endDate) || date.isAtSameMomentAs(endDate); - date = date.add(const Duration(days: 1))) { - - final dateKey = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; + + for (DateTime date = startDate; + date.isBefore(endDate) || date.isAtSameMomentAs(endDate); + date = date.add(const Duration(days: 1))) { + final dateKey = + '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; final statsData = _storage.read('${_dailyStatsKey}_$dateKey'); - + if (statsData != null) { - historyStats.add(TranslationUsageStats.fromJson(Map.from(statsData))); + historyStats.add(TranslationUsageStats.fromJson( + Map.from(statsData))); } } - + return historyStats; } catch (e) { Logger.error('获取历史统计数据失败: $e'); @@ -486,7 +511,8 @@ class TranslationUsageService extends GetxService { // 合并语言对使用统计 stats.languagePairUsage.forEach((key, value) { - combinedLanguagePairUsage[key] = (combinedLanguagePairUsage[key] ?? 0) + value; + combinedLanguagePairUsage[key] = + (combinedLanguagePairUsage[key] ?? 0) + value; }); // 合并模式使用统计 diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index dcb70b5ba..2e3680687 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -19,7 +19,7 @@ import '../../../core/utils/permission_util.dart'; import '../../../core/services/floating_window_manager.dart'; import '../../../data/services/asr_service.dart'; -import '../../../data/services/translation_usage_service.dart'; +import '../../../data/services/usage_stats_service.dart'; import '../../../data/services/ble_manager.dart'; import 'record_timer_controller.dart'; import 'translation_history_controller.dart'; @@ -48,7 +48,7 @@ class TranslationController extends GetxController with WidgetsBindingObserver { final BluetoothManager bluetoothManager = Get.find(); final BleManager bleManager = Get.find(); final _musiceManager = Get.find(); - late final TranslationUsageService _usageService; + late final UsageStatsService _usageService; late final FloatingWindowManager _floatingWindowManager; // ==================== 悬浮窗相关 ==================== @@ -435,13 +435,13 @@ class TranslationController extends GetxController with WidgetsBindingObserver { void _initUsageService() { try { // 确保统计服务已注册 - if (!Get.isRegistered()) { - Get.put(TranslationUsageService(), permanent: true); + if (!Get.isRegistered()) { + Get.put(UsageStatsService(), permanent: true); } - _usageService = Get.find(); - Logger.info('翻译统计服务初始化完成'); + _usageService = Get.find(); + Logger.info('使用量统计服务初始化完成'); } catch (e) { - Logger.error('翻译统计服务初始化失败: $e'); + Logger.error('使用量统计服务初始化失败: $e'); } } diff --git a/lib/modules/translation/controllers/translation_detail_controller.dart b/lib/modules/translation/controllers/translation_detail_controller.dart index da34f3ff2..e2d965b0e 100644 --- a/lib/modules/translation/controllers/translation_detail_controller.dart +++ b/lib/modules/translation/controllers/translation_detail_controller.dart @@ -7,14 +7,14 @@ import '../models/translation_models.dart'; // 添加这行导入 import 'package:get_storage/get_storage.dart'; import 'dart:async'; import '../../../core/utils/logger.dart'; -import '../../../data/services/translation_usage_service.dart'; +import '../../../data/services/usage_stats_service.dart'; //详情页管理 class TranslationDetailController extends GetxController { // 依赖注入 //final VolcanoAIService _aiService = Get.find(); final GetStorage _storage = GetStorage(); - late final TranslationUsageService _usageService; + late final UsageStatsService _usageService; // 存储键前缀 static const String _summaryKeyPrefix = 'translation_summary_'; @@ -40,12 +40,12 @@ class TranslationDetailController extends GetxController { // 初始化统计服务 try { - if (!Get.isRegistered()) { - Get.put(TranslationUsageService(), permanent: true); + if (!Get.isRegistered()) { + Get.put(UsageStatsService(), permanent: true); } - _usageService = Get.find(); + _usageService = Get.find(); } catch (e) { - Logger.error('初始化统计服务失败: $e'); + Logger.error('初始化使用量统计服务失败: $e'); } // 从路由参数中获取数据 diff --git a/lib/modules/usage/bindings/translation_stats_binding.dart b/lib/modules/usage/bindings/translation_stats_binding.dart index 6f0008d97..da6c55d3a 100644 --- a/lib/modules/usage/bindings/translation_stats_binding.dart +++ b/lib/modules/usage/bindings/translation_stats_binding.dart @@ -1,13 +1,13 @@ import 'package:get/get.dart'; import '../controllers/translation_stats_controller.dart'; -import '../../../data/services/translation_usage_service.dart'; +import '../../../data/services/usage_stats_service.dart'; class TranslationStatsBinding implements Bindings { @override void dependencies() { - if (!Get.isRegistered()) { - Get.put(TranslationUsageService(), permanent: true); + if (!Get.isRegistered()) { + Get.put(UsageStatsService(), permanent: true); } Get.lazyPut(() => TranslationStatsController()); } -} \ No newline at end of file +} diff --git a/lib/modules/usage/controllers/translation_cost_controller.dart b/lib/modules/usage/controllers/translation_cost_controller.dart index 90c9ac688..94a5230ba 100644 --- a/lib/modules/usage/controllers/translation_cost_controller.dart +++ b/lib/modules/usage/controllers/translation_cost_controller.dart @@ -1,11 +1,10 @@ import 'package:get/get.dart'; import 'package:flutter/material.dart'; -import '../../../data/services/translation_usage_service.dart'; +import '../../../data/services/usage_stats_service.dart'; import '../../../core/utils/logger.dart'; class TranslationCostController extends GetxController { - final TranslationUsageService _usageService = - Get.find(); + final UsageStatsService _usageService = Get.find(); final RxMap costConfig = {}.obs; final RxDouble dailyCostThreshold = 10.0.obs; final RxDouble monthlyCostThreshold = 300.0.obs; diff --git a/lib/modules/usage/controllers/translation_stats_controller.dart b/lib/modules/usage/controllers/translation_stats_controller.dart index 28bc75e21..c38393f84 100644 --- a/lib/modules/usage/controllers/translation_stats_controller.dart +++ b/lib/modules/usage/controllers/translation_stats_controller.dart @@ -1,11 +1,10 @@ import 'package:get/get.dart'; -import '../../../data/services/translation_usage_service.dart'; +import '../../../data/services/usage_stats_service.dart'; import '../../../data/models/translation_usage_stats.dart'; import '../../../core/utils/logger.dart'; class TranslationStatsController extends GetxController { - final TranslationUsageService _usageService = - Get.find(); + final UsageStatsService _usageService = Get.find(); final isLoading = false.obs; final selectedTabIndex = 0.obs; @@ -76,8 +75,7 @@ class TranslationStatsController extends GetxController { '加载失败', '统计数据加载失败,请稍后重试', snackPosition: SnackPosition.TOP, - backgroundColor: - Get.theme.colorScheme.error.withValues(alpha: 0.1), + backgroundColor: Get.theme.colorScheme.error.withValues(alpha: 0.1), colorText: Get.theme.colorScheme.error, ); } finally { @@ -275,8 +273,7 @@ class TranslationStatsController extends GetxController { '清理完成', '已清理90天前的过期数据', snackPosition: SnackPosition.TOP, - backgroundColor: - Get.theme.colorScheme.primary.withValues(alpha: 0.1), + backgroundColor: Get.theme.colorScheme.primary.withValues(alpha: 0.1), colorText: Get.theme.colorScheme.primary, ); } @@ -287,8 +284,7 @@ class TranslationStatsController extends GetxController { '导出功能', '数据导出功能正在开发中', snackPosition: SnackPosition.TOP, - backgroundColor: - Get.theme.colorScheme.primary.withValues(alpha: 0.1), + backgroundColor: Get.theme.colorScheme.primary.withValues(alpha: 0.1), colorText: Get.theme.colorScheme.primary, ); } catch (e) { @@ -323,4 +319,4 @@ class TranslationStatsController extends GetxController { return '${(number / 1000000).toStringAsFixed(1)}M'; } } -} \ No newline at end of file +}