import 'dart:convert'; import 'dart:math'; import 'package:http/http.dart' as http; import 'package:crypto/crypto.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:get/get.dart'; import '../../core/utils/logger.dart'; class VolcanoTranslationService extends GetxService { // API 配置 late String _baseUrl; final String _endpoint = '/'; // 修改为根路径 late final String _accessKey; late final String _secretKey; late final String _region; late final String _service; // 初始化标志 bool _isInitialized = false; // 语言代码映射 final Map _languageCodeMap = { "中文 (Chinese)": "zh", "英语 (English)": "en", "日语 (Japanese)": "ja", "韩语 (Korean)": "ko", "法语 (French)": "fr", "西班牙语 (Spanish)": "es", "葡萄牙语 (Portuguese)": "pt", "意大利语 (Italian)": "it", "德语 (German)": "de", "俄语 (Russian)": "ru", "阿拉伯语 (Arabic)": "ar", "泰语 (Thai)": "th", "越南语 (Vietnamese)": "vi", "印尼语 (Indonesian)": "id", "印地语 (Hindi)": "hi", "土耳其语 (Turkish)": "tr", "波兰语 (Polish)": "pl", "荷兰语 (Dutch)": "nl", "瑞典语 (Swedish)": "sv", "捷克语 (Czech)": "cs", "希腊语 (Greek)": "el", "罗马尼亚语 (Romanian)": "ro", "匈牙利语 (Hungarian)": "hu", "乌克兰语 (Ukrainian)": "uk", "保加利亚语 (Bulgarian)": "bg", "丹麦语 (Danish)": "da", "芬兰语 (Finnish)": "fi", "挪威语 (Norwegian)": "no", "马来语 (Malay)": "ms", }; // 初始化服务 Future initialize({ String? accessKey, String? secretKey, String? region, }) async { try { _accessKey = accessKey ?? dotenv.env['VOLCANO_TRANSLATION_ACCESS_KEY'] ?? ''; _secretKey = secretKey ?? dotenv.env['VOLCANO_TRANSLATION_SECRET_KEY'] ?? ''; _region = region ?? dotenv.env['VOLCANO_TRANSLATION_REGION'] ?? 'cn-north-1'; _service = 'translate'; _baseUrl = 'https://translate.volcengineapi.com'; if (_accessKey.isEmpty || _secretKey.isEmpty) { return false; } _isInitialized = true; return true; } catch (e) { return false; } } // 获取语言代码 String _getLanguageCode(String language) { return _languageCodeMap[language] ?? 'zh'; } // 生成签名 Map _generateSignature(String method, Map requestBody, Map queryParams) { // 1. 准备时间相关参数 final now = DateTime.now().toUtc(); final date = now.toIso8601String().split('T')[0].replaceAll('-', ''); final timestamp = now.toIso8601String().replaceAll(':', '').replaceAll('-', '').split('.')[0] + 'Z'; // 2. 构建规范查询字符串 - 按照参数名ASCII排序 final sortedParams = Map.fromEntries(queryParams.entries.toList()..sort((a, b) => a.key.compareTo(b.key))); final List queryParts = []; sortedParams.forEach((key, value) { // 使用RFC 3986编码,确保特殊字符被正确处理 final encodedKey = Uri.encodeComponent(key).replaceAll('+', '%20'); final encodedValue = Uri.encodeComponent(value).replaceAll('+', '%20'); queryParts.add('$encodedKey=$encodedValue'); }); final canonicalQueryString = queryParts.join('&'); // 3. 创建规范请求 final contentType = 'application/json'; final payloadHash = sha256.convert(utf8.encode(json.encode(requestBody))).toString(); final host = 'translate.volcengineapi.com'; // 确保头部按照字典序排序,且键名小写 // 注意:只包含host和x-date头部,与示例保持一致 final canonicalHeaders = 'host:$host\nx-date:$timestamp\n'; final signedHeaders = 'host;x-date'; // 构建规范请求,格式必须与示例完全一致 final canonicalRequest = '$method\n$_endpoint\n$canonicalQueryString\n$canonicalHeaders\n$signedHeaders\n$payloadHash'; // 4. 创建待签字符串 final algorithm = 'HMAC-SHA256'; final credentialScope = '$date/$_region/$_service/request'; final stringToSign = '$algorithm\n$timestamp\n$credentialScope\n${sha256.convert(utf8.encode(canonicalRequest)).toString()}'; // 5. 计算签名 - 按照文档正确派生密钥 // 注意:这里是关键修复点,确保密钥派生过程完全符合文档 final kSecret = utf8.encode(_secretKey); // 直接使用原始密钥,不添加前缀 // kDate = HMAC(kSecret, Date) var kDate = Hmac(sha256, kSecret).convert(utf8.encode(date)).bytes; // kRegion = HMAC(kDate, Region) var kRegion = Hmac(sha256, kDate).convert(utf8.encode(_region)).bytes; // kService = HMAC(kRegion, Service) var kService = Hmac(sha256, kRegion).convert(utf8.encode(_service)).bytes; // kSigning = HMAC(kService, "request") var kSigning = Hmac(sha256, kService).convert(utf8.encode('request')).bytes; // 使用派生的签名密钥计算最终签名 final signature = Hmac(sha256, kSigning).convert(utf8.encode(stringToSign)).toString(); // 6. 添加签名到请求头 - 格式必须与示例完全一致 final authorization = '$algorithm Credential=$_accessKey/$credentialScope, SignedHeaders=$signedHeaders, Signature=$signature'; // 只返回必要的头部,与示例保持一致 return { 'Content-Type': contentType, 'X-Date': timestamp, 'Authorization': authorization, 'Host': host, }; } // 翻译文本 Future translateText({ required String text, required String sourceLanguage, required String targetLanguage, }) async { if (!_isInitialized) { return null; } try { final sourceCode = _getLanguageCode(sourceLanguage); final targetCode = _getLanguageCode(targetLanguage); // 构建请求体 final requestBody = { 'SourceLanguage': sourceCode, 'TargetLanguage': targetCode, 'TextList': [text], }; // 添加必要的查询参数 - 确保包含所有必需参数 final queryParams = { 'Action': 'TranslateText', 'Version': '2020-06-01', 'Region': _region, 'Service': _service, }; // 生成包含查询参数的签名 final headers = _generateSignature('POST', requestBody, queryParams); // 构建带有查询参数的URL final queryString = Uri(queryParameters: queryParams).query; final url = '$_baseUrl/?$queryString'; final response = await http.post( Uri.parse(url), headers: headers, body: json.encode(requestBody), ); if (response.statusCode == 200) { final responseData = json.decode(response.body); // 尝试从不同的响应结构中提取翻译结果 String? translation; // 1. 标准响应结构 if (responseData['TranslationList'] != null && responseData['TranslationList'] is List && responseData['TranslationList'].isNotEmpty) { final translationItem = responseData['TranslationList'][0]; if (translationItem is Map && translationItem.containsKey('Translation')) { translation = translationItem['Translation']; } } // 2. 检查是否有错误响应 if (translation == null && responseData['ResponseMetadata'] != null && responseData['ResponseMetadata'] is Map && responseData['ResponseMetadata']['Error'] != null) { return null; } // 3. 尝试其他可能的响应结构 if (translation == null) { if (responseData.containsKey('Translation')) { translation = responseData['Translation']; } else if (responseData.containsKey('Result') && responseData['Result'] is Map && responseData['Result'].containsKey('Translation')) { translation = responseData['Result']['Translation']; } else if (responseData.containsKey('Data') && responseData['Data'] is Map) { final data = responseData['Data']; if (data.containsKey('Translation')) { translation = data['Translation']; } else if (data.containsKey('TranslationList') && data['TranslationList'] is List && data['TranslationList'].isNotEmpty) { final item = data['TranslationList'][0]; if (item is Map && item.containsKey('Translation')) { translation = item['Translation']; } } } } // 返回提取到的翻译结果 return translation; } return null; } catch (e) { return null; } } }