Browse Source

refactor(meeting_task_service): 重构语音服务配置从硬编码改为环境变量读取

将火山引擎语音服务的配置参数从硬编码改为从AppConfig环境变量读取,提高安全性和可配置性
移除不再使用的旧版API相关代码,简化服务初始化逻辑
添加配置参数校验和错误处理

fix(translation_usage_service): 调整日成本预警阈值从10元提高到50元
newdev_shunjiawei
lxm 11 months ago
parent
commit
17c52e4e0c
  1. 124
      lib/data/services/meeting/meeting_task_service.dart
  2. 122
      lib/data/services/translation_usage_service.dart

124
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<String, String> _headers = {};
Map<String, String> _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<bool> _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<Map<String, dynamic>> _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;

122
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<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 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<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;
});
// 合并模式使用统计

Loading…
Cancel
Save