基础库
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

net_util.dart 18 KiB

4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  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:zhiying_comm/util/empty_util.dart';
  13. import 'package:zhiying_comm/zhiying_comm.dart';
  14. import 'encode_util.dart';
  15. import 'global_config.dart';
  16. import 'shared_prefe_util.dart';
  17. import 'time_util.dart';
  18. import 'package:provider/provider.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. /// 同步请求
  82. static Future<dynamic> post(String path, {Map<String, dynamic> params, Map<String, dynamic> queryParameters, NetMethod method = NetMethod.POST, bool cache = false, bool showToast = true}) async {
  83. if (params == null) {
  84. params = {};
  85. }
  86. // 根据请求参数,获取缓存的Key
  87. String cacheKey = getRequestParamsCachedKey(params, path);
  88. // 参数签名 TODO 加密?
  89. // post请求的参数
  90. Map<String, dynamic> bodyParams = params;
  91. // 请求头参数
  92. Map<String, dynamic> headParam = await _getMustHeadParams();
  93. Response response;
  94. try {
  95. Dio dio = await NetUtil.getInstance().dio;
  96. response = await dio.request(
  97. path,
  98. data: !EmptyUtil.isEmpty(bodyParams) ? bodyParams : null,
  99. options: Options(method: enumToString(method), headers: headParam),
  100. queryParameters: !EmptyUtil.isEmpty(queryParameters) ? queryParameters : null,
  101. );
  102. } on DioError catch (e) {
  103. _formatError(e);
  104. }
  105. try {
  106. var result = response.data is Map ? response.data : jsonDecode(response.data);
  107. // TODO 解密?
  108. if (isSuccess(result)) {
  109. // 缓存成功的数据
  110. if (cache) {
  111. _setCallBackCacheData(cacheKey, response.data is Map ? jsonEncode(response.data) : response.data);
  112. }
  113. return result;
  114. // 缓存返回的数据
  115. } else {
  116. Logger.error('error: ' + result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]);
  117. ///
  118. /// 401000 验证用户失败(不提示Toast)
  119. /// 404004 没有找到对应模块(跳空页面,不提示toast)
  120. ///
  121. if (result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] != 401000 && result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] != 404004) {
  122. if(showToast) {
  123. Fluttertoast.showToast(
  124. msg: result[GlobalConfig.HTTP_RESPONSE_KEY_MSG],
  125. toastLength: Toast.LENGTH_SHORT,
  126. gravity: ToastGravity.BOTTOM,
  127. );
  128. }
  129. }
  130. ///
  131. /// 401003 用户被逼下线
  132. /// 退出登陆(清理token等用户信息)
  133. ///
  134. if (result[GlobalConfig.HTTP_RESPONSE_KEY_CODE]?.toString() == '401003') {
  135. try {
  136. Future.delayed(Duration(seconds: 0)).then((onValue) {
  137. BuildContext context = navigatorKey.currentState.overlay.context;
  138. Provider.of<UserInfoNotifier>(context, listen: false).unLogin();
  139. });
  140. } catch (e, s) {
  141. Logger.error(e, s);
  142. }
  143. }
  144. ///
  145. /// 403028 账号被冻结
  146. /// 403029 账号被禁用
  147. /// 提示并且退出登录
  148. ///
  149. if(result[GlobalConfig.HTTP_RESPONSE_KEY_CODE]?.toString() == '403028' || result[GlobalConfig.HTTP_RESPONSE_KEY_CODE]?.toString() == '403029') {
  150. try {
  151. // 提示
  152. Fluttertoast.showToast(
  153. msg: result[GlobalConfig.HTTP_RESPONSE_KEY_MSG],
  154. toastLength: Toast.LENGTH_SHORT,
  155. gravity: ToastGravity.BOTTOM,
  156. );
  157. // 退出登录
  158. Future.delayed(Duration(seconds: 0)).then((onValue) {
  159. BuildContext context = navigatorKey.currentState.overlay.context;
  160. Provider.of<UserInfoNotifier>(context, listen: false).unLogin();
  161. });
  162. } catch (e, s) {
  163. Logger.error(e, s);
  164. }
  165. }
  166. return result;
  167. }
  168. } catch (e) {
  169. return null;
  170. }
  171. }
  172. /// 异步请求
  173. static void request(String path, {NetMethod method = NetMethod.GET, Map<String, dynamic> params,Map<String, dynamic> queryParameters, OnSuccess onSuccess, OnError onError, OnCache onCache, bool showToast = true}) async {
  174. if (params == null) {
  175. params = {};
  176. }
  177. // 根据请求参数,获取缓存的Key
  178. String cacheKey = getRequestParamsCachedKey(params, path);
  179. // // 读取缓存回调
  180. if (onCache != null) {
  181. await _onCallBackCacheData(onCache, cacheKey);
  182. }
  183. try {
  184. Map result = await NetUtil.post(path, method: method, params: params, queryParameters: queryParameters, showToast: showToast, cache: onCache != null);
  185. // TODO 解密?
  186. if (isSuccess(result)) {
  187. if (onSuccess != null) {
  188. onSuccess(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]);
  189. }
  190. return;
  191. }
  192. if (onError != null) {
  193. onError(!EmptyUtil.isEmpty(result) ? !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]) ? result[GlobalConfig.HTTP_RESPONSE_KEY_MSG] : '未知错误' : '未知错误');
  194. }
  195. } catch (e) {
  196. Logger.error('error: ' + e.toString());
  197. if (onError != null) {
  198. onError(e?.toString() ?? '未知错误');
  199. }
  200. }
  201. return;
  202. }
  203. /// 统一添加必要的参数
  204. // static Future<Map<String, dynamic>> signParams(Map<String, dynamic> params) async {
  205. // // 应用信息
  206. // PackageInfo packageInfo = await PackageInfo.fromPlatform();
  207. // // 原生传的信息
  208. // Map setting = await NativeUtil.getSetting();
  209. //
  210. // if (Platform.isIOS) {
  211. // IosDeviceInfo iosInfo = await DeviceInfoPlugin().iosInfo;
  212. // // 设备
  213. // params["platform"] = "ios";
  214. // // 设备系统版本
  215. // params["system_version"] = iosInfo?.systemVersion;
  216. // // 设备型号 如:iPhone 11pro
  217. // params['device_model'] = iosInfo?.name;
  218. // // 设备型号,如:MLMF2LL/A
  219. // // params['device_number'] = iosInfo?.model;
  220. // // 设备ID
  221. // params['device_id'] = iosInfo?.identifierForVendor;
  222. // } else if (Platform.isAndroid) {
  223. // AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo;
  224. // // 设备
  225. // params["platform"] = "android";
  226. // // 设备系统版本
  227. // params["system_version"] = "Android ${androidInfo?.version?.release}";
  228. // // 设备型号 如:iPhone 11pro
  229. // params['device_model'] = androidInfo?.model;
  230. // // 设备型号,如:MLMF2LL/A
  231. // // params['device_number'] = androidInfo?.id;
  232. // // 设备Id
  233. // params['device_id'] = androidInfo?.androidId;
  234. // }
  235. // // 应用版本号
  236. // params["app_version_name"] = packageInfo.version;
  237. // params["app_version"] = -1 ;// packageInfo.buildNumber;
  238. // // 分辨率
  239. // params["solution"] =
  240. // "${window.physicalSize.width.floor()}*${window.physicalSize.height.floor()}";
  241. //
  242. // // 站长ID
  243. // String masterId = setting['master_id'];
  244. // if (null != masterId &&
  245. // masterId != '' &&
  246. // (!params.containsKey('master_id') || params['master_id'] == '')) {
  247. // params['master_id'] = masterId;
  248. // }
  249. //
  250. // // secret_key
  251. // params['secret_key'] = setting['secret_key'];
  252. //
  253. // // token 读取SP缓存中的用户token
  254. // String token = await SharedPreferencesUtil.getStringValue(
  255. // GlobalConfig.SHARED_KEY_TOKEN);
  256. // if (!EmptyUtil.isEmpty(token)) {
  257. // params['token'] = token;
  258. // }
  259. //
  260. // // 当前时间戳:秒
  261. // params["time"] = TimeUtil.getNowTime();
  262. //
  263. // // 过滤空字段,过滤首尾空格
  264. // Map<String, dynamic> filters = Map<String, dynamic>();
  265. // params.forEach((key, value) {
  266. // if (key != '' && value != '') {
  267. // filters[key] = (value is String) ? value.trim() : value;
  268. // }
  269. // });
  270. // params = filters;
  271. //
  272. // List<String> list = List<String>();
  273. // params.forEach((key, value) {
  274. // list.add(key.toString() + value.toString());
  275. // });
  276. // params["sign"] = signWithArray(list);
  277. // return params;
  278. // }
  279. /// 获取必须的请求参数(用于请求头部)
  280. static Future<Map<String, String>> _getMustHeadParams() async {
  281. Map<String, String> params = new HashMap<String, String>();
  282. // 应用信息
  283. PackageInfo packageInfo = await PackageInfo.fromPlatform();
  284. // 原生传的信息
  285. Map setting = await NativeUtil.getSetting();
  286. if (Platform.isIOS) {
  287. IosDeviceInfo iosInfo = await DeviceInfoPlugin().iosInfo;
  288. // 设备
  289. params["platform"] = "ios";
  290. // 设备系统版本
  291. params["os_version"] = iosInfo?.systemVersion?.toString();
  292. // 设备型号 如:iPhone 11pro
  293. params['device_model'] =
  294. EncodeUtil.encodeBase64(iosInfo?.name?.toString() ?? '');
  295. // 设备ID
  296. params['device_id'] = iosInfo?.identifierForVendor?.toString();
  297. // idfa
  298. params['idfa'] = iosInfo?.identifierForVendor?.toString();
  299. } else if (Platform.isAndroid) {
  300. AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo;
  301. // 设备
  302. params["platform"] = "android";
  303. // 设备系统版本
  304. params["os_version"] = "Android ${androidInfo?.version?.release}";
  305. // 设备型号 如:iPhone 11pro
  306. params['device_model'] = androidInfo?.model?.toString();
  307. // 设备Id
  308. params['device_id'] = androidInfo?.androidId?.toString();
  309. // imei
  310. params['imei'] = await _getImei();
  311. }
  312. // 应用版本号
  313. params["app_version_name"] = packageInfo.version?.toString();
  314. params["app_version"] = packageInfo.buildNumber?.toString();
  315. // 分辨率
  316. params["solution"] = "${window.physicalSize.width.floor()}*${window.physicalSize.height.floor()}";
  317. // 站长ID
  318. String masterId = setting['master_id'];
  319. if (null != masterId && masterId != '' && (!params.containsKey('master_id') || params['master_id'] == '')) {
  320. params['master_id'] = masterId; //!EmptyUtil.isEmpty(masterId) ? masterId : 'template_database';
  321. }
  322. // token 读取SP缓存中的用户token
  323. String token = await SharedPreferencesUtil.getStringValue(GlobalConfig.SHARED_KEY_TOKEN);
  324. if (!EmptyUtil.isEmpty(token)) {
  325. // params['token'] = token;
  326. params['Authorization'] = 'Bearer ' + token;
  327. }
  328. // secret_key
  329. params['secret_key'] = setting['secret_key'] ?? '';
  330. // 当前时间戳:秒
  331. params["time"] = TimeUtil.getNowTime();
  332. // 过滤空字段,过滤首尾空格
  333. Map<String, String> filters = Map<String, String>();
  334. params.forEach((key, value) {
  335. if (key != '' && value != '') {
  336. filters[key] = (value is String) ? value.trim() : value;
  337. }
  338. });
  339. params = filters;
  340. List<String> list = List<String>();
  341. params.forEach((key, value) {
  342. list.add(key.toString() + '=' + value.toString() + '&');
  343. });
  344. params["sign"] = signWithArray(list);
  345. params.remove('secret_key');
  346. return params;
  347. }
  348. /// 获取Android imei
  349. static Future<String> _getImei() async {
  350. try {
  351. return await ImeiPlugin.getImei(shouldShowRequestPermissionRationale: true);
  352. } catch (e, s) {
  353. Logger.error(e, s);
  354. }
  355. return null;
  356. }
  357. /// 获取请求缓存成功的数据
  358. static Future<void> _onCallBackCacheData(OnCache onCache, String cacheKey) async {
  359. // 读取缓存
  360. Map<String, dynamic> cacheMap = await SharedPreferencesUtil.getNetCacheResult(cacheKey);
  361. if (!EmptyUtil.isEmpty(cacheMap) &&
  362. cacheMap.containsKey(GlobalConfig.HTTP_RESPONSE_KEY_CODE) &&
  363. (cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE ||
  364. cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}') &&
  365. !EmptyUtil.isEmpty(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) {
  366. onCache(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA]);
  367. return;
  368. }
  369. return;
  370. }
  371. /// 缓存请求成功的数据
  372. static void _setCallBackCacheData(String cacheKey, String value) async {
  373. SharedPreferencesUtil.setNetCacheResult(cacheKey, value);
  374. }
  375. /// 根据请求参数,获取缓存的数据
  376. static Future<dynamic> getRequestCachedData(String url, {Map<String, dynamic> params}) async {
  377. Map<String, dynamic> cacheMap = await SharedPreferencesUtil.getNetCacheResult(getRequestParamsCachedKey(params, url));
  378. if (!EmptyUtil.isEmpty(cacheMap) &&
  379. cacheMap.containsKey(GlobalConfig.HTTP_RESPONSE_KEY_CODE) &&
  380. (cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE ||
  381. cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}') &&
  382. !EmptyUtil.isEmpty(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) {
  383. return cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA];
  384. }
  385. return null;
  386. }
  387. /// 判断后台返回是否成功
  388. static bool isSuccess(Map<String, dynamic> data) {
  389. try {
  390. if (!EmptyUtil.isEmpty(data) &&
  391. (data[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE ||
  392. data[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}')) {
  393. return true;
  394. }
  395. } catch (e) {
  396. return false;
  397. }
  398. return false;
  399. }
  400. /// 根据请求参数,获取缓存的Key
  401. static String getRequestParamsCachedKey(Map<String, dynamic> map, String path) {
  402. if (EmptyUtil.isEmpty(map)) {
  403. return EncodeUtil.generateMd5(path);
  404. }
  405. return EncodeUtil.generateMd5(path + map.toString());
  406. }
  407. // 七牛云文件上传
  408. static Future uploadFile(String url, File file, {String method = 'POST', Map<String, dynamic> params, OnSuccess onSuccess, OnError onError}) async {
  409. if (params == null) {
  410. params = {};
  411. }
  412. Dio dio = Dio(BaseOptions(
  413. method: "post",
  414. connectTimeout: 15000,
  415. receiveTimeout: 15000,
  416. contentType: Headers.jsonContentType,
  417. followRedirects: true,
  418. ));
  419. params['file'] = await MultipartFile.fromFile(file.path);
  420. FormData format = FormData.fromMap(params);
  421. return dio.request(url, data: format, options: Options(method: method));
  422. }
  423. /// 签名
  424. static String signWithArray(List<String> params) {
  425. // 字母升序
  426. params.sort();
  427. String result = "";
  428. params.forEach((param) {
  429. result += param;
  430. });
  431. result = result.substring(0, result.length - 1);
  432. return EncodeUtil.generateMd5(result);
  433. }
  434. /*
  435. * error统一处理
  436. */
  437. static void _formatError(DioError e) {
  438. if (e.type == DioErrorType.CONNECT_TIMEOUT) {
  439. Logger.error('连接超时: ${e.toString()}');
  440. } else if (e.type == DioErrorType.SEND_TIMEOUT) {
  441. Logger.error('请求超时: ${e.toString()}');
  442. } else if (e.type == DioErrorType.RECEIVE_TIMEOUT) {
  443. Logger.error('响应超时: ${e.toString()}');
  444. } else if (e.type == DioErrorType.RESPONSE) {
  445. Logger.error('出现异常: ${e.toString()}');
  446. } else if (e.type == DioErrorType.CANCEL) {
  447. Logger.error('请求取消: ${e.toString()}');
  448. } else {
  449. Logger.error('未知错误: ${e.toString()}');
  450. }
  451. }
  452. }
  453. /**
  454. * @description: 网络请求拦截器
  455. * @param {type}
  456. * @return:
  457. */
  458. class _NetInterceptors extends InterceptorsWrapper {
  459. @override
  460. Future onRequest(RequestOptions options) {
  461. Logger.net(options?.uri?.toString(), data: options.data.toString());
  462. // TODO 加密?
  463. return super.onRequest(options);
  464. }
  465. @override
  466. Future onResponse(Response response) {
  467. Logger.endNet(response?.request?.uri?.toString(), data: response?.data?.toString() ?? '');
  468. // TODO 解密?
  469. return super.onResponse(response);
  470. }
  471. @override
  472. Future onError(DioError err) {
  473. // Logger.error(err);
  474. return super.onError(err);
  475. }
  476. }