import 'dart:async'; import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import '../api/api_client.dart'; import '../api/proxy_client.dart'; import '../api/services/user_api.dart'; import '../referrer/referrer_service.dart'; import '../user/user_state.dart'; /// 认证服务:APP 启动时执行快速登录 class AuthService { AuthService._(); static const _tag = '[AuthService]'; static Future? _loginFuture; /// 登录完成后的 Future,需鉴权接口应 await 此 Future 再请求 static Future get loginComplete => _loginFuture ?? Future.value(); static void _log(String msg) { debugPrint('$_tag $msg'); } /// 获取设备 ID(Android: androidId, iOS: identifierForVendor, Web: fallback) static Future _getDeviceId() async { final deviceInfo = DeviceInfoPlugin(); switch (defaultTargetPlatform) { case TargetPlatform.android: final android = await deviceInfo.androidInfo; return android.id; case TargetPlatform.iOS: final ios = await deviceInfo.iosInfo; return ios.identifierForVendor ?? 'ios-unknown'; default: return 'device-${DateTime.now().millisecondsSinceEpoch}'; } } /// 计算 sign:MD5(deviceId) 大写 32 位 static String _computeSign(String deviceId) { final bytes = utf8.encode(deviceId); final digest = md5.convert(bytes); return digest.toString().toUpperCase(); } /// APP 启动时调用快速登录 /// 启动时网络可能未就绪,会延迟后重试 static Future init() async { if (_loginFuture != null) return _loginFuture!; final completer = Completer(); _loginFuture = completer.future; _log('init: 开始快速登录'); const maxRetries = 3; const retryDelay = Duration(seconds: 2); try { // 等待网络就绪(浏览器能访问但 App 报错时,多为启动时网络未初始化) await Future.delayed(const Duration(seconds: 2)); final deviceId = await _getDeviceId(); _log('init: deviceId=$deviceId'); final sign = _computeSign(deviceId); _log('init: sign=$sign'); final crest = await ReferrerService.getReferrer(); if (crest != null && crest.isNotEmpty) { _log('init: crest(referrer)=$crest'); } ApiResponse? res; for (var i = 0; i < maxRetries; i++) { if (i > 0) { _log('init: 第 ${i + 1} 次重试,等待 ${retryDelay.inSeconds}s...'); await Future.delayed(retryDelay); } try { res = await UserApi.fastLogin( origin: deviceId, resolution: sign, digest: crest ?? '', crest: crest, ); break; } catch (e) { _log('init: 第 ${i + 1} 次请求失败: $e'); if (i == maxRetries - 1) rethrow; } } if (res == null) return; _log('init: 登录结果 code=${res.code} msg=${res.msg}'); if (res.isSuccess && res.data != null) { final data = res.data as Map?; final token = data?['reevaluate'] as String?; if (token != null && token.isNotEmpty) { ApiClient.instance.setUserToken(token); _log('init: 已设置 userToken'); } else { _log('init: 响应中无 reevaluate (userToken)'); } final credits = data?['reveal'] as int?; if (credits != null) { UserState.setCredits(credits); _log('init: 已同步积分 $credits'); } final uid = data?['asset'] as String?; if (uid != null && uid.isNotEmpty) { UserState.setUserId(uid); _log('init: 已设置 userId'); } final avatarUrl = data?['realm'] as String?; if (avatarUrl != null && avatarUrl.isNotEmpty) { UserState.setAvatar(avatarUrl); } final name = data?['terminal'] as String?; if (name != null && name.isNotEmpty) { UserState.setUserName(name); } } else { _log('init: 登录失败'); } } catch (e, st) { _log('init: 异常 $e'); _log('init: 堆栈 $st'); } finally { if (!completer.isCompleted) completer.complete(); } } }