From bcfb3e848637ffe09ad82d814abffcae569a0959 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Wed, 5 Nov 2025 14:18:25 +0800 Subject: [PATCH] =?UTF-8?q?refactor(meeting=5Ftask=5Fservice):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E8=AF=AD=E9=9F=B3=E6=9C=8D=E5=8A=A1=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=BB=8E=E7=A1=AC=E7=BC=96=E7=A0=81=E6=94=B9=E4=B8=BA=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=8F=98=E9=87=8F=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将火山引擎语音服务的配置参数从硬编码改为从AppConfig环境变量读取,提高安全性和可配置性 移除不再使用的旧版API相关代码,简化服务初始化逻辑 添加配置参数校验和错误处理 fix(translation_usage_service): 调整日成本预警阈值从10元提高到50元 --- .../meeting/meeting_task_service.dart | 124 +++++++++++------- .../services/translation_usage_service.dart | 122 ++++++++++------- 2 files changed, 154 insertions(+), 92 deletions(-) diff --git a/lib/data/services/meeting/meeting_task_service.dart b/lib/data/services/meeting/meeting_task_service.dart index 7ec049fc7..5688628b8 100644 --- a/lib/data/services/meeting/meeting_task_service.dart +++ b/lib/data/services/meeting/meeting_task_service.dart @@ -9,6 +9,7 @@ import '../../../core/utils/synchrodata.dart'; import '../db/sqflite_api.dart'; import '../deapsound_ai_service.dart'; import 'meeting_upload_service.dart'; +import '../../../data/models/appconfig.dart'; ///会议任务服务类,负责管理会议音频的转写任务处理流程,包括任务提交、状态轮询、结果处理和AI分析等功能。 class MeetingTaskService extends GetxService { @@ -23,14 +24,21 @@ class MeetingTaskService extends GetxService { String _userId = ''; + // final String _serviceUrl = 'https://openspeech.bytedance.com/api/v1/auc'; // + // final String _cluster = 'volc_auc_video_flash'; //字节跳动(火山引擎)语音服务配置 - final String _appid = '2316081933'; - final String _token = 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En'; - final String _cluster = 'volc_auc_video_flash'; - final String _cluster1 = 'volc.bigasr.auc'; - final String _serviceUrl = 'https://openspeech.bytedance.com/api/v1/auc'; // - final String _serviceUrl1 = - 'https://openspeech.bytedance.com/api/v3/auc/bigmodel'; //大模型录音文件识别api地址 + // final String _appid = '2316081933'; + // final String _token = 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En'; + // final String _cluster1 = 'volc.bigasr.auc'; + + // final String _serviceUrl1 = + // 'https://openspeech.bytedance.com/api/v3/auc/bigmodel'; //大模型录音文件识别api地址 + late final String _appid; + late final String _token; + late final String _cluster1; + late final String _serviceUrl1; + late final String _modelName; + late final String _modelVersion; Map _headers = {}; Map _headers1 = {}; @@ -43,22 +51,50 @@ class MeetingTaskService extends GetxService { @override void onInit() { super.onInit(); - _headers = { - 'Authorization': 'Bearer; $_token', - 'Content-Type': 'application/json', - }; - _headers1 = { - 'X-Api-App-Key': _appid, - 'X-Api-Access-Key': _token, - 'X-Api-Resource-Id': _cluster1, - 'X-Api-Request-Id': '', - 'X-Api-Sequence': '-1', - }; + + _initialize(); _getAgent(); _getTaskList(); } + Future _initialize() async { + try { + _appid = AppConfig.env('VOLC_OPENSPEECH_APP_ID') ?? ''; + _token = AppConfig.env('VOLC_OPENSPEECH_ACCESS_TOKEN') ?? ''; + _cluster1 = AppConfig.env('VOLC_OPENSPEECH_CLUSTER_BIGMODEL') ?? ''; + _serviceUrl1 = AppConfig.env('VOLC_OPENSPEECH_BASE_URL_BIGMODEL') ?? ''; + _modelName = AppConfig.env('VOLC_OPENSPEECH_MODEL_NAME') ?? 'bigmodel'; + _modelVersion = AppConfig.env('VOLC_OPENSPEECH_MODEL_VERSION') ?? '400'; + + if (_appid.isEmpty || + _token.isEmpty || + _cluster1.isEmpty || + _serviceUrl1.isEmpty || + _modelName.isEmpty || + _modelVersion.isEmpty) { + Logger.error('初始化会议任务服务失败:配置参数为空'); + return false; + } + + _headers = { + 'Authorization': 'Bearer; $_token', + 'Content-Type': 'application/json', + }; + _headers1 = { + 'X-Api-App-Key': _appid, + 'X-Api-Access-Key': _token, + 'X-Api-Resource-Id': _cluster1, + 'X-Api-Request-Id': '', + 'X-Api-Sequence': '-1', + }; + } catch (e) { + Logger.error('初始化会议任务服务失败:$e'); + return false; + } + return true; + } + void _getAgent() async { Map userInfo = _storage.read("user_info"); _userId = userInfo['user']['uid']; @@ -183,8 +219,8 @@ class MeetingTaskService extends GetxService { "format": format, }, "request": { - "model_name": "bigmodel", // 模型名称,目前只有bigmodel - "model_version": "400", // 模型版本,400模型性能略有提升,且ITN有较大优化 + "model_name": _modelName, // 模型名称,目前只有bigmodel + "model_version": _modelVersion, // 模型版本,400模型性能略有提升,且ITN有较大优化 //"enable_itn": true, // 启用文本规范化,默认为true "enable_punc": true, // 启用标点,默认为false //"enable_ddc": false, // 启用语义顺滑,默认为false @@ -199,24 +235,24 @@ class MeetingTaskService extends GetxService { Logger.info("lxm--大模型asr请求体$requestBody1"); - final requestBody = jsonEncode({ - "app": { - "appid": _appid, - "token": _token, - "cluster": _cluster, - }, - "user": { - "uid": _userId, - }, - "audio": { - "format": format, - "url": audiourl, - }, - "additions": { - "language": language, - "with_speaker_info": isSpeaker ? "True" : "False", - } - }); + // final requestBody = jsonEncode({ + // "app": { + // "appid": _appid, + // "token": _token, + // "cluster": _cluster, + // }, + // "user": { + // "uid": _userId, + // }, + // "audio": { + // "format": format, + // "url": audiourl, + // }, + // "additions": { + // "language": language, + // "with_speaker_info": isSpeaker ? "True" : "False", + // } + // }); try { final response = await http.post( @@ -253,12 +289,12 @@ class MeetingTaskService extends GetxService { ///轮询语音转文字任务状态 ///[taskId] 任务ID Future> _queryTask(String taskId) async { - final queryBody = jsonEncode({ - "appid": _appid, - "token": _token, - "id": taskId, - "cluster": _cluster, - }); + // final queryBody = jsonEncode({ + // "appid": _appid, + // "token": _token, + // "id": taskId, + // "cluster": _cluster, + // }); try { _headers1['X-Api-Request-Id'] = taskId; diff --git a/lib/data/services/translation_usage_service.dart b/lib/data/services/translation_usage_service.dart index 90f3db799..5c4c5c72f 100644 --- a/lib/data/services/translation_usage_service.dart +++ b/lib/data/services/translation_usage_service.dart @@ -9,22 +9,23 @@ class TranslationUsageService extends GetxService { static const String _storageKey = 'translation_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 dailyCostThreshold = 50.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; }); // 合并模式使用统计