import 'dart:convert'; import 'dart:math'; import 'package:cryptography/cryptography.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import '../../data/models/appconfig.dart'; /// 生成加密评论页 URL(与 App Comment 服务端 SIGN_SECRET 一致) /// /// [userId]、[packageName]、[appName]、[appVersion] 必填;其余可选。 /// [deviceBrand]/[deviceModel]/[osType]/[osVersion] 用于评论后台按设备维度筛选。 Future buildCommentUrl({ required String userId, String? userName, required String packageName, required String appName, required String appVersion, String lang = 'en', String? deviceBrand, String? deviceModel, String? osType, String? osVersion, }) async { final signSecret = _commentSignSecret(); var commentServer = _commentServerUrl(); if (signSecret.isEmpty || commentServer.isEmpty) { throw Exception( 'COMMENT_SIGN_SECRET / COMMENT_SERVER_URL 未配置:' '请在 getAppConfig 的 env 中下发,或在 .env 中填写(本地调试)', ); } commentServer = commentServer.trim().replaceAll(RegExp(r'/$'), ''); final payload = { 'userId': userId, 'userName': userName ?? userId, 'packageName': packageName, 'appName': appName, 'appVersion': appVersion, 'lang': lang, 'ts': DateTime.now().millisecondsSinceEpoch ~/ 1000, 'nonce': _randomHex(8), }; if (deviceBrand != null && deviceBrand.isNotEmpty) { payload['deviceBrand'] = deviceBrand; } if (deviceModel != null && deviceModel.isNotEmpty) { payload['deviceModel'] = deviceModel; } if (osType != null && osType.isNotEmpty) payload['osType'] = osType; if (osVersion != null && osVersion.isNotEmpty) { payload['osVersion'] = osVersion; } final algo = AesGcm.with256bits(); final key = await algo.newSecretKeyFromBytes(_hexDecode(signSecret)); final nonce = algo.newNonce(); final box = await algo.encrypt( utf8.encode(jsonEncode(payload)), secretKey: key, nonce: nonce, ); // 格式: iv(12 bytes) + tag(16 bytes) + ciphertext final blob = [...nonce, ...box.mac.bytes, ...box.cipherText]; final token = base64Url.encode(blob).replaceAll('=', ''); return '$commentServer/?d=$token'; } /// 优先使用服务端 [getAppConfig] 下发的 `env`,否则回退 `.env`(本地调试)。 String _commentSignSecret() { if (AppConfig.isInitialized()) { final v = AppConfig.env('COMMENT_SIGN_SECRET'); if (v != null && v.isNotEmpty) return v; } return dotenv.env['COMMENT_SIGN_SECRET'] ?? ''; } String _commentServerUrl() { if (AppConfig.isInitialized()) { final v = AppConfig.env('COMMENT_SERVER_URL'); if (v != null && v.isNotEmpty) return v; } return dotenv.env['COMMENT_SERVER_URL'] ?? ''; } List _hexDecode(String hex) => List.generate( hex.length ~/ 2, (i) => int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16), ); String _randomHex(int byteLen) { final r = Random.secure(); return List.generate( byteLen, (_) => r.nextInt(256).toRadixString(16).padLeft(2, '0'), ).join(); }