Initial commit: 91dj android client dev_1.0.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-10-08 11:37:25 +07:00
co-authored by Claude Opus 5.5
commit 97dd6a739a
1262 changed files with 119813 additions and 0 deletions
@@ -0,0 +1,216 @@
import 'device_fingerprint_native.dart'
// ignore: uri_does_not_exist
if (dart.library.html) 'device_fingerprint_web.dart';
import 'dart:convert';
import 'package:crypto/crypto.dart';
/// 设备指纹工具类
///
/// SDK 内部自动调用,业务方无需手动传入。
///
/// **生成规则**
/// 1. 现行外层 `device_fingerprint` 使用 v3 规则:
/// 基于 `sdk_init` payload 内的平台稳定字段,按固定顺序规范化后做
/// SHA-256,输出 64 位小写十六进制。
/// 2. 网络、电量、页面、行为等易变字段不参与 v3 指纹。
/// 3. [getFingerprint] 保留为旧调用方兼容入口;SDK 自动上报链路使用
/// [generateV3FromSdkInitPayload]。
///
/// **平台差异**
/// - Native(Android/iOS/桌面):降级 UUID 存于 `applicationDocumentsDirectory/analytics_fingerprint.txt`
/// - Web:降级 UUID 存于 `localStorage['analytics_fp']`
///
/// **失败兜底**:任何异常均返回空字符串,事件正常上报,仅 `device_fingerprint` 字段为空。
///
/// **注意**:`device_fingerprint` 不参与 `event_id` 的计算。
class DeviceFingerprintUtil {
/// 指纹生成规则版本号。
///
/// 每次生成规则(因子组合、哈希算法)发生变更时,此版本号必须同步递增,
/// 以便服务端区分不同规则下生成的指纹,支持跨版本数据对比分析。
///
/// 版本历史:
/// - `3.0.0`:按 sdk_init 启动快照中的平台稳定字段生成。
static const String kVersion = '3.0.0';
static String? _cache;
/// 获取设备指纹。
///
/// 首次调用时根据传入的设备因子计算并内存缓存,后续直接返回缓存值。
/// 若设备因子发生变化需重新计算,请先调用 [clearCache]。
static Future<String> getFingerprint({
String deviceId = '',
String device = '',
String deviceBrand = '',
String deviceModel = '',
String systemName = '',
String systemVersion = '',
String userAgent = '',
}) async {
if (_cache != null) return _cache!;
_cache = await generateOrLoadFingerprint(
deviceId: deviceId,
device: device,
deviceBrand: deviceBrand,
deviceModel: deviceModel,
systemName: systemName,
systemVersion: systemVersion,
userAgent: userAgent,
);
return _cache!;
}
/// 清除内存缓存(通常仅测试时使用,不会删除本地存储文件)
static void clearCache() {
_cache = null;
}
/// 按产品文档 v3 规则,基于 sdk_init payload 生成外层设备指纹。
///
/// 该方法只使用启动快照内相对稳定的字段;网络、电量、页面、行为等
/// 易变字段不参与。输入缺失时保留字段名并使用文档默认值,确保同一
/// 平台的参与字段顺序稳定。
static String generateV3FromSdkInitPayload(Map<String, dynamic> payload) {
try {
final platform = _fingerprintPlatform(payload);
final values = platform == 'web'
? _webFingerprintValues(payload)
: platform == 'ios'
? _iosFingerprintValues(payload)
: _androidFingerprintValues(payload);
final canonical =
'fp_v3|fingerprint_platform=$platform|${values.join('|')}';
return sha256.convert(utf8.encode(canonical)).toString();
} catch (_) {
return '';
}
}
static String _fingerprintPlatform(Map<String, dynamic> payload) {
if (_string(payload['platform']) == 'web') return 'web';
final osFamily = _string(payload['os_family']);
if (osFamily == 'ios') return 'ios';
return 'android';
}
static List<String> _androidFingerprintValues(Map<String, dynamic> payload) {
final id = _selectFirstId(payload, const [
'oaid',
'aaid',
'android_id',
'guid',
]);
return [
_kvString(payload, 'os_family'),
_kvString(payload, 'brand'),
_kvString(payload, 'manufacturer'),
_kvString(payload, 'model'),
_kvString(payload, 'abi'),
_kvInt(payload, 'screen_width'),
_kvInt(payload, 'screen_height'),
_kvInt(payload, 'screen_dpi'),
_kvInt(payload, 'device_pixel_ratio'),
_kvInt(payload, 'cpu_cores'),
_kvInt(payload, 'device_memory_gb'),
_kvString(payload, 'build_hardware'),
_kvString(payload, 'build_product'),
_kvString(payload, 'build_fingerprint'),
_kvInt(payload, 'has_baseband'),
_kvString(payload, 'language'),
_kvString(payload, 'timezone'),
'id_source=${id.source}',
'id_value=${id.value}',
];
}
static List<String> _iosFingerprintValues(Map<String, dynamic> payload) {
final idfv = _string(payload['idfv']);
return [
_kvString(payload, 'os_family'),
_kvString(payload, 'ios_device_model_raw'),
_kvInt(payload, 'screen_width'),
_kvInt(payload, 'screen_height'),
_kvInt(payload, 'screen_dpi'),
_kvInt(payload, 'device_pixel_ratio'),
_kvInt(payload, 'cpu_cores'),
_kvInt(payload, 'device_memory_gb'),
_kvString(payload, 'language'),
_kvString(payload, 'timezone'),
'id_source=${idfv.isEmpty ? 'none' : 'idfv'}',
'id_value=$idfv',
];
}
static List<String> _webFingerprintValues(Map<String, dynamic> payload) {
return [
_kvString(payload, 'os_family'),
_kvString(payload, 'browser_kernel'),
_kvString(payload, 'browser_platform'),
_kvInt(payload, 'screen_width'),
_kvInt(payload, 'screen_height'),
_kvInt(payload, 'screen_dpi'),
_kvInt(payload, 'device_pixel_ratio'),
_kvInt(payload, 'color_depth'),
_kvInt(payload, 'max_touch_points'),
_kvInt(payload, 'hardware_concurrency'),
_kvString(payload, 'language'),
_kvString(payload, 'timezone'),
_kvInt(payload, 'cookie_enabled'),
_kvInt(payload, 'local_storage_enabled'),
_kvInt(payload, 'session_storage_enabled'),
_kvInt(payload, 'indexeddb_enabled'),
_kvInt(payload, 'service_worker_enabled'),
_kvString(payload, 'canvas_fp'),
_kvString(payload, 'webgl_fp'),
_kvString(payload, 'audio_fp'),
_kvString(payload, 'fonts_hash'),
_kvString(payload, 'plugins_hash'),
_kvString(payload, 'mimes_hash'),
];
}
static _SelectedId _selectFirstId(
Map<String, dynamic> payload,
List<String> keys,
) {
for (final key in keys) {
final value = _string(payload[key]);
if (value.isNotEmpty) return _SelectedId(key, value);
}
return const _SelectedId('none', '');
}
static String _kvString(Map<String, dynamic> payload, String key) =>
'$key=${_string(payload[key])}';
static String _kvInt(Map<String, dynamic> payload, String key) =>
'$key=${_int(payload[key])}';
static String _string(dynamic value) {
if (value == null) return '';
return value.toString().trim().toLowerCase();
}
static int _int(dynamic value) {
if (value is int) return value;
if (value is double && value.isFinite) return value.round();
if (value is String) {
final text = value.trim();
final asInt = int.tryParse(text);
if (asInt != null) return asInt;
final asDouble = double.tryParse(text);
if (asDouble != null && asDouble.isFinite) return asDouble.round();
}
return -1;
}
}
class _SelectedId {
final String source;
final String value;
const _SelectedId(this.source, this.value);
}