You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

546 lines
18 KiB

import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import '../models/translation_usage_stats.dart';
import '../models/meeting_usage_stats.dart';
import '../../core/utils/logger.dart';
import 'network/api.dart';
import '../models/goods.dart';
import '../models/user_Info.dart';
/// 使用量统计服务
///
class UsageStatsService extends GetxService {
static const String _dailyStatsKey = 'daily_translation_stats';
static const String _meetingDailyKey = 'daily_meeting_asr_stats';
final GetStorage _storage = GetStorage();
// 实时统计数据
final Rx<RealtimeUsageStats> realtimeTranslationStats =
RealtimeUsageStats().obs;
// 当日统计数据
final Rx<TranslationUsageStats> todayTranslationStats =
TranslationUsageStats.empty('').obs;
@override
void onInit() {
super.onInit();
_initializeService();
}
/// 初始化服务
void _initializeService() {
_loadTodayTranslationStats();
Logger.info('lxm--使用量统计服务初始化完成');
}
/// 加载当日翻译统计数据
void _loadTodayTranslationStats() {
try {
final today = DateTime.now();
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) {
todayTranslationStats.value = TranslationUsageStats.fromJson(
Map<String, dynamic>.from(statsData));
} else {
todayTranslationStats.value =
TranslationUsageStats.empty(getCurrentUserId());
}
} catch (e) {
Logger.error('加载当日翻译统计数据失败: $e');
todayTranslationStats.value =
TranslationUsageStats.empty(getCurrentUserId());
}
}
/// 获取当前用户ID
String getCurrentUserId() {
try {
final userInfo = _storage.read('user_info');
if (userInfo != null && userInfo['user'] != null) {
return userInfo['user']['uid'] ?? 'anonymous';
}
} catch (e) {
Logger.warning('获取用户ID失败: $e');
}
return 'anonymous';
}
MeetingAsrDailyStats _loadMeetingDaily(String dateKey) {
final data = _storage.read('${_meetingDailyKey}_$dateKey');
if (data != null) {
return MeetingAsrDailyStats.fromJson(Map<String, dynamic>.from(data));
}
return MeetingAsrDailyStats(
date: dateKey, userId: getCurrentUserId(), items: []);
}
void _saveMeetingDaily(MeetingAsrDailyStats stats) {
_storage.write('${_meetingDailyKey}_${stats.date}', stats.toJson());
}
String _formatDate(DateTime d) {
final y = d.year;
final m = d.month.toString().padLeft(2, '0');
final day = d.day.toString().padLeft(2, '0');
return '$y-$m-$day';
}
void addMeetingAsrRecord(MeetingAsrRecord record) {
final dateKey = _formatDate(
DateTime.fromMillisecondsSinceEpoch(record.submitTimestamp));
final daily = _loadMeetingDaily(dateKey);
final idx = daily.items.indexWhere((e) => e.id == record.id);
if (idx >= 0) {
daily.items[idx] = record;
} else {
daily.items.add(record);
}
_saveMeetingDaily(daily);
}
void completeMeetingAsrRecord({
required String id,
required int transcriptBytes,
required int completeTimestamp,
}) {
final now = DateTime.now();
for (int i = 0; i < 3; i++) {
final dateKey = _formatDate(now.subtract(Duration(days: i)));
final daily = _loadMeetingDaily(dateKey);
final idx = daily.items.indexWhere((e) => e.id == id);
if (idx >= 0) {
final old = daily.items[idx];
daily.items[idx] = MeetingAsrRecord(
id: old.id,
meetingId: old.meetingId,
taskId: old.taskId,
audioSeconds: old.audioSeconds,
fileSizeBytes: old.fileSizeBytes,
language: old.language,
submitTimestamp: old.submitTimestamp,
status: asrStatusCompleted,
transcriptBytes: transcriptBytes,
completeTimestamp: completeTimestamp,
);
_saveMeetingDaily(daily);
return;
}
}
}
/// 记录翻译API调用
void recordTranslationApiCall({
required String sourceText,
required String targetText,
required String sourceLanguage,
required String targetLanguage,
required String mode,
}) {
try {
// 更新实时统计
realtimeTranslationStats.value.translationCount++;
realtimeTranslationStats.value.sourceTextCharacters += sourceText.length;
realtimeTranslationStats.value.ttsTextCharacters += targetText.length;
realtimeTranslationStats.value.currentLanguagePair =
'$sourceLanguage-$targetLanguage';
realtimeTranslationStats.value.currentMode = mode;
realtimeTranslationStats.refresh();
// 更新当日统计 - 使用多维度统计
final languagePair = '$sourceLanguage→$targetLanguage';
// 更新语言对统计
final newLanguagePairStats = Map<String, LanguagePairStats>.from(
todayTranslationStats.value.languagePairStats);
if (newLanguagePairStats.containsKey(languagePair)) {
final existing = newLanguagePairStats[languagePair]!;
newLanguagePairStats[languagePair] = LanguagePairStats(
languagePair: languagePair,
translationCount: existing.translationCount + 1,
sourceCharacters: existing.sourceCharacters + sourceText.length,
);
} else {
newLanguagePairStats[languagePair] = LanguagePairStats(
languagePair: languagePair,
translationCount: 1,
sourceCharacters: sourceText.length,
);
}
// 更新模式统计
final newModeStats = Map<String, TranslationModeStats>.from(
todayTranslationStats.value.modeStats);
if (newModeStats.containsKey(mode)) {
final existing = newModeStats[mode]!;
newModeStats[mode] = existing.copyWith(
apiCalls: existing.apiCalls + 1,
sourceCharacters: existing.sourceCharacters + sourceText.length,
ttsCharacters: existing.ttsCharacters + targetText.length,
);
} else {
newModeStats[mode] = TranslationModeStats(
apiCalls: 1,
sourceCharacters: sourceText.length,
ttsCharacters: targetText.length,
);
}
todayTranslationStats.value = todayTranslationStats.value.copyWith(
translationApiCalls:
todayTranslationStats.value.translationApiCalls + 1,
sourceTextCharacters: todayTranslationStats.value.sourceTextCharacters +
sourceText.length,
ttsTextCharacters:
todayTranslationStats.value.ttsTextCharacters + targetText.length,
languagePairStats: newLanguagePairStats,
modeStats: newModeStats,
);
_saveTodayStats();
Logger.info(
'记录翻译API调用: 源文本${sourceText.length}字符, 目标文本${targetText.length}字符');
} catch (e) {
Logger.error('记录翻译API调用失败: $e');
}
}
/// 记录ASR API调用 - 优化版(精确统计参数)
void recordAsrApiCall({
required int effectiveDurationSeconds, // 有效语音时长(VAD检测的实际语音)
required int totalDurationSeconds, // 总会话时长(包含静音)
required String language,
required String mode, // 翻译模式
String? audioFormat, // 音频格式(可选)
int? sampleRate, // 采样率(可选)
}) {
try {
// 更新实时统计(使用有效语音时长作为主要计费依据)
realtimeTranslationStats.value.effectiveAudioDuration +=
effectiveDurationSeconds;
realtimeTranslationStats.value.totalSessionDuration +=
totalDurationSeconds;
realtimeTranslationStats.refresh();
// 更新模式统计
final newModeStats = Map<String, TranslationModeStats>.from(
todayTranslationStats.value.modeStats);
if (newModeStats.containsKey(mode)) {
final existing = newModeStats[mode]!;
newModeStats[mode] = existing.copyWith(
effectiveAudioDuration:
existing.effectiveAudioDuration + effectiveDurationSeconds,
);
} else {
newModeStats[mode] = TranslationModeStats(
effectiveAudioDuration: effectiveDurationSeconds,
);
}
// 更新当日统计
todayTranslationStats.value = todayTranslationStats.value.copyWith(
asrApiCalls: todayTranslationStats.value.asrApiCalls + 1,
effectiveAudioDuration:
todayTranslationStats.value.effectiveAudioDuration +
effectiveDurationSeconds,
totalSessionDuration: todayTranslationStats.value.totalSessionDuration +
totalDurationSeconds,
modeStats: newModeStats,
);
_saveTodayStats();
Logger.info(
'ASR统计: 有效语音${effectiveDurationSeconds}秒, 总时长${totalDurationSeconds}秒, 语言$language, 模式$mode');
} catch (e) {
Logger.error('记录ASR API调用失败: $e');
}
_reportAsrUsageToServer(
effectiveDurationSeconds: effectiveDurationSeconds,
//totalDurationSeconds: totalDurationSeconds,
language: language,
mode: mode,
);
}
/// 上报ASR用量到服务器
Future<void> _reportAsrUsageToServer({
required int effectiveDurationSeconds,
//required int totalDurationSeconds,
required String language,
required String mode,
}) async {
try {
final params = {
'usagetype': UsageType.Translation.toInt,
'usages': {
// 'TRANSLATION_ASR_EFFECTIVE_SECONDS': effectiveDurationSeconds,
//'TRANSLATION_ASR_TOTAL_SECONDS': totalDurationSeconds,
'TRANSLATION_MODE_$mode': effectiveDurationSeconds,
'TRANSLATION_ASR_LANGUAGE_$language': 1,
'TRANSLATION_SOURCE_CHARACTERS':
realtimeTranslationStats.value.sourceTextCharacters,
'TRANSLATION_TARGET_CHARACTERS':
realtimeTranslationStats.value.ttsTextCharacters,
},
};
Logger.info('lxm---ASR用量上报参数: $params');
final response = await Api.usagesResPoints(params);
Logger.info('lxm---ASR用量上报响应: $response');
_applyUsageResponse(response);
} catch (e) {
Logger.error('记录ASR API调用失败: $e');
}
}
/// 根据服务器用量上报回应,更新用户积分(非总积分)
/// 用量类型:0 AI聊天、1 翻译、2 会议
void _applyUsageResponse(dynamic response) {
try {
if (response is! Map) return;
final usagesObj = response['usages'];
if (usagesObj is! Map) return;
final usages = Map<String, dynamic>.from(usagesObj as Map);
int _asInt(dynamic v) {
if (v == null) return 0;
if (v is int) return v;
if (v is num) return v.toInt();
if (v is String) return int.tryParse(v) ?? 0;
return 0;
}
final Map<String, dynamic> update = {};
usages.forEach((k, v) {
final key = int.tryParse(k.toString());
final val = _asInt(v);
if (key == null) return;
switch (key) {
case 0:
update['aichatintegral'] = val;
break;
case 1:
update['tradeintegral'] = val;
break;
case 2:
update['meetintegral'] = val;
break;
}
});
if (update.isNotEmpty) {
User.instance.updateUserInfo(update);
Logger.info('积分更新: $update');
}
} catch (e) {
Logger.error('解析用量回应并更新积分失败: $e');
}
}
/// 记录TTS API调用
void recordTtsApiCall({
required String text,
required int durationSeconds,
required String language,
}) {
try {
// 更新实时统计
realtimeTranslationStats.value.totalSessionDuration += durationSeconds;
realtimeTranslationStats.refresh();
// 更新当日统计(TTS按字符数计费,不是按时长)
todayTranslationStats.value = todayTranslationStats.value.copyWith(
ttsApiCalls: todayTranslationStats.value.ttsApiCalls + 1,
ttsTextCharacters:
todayTranslationStats.value.ttsTextCharacters + text.length,
totalSessionDuration:
todayTranslationStats.value.totalSessionDuration + durationSeconds,
);
_saveTodayStats();
Logger.info('记录TTS API调用: 文本${text.length}字符, 时长${durationSeconds}秒');
} catch (e) {
Logger.error('记录TTS API调用失败: $e');
}
}
/// 记录AI摘要API调用
void recordAiSummaryApiCall() {
try {
// 更新当日统计
todayTranslationStats.value = todayTranslationStats.value.copyWith(
aiSummaryApiCalls: todayTranslationStats.value.aiSummaryApiCalls + 1,
);
_saveTodayStats();
Logger.info('记录AI摘要API调用');
} catch (e) {
Logger.error('记录AI摘要API调用失败: $e');
}
}
/// 开始新会话
void startNewSession() {
realtimeTranslationStats.value.reset();
// 更新当日会话数
todayTranslationStats.value = todayTranslationStats.value.copyWith(
sessionCount: todayTranslationStats.value.sessionCount + 1,
);
_saveTodayStats();
Logger.info('开始新的翻译会话');
}
/// 保存当日统计数据
void _saveTodayStats() {
try {
final today = DateTime.now();
final todayKey =
'${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}';
_storage.write(
'${_dailyStatsKey}_$todayKey', todayTranslationStats.value.toJson());
} catch (e) {
Logger.error('保存当日统计数据失败: $e');
}
}
/// 获取历史统计数据
Future<List<TranslationUsageStats>> getHistoryStats({
required DateTime startDate,
required DateTime endDate,
}) 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')}';
final statsData = _storage.read('${_dailyStatsKey}_$dateKey');
if (statsData != null) {
historyStats.add(TranslationUsageStats.fromJson(
Map<String, dynamic>.from(statsData)));
}
}
return historyStats;
} catch (e) {
Logger.error('获取历史统计数据失败: $e');
return [];
}
}
/// 清理过期数据(保留最近90天)
void cleanupOldData() {
try {
final cutoffDate = DateTime.now().subtract(const Duration(days: 90));
final allKeys = _storage.getKeys();
for (final key in allKeys) {
if (key.startsWith(_dailyStatsKey)) {
final dateStr = key.replaceFirst('${_dailyStatsKey}_', '');
try {
final date = DateTime.parse(dateStr);
if (date.isBefore(cutoffDate)) {
_storage.remove(key);
Logger.info('清理过期统计数据: $key');
}
} catch (e) {
// 忽略解析错误的键
}
}
}
} catch (e) {
Logger.error('清理过期数据失败: $e');
}
}
/// 获取月度统计汇总
Future<TranslationUsageStats> getMonthlyStats(int year, int month) async {
try {
final startDate = DateTime(year, month, 1);
final endDate = DateTime(year, month + 1, 0); // 月末
final dailyStats = await getHistoryStats(
startDate: startDate,
endDate: endDate,
);
if (dailyStats.isEmpty) {
return TranslationUsageStats.empty(getCurrentUserId());
}
// 汇总月度数据
int totalTranslationCalls = 0;
int totalAsrCalls = 0;
int totalTtsCalls = 0;
int totalAiSummaryCalls = 0;
int totalSourceChars = 0;
int totalTargetChars = 0;
int totalRecordingDuration = 0;
int totalTtsPlayDuration = 0;
int totalSessions = 0;
//double totalCost = 0.0;
final Map<String, int> combinedLanguagePairUsage = {};
final Map<String, int> combinedModeUsage = {};
for (final stats in dailyStats) {
totalTranslationCalls += stats.translationApiCalls;
totalAsrCalls += stats.asrApiCalls;
totalTtsCalls += stats.ttsApiCalls;
totalAiSummaryCalls += stats.aiSummaryApiCalls;
totalSourceChars += stats.sourceCharacterCount;
totalTargetChars += stats.targetCharacterCount;
totalRecordingDuration += stats.audioRecordingDuration;
totalTtsPlayDuration += stats.ttsPlaybackDuration;
totalSessions += stats.sessionCount;
// totalCost += stats.estimatedCost;
// 合并语言对使用统计
stats.languagePairUsage.forEach((key, value) {
combinedLanguagePairUsage[key] =
(combinedLanguagePairUsage[key] ?? 0) + value;
});
// 合并模式使用统计
stats.modeUsage.forEach((key, value) {
combinedModeUsage[key] = (combinedModeUsage[key] ?? 0) + value;
});
}
return TranslationUsageStats(
id: 'monthly_${year}_$month',
date: startDate,
userId: getCurrentUserId(),
timeGranularity: 'month',
translationApiCalls: totalTranslationCalls,
asrApiCalls: totalAsrCalls,
ttsApiCalls: totalTtsCalls,
aiSummaryApiCalls: totalAiSummaryCalls,
sourceTextCharacters: totalSourceChars,
effectiveAudioDuration: totalRecordingDuration,
ttsTextCharacters: totalTargetChars,
totalSessionDuration: totalTtsPlayDuration,
sessionCount: totalSessions,
// estimatedCost: totalCost,
);
} catch (e) {
Logger.error('获取月度统计失败: $e');
return TranslationUsageStats.empty(getCurrentUserId());
}
}
}