import 'dart:convert'; import 'package:archive/archive.dart'; import 'package:dio/dio.dart'; import 'package:get/get.dart' as getx; import '../../data/sp/ix_sp.dart'; import '../../../utils/developer/ix_developer_tools.dart'; import '../../../utils/log/logger.dart'; import '../../../utils/crypto.dart'; import '../../components/country_restricted_overlay.dart'; import '../../constants/errors.dart'; import '../../constants/keys.dart'; import '../../data/models/api_exception.dart'; import '../../data/models/api_result.dart'; import '../../data/models/disconnect_domain.dart'; import '../../data/models/launch/user.dart'; import '../../routes/app_pages.dart'; import '../base/base_api.dart'; import 'api_core_paths.dart'; /// 核心API class ApiCore extends BaseApi { static final _instance = ApiCore._internal(); factory ApiCore() => _instance; ApiCore._internal() { _initDio(); } /// 初始化Dio void _initDio() { // 此处可以设置默认的DIO参数 final options = BaseOptions(); options.connectTimeout = const Duration(seconds: 15); options.receiveTimeout = const Duration(seconds: 15); options.responseType = ResponseType.bytes; options.headers['content-type'] = 'application/json'; options.followRedirects = true; options.maxRedirects = 5; dio = Dio(options); // 添加拦截器 _setupInterceptors(); dio.interceptors.add(SimpleApiMonitorInterceptor()); // 添加重试拦截器 // dio.interceptors.add(RetryInterceptor()); } bool _isRefreshing = false; void _setupInterceptors() { dio.interceptors.add( InterceptorsWrapper( onRequest: (options, handler) { // 在请求发送前添加token final user = IXSP.getUser(); if (user != null) { options.headers['Authorization'] = user.accessToken; } return handler.next(options); }, onError: (DioException error, handler) async { IXSP.addDisconnectDomain( DisconnectDomain( domain: error.requestOptions.baseUrl, status: error.response?.statusCode ?? -1, msg: error.message ?? '', startTime: DateTime.now().millisecondsSinceEpoch, endTime: DateTime.now().millisecondsSinceEpoch, count: 1, ), ); if (error.response?.statusCode == Errors.eRegionNotAvailable) { // 403 eRegionNotAvailable 当前区域不可用 // 这里可以发送事件通知UI层处理 IXSP.setLastIsRegionDisabled(true); getx.Get.offAll( () => const CountryRestrictedOverlay(type: RestrictedType.region), transition: getx.Transition.fadeIn, ); return handler.reject(error); } else if (error.response?.statusCode == Errors.eDeviceRestricted) { // 423 eDeviceRestricted 当前设备被禁用 // 这里可以发送事件通知UI层处理 IXSP.setLastIsDeviceDisabled(true); getx.Get.offAll( () => const CountryRestrictedOverlay(type: RestrictedType.device), transition: getx.Transition.fadeIn, ); return handler.reject(error); } else if (error.response?.statusCode == Errors.eUserDisabled) { // 424 eUserDisabled 当前用户被禁用 // 这里可以发送事件通知UI层处理 IXSP.setLastIsUserDisabled(true); getx.Get.offAll( () => CountryRestrictedOverlay( type: RestrictedType.user, onPressed: () async { await IXSP.clearLaunchData(); getx.Get.offAllNamed(Routes.SPLASH); }, ), transition: getx.Transition.fadeIn, ); return handler.reject(error); } else if (error.response?.statusCode == Errors.eTokenExpired) { // 401 eTokenExpired token实效 // token实效,刷新token RequestOptions originalRequest = error.requestOptions; try { final token = await _refreshToken(originalRequest); if (token != null) { // 使用新token重试原始请求 final response = await _retryRequest(originalRequest, token); return handler.resolve(response); } } catch (e) { // Token刷新失败,可能需要重新登录 // 这里可以发送事件通知UI层处理登录过期 /// 退出跳转到登录页 await IXSP.clearLaunchData(); getx.Get.offAllNamed(Routes.SPLASH); return handler.reject(error); } } return handler.next(error); }, ), ); } Future _refreshToken(RequestOptions originalRequest) async { if (_isRefreshing) { return await Future.delayed(const Duration(seconds: 1), () async { return _refreshToken(originalRequest); }); } _isRefreshing = true; try { final user = IXSP.getUser(); if (user == null) return null; final tokenDio = Dio(dio.options); final response = await tokenDio.post( ApiCorePaths.refreshToken, options: Options( headers: { 'Authorization': user.refreshToken, 'X-NL-Product-Code': 'nomo', 'X-NL-Content-Encoding': 'gzip', }, ), data: originalRequest.data, ); if (response.statusCode == 200) { final result = getApiResult(decrypt(response.data)); if (result.success) { final accessToken = result.data['accessToken']; final refreshToken = result.data['refreshToken']; final newUser = User( country: user.country, userIp: user.userIp, accessToken: accessToken, refreshToken: refreshToken, accountKey: user.accountKey, accountPassword: user.accountPassword, createTime: user.createTime, geographyEea: user.geographyEea, activated: user.activated, memberLevel: user.memberLevel, account: user.account, ); await IXSP.saveUser(newUser); _isRefreshing = false; return accessToken; } } } catch (e) { _isRefreshing = false; rethrow; } _isRefreshing = false; return null; } Future _retryRequest( RequestOptions requestOptions, String newToken, ) async { final options = Options( method: requestOptions.method, headers: {...requestOptions.headers, 'Authorization': newToken}, ); return dio.request( requestOptions.path, data: requestOptions.data, queryParameters: requestOptions.queryParameters, options: options, ); } @override Map? getDefaultHeader() { // 可以设置自定义Header final headers = { 'X-NL-Product-Code': 'nomo', 'X-NL-Content-Encoding': 'gzip', }; return headers; } @override Map? getDefaultQuery() { // 可以设置自定义Query return null; } @override dynamic encrypt(dynamic input) { try { final data = jsonEncode(input); final bytes = utf8.encode(data); final gzipEncoder = GZipEncoder(); final compressedBytes = gzipEncoder.encode(bytes); log('ApiCore', '>>: $data'); return Crypto.encryptBytesUint8(compressedBytes, Keys.aesKey, Keys.aesIv); } catch (error) { throw ApiException("-2", "encrypt error: $error"); } } @override dynamic decrypt(dynamic input) { try { final decryptedBytes = Crypto.decryptBytes( base64Encode(input), Keys.aesKey, Keys.aesIv, ); // 使用GZip解压 final gzipDecoder = GZipDecoder(); final decodeBytes = gzipDecoder.decodeBytes(decryptedBytes); final data = utf8.decode(decodeBytes); log('ApiCore', '<<:$data'); return jsonDecode(data); } catch (error) { throw ApiException("-2", "decrypt error: $error"); } } /// 返回所有信息接口 Future launch(dynamic data) async { return post(ApiCorePaths.launch, data: data); } /// 注册 Future register(dynamic data) async { return post(ApiCorePaths.register, data: data); } /// 登录 Future login(dynamic data) async { return post(ApiCorePaths.login, data: data); } /// 退出登录 Future logout(dynamic data) async { return post(ApiCorePaths.logout, data: data); } /// 删除账户 Future deleteAccount(dynamic data) async { return post(ApiCorePaths.deleteAccount, data: data); } /// 修改密码 Future changePassword(dynamic data) async { return post(ApiCorePaths.changePassword, data: data); } /// 忘记密码 Future forgetPassword(dynamic data) async { return post(ApiCorePaths.forgetPassword, data: data); } /// 忘记密码 Future forgetSendVerificationCode(dynamic data) async { return post(ApiCorePaths.forgetSendVerificationCode, data: data); } /// 发送验证码 Future sendVerificationCode(dynamic data) async { return post(ApiCorePaths.sendVerificationCode, data: data); } /// 验证验证码 Future verifyVerificationCode(dynamic data) async { return post(ApiCorePaths.verifyVerificationCode, data: data); } /// 激活发送验证码 Future activateSendVerificationCode(dynamic data) async { return post(ApiCorePaths.activateSendVerificationCode, data: data); } /// 激活账号 Future activateAccount(dynamic data) async { return post(ApiCorePaths.activateAccount, data: data); } /// 获取用户信息 Future getUserConfig(dynamic data) async { return post(ApiCorePaths.getUserConfig, data: data); } /// 获取banner Future getBanner(dynamic data) async { return post(ApiCorePaths.banner, data: data); } /// 获取支持的第三方登录 Future providerList(dynamic data) async { return post(ApiCorePaths.providerList, data: data); } /// 第三方登录前获取登录url Future providerLogin(dynamic data) async { return post(ApiCorePaths.providerLogin, data: data); } /// 第三方登录检测回调 Future providerLoginCheck(dynamic data) async { return post(ApiCorePaths.providerLoginCheck, data: data); } /// 获取所有节点 Future getLocations(dynamic data) async { return post(ApiCorePaths.getLocations, data: data); } /// 获取套餐列表 Future getChannelPlanList(dynamic data) async { return post(ApiCorePaths.channelPlanList, data: data); } /// 购买套餐 Future subscribe(dynamic data) async { return post(ApiCorePaths.subscribe, data: data); } /// 恢复购买 Future restore(dynamic data) async { return post(ApiCorePaths.restore, data: data); } }