Browse Source

refactor(usage): 重命名TranslationUsageService为UsageStatsService并重构统计逻辑

重构翻译使用统计服务,将TranslationUsageService更名为更通用的UsageStatsService
优化统计逻辑,增加多维度统计和精确成本计算
添加月度统计汇总和历史数据清理功能
newdev_shunjiawei
lxm 11 months ago
parent
commit
bd7b4659f1
  1. 126
      lib/data/services/usage_stats_service.dart
  2. 14
      lib/modules/translation/controllers/translation_controller.dart
  3. 12
      lib/modules/translation/controllers/translation_detail_controller.dart
  4. 8
      lib/modules/usage/bindings/translation_stats_binding.dart
  5. 5
      lib/modules/usage/controllers/translation_cost_controller.dart
  6. 16
      lib/modules/usage/controllers/translation_stats_controller.dart

126
lib/data/services/translation_usage_service.dart → 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<RealtimeUsageStats> realtimeStats = RealtimeUsageStats().obs;
// 当日统计数据
final Rx<TranslationUsageStats> todayStats = TranslationUsageStats.empty('').obs;
final Rx<TranslationUsageStats> todayStats =
TranslationUsageStats.empty('').obs;
// 成本配置
final RxMap<String, double> costConfig = <String, double>{}.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<String, dynamic>.from(statsData));
todayStats.value = TranslationUsageStats.fromJson(
Map<String, dynamic>.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<String, LanguagePairStats>.from(todayStats.value.languagePairStats);
final newLanguagePairStats = Map<String, LanguagePairStats>.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<String, TranslationModeStats>.from(todayStats.value.modeStats);
final newModeStats =
Map<String, TranslationModeStats>.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<String, TranslationModeStats>.from(todayStats.value.modeStats);
final newModeStats =
Map<String, TranslationModeStats>.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<TranslationUsageStats> 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<String, dynamic>.from(statsData)));
historyStats.add(TranslationUsageStats.fromJson(
Map<String, dynamic>.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;
});
// 合并模式使用统计

14
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<BluetoothManager>();
final BleManager bleManager = Get.find<BleManager>();
final _musiceManager = Get.find<MusicManager>();
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<TranslationUsageService>()) {
Get.put(TranslationUsageService(), permanent: true);
if (!Get.isRegistered<UsageStatsService>()) {
Get.put(UsageStatsService(), permanent: true);
}
_usageService = Get.find<TranslationUsageService>();
Logger.info('翻译统计服务初始化完成');
_usageService = Get.find<UsageStatsService>();
Logger.info('使用量统计服务初始化完成');
} catch (e) {
Logger.error('翻译统计服务初始化失败: $e');
Logger.error('使用量统计服务初始化失败: $e');
}
}

12
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<VolcanoAIService>();
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<TranslationUsageService>()) {
Get.put(TranslationUsageService(), permanent: true);
if (!Get.isRegistered<UsageStatsService>()) {
Get.put(UsageStatsService(), permanent: true);
}
_usageService = Get.find<TranslationUsageService>();
_usageService = Get.find<UsageStatsService>();
} catch (e) {
Logger.error('初始化统计服务失败: $e');
Logger.error('初始化使用量统计服务失败: $e');
}
// 从路由参数中获取数据

8
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<TranslationUsageService>()) {
Get.put(TranslationUsageService(), permanent: true);
if (!Get.isRegistered<UsageStatsService>()) {
Get.put(UsageStatsService(), permanent: true);
}
Get.lazyPut<TranslationStatsController>(() => TranslationStatsController());
}
}
}

5
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<TranslationUsageService>();
final UsageStatsService _usageService = Get.find<UsageStatsService>();
final RxMap<String, double> costConfig = <String, double>{}.obs;
final RxDouble dailyCostThreshold = 10.0.obs;
final RxDouble monthlyCostThreshold = 300.0.obs;

16
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<TranslationUsageService>();
final UsageStatsService _usageService = Get.find<UsageStatsService>();
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';
}
}
}
}

Loading…
Cancel
Save