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
+439
View File
@@ -0,0 +1,439 @@
import 'dart:convert';
import 'package:analytics_sdk/utils/device_fingerprint_util.dart';
import 'package:analytics_sdk/utils/logger.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
/// SDK 全局上下文:持有所有公共字段、提供 event_id 计算。
///
/// 作为 SDK 内部的单一状态来源(SSOT),所有事件构造时从此处读取公共字段。
/// 不对外暴露,接入方无需直接使用。
class SdkContext {
/// SDK 版本号(写死,与 pubspec.yaml 保持一致)
static const String kSdkVersion = '1.4.6';
/// appVersion 公共字段默认值。
/// 接入方未传 `appVersion`、或 release 模式下传入非法值时均使用该值,
/// 保证事件 payload 与 `AnalyticsSdk.getParams()` 返回值保持一致。
static const String kDefaultAppVersion = '1.0.0';
/// 客户端事件签名规则版本。
static const String kEventSignatureVersion = '2.0.0';
static String _eventSignatureHmacKey = '';
static String _eventSignatureVersion = kEventSignatureVersion;
static String? _appId;
static String? _channel;
static String? _uid;
static String? _appVersion;
static String? _device;
static String? _deviceId;
static String? _deviceBrand;
static String? _deviceModel;
static String? _userAgent;
static String? _systemName;
static String? _systemVersion;
static String? _deviceFingerprint;
static String _fingerprintVersion = DeviceFingerprintUtil.kVersion;
static Map<String, dynamic> _environmentFields = _defaultEnvironmentFields();
/// SID 提供者(由 AnalyticsSdk.init() 注入)。
/// 调用时同时返回当前 SID 并记录用户行为,避免 entity 层直接依赖 SessionManager。
static String Function()? sidProvider;
/// 配置公共字段
///
/// 所有参数均为可选,非 null 值才会覆盖现有值。
/// 通常在 [AnalyticsSdk.init] 中调用,也可在 init 之后按需更新。
/// deviceFingerprint / fingerprintVersion 不再支持从外部传入。
static void configure({
String? appId,
String? channel,
String? uid,
String? appVersion,
String? device,
String? deviceId,
String? deviceBrand,
String? deviceModel,
String? userAgent,
String? systemName,
String? systemVersion,
String? deviceFingerprint,
String? fingerprintVersion,
Map<String, dynamic>? environmentFields,
}) {
if (appId != null) _appId = appId;
if (channel != null) _channel = channel;
if (uid != null) _uid = uid;
if (appVersion != null) {
if (RegExp(r'^\d+\.\d+\.\d+$').hasMatch(appVersion)) {
_appVersion = appVersion;
} else if (kDebugMode) {
Logger.analyticsSdk('appVersion 格式不正确,应为 x.y.z,当前值:$appVersion,已忽略',
level: LogLevel.error);
} else {
_appVersion = kDefaultAppVersion;
}
}
if (device != null) _device = device;
if (deviceId != null) _deviceId = deviceId;
if (deviceBrand != null) _deviceBrand = deviceBrand;
if (deviceModel != null) _deviceModel = deviceModel;
if (userAgent != null) _userAgent = userAgent;
if (systemName != null) _systemName = systemName;
if (systemVersion != null) _systemVersion = systemVersion;
if (environmentFields != null) {
_environmentFields = {
..._defaultEnvironmentFields(),
..._normalizeEnvironmentFields(environmentFields),
};
}
}
/// SDK 内部调用:重置自动生成的设备指纹状态。
static void resetGeneratedDeviceFingerprint() {
_deviceFingerprint = null;
_fingerprintVersion = DeviceFingerprintUtil.kVersion;
}
/// SDK 内部调用:写入已生成的设备指纹。
static void configureGeneratedDeviceFingerprint(String deviceFingerprint) {
final normalized = deviceFingerprint.trim();
_deviceFingerprint = normalized.isEmpty ? null : normalized;
_fingerprintVersion = DeviceFingerprintUtil.kVersion;
}
/// 更新用户 ID(登录或切换账号时调用)
static void setUid(String newUid) {
_uid = newUid;
}
/// 更新渠道(渠道变更时调用)
static void setChannel(String channel) {
_channel = channel;
}
/// 增量更新系统、网络、硬件和浏览器环境快照字段。
///
/// 用于网络、电量、充电状态等运行期可能变化的字段刷新。
static void updateEnvironmentFields(Map<String, dynamic> fields) {
if (fields.isEmpty) return;
_environmentFields = {
..._environmentFields,
..._normalizeEnvironmentFields(fields),
};
}
/// 配置客户端事件签名密钥。
///
/// 密钥来自加密配置解密后的 `signKey`,不写入事件、payload、日志或缓存。
static void configureEventSignature({
String? signKey,
String? version,
}) {
if (signKey != null) {
_eventSignatureHmacKey = signKey.trim();
}
final normalizedVersion = version?.trim();
if (normalizedVersion != null && normalizedVersion.isNotEmpty) {
_eventSignatureVersion = normalizedVersion;
}
}
/// 重置所有公共字段为 null(SDK 重新初始化时使用)
///
/// 与 [configure] 不同,此方法会将所有字段清为 null,
/// 而 configure 中传 null 表示"不修改"。
static void reset() {
_appId = null;
_channel = null;
_uid = null;
_appVersion = null;
_device = null;
_deviceId = null;
_deviceBrand = null;
_deviceModel = null;
_userAgent = null;
_systemName = null;
_systemVersion = null;
_deviceFingerprint = null;
_fingerprintVersion = DeviceFingerprintUtil.kVersion;
_environmentFields = _defaultEnvironmentFields();
_eventSignatureHmacKey = '';
_eventSignatureVersion = kEventSignatureVersion;
sidProvider = null;
}
// ──────── Getters(调试 / 外部复用) ────────────────────────────────────
static String? get appId => _appId;
static String? get channel => _channel;
static String? get uid => _uid;
static String? get appVersion => _appVersion;
static String? get deviceId => _deviceId;
static String? get deviceBrand => _deviceBrand;
static String? get deviceModel => _deviceModel;
static String get deviceFingerprint => _deviceFingerprint ?? '';
static String get fingerprintVersion => _fingerprintVersion;
static Map<String, dynamic> get environmentFields =>
Map<String, dynamic>.from(_environmentFields);
/// 获取设备公共字段(供产品方在服务端上报事件时复用)
///
/// 返回的 Map 包含:sid、device、device_id、device_brand、device_model、
/// user_agent、system_name、system_version、device_fingerprint、fp_version。
/// 未配置的字段返回空字符串。
static Map<String, String> getDeviceCommonFields() {
String sid = '';
try {
sid = sidProvider?.call() ?? '';
} catch (_) {
// 未初始化或 SID 获取异常时保持为空,确保该读取 API 不抛异常。
}
return {
'sid': sid,
'device': _device ?? '',
'device_id': _deviceId ?? '',
'device_brand': _deviceBrand ?? '',
'device_model': _deviceModel ?? '',
'user_agent': _userAgent ?? '',
'system_name': _systemName ?? '',
'system_version': _systemVersion ?? '',
'device_fingerprint': _deviceFingerprint ?? '',
'fp_version': _fingerprintVersion,
};
}
/// 获取系统、网络、硬件和浏览器环境快照字段。
///
/// 仅供 SDK 内部构建 `sdk_init` 启动快照和少量汇总事件使用,普通事件公共字段
/// 不再重复携带这些环境快照。
static Map<String, dynamic> getEnvironmentSnapshotFields() {
return Map<String, dynamic>.from(_environmentFields);
}
/// 给 SDK 客户端事件补充外层签名字段。
///
/// 历史版本曾把轻量环境签名写入 payload;这里会清理这些保留字段,
/// 避免同一事件同时出现新旧两套签名。
static Map<String, dynamic> attachDeviceEnvIfNeeded(
Map<String, dynamic> event,
) {
final eventName = event['event']?.toString() ?? '';
final isServerOnlyEvent = _serverOnlyEvents.contains(eventName);
final result = Map<String, dynamic>.from(event);
final existingPayload = result['payload'];
final existingDeviceEnv = result.remove('device_env');
if (existingPayload is Map || existingPayload == null) {
final payload = <String, dynamic>{
if (existingPayload is Map)
...Map<String, dynamic>.from(existingPayload),
if (existingDeviceEnv is Map)
...Map<String, dynamic>.from(existingDeviceEnv),
};
payload.removeWhere(
(key, _) => _reservedPayloadSignatureFields.contains(key));
result['payload'] = payload;
}
if (!isServerOnlyEvent && _eventSignatureHmacKey.isNotEmpty) {
final eventForSignature = Map<String, dynamic>.from(result);
result['event_sig_version'] = _eventSignatureVersion;
result['event_sig'] = _eventSignature(eventForSignature);
}
return result;
}
/// 生成公共字段 Map
///
/// 包含所有客户端事件上报所需的公共字段:
/// event、channel、app_id、uid、sid、client_ts、
/// device、device_id、device_brand、device_model、
/// user_agent、system_name、system_version、
/// device_fingerprint、fp_version、sdk_version、app_version。
///
/// 本方法永远不会抛出异常。
static Map<String, dynamic> generateCommonFields(String event) {
final now = DateTime.now();
final clientTs = now.millisecondsSinceEpoch ~/ 1000;
String sid;
try {
sid = sidProvider?.call() ?? 'fallback_${now.millisecondsSinceEpoch}';
} catch (_) {
sid = 'fallback_${now.millisecondsSinceEpoch}';
}
return {
'event': event,
'channel': _channel ?? '',
'app_id': _appId ?? '',
'uid': _uid ?? '',
'sid': sid,
'client_ts': clientTs,
'device': _device ?? '',
'device_id': _deviceId ?? '',
'device_brand': _deviceBrand ?? '',
'device_model': _deviceModel ?? '',
'user_agent': _userAgent ?? '',
'system_name': _systemName ?? '',
'system_version': _systemVersion ?? '',
'device_fingerprint': _deviceFingerprint ?? '',
'fp_version': _fingerprintVersion,
'sdk_version': kSdkVersion,
'app_version': _appVersion ?? kDefaultAppVersion,
};
}
/// 根据字段列表生成 event_id
///
/// 计算规则:
/// 1. 对每个字段值单独做 MD5
/// 2. 将所有 MD5 用 `|` 拼接
/// 3. 对拼接结果再做一次 MD5,作为最终 event_id
///
/// **注意**:`device_fingerprint` 不参与 event_id 计算。
/// 本方法永远不会抛出异常,失败时返回基于时间戳的备选 ID。
static String generateEventId(List<String> fields) {
try {
final fieldDigests = <String>[];
for (final value in fields) {
final v = value;
final bytes = utf8.encode(v);
final digest = md5.convert(bytes);
fieldDigests.add(digest.toString());
}
final joined = fieldDigests.join('|');
final finalDigest = md5.convert(utf8.encode(joined));
return finalDigest.toString();
} catch (e) {
try {
final content = fields.join('|');
return 'h${(content.hashCode & 0x7FFFFFFF).toRadixString(16)}';
} catch (_) {
return 't${DateTime.now().millisecondsSinceEpoch}';
}
}
}
static Map<String, dynamic> _defaultEnvironmentFields() => {
'language': '',
'timezone': '',
'package_name': '',
'screen_width': -1,
'screen_height': -1,
'screen_dpi': -1,
'device_pixel_ratio': -1,
'orientation': '',
'screen_brightness': -1,
'volume': -1,
'is_screen_on': -1,
'network_type': 'unknown',
'battery_level': -1,
'is_charging': -1,
'battery_state': 'unknown',
'cpu_cores': -1,
'device_memory_gb': -1,
'browser_name': '',
'browser_version': '',
'cookie_enabled': -1,
'local_storage_enabled': -1,
'touch_support': -1,
'max_touch_points': -1,
'color_depth': -1,
'is_pwa': -1,
};
static Map<String, dynamic> _normalizeEnvironmentFields(
Map<String, dynamic> fields,
) {
final normalized = Map<String, dynamic>.from(fields);
if (normalized.containsKey('device_pixel_ratio')) {
normalized['device_pixel_ratio'] =
_normalizeDevicePixelRatio(normalized['device_pixel_ratio']);
}
return normalized;
}
static int _normalizeDevicePixelRatio(dynamic value) {
if (value == null) return -1;
final ratio =
value is num ? value.toDouble() : double.tryParse(value.toString());
if (ratio == null || !ratio.isFinite) return -1;
if (ratio > 10) return ratio.round();
return (ratio * 100).round();
}
static const Set<String> _serverOnlyEvents = {
'user_register',
'user_login',
'realtime_online',
'order_created',
'order_paid',
'coin_consume',
'video_status_change',
};
static const Set<String> _reservedPayloadSignatureFields = {
'sdk_type',
'client_type',
'id_type',
'event_sig',
'event_sig_version',
'security_key_version',
'sign_algorithm',
'sign_ts',
};
static String _eventSignature(
Map<String, dynamic> event,
) {
try {
if (_eventSignatureHmacKey.isEmpty) return '';
final factors = [
event['event']?.toString() ?? '',
event['event_id']?.toString() ?? '',
event['app_id']?.toString() ?? '',
event['client_ts']?.toString() ?? '',
event['device_fingerprint']?.toString() ?? '',
];
final signatureInput = factors.join('|');
final signature = Hmac(sha256, utf8.encode(_eventSignatureHmacKey))
.convert(utf8.encode(signatureInput))
.toString();
Logger.analyticsSdk(
'event_sig input="$signatureInput" '
'event_sig=$signature '
'event_sig_version=$_eventSignatureVersion',
);
return signature;
} catch (_) {
return '';
}
}
/// 从公共字段 Map 和业务特有字段列表生成 event_id。
///
/// 本方法永远不会抛出异常。
static String generateEventIdFromCommonFields(
Map<String, dynamic> commonFields, List<String> extraFields) {
return generateEventId([
commonFields['event'] as String? ?? '',
commonFields['app_id'] as String? ?? '',
commonFields['channel'] as String? ?? '',
commonFields['uid'] as String? ?? '',
commonFields['device'] as String? ?? '',
commonFields['device_id'] as String? ?? '',
commonFields['device_brand'] as String? ?? '',
commonFields['device_model'] as String? ?? '',
(commonFields['client_ts'] ?? '').toString(),
commonFields['sid'] as String? ?? '',
...extraFields,
]);
}
}