feat:参照研听的基础工程

This commit is contained in:
jingyun
2026-06-18 15:02:28 +08:00
commit 364fc837b3
367 changed files with 16495 additions and 0 deletions
@@ -0,0 +1,274 @@
import 'dart:io' show Platform;
import 'package:android_id/android_id.dart';
import 'package:android_oaid/android_oaid.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter_paid/flutter_paid.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:platform_metadata/platform_metadata.dart';
import 'package:uuid/uuid.dart';
import '../storage/storage_keys.dart';
import '../storage/storage_service.dart';
/// 请求公共 + 平台身份 header 来源(完全对齐研值契约)。
/// AuthInterceptor 每个请求注入 commonHeaders() + platformIdentityHeaders()
/// token 另由拦截器注入。空值 header 一律过滤。
///
/// 注:`User-Agent` 这里不表示浏览器 UA,而是沿用后端请求头契约中的平台标识,
/// 值同:iOS/Android/Web/Other。
///
/// 广告/设备标识(OAID/IMEI/MAC/IDFA/Paid/DeviceToken)由各自 SDK/ATT 流程
/// 写入 StorageService,这里读出注入;Android 标识仅在用户同意隐私后采集。
class DeviceHeaderService {
DeviceHeaderService._();
static final DeviceHeaderService to = DeviceHeaderService._();
static const _storage = FlutterSecureStorage();
static const _kDeviceIdKey = 'device_id';
// static const _platformMetadataChannel = MethodChannel('platform_metadata');
// 渠道:优先编译期 --dart-define=UMENG_CHANNEL=xxxAndroid 其次读 manifest
static const _umengChannel = String.fromEnvironment('UMENG_CHANNEL');
String _deviceId = '';
String _version = '';
String _build = '';
String _deviceModel = '';
String _userChannel = '';
String _iosIdfv = '';
String _iosPaid = '';
String _androidId = '';
String _androidOaid = '';
String _androidImei = '';
String _androidMac = '';
Future<void>? _baseInitialization;
Future<void>? _identityInitialization;
Future<void> ensureInitialized() async {
_baseInitialization ??= _initializeBase();
await _baseInitialization;
// Align with tradingagents-flutter-app: Android only initializes device
// information after the user has accepted the privacy policy.
if (!kIsWeb &&
Platform.isAndroid &&
!StorageService.to.getBool(storagePrivacyAgreed)) {
return;
}
_identityInitialization ??= _initializeIdentity();
await _identityInitialization;
}
Future<void> _initializeBase() async {
try {
final info = await PackageInfo.fromPlatform();
_version = info.version;
_build = info.buildNumber;
} catch (_) {}
_userChannel = await _resolveUserChannel();
}
Future<void> _initializeIdentity() async {
_deviceId = await _resolveDeviceId();
_androidOaid = StorageService.to.getString(storageAndroidOaid) ?? '';
_androidImei = StorageService.to.getString(storageAndroidImei) ?? '';
_androidMac = StorageService.to.getString(storageAndroidMac) ?? '';
_androidId = StorageService.to.getString(storageAndroidId) ?? '';
_iosPaid = StorageService.to.getString(storageIosPaid) ?? '';
await _resolvePlatformIdentity();
}
/// X-Device-IdiOS 优先 IDFVper-vendor 稳定),否则 UUIDv4
/// 首次生成后写入 secure_storage,后续复用。
Future<String> _resolveDeviceId() async {
final stored = await _storage.read(key: _kDeviceIdKey);
if (stored != null && stored.isNotEmpty) return stored;
var id = '';
if (!kIsWeb && Platform.isIOS) {
id = await _resolveIdfv();
}
if (id.isEmpty) id = const Uuid().v4();
await _storage.write(key: _kDeviceIdKey, value: id);
return id;
}
Future<String> _resolveIdfv() async {
try {
final idfv = (await DeviceInfoPlugin().iosInfo).identifierForVendor;
return idfv ?? '';
} catch (_) {
return '';
}
}
/// 设备型号 + 平台身份标识。Android 标识需用户同意隐私后才采集。
Future<void> _resolvePlatformIdentity() async {
if (kIsWeb) return;
try {
final plugin = DeviceInfoPlugin();
if (Platform.isAndroid) {
_deviceModel = (await plugin.androidInfo).model;
await _collectAndroidIdIfAgreed();
await _collectAndroidOaidIfAgreed();
await _collectAndroidImeiIfAgreed();
await _collectAndroidMacIfAgreed();
} else if (Platform.isIOS) {
_deviceModel = (await plugin.iosInfo).utsname.machine;
_iosIdfv = await _resolveIdfv();
await _collectIosPaidIfMissing();
}
} catch (_) {}
}
Future<void> _collectAndroidIdIfAgreed() async {
if (_androidId.isNotEmpty) return;
if (!StorageService.to.getBool(storagePrivacyAgreed)) return; // 隐私同意后才采集
try {
_androidId = await const AndroidId().getId() ?? '';
if (_androidId.isNotEmpty) {
await StorageService.to.setString(storageAndroidId, _androidId);
}
} catch (_) {}
}
Future<void> _collectAndroidOaidIfAgreed() async {
if (_androidOaid.isNotEmpty) return;
if (!StorageService.to.getBool(storagePrivacyAgreed)) return;
try {
_androidOaid = await const AndroidOaidId().getOaid() ?? '';
if (_androidOaid.isNotEmpty) {
await StorageService.to.setString(storageAndroidOaid, _androidOaid);
}
} catch (_) {}
}
Future<void> _collectAndroidImeiIfAgreed() async {
if (_androidImei.isNotEmpty) return;
if (!StorageService.to.getBool(storagePrivacyAgreed)) return;
try {
_androidImei = await const AndroidId().getImei() ?? '';
if (_androidImei.isNotEmpty) {
await StorageService.to.setString(storageAndroidImei, _androidImei);
}
} catch (_) {}
}
Future<void> _collectAndroidMacIfAgreed() async {
if (_androidMac.isNotEmpty) return;
if (!StorageService.to.getBool(storagePrivacyAgreed)) return;
try {
_androidMac = await const AndroidId().getMaAddress() ?? '';
if (_androidMac.isNotEmpty) {
await StorageService.to.setString(storageAndroidMac, _androidMac);
}
} catch (_) {}
}
Future<void> _collectIosPaidIfMissing() async {
if (_iosPaid.isNotEmpty) return;
try {
_iosPaid = await FlutterPaid().paid() ?? '';
if (_iosPaid.isNotEmpty) {
await StorageService.to.setString(storageIosPaid, _iosPaid);
}
} catch (_) {}
}
/// 用户同意隐私协议后调用。设备信息由随后调用的 ensureInitialized 补采。
Future<void> markPrivacyAgreed() async {
await StorageService.to.setBool(storagePrivacyAgreed, true);
}
/// 是否需要首启隐私合规确认。**仅 Android**(合规要求:同意前不得采集设备
/// 标识)。由 go_router redirect 调用,未同意则强制跳隐私页。
bool get privacyConsentRequired =>
!kIsWeb &&
Platform.isAndroid &&
!StorageService.to.getBool(storagePrivacyAgreed);
/// iOS 首次启动页:仅在首次安装后展示一次。
bool get iosStartupRequired =>
!kIsWeb &&
Platform.isIOS &&
!StorageService.to.getBool(storageIosStartupSeen);
Future<void> markIosStartupSeen() async {
if (kIsWeb || !Platform.isIOS) return;
await StorageService.to.setBool(storageIosStartupSeen, true);
}
String get deviceId => _deviceId;
// User-Platform:平台标识(替代研值的 User-Agent)。
String _platform() {
if (kIsWeb) return 'Web';
if (Platform.isIOS) return 'iOS';
if (Platform.isAndroid) return 'Android';
return 'Other';
}
Future<String> _resolveUserChannel() async {
if (_umengChannel.isNotEmpty) return _umengChannel;
if (!kIsWeb && Platform.isAndroid) {
try {
final manifestChannel = await PlatformMetadata.getMetaDataValue(
'UMENG_CHANNEL',
);
if (manifestChannel != null && manifestChannel.isNotEmpty) {
return manifestChannel;
}
} catch (_) {}
}
if (kIsWeb) return 'web';
if (Platform.isIOS) return 'ios';
if (Platform.isAndroid) return 'android';
return 'other';
}
String _s(String key) => StorageService.to.getString(key) ?? '';
/// 公共 header(不含 token)。11
Map<String, String> commonHeaders() => _compact({
'X-Device-Id': _deviceId,
'User-Version': _version,
'User-BuildID': _build,
'User-Platform': _platform(),
'User-Channel': _userChannel,
'User-DeviceType': _deviceModel,
'User-DeviceToken': privacyConsentRequired ? '' : _s(storageDeviceToken),
});
/// 平台身份 / 广告标识 header(按平台)。
Map<String, String> platformIdentityHeaders() {
if (kIsWeb) return const {};
if (Platform.isAndroid) {
if (!StorageService.to.getBool(storagePrivacyAgreed)) return const {};
return _compact({
'User-Android-ID': _androidId,
'User-Android-OAID': _androidOaid,
'User-Android-IMEI': _androidImei,
'User-Android-Mac': _androidMac,
});
}
if (Platform.isIOS) {
return _compact({
'User-iOS-IDFV': _iosIdfv,
'User-iOS-IDFA': _s(storageIosIdfa),
'User-iOS-Paid': _iosPaid,
});
}
return const {};
}
}
Map<String, String> _compact(Map<String, String> headers) {
final out = <String, String>{};
headers.forEach((k, v) {
if (v.trim().isNotEmpty) out[k] = v;
});
return out;
}