device_manager.dart 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. // lib/utils/device_manager.dart
  2. import 'dart:io';
  3. import 'dart:convert';
  4. import 'package:crypto/crypto.dart';
  5. import 'package:device_info_plus/device_info_plus.dart';
  6. import 'package:get/get.dart';
  7. import 'package:path/path.dart' as path;
  8. import 'package:path_provider/path_provider.dart';
  9. import 'package:uuid/uuid.dart';
  10. import 'package:flutter_secure_storage/flutter_secure_storage.dart';
  11. import '../app/data/sp/ix_sp.dart';
  12. import 'log/logger.dart';
  13. import 'machine_guid_windows.dart';
  14. import 'permission_manager.dart';
  15. class DeviceManager {
  16. static const String TAG = 'DeviceManager';
  17. static final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
  18. static const String _folderPrefix = '.omon_cache_';
  19. static const String _iosDeviceIdKey = 'omon_device_id';
  20. static const String _iosDeviceFolderKey = 'omon_device_folder';
  21. /// 获取设备唯一标识并确保文件夹存在
  22. static Future<String> getDeviceId() async {
  23. try {
  24. // 1. 先从 SharedPreferences 获取
  25. String? deviceId = IXSP.getDeviceId();
  26. // 2. 如果SP中没有,尝试从存储位置获取
  27. if (deviceId == null || deviceId.isEmpty) {
  28. try {
  29. deviceId = await _getDeviceIdFromStorage();
  30. } catch (e) {
  31. log(TAG, 'Error reading from storage: $e');
  32. }
  33. }
  34. // 3. 如果还是没有,生成新的
  35. if (deviceId == null || deviceId.isEmpty) {
  36. try {
  37. deviceId = await _generateNewDeviceId();
  38. } catch (e) {
  39. log(TAG, 'Error generating device ID: $e');
  40. deviceId = _generateFallbackId();
  41. }
  42. }
  43. // 4. 确保保存到存储位置
  44. try {
  45. await _saveDeviceId(deviceId);
  46. } catch (e) {
  47. log(TAG, 'Error saving device ID: $e');
  48. }
  49. // 5. 尝试创建文件夹(仅Android),但不影响返回结果
  50. try {
  51. await _ensureDeviceFolder(deviceId);
  52. } catch (e) {
  53. log(TAG, 'Error creating folder: $e');
  54. // 文件夹创建失败不影响 ID 的使用
  55. }
  56. return deviceId;
  57. } catch (e) {
  58. log(TAG, 'Critical error in getDeviceId: $e');
  59. // 最后的后备方案:生成随机ID
  60. final fallbackId = _generateFallbackId();
  61. try {
  62. await _saveDeviceId(fallbackId);
  63. } catch (e) {
  64. log(TAG, 'Error saving fallback ID: $e');
  65. }
  66. return fallbackId;
  67. }
  68. }
  69. /// 获取缓存
  70. static String getCacheDeviceId() {
  71. return (IXSP.getDeviceId() ?? '')
  72. .replaceAll(RegExp(r'[\u0000-\u001F\u007F]'), '')
  73. .trim();
  74. }
  75. /// 从存储位置获取设备ID
  76. static Future<String?> _getDeviceIdFromStorage() async {
  77. if (Platform.isIOS) {
  78. // iOS 从钥匙串获取
  79. try {
  80. const storage = FlutterSecureStorage();
  81. final deviceId = await storage.read(key: _iosDeviceIdKey);
  82. if (deviceId != null && deviceId.isNotEmpty) {
  83. await IXSP.setDeviceId(deviceId);
  84. return deviceId;
  85. }
  86. } catch (e) {
  87. log(TAG, 'Error reading from iOS keychain: $e');
  88. }
  89. } else if (Platform.isAndroid) {
  90. // Android 从文件夹获取
  91. try {
  92. final downloadsDir = await _getDownloadsDirectory();
  93. if (downloadsDir == null || !await downloadsDir.exists()) {
  94. return null;
  95. }
  96. final entities = await downloadsDir.list().toList();
  97. for (var entity in entities) {
  98. if (entity is Directory) {
  99. final folderName = path.basename(entity.path);
  100. if (folderName.startsWith(_folderPrefix)) {
  101. final uuid = folderName.substring(_folderPrefix.length);
  102. // 验证UUID格式
  103. if (_isValidUuid(uuid)) {
  104. // 找到有效的UUID,保存到SP
  105. await IXSP.setDeviceId(uuid);
  106. return uuid;
  107. }
  108. }
  109. }
  110. }
  111. } catch (e) {
  112. log(TAG, 'Error getting device ID from Android folder: $e');
  113. }
  114. } else if (Platform.isWindows) {
  115. try {
  116. final machineId = getMachineGuid();
  117. await IXSP.setDeviceId(machineId);
  118. return machineId;
  119. } catch (e) {
  120. log(TAG, 'Error getting machine ID from Windows: $e');
  121. }
  122. }
  123. return null;
  124. }
  125. /// 保存设备ID到存储位置
  126. static Future<void> _saveDeviceId(String deviceId) async {
  127. // 保存到 SharedPreferences(跨平台)
  128. await IXSP.setDeviceId(deviceId);
  129. if (Platform.isIOS) {
  130. // iOS 保存到钥匙串
  131. try {
  132. const storage = FlutterSecureStorage();
  133. await storage.write(key: _iosDeviceIdKey, value: deviceId);
  134. } catch (e) {
  135. log(TAG, 'Error saving to iOS keychain: $e');
  136. }
  137. }
  138. }
  139. /// 生成新的设备ID
  140. static Future<String> _generateNewDeviceId() async {
  141. if (GetPlatform.isAndroid) {
  142. final AndroidDeviceInfo androidInfo = await _deviceInfo.androidInfo;
  143. final deviceInfo = {
  144. 'androidId': androidInfo.id,
  145. 'brand': androidInfo.brand,
  146. 'model': androidInfo.model,
  147. 'product': androidInfo.product,
  148. 'hardware': androidInfo.hardware,
  149. 'manufacturer': androidInfo.manufacturer,
  150. 'uuid': const Uuid().v4(),
  151. };
  152. final deviceId = _generateHash(jsonEncode(deviceInfo));
  153. await _saveDeviceId(deviceId);
  154. return deviceId;
  155. } else if (GetPlatform.isIOS) {
  156. final IosDeviceInfo iosInfo = await _deviceInfo.iosInfo;
  157. final deviceInfo = {
  158. 'identifierForVendor': iosInfo.identifierForVendor,
  159. 'model': iosInfo.model,
  160. 'systemVersion': iosInfo.systemVersion,
  161. 'uuid': const Uuid().v4(),
  162. };
  163. final deviceId = _generateHash(jsonEncode(deviceInfo));
  164. await _saveDeviceId(deviceId);
  165. return deviceId;
  166. } else if (Platform.isWindows) {
  167. final deviceId = const Uuid().v4();
  168. await IXSP.setDeviceId(deviceId);
  169. return deviceId;
  170. }
  171. return _generateFallbackId();
  172. }
  173. /// 确保设备文件夹存在
  174. static Future<void> _ensureDeviceFolder(String deviceId) async {
  175. if (Platform.isAndroid) {
  176. try {
  177. final downloadsDir = await _getDownloadsDirectory();
  178. if (downloadsDir == null || !await downloadsDir.exists()) {
  179. throw Exception('Downloads directory not found');
  180. }
  181. final folderName = '$_folderPrefix$deviceId';
  182. final deviceFolder = Directory('${downloadsDir.path}/$folderName');
  183. if (!await deviceFolder.exists()) {
  184. await deviceFolder.create();
  185. // 创建.nomedia文件
  186. final nomedia = File('${deviceFolder.path}/.nomedia');
  187. await nomedia.create();
  188. }
  189. } catch (e) {
  190. log(TAG, 'Error ensuring device folder: $e');
  191. rethrow;
  192. }
  193. }
  194. }
  195. /// 获取下载目录
  196. static Future<Directory?> _getDownloadsDirectory() async {
  197. if (Platform.isAndroid) {
  198. try {
  199. // 首先尝试获取应用的外部存储目录
  200. // 判断是不是Android10以下
  201. final hasPermission =
  202. await PermissionManager.requestStoragePermission();
  203. if (!hasPermission) {
  204. throw Exception('Storage permission denied');
  205. }
  206. final downloadsDir = Directory('/storage/emulated/0/Download');
  207. return downloadsDir;
  208. } catch (e) {
  209. log(TAG, 'Error getting downloads directory: $e');
  210. final downloadsDir = getExternalStorageDirectory();
  211. return downloadsDir;
  212. }
  213. }
  214. return null;
  215. }
  216. /// 生成哈希值
  217. static String _generateHash(String input) {
  218. final bytes = utf8.encode(input);
  219. final digest = md5.convert(bytes);
  220. return digest.toString();
  221. }
  222. /// 生成后备ID
  223. static String _generateFallbackId() {
  224. final random = DateTime.now().millisecondsSinceEpoch.toString();
  225. return _generateHash(random);
  226. }
  227. /// 验证UUID格式
  228. static bool _isValidUuid(String uuid) {
  229. // 验证是否为32位十六进制字符
  230. return RegExp(r'^[a-f0-9]{32}$').hasMatch(uuid);
  231. }
  232. /// 获取设备文件夹路径
  233. static Future<String> getDeviceFolderPath() async {
  234. if (Platform.isIOS) {
  235. // iOS 返回钥匙串标识符
  236. return 'iOS Keychain: $_iosDeviceIdKey';
  237. }
  238. final deviceId = await getDeviceId();
  239. return '/storage/emulated/0/Download/$_folderPrefix$deviceId';
  240. }
  241. /// 清除所有数据
  242. static Future<void> clearAll() async {
  243. try {
  244. if (Platform.isIOS) {
  245. // iOS 清除钥匙串数据
  246. try {
  247. const storage = FlutterSecureStorage();
  248. await storage.delete(key: _iosDeviceIdKey);
  249. await storage.delete(key: _iosDeviceFolderKey);
  250. } catch (e) {
  251. log(TAG, 'Error clearing iOS keychain: $e');
  252. }
  253. } else {
  254. // Android 删除文件夹
  255. try {
  256. final downloadsDir = await _getDownloadsDirectory();
  257. if (downloadsDir != null && await downloadsDir.exists()) {
  258. final entities = await downloadsDir.list().toList();
  259. for (var entity in entities) {
  260. if (entity is Directory) {
  261. final folderName = path.basename(entity.path);
  262. if (folderName.startsWith(_folderPrefix)) {
  263. await entity.delete(recursive: true);
  264. }
  265. }
  266. }
  267. }
  268. } catch (e) {
  269. log(TAG, 'Error clearing Android folders: $e');
  270. }
  271. }
  272. // 清除 SP 中的 UUID
  273. await IXSP.setDeviceId('');
  274. } catch (e) {
  275. log(TAG, 'Error clearing device data: $e');
  276. }
  277. }
  278. }