import 'package:dio/dio.dart'; import 'package:dio/adapter.dart'; import 'package:provider/provider.dart'; import 'package:zhiying_comm/util/empty_util.dart'; import 'dart:io'; import 'dart:ui'; import 'dart:convert'; import 'package:zhiying_comm/zhiying_comm.dart'; import 'package:package_info/package_info.dart'; import 'package:device_info/device_info.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'global_config.dart'; import 'shared_prefe_util.dart'; import 'time_util.dart'; typedef OnSuccess = dynamic Function(dynamic data); typedef OnError = dynamic Function(String e); typedef OnCache = dynamic Function(dynamic data); enum NetMethod { GET, POST, PUT, DELETE, OPTIONS, PATCH, UPDATE, HEAD, } class NetUtil { Dio _dio; NetUtil._(); static NetUtil _instance; static NetUtil getInstance() { if (_instance == null) { _instance = NetUtil._(); } return _instance; } get dio async { if (_dio == null) { var setting = await NativeUtil.getSetting(); String domain = setting['domain']; //'http://www.hairuyi.com/'; _config(domain, proxyUrl: '192.168.0.66:8866'); } return _dio; } /// 配置网络请求,基础地址,代理地址,如:192.168.0.123:8888(代理地址生产环境无效) /// apiVersion 接口版本 void _config(String baseUrl, {String proxyUrl}) { _dio = Dio(BaseOptions( method: "post", baseUrl: baseUrl, connectTimeout: 15000, receiveTimeout: 15000, contentType: Headers.jsonContentType, followRedirects: true, headers: {'device': 'wx_applet', 'Platform': 'wx_applet'}, )); _dio.interceptors.add(_NetInterceptors()); // _dio.interceptors.add(LogInterceptor()); const bool inProduction = const bool.fromEnvironment("dart.vm.product"); if (proxyUrl != null && proxyUrl != '' && !inProduction) { _setProxy(proxyUrl); } } /// 设置代理 void _setProxy(String proxyUrl) { (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (HttpClient client) { client.findProxy = (uri) { return "PROXY $proxyUrl"; }; client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; }; } /// 同步请求 static Future post(String path, {Map params, NetMethod method = NetMethod.POST, bool cache = false}) async { if (params == null) { params = {}; } // 根据请求参数,获取缓存的Key String cacheKey = getRequestParamsCachedKey(params, path); // 参数签名 TODO 加密? Map sign = await signParams(params); Response response; try { Dio dio = await NetUtil.getInstance().dio; response = await dio.request(path, data: sign, options: Options(method: enumToString(method))); } on DioError catch (e) { _formatError(e); } try { var result = response.data is Map ? response.data : jsonDecode(response.data); // TODO 解密? if (isSuccess(result)) { // 缓存成功的数据 if(cache) { _setCallBackCacheData(cacheKey, response.data is Map ? jsonEncode(response.data) : response.data); } return result; // 缓存返回的数据 } Logger.error('error: ' + result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]); Fluttertoast.showToast(msg: result[GlobalConfig.HTTP_RESPONSE_KEY_MSG], toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM,); return result; } catch (e) { return null; } } /// 异步请求 static void request(String path, {NetMethod method = NetMethod.GET, Map params, Map multiFiles, OnSuccess onSuccess, OnError onError, OnCache onCache}) async { if (params == null) { params = {}; } // 根据请求参数,获取缓存的Key String cacheKey = getRequestParamsCachedKey(params, path); // // 读取缓存回调 if (onCache != null) { await _onCallBackCacheData(onCache, cacheKey); } // 参数签名 Map sign = await signParams(params); Response response; try { Dio dio = await NetUtil.getInstance().dio; response = await dio.request(path, data: sign, options: Options(method: enumToString(method))); } on DioError catch (e) { _formatError(e); } try { var result = response.data is Map ? response.data : jsonDecode(response.data); // TODO 解密? if (isSuccess(result)) { if (onSuccess != null) { onSuccess(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); // 缓存返回的数据 _setCallBackCacheData(cacheKey, response.data is Map ? jsonEncode(response.data) : response.data); } return; } Logger.error('error: ' + result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]); Fluttertoast.showToast( msg: result[GlobalConfig.HTTP_RESPONSE_KEY_MSG], toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM, ); if (onError != null) { onError( !EmptyUtil.isEmpty(result) ? !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]) ? result[GlobalConfig.HTTP_RESPONSE_KEY_MSG] : '未知错误' : '未知错误'); } } catch (e) { Logger.error('error: ' + e.toString()); if (onError != null) { onError(e?.toString() ?? '未知错误'); } } return; } /// 统一添加必要的参数 static Future> signParams( Map params) async { // 应用信息 PackageInfo packageInfo = await PackageInfo.fromPlatform(); // 原生传的信息 Map setting = await NativeUtil.getSetting(); if (Platform.isIOS) { IosDeviceInfo iosInfo = await DeviceInfoPlugin().iosInfo; // 设备 params["platform"] = "ios"; // 设备系统版本 params["system_version"] = iosInfo?.systemVersion; // 设备型号 如:iPhone 11pro params['device_model'] = iosInfo?.name; // 设备型号,如:MLMF2LL/A // params['device_number'] = iosInfo?.model; // 设备ID params['device_id'] = iosInfo?.identifierForVendor; } else if (Platform.isAndroid) { AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo; // 设备 params["platform"] = "android"; // 设备系统版本 params["system_version"] = "Android ${androidInfo?.version?.release}"; // 设备型号 如:iPhone 11pro params['device_model'] = androidInfo?.model; // 设备型号,如:MLMF2LL/A // params['device_number'] = androidInfo?.id; // 设备Id params['device_id'] = androidInfo?.androidId; } // 应用版本号 params["app_version"] = packageInfo.version; // 分辨率 params["solution"] = "${window.physicalSize.width.floor()}*${window.physicalSize.height.floor()}"; // 站长ID String masterId = setting['master_id']; if (null != masterId && masterId != '' && (!params.containsKey('master_id') || params['master_id'] == '')) { params['master_id'] = masterId; } // secret_key params['secret_key'] = setting['secret_key']; // token 读取SP缓存中的用户token String token = await SharedPreferencesUtil.getStringValue(GlobalConfig.SHARED_KEY_TOKEN); if(!EmptyUtil.isEmpty(token)){ params['token'] = token; } // 当前时间戳:秒 params["time"] = TimeUtil.getNowTime(); // 过滤空字段,过滤首尾空格 Map filters = Map(); params.forEach((key, value) { if (key != '' && value != '') { filters[key] = (value is String) ? value.trim() : value; } }); params = filters; List list = List(); params.forEach((key, value) { list.add(key.toString() + value.toString()); }); params["sign"] = signWithArray(list); return params; } /// 获取请求缓存成功的数据 static Future _onCallBackCacheData( OnCache onCache, String cacheKey) async { // 读取缓存 Map cacheMap = await SharedPreferencesUtil.getNetCacheResult(cacheKey); if (!EmptyUtil.isEmpty(cacheMap) && cacheMap.containsKey(GlobalConfig.HTTP_RESPONSE_KEY_CODE) && (cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE || cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}') && !EmptyUtil.isEmpty(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { onCache(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); return; } return; } /// 缓存请求成功的数据 static void _setCallBackCacheData(String cacheKey, String value) async { SharedPreferencesUtil.setNetCacheResult(cacheKey, value); } /// 根据请求参数,获取缓存的数据 static Future getRequestCachedData(String url, {Map params}) async{ Map cacheMap = await SharedPreferencesUtil.getNetCacheResult(getRequestParamsCachedKey(params, url)); if (!EmptyUtil.isEmpty(cacheMap) && cacheMap.containsKey(GlobalConfig.HTTP_RESPONSE_KEY_CODE) && (cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE || cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}') && !EmptyUtil.isEmpty(cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { return cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA]; } return null; } /// 判断后台返回是否成功 static bool isSuccess(Map data){ try { if (!EmptyUtil.isEmpty(data) && (data[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE || data[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}')) { return true; } }catch(e){ return false; } return false; } /// 根据请求参数,获取缓存的Key static String getRequestParamsCachedKey( Map map, String path) { if (EmptyUtil.isEmpty(map)) { return EncodeUtil.generateMd5(path); } return EncodeUtil.generateMd5(path + map.toString()); } /// 签名 static String signWithArray(List params) { // 字母升序 params.sort(); String result = ""; // result += secret_key; params.forEach((param) { result += param; }); // result += secret_key; return EncodeUtil.generateMd5(result); } /* * error统一处理 */ static void _formatError(DioError e) { if (e.type == DioErrorType.CONNECT_TIMEOUT) { Logger.error('连接超时: ${e.toString()}'); } else if (e.type == DioErrorType.SEND_TIMEOUT) { Logger.error('请求超时: ${e.toString()}'); } else if (e.type == DioErrorType.RECEIVE_TIMEOUT) { Logger.error('响应超时: ${e.toString()}'); } else if (e.type == DioErrorType.RESPONSE) { Logger.error('出现异常: ${e.toString()}'); } else if (e.type == DioErrorType.CANCEL) { Logger.error('请求取消: ${e.toString()}'); } else { Logger.error('未知错误: ${e.toString()}'); } } } /** * @description: 网络请求拦截器 * @param {type} * @return: */ class _NetInterceptors extends InterceptorsWrapper { @override Future onRequest(RequestOptions options) { Logger.net(options?.uri?.toString(), data: options.data.toString()); // TODO 加密? return super.onRequest(options); } @override Future onResponse(Response response) { Logger.endNet(response?.request?.uri?.toString(), data: response?.data?.toString() ?? ''); // TODO 解密? return super.onResponse(response); } @override Future onError(DioError err) { // Logger.error(err); return super.onError(err); } }