api_core.dart 12 KB

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