api_core.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. import 'dart:convert';
  2. import 'package:archive/archive.dart';
  3. import 'package:dio/dio.dart';
  4. import 'package:get/get.dart' as getx;
  5. import '../../data/sp/ix_sp.dart';
  6. import '../../../utils/developer/ix_developer_tools.dart';
  7. import '../../../utils/log/logger.dart';
  8. import '../../../utils/crypto.dart';
  9. import '../../components/country_restricted_overlay.dart';
  10. import '../../constants/errors.dart';
  11. import '../../constants/keys.dart';
  12. import '../../data/models/api_exception.dart';
  13. import '../../data/models/api_result.dart';
  14. import '../../data/models/launch/user.dart';
  15. import '../../routes/app_pages.dart';
  16. import '../base/base_api.dart';
  17. import 'api_core_paths.dart';
  18. /// 核心API
  19. class ApiCore extends BaseApi {
  20. static final _instance = ApiCore._internal();
  21. factory ApiCore() => _instance;
  22. ApiCore._internal() {
  23. _initDio();
  24. }
  25. /// 初始化Dio
  26. void _initDio() {
  27. // 此处可以设置默认的DIO参数
  28. final options = BaseOptions();
  29. options.connectTimeout = const Duration(seconds: 15);
  30. options.receiveTimeout = const Duration(seconds: 15);
  31. options.responseType = ResponseType.bytes;
  32. options.headers['content-type'] = 'application/json';
  33. options.followRedirects = true;
  34. options.maxRedirects = 5;
  35. dio = Dio(options);
  36. // 添加拦截器
  37. _setupInterceptors();
  38. dio.interceptors.add(SimpleApiMonitorInterceptor());
  39. // 添加重试拦截器
  40. // dio.interceptors.add(RetryInterceptor());
  41. }
  42. bool _isRefreshing = false;
  43. void _setupInterceptors() {
  44. dio.interceptors.add(
  45. InterceptorsWrapper(
  46. onRequest: (options, handler) {
  47. // 在请求发送前添加token
  48. final user = IXSP.getUser();
  49. if (user != null) {
  50. options.headers['Authorization'] = user.accessToken;
  51. }
  52. return handler.next(options);
  53. },
  54. onError: (DioException error, handler) async {
  55. if (error.response?.statusCode == Errors.eRegionNotAvailable) {
  56. // 403 eRegionNotAvailable 当前区域不可用
  57. // 这里可以发送事件通知UI层处理
  58. IXSP.setLastIsRegionDisabled(true);
  59. getx.Get.offAll(
  60. () => const CountryRestrictedOverlay(type: RestrictedType.region),
  61. transition: getx.Transition.fadeIn,
  62. );
  63. return handler.reject(error);
  64. } else if (error.response?.statusCode == Errors.eDeviceRestricted) {
  65. // 423 eDeviceRestricted 当前设备被禁用
  66. // 这里可以发送事件通知UI层处理
  67. IXSP.setLastIsDeviceDisabled(true);
  68. getx.Get.offAll(
  69. () => const CountryRestrictedOverlay(type: RestrictedType.device),
  70. transition: getx.Transition.fadeIn,
  71. );
  72. return handler.reject(error);
  73. } else if (error.response?.statusCode == Errors.eUserDisabled) {
  74. // 424 eUserDisabled 当前用户被禁用
  75. // 这里可以发送事件通知UI层处理
  76. IXSP.setLastIsUserDisabled(true);
  77. getx.Get.offAll(
  78. () => CountryRestrictedOverlay(
  79. type: RestrictedType.user,
  80. onPressed: () async {
  81. await IXSP.clearLaunchData();
  82. getx.Get.offAllNamed(Routes.SPLASH);
  83. },
  84. ),
  85. transition: getx.Transition.fadeIn,
  86. );
  87. return handler.reject(error);
  88. } else if (error.response?.statusCode == Errors.eTokenExpired) {
  89. // 401 eTokenExpired token实效
  90. // token实效,刷新token
  91. RequestOptions originalRequest = error.requestOptions;
  92. try {
  93. final token = await _refreshToken(originalRequest);
  94. if (token != null) {
  95. // 使用新token重试原始请求
  96. final response = await _retryRequest(originalRequest, token);
  97. return handler.resolve(response);
  98. }
  99. } catch (e) {
  100. // Token刷新失败,可能需要重新登录
  101. // 这里可以发送事件通知UI层处理登录过期
  102. /// 退出跳转到登录页
  103. await IXSP.clearLaunchData();
  104. getx.Get.offAllNamed(Routes.SPLASH);
  105. return handler.reject(error);
  106. }
  107. }
  108. return handler.next(error);
  109. },
  110. ),
  111. );
  112. }
  113. Future<String?> _refreshToken(RequestOptions originalRequest) async {
  114. if (_isRefreshing) {
  115. return await Future.delayed(const Duration(seconds: 1), () async {
  116. return _refreshToken(originalRequest);
  117. });
  118. }
  119. _isRefreshing = true;
  120. try {
  121. final user = IXSP.getUser();
  122. if (user == null) return null;
  123. final tokenDio = Dio(dio.options);
  124. final response = await tokenDio.post(
  125. ApiCorePaths.refreshToken,
  126. options: Options(
  127. headers: {
  128. 'Authorization': user.refreshToken,
  129. 'X-NL-Product-Code': 'nomo',
  130. 'X-NL-Content-Encoding': 'gzip',
  131. },
  132. ),
  133. data: originalRequest.data,
  134. );
  135. if (response.statusCode == 200) {
  136. final result = getApiResult(decrypt(response.data));
  137. if (result.success) {
  138. final accessToken = result.data['accessToken'];
  139. final refreshToken = result.data['refreshToken'];
  140. final newUser = User(
  141. country: user.country,
  142. userIp: user.userIp,
  143. accessToken: accessToken,
  144. refreshToken: refreshToken,
  145. accountKey: user.accountKey,
  146. accountPassword: user.accountPassword,
  147. createTime: user.createTime,
  148. geographyEea: user.geographyEea,
  149. activated: user.activated,
  150. memberLevel: user.memberLevel,
  151. account: user.account,
  152. );
  153. await IXSP.saveUser(newUser);
  154. _isRefreshing = false;
  155. return accessToken;
  156. }
  157. }
  158. } catch (e) {
  159. _isRefreshing = false;
  160. rethrow;
  161. }
  162. _isRefreshing = false;
  163. return null;
  164. }
  165. Future<Response> _retryRequest(
  166. RequestOptions requestOptions,
  167. String newToken,
  168. ) async {
  169. final options = Options(
  170. method: requestOptions.method,
  171. headers: {...requestOptions.headers, 'Authorization': newToken},
  172. );
  173. return dio.request(
  174. requestOptions.path,
  175. data: requestOptions.data,
  176. queryParameters: requestOptions.queryParameters,
  177. options: options,
  178. );
  179. }
  180. @override
  181. Map<String, dynamic>? getDefaultHeader() {
  182. // 可以设置自定义Header
  183. final headers = {
  184. 'X-NL-Product-Code': 'nomo',
  185. 'X-NL-Content-Encoding': 'gzip',
  186. };
  187. return headers;
  188. }
  189. @override
  190. Map<String, dynamic>? getDefaultQuery() {
  191. // 可以设置自定义Query
  192. return null;
  193. }
  194. @override
  195. dynamic encrypt(dynamic input) {
  196. try {
  197. final data = jsonEncode(input);
  198. final bytes = utf8.encode(data);
  199. final gzipEncoder = GZipEncoder();
  200. final compressedBytes = gzipEncoder.encode(bytes);
  201. log('ApiCore', '>>: $data');
  202. return Crypto.encryptBytesUint8(compressedBytes, Keys.aesKey, Keys.aesIv);
  203. } catch (error) {
  204. throw ApiException("-2", "encrypt error: $error");
  205. }
  206. }
  207. @override
  208. dynamic decrypt(dynamic input) {
  209. try {
  210. final decryptedBytes = Crypto.decryptBytes(
  211. base64Encode(input),
  212. Keys.aesKey,
  213. Keys.aesIv,
  214. );
  215. // 使用GZip解压
  216. final gzipDecoder = GZipDecoder();
  217. final decodeBytes = gzipDecoder.decodeBytes(decryptedBytes);
  218. final data = utf8.decode(decodeBytes);
  219. log('ApiCore', '<<:$data');
  220. return jsonDecode(data);
  221. } catch (error) {
  222. throw ApiException("-2", "decrypt error: $error");
  223. }
  224. }
  225. /// 返回所有信息接口
  226. Future<ApiResult> launch(dynamic data) async {
  227. return post(ApiCorePaths.launch, data: data);
  228. }
  229. /// 刷新全量数据
  230. Future<ApiResult> refreshLaunch(dynamic data) async {
  231. return post(ApiCorePaths.refreshLaunch, data: data);
  232. }
  233. /// 注册
  234. Future<ApiResult> register(dynamic data) async {
  235. return post(ApiCorePaths.register, data: data);
  236. }
  237. /// 登录
  238. Future<ApiResult> login(dynamic data) async {
  239. return post(ApiCorePaths.login, data: data);
  240. }
  241. /// 退出登录
  242. Future<ApiResult> logout(dynamic data) async {
  243. return post(ApiCorePaths.logout, data: data);
  244. }
  245. /// 删除账户
  246. Future<ApiResult> deleteAccount(dynamic data) async {
  247. return post(ApiCorePaths.deleteAccount, data: data);
  248. }
  249. /// 修改密码
  250. Future<ApiResult> changePassword(dynamic data) async {
  251. return post(ApiCorePaths.changePassword, data: data);
  252. }
  253. /// 忘记密码
  254. Future<ApiResult> forgetPassword(dynamic data) async {
  255. return post(ApiCorePaths.forgetPassword, data: data);
  256. }
  257. /// 忘记密码
  258. Future<ApiResult> forgetSendVerificationCode(dynamic data) async {
  259. return post(ApiCorePaths.forgetSendVerificationCode, data: data);
  260. }
  261. /// 发送验证码
  262. Future<ApiResult> sendVerificationCode(dynamic data) async {
  263. return post(ApiCorePaths.sendVerificationCode, data: data);
  264. }
  265. /// 验证验证码
  266. Future<ApiResult> verifyVerificationCode(dynamic data) async {
  267. return post(ApiCorePaths.verifyVerificationCode, data: data);
  268. }
  269. /// 激活发送验证码
  270. Future<ApiResult> activateSendVerificationCode(dynamic data) async {
  271. return post(ApiCorePaths.activateSendVerificationCode, data: data);
  272. }
  273. /// 激活账号
  274. Future<ApiResult> activateAccount(dynamic data) async {
  275. return post(ApiCorePaths.activateAccount, data: data);
  276. }
  277. /// 获取用户信息
  278. Future<ApiResult> getUserConfig(dynamic data) async {
  279. return post(ApiCorePaths.getUserConfig, data: data);
  280. }
  281. /// 获取banner
  282. Future<ApiResult> getBanner(dynamic data) async {
  283. return post(ApiCorePaths.banner, data: data);
  284. }
  285. /// 获取支持的第三方登录
  286. Future<ApiResult> providerList(dynamic data) async {
  287. return post(ApiCorePaths.providerList, data: data);
  288. }
  289. /// 第三方登录前获取登录url
  290. Future<ApiResult> providerLogin(dynamic data) async {
  291. return post(ApiCorePaths.providerLogin, data: data);
  292. }
  293. /// 第三方登录检测回调
  294. Future<ApiResult> providerLoginCheck(dynamic data) async {
  295. return post(ApiCorePaths.providerLoginCheck, data: data);
  296. }
  297. /// 获取所有节点
  298. Future<ApiResult> getLocations(dynamic data) async {
  299. return post(ApiCorePaths.getLocations, data: data);
  300. }
  301. /// 获取套餐列表
  302. Future<ApiResult> getChannelPlanList(dynamic data) async {
  303. return post(ApiCorePaths.channelPlanList, data: data);
  304. }
  305. /// 购买套餐
  306. Future<ApiResult> subscribe(dynamic data) async {
  307. return post(ApiCorePaths.subscribe, data: data);
  308. }
  309. /// 恢复购买
  310. Future<ApiResult> restore(dynamic data) async {
  311. return post(ApiCorePaths.restore, data: data);
  312. }
  313. }