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.
101 lines
3.1 KiB
101 lines
3.1 KiB
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<String> 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 = <String, dynamic>{
|
|
'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 = <int>[...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<int> _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();
|
|
}
|
|
|