基础库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

609 lines
20 KiB

  1. import 'dart:collection';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'dart:ui';
  5. import 'package:device_info/device_info.dart';
  6. import 'package:dio/adapter.dart';
  7. import 'package:dio/dio.dart';
  8. import 'package:flutter/material.dart';
  9. import 'package:fluttertoast/fluttertoast.dart';
  10. import 'package:imei_plugin/imei_plugin.dart';
  11. import 'package:package_info/package_info.dart';
  12. import 'package:provider/provider.dart';
  13. import 'package:zhiying_comm/util/empty_util.dart';
  14. import 'package:zhiying_comm/zhiying_comm.dart';
  15. import 'encode_util.dart';
  16. import 'global_config.dart';
  17. import 'shared_prefe_util.dart';
  18. import 'time_util.dart';
  19. typedef OnSuccess = dynamic Function(dynamic data);
  20. typedef OnError = dynamic Function(String e);
  21. typedef OnCache = dynamic Function(dynamic data);
  22. final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
  23. enum NetMethod {
  24. GET,
  25. POST,
  26. PUT,
  27. DELETE,
  28. OPTIONS,
  29. PATCH,
  30. UPDATE,
  31. HEAD,
  32. }
  33. class NetUtil {
  34. Dio _dio;
  35. NetUtil._();
  36. static NetUtil _instance;
  37. static NetUtil getInstance() {
  38. if (_instance == null) {
  39. _instance = NetUtil._();
  40. }
  41. return _instance;
  42. }
  43. get dio async {
  44. if (_dio == null) {
  45. var setting = await NativeUtil.getSetting();
  46. String domain = setting['domain']; //'http://www.hairuyi.com/';
  47. _config(domain, proxyUrl: ''); // 192.168.0.66:8866
  48. }
  49. return _dio;
  50. }
  51. /// 配置网络请求,基础地址,代理地址,如:192.168.0.123:8888(代理地址生产环境无效)
  52. /// apiVersion 接口版本
  53. void _config(String baseUrl, {String proxyUrl}) {
  54. _dio = Dio(BaseOptions(
  55. method: "post",
  56. baseUrl: baseUrl,
  57. connectTimeout: 15000,
  58. receiveTimeout: 15000,
  59. contentType: Headers.jsonContentType,
  60. followRedirects: true,
  61. // headers: {'device': 'wx_applet', 'Platform': 'wx_applet'},
  62. validateStatus: (_) {
  63. return true;
  64. }));
  65. _dio.interceptors.add(_NetInterceptors());
  66. // _dio.interceptors.add(LogInterceptor());
  67. const bool inProduction = const bool.fromEnvironment("dart.vm.product");
  68. if (proxyUrl != null && proxyUrl != '' && !inProduction) {
  69. _setProxy(proxyUrl);
  70. }
  71. }
  72. /// 设置代理
  73. void _setProxy(String proxyUrl) {
  74. (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (HttpClient client) {
  75. client.findProxy = (uri) {
  76. return "PROXY $proxyUrl";
  77. };
  78. client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
  79. };
  80. }
  81. static Map<String, dynamic> headParam;
  82. /// 同步请求
  83. static Future<dynamic> post(String path,
  84. {Map<String, dynamic> params, Map<String, dynamic> queryParameters, NetMethod method = NetMethod.POST, bool cache = false, bool showToast = true}) async {
  85. var paramsData = {'postData': params ?? {}, 'queryParameters': queryParameters ?? {}};
  86. // 根据请求参数,获取缓存的Key
  87. String cacheKey = getRequestParamsCachedKey(paramsData, path);
  88. // 参数签名 TODO 加密?
  89. // post请求的参数
  90. Map<String, dynamic> bodyParams = params;
  91. ///只拿一次头部数据
  92. if (NetUtil.headParam == null) {
  93. NetUtil.headParam = await _getMustHeadParams();
  94. }
  95. // token 读取SP缓存中的用户token
  96. String token = await SharedPreferencesUtil.getStringValue(GlobalConfig.SHARED_KEY_TOKEN);
  97. if (EmptyUtil.isEmpty(token) && !bool.fromEnvironment("dart.vm.product")) {
  98. try {
  99. Map setting = await NativeUtil.getSetting();
  100. token = setting['token'];
  101. } catch (e, s) {
  102. print(s);
  103. print(e);
  104. }
  105. }
  106. if (!EmptyUtil.isEmpty(token)) {
  107. // params['token'] = token;
  108. NetUtil.headParam['Authorization'] = 'Bearer ' + token;
  109. }
  110. //获取imei
  111. if (!NetUtil.headParam.containsKey('imei') && path.contains("/api/v1/app/push_dialog")) {
  112. getImei();
  113. }
  114. if (NetUtil.headParam['token'] == null) {}
  115. // 请求头参数
  116. Map<String, dynamic> headParam = NetUtil.headParam;
  117. Response response;
  118. try {
  119. Dio dio = await NetUtil
  120. .getInstance()
  121. .dio;
  122. response = await dio.request(
  123. path,
  124. data: !EmptyUtil.isEmpty(bodyParams) ? bodyParams : null,
  125. options: Options(method: enumToString(method), headers: headParam),
  126. queryParameters: !EmptyUtil.isEmpty(queryParameters) ? queryParameters : null,
  127. );
  128. } on DioError catch (e) {
  129. _formatError(e);
  130. }
  131. try {
  132. var result = response.data is Map ? response.data : jsonDecode(response.data);
  133. // TODO 解密?
  134. if (isSuccess(result)) {
  135. // 缓存成功的数据
  136. if (cache) {
  137. _setCallBackCacheData(cacheKey, response.data is Map ? jsonEncode(response.data) : response.data);
  138. }
  139. return result;
  140. // 缓存返回的数据
  141. } else {
  142. Logger.error('error: ' + result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]);
  143. ///
  144. /// 401000 验证用户失败(不提示Toast)
  145. /// 404004 没有找到对应模块(跳空页面,不提示toast)
  146. ///
  147. if (result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] != 401000 && result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] != 404004) {
  148. if (showToast) {
  149. Fluttertoast.showToast(
  150. msg: result[GlobalConfig.HTTP_RESPONSE_KEY_MSG],
  151. toastLength: Toast.LENGTH_SHORT,
  152. gravity: ToastGravity.BOTTOM,
  153. );
  154. }
  155. }
  156. ///
  157. /// 401003 用户被逼下线
  158. /// 退出登陆(清理token等用户信息)
  159. ///
  160. if (result[GlobalConfig.HTTP_RESPONSE_KEY_CODE]?.toString() == '401003') {
  161. try {
  162. Future.delayed(Duration(seconds: 0)).then((onValue) {
  163. BuildContext context = navigatorKey.currentState.overlay.context;
  164. Provider.of<UserInfoNotifier>(context, listen: false).unLogin();
  165. });
  166. } catch (e, s) {
  167. Logger.error(e, s);
  168. }
  169. }
  170. ///非法用户
  171. if (result[GlobalConfig.HTTP_RESPONSE_KEY_CODE]?.toString() == '401000') {
  172. try {
  173. Future.delayed(Duration(seconds: 0)).then((onValue) async {
  174. String token = await SharedPreferencesUtil.getStringValue(GlobalConfig.SHARED_KEY_TOKEN);
  175. if (token != null && token.length > 2) {
  176. BuildContext context = navigatorKey.currentState.overlay.context;
  177. Provider.of<UserInfoNotifier>(context, listen: false).unLogin();
  178. }
  179. });
  180. } catch (e, s) {
  181. Logger.error(e, s);
  182. }
  183. }
  184. ///
  185. /// 403028 账号被冻结
  186. /// 403029 账号被禁用
  187. /// 提示并且退出登录
  188. ///
  189. if (result[GlobalConfig.HTTP_RESPONSE_KEY_CODE]?.toString() == '403028' || result[GlobalConfig.HTTP_RESPONSE_KEY_CODE]?.toString() == '403029') {
  190. try {
  191. // 提示
  192. Fluttertoast.showToast(
  193. msg: result[GlobalConfig.HTTP_RESPONSE_KEY_MSG],
  194. toastLength: Toast.LENGTH_SHORT,
  195. gravity: ToastGravity.BOTTOM,
  196. );
  197. // 退出登录
  198. Future.delayed(Duration(seconds: 0)).then((onValue) {
  199. BuildContext context = navigatorKey.currentState.overlay.context;
  200. Provider.of<UserInfoNotifier>(context, listen: false).unLogin();
  201. });
  202. } catch (e, s) {
  203. Logger.error(e, s);
  204. }
  205. }
  206. return result;
  207. }
  208. } catch (e) {
  209. return null;
  210. }
  211. }
  212. /// 异步请求
  213. static void request(String path,
  214. {NetMethod method = NetMethod.GET,
  215. Map<String, dynamic> params,
  216. Map<String, dynamic> queryParameters,
  217. OnSuccess onSuccess,
  218. OnError onError,
  219. OnCache onCache,
  220. bool showToast = true}) async {
  221. var paramsData = {'postData': params ?? {}, 'queryParameters': queryParameters ?? {}};
  222. // 根据请求参数,获取缓存的Key
  223. String cacheKey = getRequestParamsCachedKey(paramsData, path);
  224. print("缓存Key" + cacheKey);
  225. // // 读取缓存回调
  226. if (onCache != null) {
  227. await _onCallBackCacheData(onCache, cacheKey);
  228. }
  229. try {
  230. Map result = await NetUtil.post(path, method: method,
  231. params: params,
  232. queryParameters: queryParameters,
  233. showToast: showToast,
  234. cache: onCache != null);
  235. // TODO 解密?
  236. if (isSuccess(result)) {
  237. if (onSuccess != null) {
  238. onSuccess(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]);
  239. }
  240. return;
  241. }
  242. if (onError != null) {
  243. onError(!EmptyUtil.isEmpty(result)
  244. ? !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_MSG])
  245. ? result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]
  246. : '未知错误'
  247. : '未知错误');
  248. }
  249. } catch (e) {
  250. Logger.error('error: ' + e.toString());
  251. if (onError != null) {
  252. onError(e?.toString() ?? '未知错误');
  253. }
  254. }
  255. return;
  256. }
  257. /// 统一添加必要的参数
  258. // static Future<Map<String, dynamic>> signParams(Map<String, dynamic> params) async {
  259. // // 应用信息
  260. // PackageInfo packageInfo = await PackageInfo.fromPlatform();
  261. // // 原生传的信息
  262. // Map setting = await NativeUtil.getSetting();
  263. //
  264. // if (Platform.isIOS) {
  265. // IosDeviceInfo iosInfo = await DeviceInfoPlugin().iosInfo;
  266. // // 设备
  267. // params["platform"] = "ios";
  268. // // 设备系统版本
  269. // params["system_version"] = iosInfo?.systemVersion;
  270. // // 设备型号 如:iPhone 11pro
  271. // params['device_model'] = iosInfo?.name;
  272. // // 设备型号,如:MLMF2LL/A
  273. // // params['device_number'] = iosInfo?.model;
  274. // // 设备ID
  275. // params['device_id'] = iosInfo?.identifierForVendor;
  276. // } else if (Platform.isAndroid) {
  277. // AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo;
  278. // // 设备
  279. // params["platform"] = "android";
  280. // // 设备系统版本
  281. // params["system_version"] = "Android ${androidInfo?.version?.release}";
  282. // // 设备型号 如:iPhone 11pro
  283. // params['device_model'] = androidInfo?.model;
  284. // // 设备型号,如:MLMF2LL/A
  285. // // params['device_number'] = androidInfo?.id;
  286. // // 设备Id
  287. // params['device_id'] = androidInfo?.androidId;
  288. // }
  289. // // 应用版本号
  290. // params["app_version_name"] = packageInfo.version;
  291. // params["app_version"] = -1 ;// packageInfo.buildNumber;
  292. // // 分辨率
  293. // params["solution"] =
  294. // "${window.physicalSize.width.floor()}*${window.physicalSize.height.floor()}";
  295. //
  296. // // 站长ID
  297. // String masterId = setting['master_id'];
  298. // if (null != masterId &&
  299. // masterId != '' &&
  300. // (!params.containsKey('master_id') || params['master_id'] == '')) {
  301. // params['master_id'] = masterId;
  302. // }
  303. //
  304. // // secret_key
  305. // params['secret_key'] = setting['secret_key'];
  306. //
  307. // // token 读取SP缓存中的用户token
  308. // String token = await SharedPreferencesUtil.getStringValue(
  309. // GlobalConfig.SHARED_KEY_TOKEN);
  310. // if (!EmptyUtil.isEmpty(token)) {
  311. // params['token'] = token;
  312. // }
  313. //
  314. // // 当前时间戳:秒
  315. // params["time"] = TimeUtil.getNowTime();
  316. //
  317. // // 过滤空字段,过滤首尾空格
  318. // Map<String, dynamic> filters = Map<String, dynamic>();
  319. // params.forEach((key, value) {
  320. // if (key != '' && value != '') {
  321. // filters[key] = (value is String) ? value.trim() : value;
  322. // }
  323. // });
  324. // params = filters;
  325. //
  326. // List<String> list = List<String>();
  327. // params.forEach((key, value) {
  328. // list.add(key.toString() + value.toString());
  329. // });
  330. // params["sign"] = signWithArray(list);
  331. // return params;
  332. // }
  333. /// 获取必须的请求参数(用于请求头部)
  334. static Future<Map<String, String>> _getMustHeadParams() async {
  335. Map<String, String> params = new HashMap<String, String>();
  336. // 应用信息
  337. PackageInfo packageInfo = await PackageInfo.fromPlatform();
  338. // 原生传的信息
  339. Map setting = await NativeUtil.getSetting();
  340. if (Platform.isIOS) {
  341. IosDeviceInfo iosInfo = await DeviceInfoPlugin().iosInfo;
  342. // 设备
  343. params["platform"] = "ios";
  344. // 设备系统版本
  345. params["os_version"] = iosInfo?.systemVersion?.toString();
  346. // 设备型号 如:iPhone 11pro
  347. params['device_model'] = EncodeUtil.encodeBase64(iosInfo?.name?.toString() ?? '');
  348. // 设备ID
  349. params['device_id'] = iosInfo?.identifierForVendor?.toString();
  350. // idfa
  351. params['idfa'] = iosInfo?.identifierForVendor?.toString();
  352. } else if (Platform.isAndroid) {
  353. AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo;
  354. // 设备
  355. params["platform"] = "android";
  356. // 设备系统版本
  357. params["os_version"] = "Android ${androidInfo?.version?.release}";
  358. // 设备型号 如:iPhone 11pro
  359. params['device_model'] = androidInfo?.model?.toString();
  360. // 设备Id
  361. params['device_id'] = androidInfo?.androidId?.toString();
  362. }
  363. // 应用版本号
  364. params["app_version_name"] = packageInfo.version?.toString();
  365. params["AppVersionName"] = packageInfo.version?.toString();
  366. params["app_version"] = packageInfo.buildNumber?.toString();
  367. // 分辨率
  368. params["solution"] = "${window.physicalSize.width.floor()}*${window.physicalSize.height.floor()}";
  369. // 站长ID
  370. String masterId = setting['master_id'];
  371. if (null != masterId && masterId != '' && (!params.containsKey('master_id') || params['master_id'] == '')) {
  372. params['master_id'] = masterId; //!EmptyUtil.isEmpty(masterId) ? masterId : 'template_database';
  373. params['MasterId'] = masterId; //!EmptyUtil.isEmpty(masterId) ? masterId : 'template_database';
  374. }
  375. // token 读取SP缓存中的用户token
  376. String token = await SharedPreferencesUtil.getStringValue(GlobalConfig.SHARED_KEY_TOKEN);
  377. if (EmptyUtil.isEmpty(token) && !bool.fromEnvironment("dart.vm.product")) {
  378. try {
  379. token = setting['token'];
  380. } catch (e, s) {
  381. print(s);
  382. print(e);
  383. }
  384. }
  385. if (!EmptyUtil.isEmpty(token)) {
  386. // params['token'] = token;
  387. params['Authorization'] = 'Bearer ' + token;
  388. }
  389. // secret_key
  390. params['secret_key'] = setting['secret_key'] ?? '';
  391. // 当前时间戳:秒
  392. params["time"] = TimeUtil.getNowTime();
  393. // 过滤空字段,过滤首尾空格
  394. Map<String, String> filters = Map<String, String>();
  395. params.forEach((key, value) {
  396. if (key != '' && value != '') {
  397. filters[key] = (value is String) ? value.trim() : value;
  398. }
  399. });
  400. params = filters;
  401. List<String> list = List<String>();
  402. params.forEach((key, value) {
  403. list.add(key.toString() + '=' + value.toString() + '&');
  404. });
  405. params["sign"] = signWithArray(list);
  406. params.remove('secret_key');
  407. return params;
  408. }
  409. /// 获取Android imei
  410. static Future<String> _getImei() async {
  411. try {
  412. return await ImeiPlugin.getImei(shouldShowRequestPermissionRationale: true);
  413. } catch (e, s) {
  414. Logger.error(e, s);
  415. }
  416. return null;
  417. }
  418. /// 获取请求缓存成功的数据
  419. static Future<void> _onCallBackCacheData(OnCache onCache, String cacheKey) async {
  420. // 读取缓存
  421. Map<String, dynamic> cacheMap = await SharedPreferencesUtil.getNetCacheResult(cacheKey);
  422. if (!EmptyUtil.isEmpty(cacheMap) &&
  423. cacheMap.containsKey(GlobalConfig.HTTP_RESPONSE_KEY_CODE) &&
  424. (cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE ||
  425. cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}') &&
  426. !EmptyUtil.isEmpty(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) {
  427. onCache(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA]);
  428. return;
  429. }
  430. return;
  431. }
  432. /// 缓存请求成功的数据
  433. static void _setCallBackCacheData(String cacheKey, String value) async {
  434. SharedPreferencesUtil.setNetCacheResult(cacheKey, value);
  435. }
  436. /// 根据请求参数,获取缓存的数据
  437. static Future<dynamic> getRequestCachedData(String url, {Map<String, dynamic> params, Map<String, dynamic> queryParameters}) async {
  438. var paramsData = {'postData': params ?? {}, 'queryParameters': queryParameters ?? {}};
  439. Map<String, dynamic> cacheMap = await SharedPreferencesUtil.getNetCacheResult(getRequestParamsCachedKey(paramsData, url));
  440. if (!EmptyUtil.isEmpty(cacheMap) &&
  441. cacheMap.containsKey(GlobalConfig.HTTP_RESPONSE_KEY_CODE) &&
  442. (cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE ||
  443. cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}') &&
  444. !EmptyUtil.isEmpty(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) {
  445. return cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA];
  446. }
  447. return null;
  448. }
  449. /// 判断后台返回是否成功
  450. static bool isSuccess(Map<String, dynamic> data) {
  451. try {
  452. if (!EmptyUtil.isEmpty(data) &&
  453. (data[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE ||
  454. data[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}')) {
  455. return true;
  456. }
  457. } catch (e) {
  458. return false;
  459. }
  460. return false;
  461. }
  462. /// 根据请求参数,获取缓存的Key
  463. static String getRequestParamsCachedKey(Map<String, dynamic> map, String path) {
  464. String key;
  465. if (EmptyUtil.isEmpty(map)) {
  466. key = EncodeUtil.generateMd5(path);
  467. } else {
  468. key = EncodeUtil.generateMd5(path + map.toString());
  469. }
  470. return key;
  471. }
  472. // 七牛云文件上传
  473. static Future uploadFile(String url, File file, {String method = 'POST', Map<String, dynamic> params, OnSuccess onSuccess, OnError onError}) async {
  474. if (params == null) {
  475. params = {};
  476. }
  477. Dio dio = Dio(BaseOptions(
  478. method: "post",
  479. connectTimeout: 15000,
  480. receiveTimeout: 15000,
  481. contentType: Headers.jsonContentType,
  482. followRedirects: true,
  483. ));
  484. params['file'] = await MultipartFile.fromFile(file.path);
  485. FormData format = FormData.fromMap(params);
  486. return dio.request(url, data: format, options: Options(method: method));
  487. }
  488. /// 签名
  489. static String signWithArray(List<String> params) {
  490. // 字母升序
  491. params.sort();
  492. String result = "";
  493. params.forEach((param) {
  494. result += param;
  495. });
  496. result = result.substring(0, result.length - 1);
  497. return EncodeUtil.generateMd5(result);
  498. }
  499. /*
  500. * error统一处理
  501. */
  502. static void _formatError(DioError e) {
  503. if (e.type == DioErrorType.CONNECT_TIMEOUT) {
  504. Logger.error('连接超时: ${e.toString()}');
  505. } else if (e.type == DioErrorType.SEND_TIMEOUT) {
  506. Logger.error('请求超时: ${e.toString()}');
  507. } else if (e.type == DioErrorType.RECEIVE_TIMEOUT) {
  508. Logger.error('响应超时: ${e.toString()}');
  509. } else if (e.type == DioErrorType.RESPONSE) {
  510. Logger.error('出现异常: ${e.toString()}');
  511. } else if (e.type == DioErrorType.CANCEL) {
  512. Logger.error('请求取消: ${e.toString()}');
  513. } else {
  514. Logger.error('未知错误: ${e.toString()}');
  515. }
  516. }
  517. static void getImei() async {
  518. NetUtil.headParam['imei'] = await _getImei();
  519. }
  520. }
  521. /**
  522. * @description: 网络请求拦截器
  523. * @param {type}
  524. * @return:
  525. */
  526. class _NetInterceptors extends InterceptorsWrapper {
  527. @override
  528. Future onRequest(RequestOptions options) {
  529. Logger.net(options?.uri?.toString(), data: options.data.toString());
  530. // TODO 加密?
  531. return super.onRequest(options);
  532. }
  533. @override
  534. Future onResponse(Response response) {
  535. Logger.endNet(response?.request?.uri?.toString(), data: response?.data?.toString() ?? '');
  536. // TODO 解密?
  537. return super.onResponse(response);
  538. }
  539. @override
  540. Future onError(DioError err) {
  541. // Logger.error(err);
  542. return super.onError(err);
  543. }
  544. }