基础库
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 

442 行
13 KiB

  1. import 'package:dio/dio.dart';
  2. import 'package:dio/adapter.dart';
  3. import 'package:provider/provider.dart';
  4. import 'package:zhiying_comm/util/empty_util.dart';
  5. import 'dart:io';
  6. import 'dart:ui';
  7. import 'dart:convert';
  8. import 'package:zhiying_comm/zhiying_comm.dart';
  9. import 'package:package_info/package_info.dart';
  10. import 'package:device_info/device_info.dart';
  11. import 'package:fluttertoast/fluttertoast.dart';
  12. import 'global_config.dart';
  13. import 'shared_prefe_util.dart';
  14. import 'time_util.dart';
  15. typedef OnSuccess = dynamic Function(dynamic data);
  16. typedef OnError = dynamic Function(String e);
  17. typedef OnCache = dynamic Function(dynamic data);
  18. enum NetMethod {
  19. GET,
  20. POST,
  21. PUT,
  22. DELETE,
  23. OPTIONS,
  24. PATCH,
  25. UPDATE,
  26. HEAD,
  27. }
  28. class NetUtil {
  29. Dio _dio;
  30. NetUtil._();
  31. static NetUtil _instance;
  32. static NetUtil getInstance() {
  33. if (_instance == null) {
  34. _instance = NetUtil._();
  35. }
  36. return _instance;
  37. }
  38. get dio async {
  39. if (_dio == null) {
  40. var setting = await NativeUtil.getSetting();
  41. String domain = setting['domain']; //'http://www.hairuyi.com/';
  42. _config(domain, proxyUrl: '192.168.0.112:8888');
  43. }
  44. return _dio;
  45. }
  46. /// 配置网络请求,基础地址,代理地址,如:192.168.0.123:8888(代理地址生产环境无效)
  47. /// apiVersion 接口版本
  48. void _config(String baseUrl, {String proxyUrl}) {
  49. _dio = Dio(BaseOptions(
  50. method: "post",
  51. baseUrl: baseUrl,
  52. connectTimeout: 15000,
  53. receiveTimeout: 15000,
  54. contentType: Headers.jsonContentType,
  55. followRedirects: true,
  56. headers: {'device': 'wx_applet', 'Platform': 'wx_applet'},
  57. ));
  58. _dio.interceptors.add(_NetInterceptors());
  59. // _dio.interceptors.add(LogInterceptor());
  60. const bool inProduction = const bool.fromEnvironment("dart.vm.product");
  61. if (proxyUrl != null && proxyUrl != '' && !inProduction) {
  62. _setProxy(proxyUrl);
  63. }
  64. }
  65. /// 设置代理
  66. void _setProxy(String proxyUrl) {
  67. (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
  68. (HttpClient client) {
  69. client.findProxy = (uri) {
  70. return "PROXY $proxyUrl";
  71. };
  72. client.badCertificateCallback =
  73. (X509Certificate cert, String host, int port) => true;
  74. };
  75. }
  76. /// 同步请求
  77. static Future<dynamic> post(String path,
  78. {Map<String, dynamic> params,
  79. NetMethod method = NetMethod.POST,
  80. bool cache = false}) async {
  81. if (params == null) {
  82. params = {};
  83. }
  84. // 根据请求参数,获取缓存的Key
  85. String cacheKey = getRequestParamsCachedKey(params, path);
  86. // 参数签名 TODO 加密?
  87. Map<String, dynamic> sign = await signParams(params);
  88. Response response;
  89. try {
  90. Dio dio = await NetUtil.getInstance().dio;
  91. response = await dio.request(path,
  92. data: sign, options: Options(method: enumToString(method)));
  93. } on DioError catch (e) {
  94. _formatError(e);
  95. }
  96. try {
  97. var result =
  98. response.data is Map ? response.data : jsonDecode(response.data);
  99. // TODO 解密?
  100. if (isSuccess(result)) {
  101. // 缓存成功的数据
  102. if (cache) {
  103. _setCallBackCacheData(cacheKey,
  104. response.data is Map ? jsonEncode(response.data) : response.data);
  105. }
  106. return result;
  107. // 缓存返回的数据
  108. }
  109. Logger.error('error: ' + result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]);
  110. Fluttertoast.showToast(
  111. msg: result[GlobalConfig.HTTP_RESPONSE_KEY_MSG],
  112. toastLength: Toast.LENGTH_SHORT,
  113. gravity: ToastGravity.BOTTOM,
  114. );
  115. return result;
  116. } catch (e) {
  117. return null;
  118. }
  119. }
  120. /// 异步请求
  121. static void request(String path,
  122. {NetMethod method = NetMethod.GET,
  123. Map<String, dynamic> params,
  124. Map<String, File> multiFiles,
  125. OnSuccess onSuccess,
  126. OnError onError,
  127. OnCache onCache}) async {
  128. if (params == null) {
  129. params = {};
  130. }
  131. // 根据请求参数,获取缓存的Key
  132. String cacheKey = getRequestParamsCachedKey(params, path);
  133. // // 读取缓存回调
  134. if (onCache != null) {
  135. await _onCallBackCacheData(onCache, cacheKey);
  136. }
  137. // 参数签名
  138. Map<String, dynamic> sign = await signParams(params);
  139. Response response;
  140. try {
  141. // token 读取SP缓存中的用户token
  142. String token = await SharedPreferencesUtil.getStringValue(
  143. GlobalConfig.SHARED_KEY_TOKEN) ??
  144. '';
  145. const bool inProduction = const bool.fromEnvironment("dart.vm.product");
  146. if (!inProduction && token != '') {
  147. token = '7f7bdb785dcb02a4536bea2ffe299eda352e127f';
  148. }
  149. Dio dio = await NetUtil.getInstance().dio;
  150. response = await dio.request(path,
  151. data: sign,
  152. options:
  153. Options(method: enumToString(method), headers: {'token': token}));
  154. } on DioError catch (e) {
  155. _formatError(e);
  156. }
  157. try {
  158. var result =
  159. response.data is Map ? response.data : jsonDecode(response.data);
  160. // TODO 解密?
  161. if (isSuccess(result)) {
  162. if (onSuccess != null) {
  163. onSuccess(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]);
  164. // 缓存返回的数据
  165. _setCallBackCacheData(cacheKey,
  166. response.data is Map ? jsonEncode(response.data) : response.data);
  167. }
  168. return;
  169. }
  170. Logger.error('error: ' + result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]);
  171. Fluttertoast.showToast(
  172. msg: result[GlobalConfig.HTTP_RESPONSE_KEY_MSG],
  173. toastLength: Toast.LENGTH_SHORT,
  174. gravity: ToastGravity.BOTTOM,
  175. );
  176. if (onError != null) {
  177. onError(!EmptyUtil.isEmpty(result)
  178. ? !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_MSG])
  179. ? result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]
  180. : '未知错误'
  181. : '未知错误');
  182. }
  183. } catch (e) {
  184. Logger.error('error: ' + e.toString());
  185. if (onError != null) {
  186. onError(e?.toString() ?? '未知错误');
  187. }
  188. }
  189. return;
  190. }
  191. /// 统一添加必要的参数
  192. static Future<Map<String, dynamic>> signParams(
  193. Map<String, dynamic> params) async {
  194. // 应用信息
  195. PackageInfo packageInfo = await PackageInfo.fromPlatform();
  196. // 原生传的信息
  197. Map setting = await NativeUtil.getSetting();
  198. if (Platform.isIOS) {
  199. IosDeviceInfo iosInfo = await DeviceInfoPlugin().iosInfo;
  200. // 设备
  201. params["platform"] = "ios";
  202. // 设备系统版本
  203. params["system_version"] = iosInfo?.systemVersion;
  204. // 设备型号 如:iPhone 11pro
  205. params['device_model'] = iosInfo?.name;
  206. // 设备型号,如:MLMF2LL/A
  207. // params['device_number'] = iosInfo?.model;
  208. // 设备ID
  209. params['device_id'] = iosInfo?.identifierForVendor;
  210. } else if (Platform.isAndroid) {
  211. AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo;
  212. // 设备
  213. params["platform"] = "android";
  214. // 设备系统版本
  215. params["system_version"] = "Android ${androidInfo?.version?.release}";
  216. // 设备型号 如:iPhone 11pro
  217. params['device_model'] = androidInfo?.model;
  218. // 设备型号,如:MLMF2LL/A
  219. // params['device_number'] = androidInfo?.id;
  220. // 设备Id
  221. params['device_id'] = androidInfo?.androidId;
  222. }
  223. // 应用版本号
  224. params["app_version"] = packageInfo.version;
  225. // 分辨率
  226. params["solution"] =
  227. "${window.physicalSize.width.floor()}*${window.physicalSize.height.floor()}";
  228. // 站长ID
  229. String masterId = setting['master_id'];
  230. if (null != masterId &&
  231. masterId != '' &&
  232. (!params.containsKey('master_id') || params['master_id'] == '')) {
  233. params['master_id'] = masterId;
  234. }
  235. // secret_key
  236. params['secret_key'] = setting['secret_key'];
  237. // token 读取SP缓存中的用户token
  238. String token = await SharedPreferencesUtil.getStringValue(
  239. GlobalConfig.SHARED_KEY_TOKEN);
  240. if (!EmptyUtil.isEmpty(token)) {
  241. params['token'] = token;
  242. }
  243. // 当前时间戳:秒
  244. params["time"] = TimeUtil.getNowTime();
  245. // 过滤空字段,过滤首尾空格
  246. Map<String, dynamic> filters = Map<String, dynamic>();
  247. params.forEach((key, value) {
  248. if (key != '' && value != '') {
  249. filters[key] = (value is String) ? value.trim() : value;
  250. }
  251. });
  252. params = filters;
  253. List<String> list = List<String>();
  254. params.forEach((key, value) {
  255. list.add(key.toString() + value.toString());
  256. });
  257. params["sign"] = signWithArray(list);
  258. return params;
  259. }
  260. /// 获取请求缓存成功的数据
  261. static Future<void> _onCallBackCacheData(
  262. OnCache onCache, String cacheKey) async {
  263. // 读取缓存
  264. Map<String, dynamic> cacheMap =
  265. await SharedPreferencesUtil.getNetCacheResult(cacheKey);
  266. if (!EmptyUtil.isEmpty(cacheMap) &&
  267. cacheMap.containsKey(GlobalConfig.HTTP_RESPONSE_KEY_CODE) &&
  268. (cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] ==
  269. GlobalConfig.RESPONSE_SUCCESS_CODE ||
  270. cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] ==
  271. '${GlobalConfig.RESPONSE_SUCCESS_CODE}') &&
  272. !EmptyUtil.isEmpty(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) {
  273. onCache(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA]);
  274. return;
  275. }
  276. return;
  277. }
  278. /// 缓存请求成功的数据
  279. static void _setCallBackCacheData(String cacheKey, String value) async {
  280. SharedPreferencesUtil.setNetCacheResult(cacheKey, value);
  281. }
  282. /// 根据请求参数,获取缓存的数据
  283. static Future<dynamic> getRequestCachedData(String url,
  284. {Map<String, dynamic> params}) async {
  285. Map<String, dynamic> cacheMap =
  286. await SharedPreferencesUtil.getNetCacheResult(
  287. getRequestParamsCachedKey(params, url));
  288. if (!EmptyUtil.isEmpty(cacheMap) &&
  289. cacheMap.containsKey(GlobalConfig.HTTP_RESPONSE_KEY_CODE) &&
  290. (cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] ==
  291. GlobalConfig.RESPONSE_SUCCESS_CODE ||
  292. cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] ==
  293. '${GlobalConfig.RESPONSE_SUCCESS_CODE}') &&
  294. !EmptyUtil.isEmpty(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) {
  295. return cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA];
  296. }
  297. return null;
  298. }
  299. /// 判断后台返回是否成功
  300. static bool isSuccess(Map<String, dynamic> data) {
  301. try {
  302. if (!EmptyUtil.isEmpty(data) &&
  303. (data[GlobalConfig.HTTP_RESPONSE_KEY_CODE] ==
  304. GlobalConfig.RESPONSE_SUCCESS_CODE ||
  305. data[GlobalConfig.HTTP_RESPONSE_KEY_CODE] ==
  306. '${GlobalConfig.RESPONSE_SUCCESS_CODE}')) {
  307. return true;
  308. }
  309. } catch (e) {
  310. return false;
  311. }
  312. return false;
  313. }
  314. /// 根据请求参数,获取缓存的Key
  315. static String getRequestParamsCachedKey(
  316. Map<String, dynamic> map, String path) {
  317. if (EmptyUtil.isEmpty(map)) {
  318. return EncodeUtil.generateMd5(path);
  319. }
  320. return EncodeUtil.generateMd5(path + map.toString());
  321. }
  322. // 七牛云文件上传
  323. static Future uploadFile(String url, File file,
  324. {String method = 'POST',
  325. Map<String, dynamic> params,
  326. OnSuccess onSuccess,
  327. OnError onError}) async {
  328. if (params == null) {
  329. params = {};
  330. }
  331. Dio dio = Dio(BaseOptions(
  332. method: "post",
  333. connectTimeout: 15000,
  334. receiveTimeout: 15000,
  335. contentType: Headers.jsonContentType,
  336. followRedirects: true,
  337. ));
  338. params['file'] = await MultipartFile.fromFile(file.path);
  339. FormData format = FormData.fromMap(params);
  340. return dio.request(url, data: format, options: Options(method: method));
  341. }
  342. /// 签名
  343. static String signWithArray(List<String> params) {
  344. // 字母升序
  345. params.sort();
  346. String result = "";
  347. // result += secret_key;
  348. params.forEach((param) {
  349. result += param;
  350. });
  351. // result += secret_key;
  352. return EncodeUtil.generateMd5(result);
  353. }
  354. /*
  355. * error统一处理
  356. */
  357. static void _formatError(DioError e) {
  358. if (e.type == DioErrorType.CONNECT_TIMEOUT) {
  359. Logger.error('连接超时: ${e.toString()}');
  360. } else if (e.type == DioErrorType.SEND_TIMEOUT) {
  361. Logger.error('请求超时: ${e.toString()}');
  362. } else if (e.type == DioErrorType.RECEIVE_TIMEOUT) {
  363. Logger.error('响应超时: ${e.toString()}');
  364. } else if (e.type == DioErrorType.RESPONSE) {
  365. Logger.error('出现异常: ${e.toString()}');
  366. } else if (e.type == DioErrorType.CANCEL) {
  367. Logger.error('请求取消: ${e.toString()}');
  368. } else {
  369. Logger.error('未知错误: ${e.toString()}');
  370. }
  371. }
  372. }
  373. /**
  374. * @description: 网络请求拦截器
  375. * @param {type}
  376. * @return:
  377. */
  378. class _NetInterceptors extends InterceptorsWrapper {
  379. @override
  380. Future onRequest(RequestOptions options) {
  381. Logger.net(options?.uri?.toString(), data: options.data.toString());
  382. // TODO 加密?
  383. return super.onRequest(options);
  384. }
  385. @override
  386. Future onResponse(Response response) {
  387. Logger.endNet(response?.request?.uri?.toString(),
  388. data: response?.data?.toString() ?? '');
  389. // TODO 解密?
  390. return super.onResponse(response);
  391. }
  392. @override
  393. Future onError(DioError err) {
  394. // Logger.error(err);
  395. return super.onError(err);
  396. }
  397. }