157 lines
5.0 KiB
Dart
157 lines
5.0 KiB
Dart
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 'package:shared_preferences/shared_preferences.dart';
|
||
|
||
import '../adjust/adjust_events.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<void>? _loginFuture;
|
||
|
||
/// 登录完成后的 Future,需鉴权接口应 await 此 Future 再请求
|
||
static Future<void> get loginComplete =>
|
||
_loginFuture ?? Future<void>.value();
|
||
|
||
static void _log(String msg) {
|
||
debugPrint('$_tag $msg');
|
||
}
|
||
|
||
/// 获取设备 ID(Android: androidId, iOS: identifierForVendor, Web: fallback)
|
||
static Future<String> _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<void> init() async {
|
||
if (_loginFuture != null) return _loginFuture!;
|
||
final completer = Completer<void>();
|
||
_loginFuture = completer.future;
|
||
|
||
_log('init: 开始快速登录');
|
||
const maxRetries = 3;
|
||
const retryDelay = Duration(seconds: 2);
|
||
|
||
try {
|
||
// 等待网络就绪(浏览器能访问但 App 报错时,多为启动时网络未初始化)
|
||
await Future<void>.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<void>.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<String, dynamic>?;
|
||
final token = data?['reevaluate'] as String?;
|
||
if (token != null && token.isNotEmpty) {
|
||
ApiClient.instance.setUserToken(token);
|
||
_log('init: 已设置 userToken');
|
||
} else {
|
||
_log('init: 响应中无 reevaluate (userToken)');
|
||
}
|
||
final prefs = await SharedPreferences.getInstance();
|
||
final hadLoggedIn = prefs.getBool('adjust_has_logged_in') ?? false;
|
||
if (!hadLoggedIn) {
|
||
AdjustEvents.trackRegister();
|
||
await prefs.setBool('adjust_has_logged_in', true);
|
||
await prefs.setString(
|
||
'adjust_register_date',
|
||
DateTime.now().toIso8601String().substring(0, 10),
|
||
);
|
||
_log('init: 首次登录,已上报 register');
|
||
}
|
||
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);
|
||
}
|
||
final countryCode = data?['navigate'] as String?;
|
||
if (countryCode != null) {
|
||
UserState.setNavigate(countryCode);
|
||
}
|
||
} else {
|
||
_log('init: 登录失败');
|
||
}
|
||
} catch (e, st) {
|
||
_log('init: 异常 $e');
|
||
_log('init: 堆栈 $st');
|
||
} finally {
|
||
if (!completer.isCompleted) completer.complete();
|
||
}
|
||
}
|
||
}
|