493 lines
16 KiB
Dart
493 lines
16 KiB
Dart
import 'dart:io';
|
||
import 'package:device_info_plus/device_info_plus.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:flutter_udid/flutter_udid.dart';
|
||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||
import '../utils/kr_secure_storage.dart';
|
||
import '../utils/kr_log_util.dart';
|
||
import 'package:crypto/crypto.dart';
|
||
import 'dart:convert';
|
||
|
||
/// 设备信息服务
|
||
/// 用于获取设备唯一标识和其他设备信息
|
||
class KRDeviceInfoService {
|
||
static final KRDeviceInfoService _instance = KRDeviceInfoService._internal();
|
||
factory KRDeviceInfoService() => _instance;
|
||
KRDeviceInfoService._internal();
|
||
|
||
final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
|
||
String? _deviceId;
|
||
Map<String, dynamic>? _deviceDetails;
|
||
|
||
// 获取设备唯一标识
|
||
String? get deviceId => _deviceId;
|
||
|
||
// 获取设备详细信息
|
||
Map<String, dynamic>? get deviceDetails => _deviceDetails;
|
||
|
||
/// 初始化设备信息
|
||
Future<void> initialize() async {
|
||
try {
|
||
KRLogUtil.kr_i('📱 开始初始化设备信息', tag: 'KRDeviceInfoService');
|
||
|
||
_deviceId = await _getDeviceId();
|
||
_deviceDetails = await _getDeviceDetails();
|
||
|
||
if (kDebugMode) {
|
||
// print('✅ 设备信息初始化成功');
|
||
// print('📱 设备ID: $_deviceId');
|
||
// print('📱 设备平台: ${getPlatformName()}');
|
||
// print('📱 设备型号: ${getDeviceModel()}');
|
||
// print('📱 系统版本: ${getOSVersion()}');
|
||
// print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
}
|
||
|
||
KRLogUtil.kr_i('✅ 设备信息初始化成功', tag: 'KRDeviceInfoService');
|
||
// KRLogUtil.kr_i('📱 设备ID - $_deviceId', tag: 'KRDeviceInfoService');
|
||
// KRLogUtil.kr_i('📱 设备详情 - $_deviceDetails', tag: 'KRDeviceInfoService');
|
||
} catch (e) {
|
||
if (kDebugMode) {
|
||
print('❌ 设备信息初始化失败: $e');
|
||
}
|
||
KRLogUtil.kr_e('❌ 设备信息初始化失败 - $e', tag: 'KRDeviceInfoService');
|
||
}
|
||
}
|
||
|
||
/// 获取设备唯一标识
|
||
/// 使用多因子组合策略确保唯一性和稳定性
|
||
Future<String> _getDeviceId() async {
|
||
try {
|
||
String identifier;
|
||
|
||
if (Platform.isAndroid) {
|
||
identifier = await _getAndroidDeviceId();
|
||
} else if (Platform.isIOS) {
|
||
identifier = await _getIOSDeviceId();
|
||
} else if (Platform.isMacOS) {
|
||
identifier = await _getMacOSDeviceId();
|
||
} else if (Platform.isWindows) {
|
||
identifier = await _getWindowsDeviceId();
|
||
} else if (Platform.isLinux) {
|
||
identifier = await _getLinuxDeviceId();
|
||
} else {
|
||
// Web或其他平台,使用生成的UUID
|
||
identifier = await _getOrCreateStoredDeviceId();
|
||
}
|
||
|
||
// 如果获取失败,使用存储的或生成新的ID
|
||
if (identifier.isEmpty) {
|
||
identifier = await _getOrCreateStoredDeviceId();
|
||
}
|
||
|
||
return identifier;
|
||
} catch (e) {
|
||
if (kDebugMode) {
|
||
print('❌ 获取设备ID失败: $e');
|
||
}
|
||
KRLogUtil.kr_e('❌ 获取设备ID失败 - $e', tag: 'KRDeviceInfoService');
|
||
// 如果获取失败,返回存储的或生成新的ID
|
||
return await _getOrCreateStoredDeviceId();
|
||
}
|
||
}
|
||
|
||
/// Android设备ID - 多因子组合
|
||
/// 组合: AndroidID + 设备型号 + 主板信息 + 硬件信息
|
||
Future<String> _getAndroidDeviceId() async {
|
||
try {
|
||
final androidInfo = await _deviceInfo.androidInfo;
|
||
|
||
// 优先使用 flutter_udid (封装了多种Android标识获取方式)
|
||
String udid = await FlutterUdid.consistentUdid;
|
||
|
||
// 构建多因子字符串
|
||
final factors = [
|
||
udid,
|
||
androidInfo.id, // Android ID
|
||
androidInfo.board, // 主板
|
||
androidInfo.bootloader, // Bootloader
|
||
androidInfo.brand, // 品牌
|
||
androidInfo.device, // 设备名
|
||
androidInfo.fingerprint, // 系统指纹
|
||
androidInfo.hardware, // 硬件名
|
||
androidInfo.manufacturer, // 制造商
|
||
androidInfo.model, // 型号
|
||
androidInfo.product, // 产品名
|
||
];
|
||
|
||
// 过滤空值并组合
|
||
final combined = factors
|
||
.where((f) => f != null && f.isNotEmpty)
|
||
.join('|');
|
||
|
||
// 生成SHA256哈希
|
||
final bytes = utf8.encode(combined);
|
||
final hash = sha256.convert(bytes);
|
||
|
||
if (kDebugMode) {
|
||
print('📱 Android多因子ID生成 - 因子数: ${factors.where((f) => f != null && f.isNotEmpty).length}');
|
||
}
|
||
KRLogUtil.kr_i('📱 Android多因子ID - $hash', tag: 'KRDeviceInfoService');
|
||
|
||
return hash.toString();
|
||
} catch (e) {
|
||
if (kDebugMode) {
|
||
print('❌ Android设备ID获取失败: $e');
|
||
}
|
||
KRLogUtil.kr_e('❌ Android设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/// iOS设备ID - 优先使用Keychain存储的UUID(卸载重装后不变)
|
||
/// iOS的identifierForVendor在所有同供应商应用卸载后会重置
|
||
/// 使用flutter_secure_storage存储到iOS Keychain,保证即使卸载重装,设备ID也不会变化
|
||
Future<String> _getIOSDeviceId() async {
|
||
try {
|
||
const keychainKey = 'kr_ios_device_persistent_id';
|
||
|
||
final storage = FlutterSecureStorage();
|
||
|
||
// 1. 优先从 Keychain 读取已存储的设备ID
|
||
String? storedId = await storage.read(key: keychainKey);
|
||
|
||
if (storedId != null && storedId.isNotEmpty) {
|
||
if (kDebugMode) {
|
||
print('📱 iOS: 从 Keychain 读取已存储的设备ID');
|
||
}
|
||
KRLogUtil.kr_i('📱 iOS: 使用 Keychain 中的持久化设备ID', tag: 'KRDeviceInfoService');
|
||
return storedId;
|
||
}
|
||
|
||
// 2. 如果 Keychain 中没有,生成新的设备ID
|
||
final iosInfo = await _deviceInfo.iosInfo;
|
||
|
||
String udid = await FlutterUdid.consistentUdid;
|
||
|
||
final factors = [
|
||
udid,
|
||
iosInfo.utsname.machine,
|
||
iosInfo.model,
|
||
iosInfo.systemName,
|
||
];
|
||
|
||
final combined = factors.where((f) => f.isNotEmpty).join('|');
|
||
|
||
final bytes = utf8.encode(combined);
|
||
final hash = sha256.convert(bytes);
|
||
final newDeviceId = hash.toString();
|
||
|
||
// 3. 保存到 Keychain
|
||
await storage.write(key: keychainKey, value: newDeviceId);
|
||
|
||
if (kDebugMode) {
|
||
print('📱 iOS: 生成新设备ID并保存到 Keychain');
|
||
}
|
||
KRLogUtil.kr_i('📱 iOS: 新设备ID已保存到 Keychain', tag: 'KRDeviceInfoService');
|
||
|
||
return newDeviceId;
|
||
} catch (e) {
|
||
if (kDebugMode) {
|
||
print('❌ iOS设备ID获取失败: $e');
|
||
}
|
||
KRLogUtil.kr_e('❌ iOS设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/// macOS设备ID - 使用硬件UUID
|
||
Future<String> _getMacOSDeviceId() async {
|
||
try {
|
||
final macInfo = await _deviceInfo.macOsInfo;
|
||
|
||
// 优先使用 flutter_udid
|
||
String udid = await FlutterUdid.consistentUdid;
|
||
|
||
// 构建多因子字符串
|
||
final factors = [
|
||
udid,
|
||
macInfo.systemGUID ?? '', // 系统GUID (最稳定)
|
||
macInfo.model, // 型号
|
||
macInfo.hostName, // 主机名
|
||
macInfo.arch, // 架构
|
||
macInfo.kernelVersion, // 内核版本
|
||
];
|
||
|
||
final combined = factors
|
||
.where((f) => f.isNotEmpty)
|
||
.join('|');
|
||
|
||
final bytes = utf8.encode(combined);
|
||
final hash = sha256.convert(bytes);
|
||
|
||
if (kDebugMode) {
|
||
print('📱 macOS多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
|
||
}
|
||
KRLogUtil.kr_i('📱 macOS多因子ID - $hash', tag: 'KRDeviceInfoService');
|
||
|
||
return hash.toString();
|
||
} catch (e) {
|
||
if (kDebugMode) {
|
||
print('❌ macOS设备ID获取失败: $e');
|
||
}
|
||
KRLogUtil.kr_e('❌ macOS设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/// Windows设备ID - 使用机器GUID
|
||
/// 🔧 修复:不使用 flutter_udid,因为它会调用 wmic 命令弹出黑窗口
|
||
Future<String> _getWindowsDeviceId() async {
|
||
try {
|
||
final windowsInfo = await _deviceInfo.windowsInfo;
|
||
|
||
// 🔧 修复:不使用 FlutterUdid.consistentUdid
|
||
// 因为它在 Windows 上会调用 "cmd.exe /c wmic csproduct get UUID"
|
||
// 这个调用没有使用 CREATE_NO_WINDOW 标志,会弹出黑色命令行窗口
|
||
|
||
// 直接使用 device_info_plus 提供的信息构建唯一标识
|
||
// windowsInfo.deviceId 已经是一个稳定的设备标识
|
||
|
||
// 构建多因子字符串
|
||
final factors = [
|
||
windowsInfo.deviceId, // 设备ID (最稳定,来自注册表 MachineGuid)
|
||
windowsInfo.computerName, // 计算机名
|
||
windowsInfo.productName, // 产品名
|
||
windowsInfo.numberOfCores.toString(), // CPU核心数
|
||
windowsInfo.systemMemoryInMegabytes.toString(), // 内存大小
|
||
];
|
||
|
||
final combined = factors
|
||
.where((f) => f.isNotEmpty)
|
||
.join('|');
|
||
|
||
final bytes = utf8.encode(combined);
|
||
final hash = sha256.convert(bytes);
|
||
|
||
if (kDebugMode) {
|
||
print('📱 Windows多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
|
||
}
|
||
KRLogUtil.kr_i('📱 Windows多因子ID - $hash', tag: 'KRDeviceInfoService');
|
||
|
||
return hash.toString();
|
||
} catch (e) {
|
||
if (kDebugMode) {
|
||
print('❌ Windows设备ID获取失败: $e');
|
||
}
|
||
KRLogUtil.kr_e('❌ Windows设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/// Linux设备ID - 使用machine-id
|
||
Future<String> _getLinuxDeviceId() async {
|
||
try {
|
||
final linuxInfo = await _deviceInfo.linuxInfo;
|
||
|
||
// 优先使用 flutter_udid
|
||
String udid = await FlutterUdid.consistentUdid;
|
||
|
||
// 构建多因子字符串
|
||
final factors = [
|
||
udid,
|
||
linuxInfo.machineId ?? '', // Machine ID (最稳定)
|
||
linuxInfo.id, // 发行版ID
|
||
linuxInfo.name, // 发行版名称
|
||
linuxInfo.version ?? '', // 版本
|
||
linuxInfo.variant ?? '', // 变体
|
||
];
|
||
|
||
final combined = factors
|
||
.where((f) => f.isNotEmpty)
|
||
.join('|');
|
||
|
||
final bytes = utf8.encode(combined);
|
||
final hash = sha256.convert(bytes);
|
||
|
||
if (kDebugMode) {
|
||
print('📱 Linux多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
|
||
}
|
||
KRLogUtil.kr_i('📱 Linux多因子ID - $hash', tag: 'KRDeviceInfoService');
|
||
|
||
return hash.toString();
|
||
} catch (e) {
|
||
if (kDebugMode) {
|
||
print('❌ Linux设备ID获取失败: $e');
|
||
}
|
||
KRLogUtil.kr_e('❌ Linux设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/// 获取或创建存储的设备ID
|
||
Future<String> _getOrCreateStoredDeviceId() async {
|
||
try {
|
||
const key = 'kr_device_unique_id';
|
||
final storage = KRSecureStorage();
|
||
|
||
String? storedId = await storage.kr_readData(key: key);
|
||
|
||
if (storedId == null || storedId.isEmpty) {
|
||
// 生成新的UUID
|
||
storedId = _generateUniqueId();
|
||
await storage.kr_saveData(key: key, value: storedId);
|
||
if (kDebugMode) {
|
||
print('📱 生成新的设备ID: $storedId');
|
||
}
|
||
KRLogUtil.kr_i('📱 生成新的设备ID - $storedId', tag: 'KRDeviceInfoService');
|
||
} else {
|
||
if (kDebugMode) {
|
||
print('📱 使用存储的设备ID: $storedId');
|
||
}
|
||
KRLogUtil.kr_i('📱 使用存储的设备ID - $storedId', tag: 'KRDeviceInfoService');
|
||
}
|
||
|
||
return storedId;
|
||
} catch (e) {
|
||
if (kDebugMode) {
|
||
print('❌ 获取存储的设备ID失败: $e');
|
||
}
|
||
KRLogUtil.kr_e('❌ 获取存储的设备ID失败 - $e', tag: 'KRDeviceInfoService');
|
||
return _generateUniqueId();
|
||
}
|
||
}
|
||
|
||
/// 生成唯一ID
|
||
String _generateUniqueId() {
|
||
final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
||
final random = DateTime.now().microsecondsSinceEpoch.toString();
|
||
final combined = '$timestamp-$random';
|
||
|
||
// 使用MD5生成唯一标识
|
||
final bytes = utf8.encode(combined);
|
||
final digest = md5.convert(bytes);
|
||
|
||
return digest.toString();
|
||
}
|
||
|
||
/// 获取设备详细信息
|
||
Future<Map<String, dynamic>> _getDeviceDetails() async {
|
||
try {
|
||
if (Platform.isAndroid) {
|
||
final androidInfo = await _deviceInfo.androidInfo;
|
||
return {
|
||
'platform': 'android',
|
||
'device': androidInfo.device,
|
||
'model': androidInfo.model,
|
||
'brand': androidInfo.brand,
|
||
'manufacturer': androidInfo.manufacturer,
|
||
'androidId': androidInfo.id,
|
||
'version': androidInfo.version.release,
|
||
'sdkInt': androidInfo.version.sdkInt,
|
||
};
|
||
} else if (Platform.isIOS) {
|
||
final iosInfo = await _deviceInfo.iosInfo;
|
||
return {
|
||
'platform': 'ios',
|
||
'name': iosInfo.name,
|
||
'model': iosInfo.model,
|
||
'systemName': iosInfo.systemName,
|
||
'systemVersion': iosInfo.systemVersion,
|
||
'identifierForVendor': iosInfo.identifierForVendor,
|
||
'isPhysicalDevice': iosInfo.isPhysicalDevice,
|
||
};
|
||
} else if (Platform.isMacOS) {
|
||
final macInfo = await _deviceInfo.macOsInfo;
|
||
return {
|
||
'platform': 'macos',
|
||
'computerName': macInfo.computerName,
|
||
'model': macInfo.model,
|
||
'hostName': macInfo.hostName,
|
||
'arch': macInfo.arch,
|
||
'systemGUID': macInfo.systemGUID,
|
||
};
|
||
} else if (Platform.isWindows) {
|
||
final windowsInfo = await _deviceInfo.windowsInfo;
|
||
return {
|
||
'platform': 'windows',
|
||
'computerName': windowsInfo.computerName,
|
||
'numberOfCores': windowsInfo.numberOfCores,
|
||
'systemMemoryInMegabytes': windowsInfo.systemMemoryInMegabytes,
|
||
};
|
||
} else if (Platform.isLinux) {
|
||
final linuxInfo = await _deviceInfo.linuxInfo;
|
||
return {
|
||
'platform': 'linux',
|
||
'name': linuxInfo.name,
|
||
'version': linuxInfo.version,
|
||
'id': linuxInfo.id,
|
||
'machineId': linuxInfo.machineId,
|
||
};
|
||
} else if (kIsWeb) {
|
||
final webInfo = await _deviceInfo.webBrowserInfo;
|
||
return {
|
||
'platform': 'web',
|
||
'browserName': webInfo.browserName.toString(),
|
||
'userAgent': webInfo.userAgent,
|
||
'vendor': webInfo.vendor,
|
||
};
|
||
}
|
||
|
||
return {
|
||
'platform': 'unknown',
|
||
};
|
||
} catch (e) {
|
||
if (kDebugMode) {
|
||
print('❌ 获取设备详情失败: $e');
|
||
}
|
||
KRLogUtil.kr_e('❌ 获取设备详情失败 - $e', tag: 'KRDeviceInfoService');
|
||
return {
|
||
'platform': 'unknown',
|
||
'error': e.toString(),
|
||
};
|
||
}
|
||
}
|
||
|
||
/// 获取平台名称
|
||
String getPlatformName() {
|
||
if (Platform.isAndroid) return 'Android';
|
||
if (Platform.isIOS) return 'iOS';
|
||
if (Platform.isMacOS) return 'macOS';
|
||
if (Platform.isWindows) return 'Windows';
|
||
if (Platform.isLinux) return 'Linux';
|
||
if (kIsWeb) return 'Web';
|
||
return 'Unknown';
|
||
}
|
||
|
||
/// 获取设备型号
|
||
String getDeviceModel() {
|
||
if (_deviceDetails == null) return 'Unknown';
|
||
|
||
if (Platform.isAndroid) {
|
||
return '${_deviceDetails!['brand']} ${_deviceDetails!['model']}';
|
||
} else if (Platform.isIOS) {
|
||
return _deviceDetails!['model'] ?? 'Unknown';
|
||
} else if (Platform.isMacOS) {
|
||
return _deviceDetails!['model'] ?? 'Unknown';
|
||
}
|
||
|
||
return 'Unknown';
|
||
}
|
||
|
||
/// 获取操作系统版本
|
||
String getOSVersion() {
|
||
if (_deviceDetails == null) return 'Unknown';
|
||
|
||
if (Platform.isAndroid) {
|
||
return _deviceDetails!['version'] ?? 'Unknown';
|
||
} else if (Platform.isIOS) {
|
||
return _deviceDetails!['systemVersion'] ?? 'Unknown';
|
||
}
|
||
|
||
return 'Unknown';
|
||
}
|
||
|
||
/// 获取User-Agent信息
|
||
String getUserAgent() {
|
||
final platform = getPlatformName();
|
||
final model = getDeviceModel();
|
||
final osVersion = getOSVersion();
|
||
|
||
return 'HiVPN/1.0.0 ($platform; $model; $osVersion) Flutter';
|
||
}
|
||
}
|