| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- 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<String?> _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<Response> _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<String, dynamic>? getDefaultHeader() {
- // 可以设置自定义Header
- final headers = {
- 'X-NL-Product-Code': 'nomo',
- 'X-NL-Content-Encoding': 'gzip',
- };
- return headers;
- }
- @override
- Map<String, dynamic>? 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<ApiResult> launch(dynamic data) async {
- return post(ApiCorePaths.launch, data: data);
- }
- /// 注册
- Future<ApiResult> register(dynamic data) async {
- return post(ApiCorePaths.register, data: data);
- }
- /// 登录
- Future<ApiResult> login(dynamic data) async {
- return post(ApiCorePaths.login, data: data);
- }
- /// 退出登录
- Future<ApiResult> logout(dynamic data) async {
- return post(ApiCorePaths.logout, data: data);
- }
- /// 删除账户
- Future<ApiResult> deleteAccount(dynamic data) async {
- return post(ApiCorePaths.deleteAccount, data: data);
- }
- /// 修改密码
- Future<ApiResult> changePassword(dynamic data) async {
- return post(ApiCorePaths.changePassword, data: data);
- }
- /// 忘记密码
- Future<ApiResult> forgetPassword(dynamic data) async {
- return post(ApiCorePaths.forgetPassword, data: data);
- }
- /// 忘记密码
- Future<ApiResult> forgetSendVerificationCode(dynamic data) async {
- return post(ApiCorePaths.forgetSendVerificationCode, data: data);
- }
- /// 发送验证码
- Future<ApiResult> sendVerificationCode(dynamic data) async {
- return post(ApiCorePaths.sendVerificationCode, data: data);
- }
- /// 验证验证码
- Future<ApiResult> verifyVerificationCode(dynamic data) async {
- return post(ApiCorePaths.verifyVerificationCode, data: data);
- }
- /// 激活发送验证码
- Future<ApiResult> activateSendVerificationCode(dynamic data) async {
- return post(ApiCorePaths.activateSendVerificationCode, data: data);
- }
- /// 激活账号
- Future<ApiResult> activateAccount(dynamic data) async {
- return post(ApiCorePaths.activateAccount, data: data);
- }
- /// 获取用户信息
- Future<ApiResult> getUserConfig(dynamic data) async {
- return post(ApiCorePaths.getUserConfig, data: data);
- }
- /// 获取banner
- Future<ApiResult> getBanner(dynamic data) async {
- return post(ApiCorePaths.banner, data: data);
- }
- /// 获取支持的第三方登录
- Future<ApiResult> providerList(dynamic data) async {
- return post(ApiCorePaths.providerList, data: data);
- }
- /// 第三方登录前获取登录url
- Future<ApiResult> providerLogin(dynamic data) async {
- return post(ApiCorePaths.providerLogin, data: data);
- }
- /// 第三方登录检测回调
- Future<ApiResult> providerLoginCheck(dynamic data) async {
- return post(ApiCorePaths.providerLoginCheck, data: data);
- }
- /// 获取所有节点
- Future<ApiResult> getLocations(dynamic data) async {
- return post(ApiCorePaths.getLocations, data: data);
- }
- /// 获取套餐列表
- Future<ApiResult> getChannelPlanList(dynamic data) async {
- return post(ApiCorePaths.channelPlanList, data: data);
- }
- /// 购买套餐
- Future<ApiResult> subscribe(dynamic data) async {
- return post(ApiCorePaths.subscribe, data: data);
- }
- /// 恢复购买
- Future<ApiResult> restore(dynamic data) async {
- return post(ApiCorePaths.restore, data: data);
- }
- }
|