api_core.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  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/disconnect_domain.dart';
  15. import '../../data/models/launch/user.dart';
  16. import '../../routes/app_pages.dart';
  17. import '../base/base_api.dart';
  18. import 'api_core_paths.dart';
  19. /// 核心API
  20. class ApiCore extends BaseApi {
  21. static final _instance = ApiCore._internal();
  22. factory ApiCore() => _instance;
  23. ApiCore._internal() {
  24. _initDio();
  25. }
  26. /// 初始化Dio
  27. void _initDio() {
  28. // 此处可以设置默认的DIO参数
  29. final options = BaseOptions();
  30. // options.baseUrl = DomainManager.instance.getCurrentDomain(DomainType.api);
  31. options.connectTimeout = const Duration(seconds: 15);
  32. options.receiveTimeout = const Duration(seconds: 15);
  33. options.responseType = ResponseType.bytes;
  34. options.headers['content-type'] = 'application/json';
  35. options.followRedirects = true;
  36. options.maxRedirects = 5;
  37. dio = Dio(options);
  38. // 添加拦截器
  39. _setupInterceptors();
  40. dio.interceptors.add(SimpleApiMonitorInterceptor());
  41. // 添加重试拦截器
  42. // dio.interceptors.add(RetryInterceptor());
  43. }
  44. bool _isRefreshing = false;
  45. void _setupInterceptors() {
  46. dio.interceptors.add(
  47. InterceptorsWrapper(
  48. onRequest: (options, handler) {
  49. // 在请求发送前添加token
  50. final user = IXSP.getUser();
  51. if (user != null) {
  52. options.headers['Authorization'] = user.accessToken;
  53. }
  54. return handler.next(options);
  55. },
  56. onError: (DioException error, handler) async {
  57. IXSP.addDisconnectDomain(
  58. DisconnectDomain(
  59. domain: error.requestOptions.baseUrl,
  60. status: error.response?.statusCode ?? -1,
  61. msg: error.message ?? '',
  62. startTime: DateTime.now().millisecondsSinceEpoch,
  63. endTime: DateTime.now().millisecondsSinceEpoch,
  64. count: 1,
  65. ),
  66. );
  67. if (error.response?.statusCode == Errors.eRegionNotAvailable) {
  68. // 403 eRegionNotAvailable 当前区域不可用
  69. // 这里可以发送事件通知UI层处理
  70. IXSP.setLastIsRegionDisabled(true);
  71. getx.Get.offAll(
  72. () => const CountryRestrictedOverlay(type: RestrictedType.region),
  73. transition: getx.Transition.fadeIn,
  74. );
  75. return handler.reject(error);
  76. } else if (error.response?.statusCode == Errors.eDeviceRestricted) {
  77. // 423 eDeviceRestricted 当前设备被禁用
  78. // 这里可以发送事件通知UI层处理
  79. IXSP.setLastIsDeviceDisabled(true);
  80. getx.Get.offAll(
  81. () => const CountryRestrictedOverlay(type: RestrictedType.device),
  82. transition: getx.Transition.fadeIn,
  83. );
  84. return handler.reject(error);
  85. } else if (error.response?.statusCode == Errors.eUserDisabled) {
  86. // 424 eUserDisabled 当前用户被禁用
  87. // 这里可以发送事件通知UI层处理
  88. IXSP.setLastIsUserDisabled(true);
  89. getx.Get.offAll(
  90. () => CountryRestrictedOverlay(
  91. type: RestrictedType.user,
  92. onPressed: () async {
  93. await IXSP.clearLaunchData();
  94. getx.Get.offAllNamed(Routes.SPLASH);
  95. },
  96. ),
  97. transition: getx.Transition.fadeIn,
  98. );
  99. return handler.reject(error);
  100. } else if (error.response?.statusCode == Errors.eTokenExpired) {
  101. // 401 eTokenExpired token实效
  102. // token实效,刷新token
  103. RequestOptions originalRequest = error.requestOptions;
  104. try {
  105. final token = await _refreshToken(originalRequest);
  106. if (token != null) {
  107. // 使用新token重试原始请求
  108. final response = await _retryRequest(originalRequest, token);
  109. return handler.resolve(response);
  110. }
  111. } catch (e) {
  112. // Token刷新失败,可能需要重新登录
  113. // 这里可以发送事件通知UI层处理登录过期
  114. /// 退出跳转到登录页
  115. await IXSP.clearLaunchData();
  116. getx.Get.offAllNamed(Routes.SPLASH);
  117. return handler.reject(error);
  118. }
  119. }
  120. return handler.next(error);
  121. },
  122. ),
  123. );
  124. }
  125. Future<String?> _refreshToken(RequestOptions originalRequest) async {
  126. if (_isRefreshing) {
  127. return await Future.delayed(const Duration(seconds: 1), () async {
  128. return _refreshToken(originalRequest);
  129. });
  130. }
  131. _isRefreshing = true;
  132. try {
  133. final user = IXSP.getUser();
  134. if (user == null) return null;
  135. final tokenDio = Dio(dio.options);
  136. final response = await tokenDio.post(
  137. ApiCorePaths.refreshToken,
  138. options: Options(
  139. headers: {
  140. 'Authorization': user.refreshToken,
  141. 'X-NL-Product-Code': 'fkey',
  142. 'X-NL-Content-Encoding': 'gzip',
  143. },
  144. ),
  145. data: originalRequest.data,
  146. );
  147. if (response.statusCode == 200) {
  148. final result = getApiResult(decrypt(response.data));
  149. if (result.success) {
  150. final accessToken = result.data['accessToken'];
  151. final refreshToken = result.data['refreshToken'];
  152. final newUser = User(
  153. country: user.country,
  154. userIp: user.userIp,
  155. accessToken: accessToken,
  156. refreshToken: refreshToken,
  157. accountKey: user.accountKey,
  158. accountPassword: user.accountPassword,
  159. createTime: user.createTime,
  160. geographyEea: user.geographyEea,
  161. activated: user.activated,
  162. memberLevel: user.memberLevel,
  163. account: user.account,
  164. );
  165. await IXSP.saveUser(newUser);
  166. _isRefreshing = false;
  167. return accessToken;
  168. }
  169. }
  170. } catch (e) {
  171. _isRefreshing = false;
  172. rethrow;
  173. }
  174. _isRefreshing = false;
  175. return null;
  176. }
  177. Future<Response> _retryRequest(
  178. RequestOptions requestOptions,
  179. String newToken,
  180. ) async {
  181. final options = Options(
  182. method: requestOptions.method,
  183. headers: {...requestOptions.headers, 'Authorization': newToken},
  184. );
  185. return dio.request(
  186. requestOptions.path,
  187. data: requestOptions.data,
  188. queryParameters: requestOptions.queryParameters,
  189. options: options,
  190. );
  191. }
  192. @override
  193. Map<String, dynamic>? getDefaultHeader() {
  194. // 可以设置自定义Header
  195. final headers = {
  196. 'X-NL-Product-Code': 'fkey',
  197. 'X-NL-Content-Encoding': 'gzip',
  198. };
  199. return headers;
  200. }
  201. @override
  202. Map<String, dynamic>? getDefaultQuery() {
  203. // 可以设置自定义Query
  204. return null;
  205. }
  206. @override
  207. dynamic encrypt(dynamic input) {
  208. try {
  209. final data = jsonEncode(input);
  210. // decryptTest(data);
  211. final bytes = utf8.encode(data);
  212. final gzipEncoder = GZipEncoder();
  213. final compressedBytes = gzipEncoder.encode(bytes);
  214. log('ApiCore', '>>: $data');
  215. return Crytpo.encryptBytesUint8(compressedBytes, Keys.aesKey, Keys.aesIv);
  216. } catch (error) {
  217. throw ApiException("-2", "encrypt error: $error");
  218. }
  219. }
  220. @override
  221. dynamic decrypt(dynamic input) {
  222. try {
  223. final decryptedBytes = Crytpo.decryptBytes(
  224. base64Encode(input),
  225. Keys.aesKey,
  226. Keys.aesIv,
  227. );
  228. // 使用GZip解压
  229. final gzipDecoder = GZipDecoder();
  230. final decodeBytes = gzipDecoder.decodeBytes(decryptedBytes);
  231. final data = utf8.decode(decodeBytes);
  232. log('ApiCore', '<<:$data');
  233. return jsonDecode(data);
  234. } catch (error) {
  235. throw ApiException("-2", "decrypt error: $error");
  236. }
  237. }
  238. /// 返回所有信息接口
  239. Future<ApiResult> launch(dynamic data) async {
  240. return post(ApiCorePaths.launch, data: data);
  241. }
  242. /// 游戏列表
  243. Future<ApiResult> getGameList(dynamic data) async {
  244. return post(ApiCorePaths.gameList, data: data);
  245. }
  246. /// 根据本地游戏获取游戏列表
  247. Future<ApiResult> getLocalGameList(dynamic data) async {
  248. return post(ApiCorePaths.localGameList, data: data);
  249. }
  250. /// 注册
  251. Future<ApiResult> register(dynamic data) async {
  252. return post(ApiCorePaths.register, data: data);
  253. }
  254. /// 登录
  255. Future<ApiResult> login(dynamic data) async {
  256. return post(ApiCorePaths.login, data: data);
  257. }
  258. /// 退出登录
  259. Future<ApiResult> logout(dynamic data) async {
  260. return post(ApiCorePaths.logout, data: data);
  261. }
  262. /// 修改密码
  263. Future<ApiResult> changePassword(dynamic data) async {
  264. return post(ApiCorePaths.changePassword, data: data);
  265. }
  266. /// 忘记密码
  267. Future<ApiResult> forgetPassword(dynamic data) async {
  268. return post(ApiCorePaths.forgetPassword, data: data);
  269. }
  270. /// 忘记密码
  271. Future<ApiResult> forgetSendVerificationCode(dynamic data) async {
  272. return post(ApiCorePaths.forgetSendVerificationCode, data: data);
  273. }
  274. /// 发送验证码
  275. Future<ApiResult> sendVerificationCode(dynamic data) async {
  276. return post(ApiCorePaths.sendVerificationCode, data: data);
  277. }
  278. /// 验证验证码
  279. Future<ApiResult> verifyVerificationCode(dynamic data) async {
  280. return post(ApiCorePaths.verifyVerificationCode, data: data);
  281. }
  282. /// 激活发送验证码
  283. Future<ApiResult> activateSendVerificationCode(dynamic data) async {
  284. return post(ApiCorePaths.activateSendVerificationCode, data: data);
  285. }
  286. /// 激活账号
  287. Future<ApiResult> activateAccount(dynamic data) async {
  288. return post(ApiCorePaths.activateAccount, data: data);
  289. }
  290. /// 获取用户信息
  291. Future<ApiResult> getUserConfig(dynamic data) async {
  292. return post(ApiCorePaths.getUserConfig, data: data);
  293. }
  294. /// 获取广告信息
  295. Future<ApiResult> getAdConfig(dynamic data) async {
  296. return post(ApiCorePaths.getAdConfig, data: data);
  297. }
  298. /// 提交游戏
  299. Future<ApiResult> submitGame(dynamic data) async {
  300. return post(ApiCorePaths.submitGame, data: data);
  301. }
  302. /// 获取banner
  303. Future<ApiResult> getBanner(dynamic data) async {
  304. return post(ApiCorePaths.banner, data: data);
  305. }
  306. /// 搜索游戏
  307. Future<ApiResult> searchGame(dynamic data) async {
  308. return post(ApiCorePaths.searchGame, data: data);
  309. }
  310. /// 游戏搜索列表
  311. Future<ApiResult> searchGameList(dynamic data) async {
  312. return post(ApiCorePaths.searchGameList, data: data);
  313. }
  314. /// 获取搜索结果游戏列表
  315. Future<ApiResult> gameListSearch(dynamic data) async {
  316. return post(ApiCorePaths.gameListSearch, data: data);
  317. }
  318. /// 获取支持的第三方登录
  319. Future<ApiResult> providerList(dynamic data) async {
  320. return post(ApiCorePaths.providerList, data: data);
  321. }
  322. /// 第三方登录前获取登录url
  323. Future<ApiResult> providerLogin(dynamic data) async {
  324. return post(ApiCorePaths.providerLogin, data: data);
  325. }
  326. /// 第三方登录检测回调
  327. Future<ApiResult> providerLoginCheck(dynamic data) async {
  328. return post(ApiCorePaths.providerLoginCheck, data: data);
  329. }
  330. /// 第三方登录检测回调
  331. Future<ApiResult> reportActive(dynamic data) async {
  332. return post(ApiCorePaths.reportActive, data: data);
  333. }
  334. /// 上传日志
  335. Future<ApiResult> uploadLogFile(dynamic data, String file) async {
  336. final String encryptedData = encrypt(data);
  337. final fileData = await MultipartFile.fromFile(file);
  338. final formData = FormData.fromMap({
  339. 'data': encryptedData,
  340. 'file': fileData,
  341. });
  342. final options = Options(headers: {'context-type': 'form-data'});
  343. final response = await rawRequest(
  344. RequestMethod.post,
  345. ApiCorePaths.uploadLog,
  346. data: formData,
  347. options: options,
  348. );
  349. final result = decrypt(response);
  350. return getApiResult(result);
  351. }
  352. }