device_manager.dart 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  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. }
  73. /// 从存储位置获取设备ID
  74. static Future<String?> _getDeviceIdFromStorage() async {
  75. if (Platform.isIOS) {
  76. // iOS 从钥匙串获取
  77. try {
  78. const storage = FlutterSecureStorage();
  79. final deviceId = await storage.read(key: _iosDeviceIdKey);
  80. if (deviceId != null && deviceId.isNotEmpty) {
  81. await IXSP.setDeviceId(deviceId);
  82. return deviceId;
  83. }
  84. } catch (e) {
  85. log(TAG, 'Error reading from iOS keychain: $e');
  86. }
  87. } else if (Platform.isAndroid) {
  88. // Android 从文件夹获取
  89. try {
  90. final downloadsDir = await _getDownloadsDirectory();
  91. if (downloadsDir == null || !await downloadsDir.exists()) {
  92. return null;
  93. }
  94. final entities = await downloadsDir.list().toList();
  95. for (var entity in entities) {
  96. if (entity is Directory) {
  97. final folderName = path.basename(entity.path);
  98. if (folderName.startsWith(_folderPrefix)) {
  99. final uuid = folderName.substring(_folderPrefix.length);
  100. // 验证UUID格式
  101. if (_isValidUuid(uuid)) {
  102. // 找到有效的UUID,保存到SP
  103. await IXSP.setDeviceId(uuid);
  104. return uuid;
  105. }
  106. }
  107. }
  108. }
  109. } catch (e) {
  110. log(TAG, 'Error getting device ID from Android folder: $e');
  111. }
  112. } else if (Platform.isWindows) {
  113. try {
  114. final machineId = getMachineGuid();
  115. await IXSP.setDeviceId(machineId);
  116. return machineId;
  117. } catch (e) {
  118. log(TAG, 'Error getting machine ID from Windows: $e');
  119. }
  120. }
  121. return null;
  122. }
  123. /// 保存设备ID到存储位置
  124. static Future<void> _saveDeviceId(String deviceId) async {
  125. // 保存到 SharedPreferences(跨平台)
  126. await IXSP.setDeviceId(deviceId);
  127. if (Platform.isIOS) {
  128. // iOS 保存到钥匙串
  129. try {
  130. const storage = FlutterSecureStorage();
  131. await storage.write(key: _iosDeviceIdKey, value: deviceId);
  132. } catch (e) {
  133. log(TAG, 'Error saving to iOS keychain: $e');
  134. }
  135. }
  136. }
  137. /// 生成新的设备ID
  138. static Future<String> _generateNewDeviceId() async {
  139. if (GetPlatform.isAndroid) {
  140. final AndroidDeviceInfo androidInfo = await _deviceInfo.androidInfo;
  141. final deviceInfo = {
  142. 'androidId': androidInfo.id,
  143. 'brand': androidInfo.brand,
  144. 'model': androidInfo.model,
  145. 'product': androidInfo.product,
  146. 'hardware': androidInfo.hardware,
  147. 'manufacturer': androidInfo.manufacturer,
  148. 'uuid': const Uuid().v4(),
  149. };
  150. final deviceId = _generateHash(jsonEncode(deviceInfo));
  151. await _saveDeviceId(deviceId);
  152. return deviceId;
  153. } else if (GetPlatform.isIOS) {
  154. final IosDeviceInfo iosInfo = await _deviceInfo.iosInfo;
  155. final deviceInfo = {
  156. 'identifierForVendor': iosInfo.identifierForVendor,
  157. 'model': iosInfo.model,
  158. 'systemVersion': iosInfo.systemVersion,
  159. 'uuid': const Uuid().v4(),
  160. };
  161. final deviceId = _generateHash(jsonEncode(deviceInfo));
  162. await _saveDeviceId(deviceId);
  163. return deviceId;
  164. } else if (Platform.isWindows) {
  165. final deviceId = const Uuid().v4();
  166. await IXSP.setDeviceId(deviceId);
  167. return deviceId;
  168. }
  169. return _generateFallbackId();
  170. }
  171. /// 确保设备文件夹存在
  172. static Future<void> _ensureDeviceFolder(String deviceId) async {
  173. if (Platform.isAndroid) {
  174. try {
  175. final downloadsDir = await _getDownloadsDirectory();
  176. if (downloadsDir == null || !await downloadsDir.exists()) {
  177. throw Exception('Downloads directory not found');
  178. }
  179. final folderName = '$_folderPrefix$deviceId';
  180. final deviceFolder = Directory('${downloadsDir.path}/$folderName');
  181. if (!await deviceFolder.exists()) {
  182. await deviceFolder.create();
  183. // 创建.nomedia文件
  184. final nomedia = File('${deviceFolder.path}/.nomedia');
  185. await nomedia.create();
  186. }
  187. } catch (e) {
  188. log(TAG, 'Error ensuring device folder: $e');
  189. rethrow;
  190. }
  191. }
  192. }
  193. /// 获取下载目录
  194. static Future<Directory?> _getDownloadsDirectory() async {
  195. if (Platform.isAndroid) {
  196. try {
  197. // 首先尝试获取应用的外部存储目录
  198. // 判断是不是Android10以下
  199. final hasPermission =
  200. await PermissionManager.requestStoragePermission();
  201. if (!hasPermission) {
  202. throw Exception('Storage permission denied');
  203. }
  204. final downloadsDir = Directory('/storage/emulated/0/Download');
  205. return downloadsDir;
  206. } catch (e) {
  207. log(TAG, 'Error getting downloads directory: $e');
  208. final downloadsDir = getExternalStorageDirectory();
  209. return downloadsDir;
  210. }
  211. }
  212. return null;
  213. }
  214. /// 生成哈希值
  215. static String _generateHash(String input) {
  216. final bytes = utf8.encode(input);
  217. final digest = md5.convert(bytes);
  218. return digest.toString();
  219. }
  220. /// 生成后备ID
  221. static String _generateFallbackId() {
  222. final random = DateTime.now().millisecondsSinceEpoch.toString();
  223. return _generateHash(random);
  224. }
  225. /// 验证UUID格式
  226. static bool _isValidUuid(String uuid) {
  227. // 验证是否为32位十六进制字符
  228. return RegExp(r'^[a-f0-9]{32}$').hasMatch(uuid);
  229. }
  230. /// 获取设备文件夹路径
  231. static Future<String> getDeviceFolderPath() async {
  232. if (Platform.isIOS) {
  233. // iOS 返回钥匙串标识符
  234. return 'iOS Keychain: $_iosDeviceIdKey';
  235. }
  236. final deviceId = await getDeviceId();
  237. return '/storage/emulated/0/Download/$_folderPrefix$deviceId';
  238. }
  239. /// 清除所有数据
  240. static Future<void> clearAll() async {
  241. try {
  242. if (Platform.isIOS) {
  243. // iOS 清除钥匙串数据
  244. try {
  245. const storage = FlutterSecureStorage();
  246. await storage.delete(key: _iosDeviceIdKey);
  247. await storage.delete(key: _iosDeviceFolderKey);
  248. } catch (e) {
  249. log(TAG, 'Error clearing iOS keychain: $e');
  250. }
  251. } else {
  252. // Android 删除文件夹
  253. try {
  254. final downloadsDir = await _getDownloadsDirectory();
  255. if (downloadsDir != null && await downloadsDir.exists()) {
  256. final entities = await downloadsDir.list().toList();
  257. for (var entity in entities) {
  258. if (entity is Directory) {
  259. final folderName = path.basename(entity.path);
  260. if (folderName.startsWith(_folderPrefix)) {
  261. await entity.delete(recursive: true);
  262. }
  263. }
  264. }
  265. }
  266. } catch (e) {
  267. log(TAG, 'Error clearing Android folders: $e');
  268. }
  269. }
  270. // 清除 SP 中的 UUID
  271. await IXSP.setDeviceId('');
  272. } catch (e) {
  273. log(TAG, 'Error clearing device data: $e');
  274. }
  275. }
  276. }