import 'package:dio/dio.dart'; import 'package:dio/adapter.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.112:8888'); } 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'}, )); _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}) async { if (params == null) { params = {}; } Map sign = await signParams(params); Response response; try { Dio dio = await NetUtil.getInstance().dio; response = await dio.post(path, data: sign); } on DioError catch (e) { _formatError(e); // Logger.error(e); } if (response == null) { return null; } if (response.statusCode != 200) { // 请求错误 const bool inProduction = const bool.fromEnvironment("dart.vm.product"); if (!inProduction) { Fluttertoast.showToast(msg: response.statusMessage); } // log.e(response.statusMessage); return null; } var result = response.data; if (result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE || result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}') { Logger.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; } /// 异步请求 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); // // 读取缓存回调 await _onCallBackCacheData(onCache, cacheKey); // 参数签名 Map sign = await signParams(params); Response response; try { Dio dio = await NetUtil.getInstance().dio; dio.request(path, data: sign, options: Options(method: enumToString(method))); } on DioError catch (e) { _formatError(e); } if (response == null) { return null; } var result = jsonDecode(response.data); //TODO 加密? if (result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE || result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}') { if (onSuccess != null) { onSuccess(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); // 缓存返回的数据 _setCallBackCacheData(cacheKey, 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(result[GlobalConfig.HTTP_RESPONSE_KEY_MSG] ?? '未知错误'); } 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 != null && masterId != '' && (!params.containsKey('master_id') || params['master_id'] == '')) { params['master_id'] = masterId; } // secret_key params['secret_key'] = setting['secret_key']; // token String token = setting['token']; if (token != null && token != '' && (!params.containsKey('token') || params['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.RESPONSE_SUCCESS_CODE) && (cacheMap[GlobalConfig.RESPONSE_SUCCESS_CODE] == GlobalConfig.RESPONSE_SUCCESS_CODE || cacheMap[GlobalConfig.RESPONSE_SUCCESS_CODE] == '${GlobalConfig.RESPONSE_SUCCESS_CODE}') && null != cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA]) { onCache(cacheMap['data']); return; } return; } /// 缓存请求成功的数据 static void _setCallBackCacheData(String cacheKey, String value) async { SharedPreferencesUtil.setNetCacheResult(cacheKey, value); } /// 根据请求参数,获取缓存的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?.path, data: options.data.toString()); // TODO 加密? return super.onRequest(options); } @override Future onResponse(Response response) { Logger.endNet(response?.statusCode?.toString(), data: response?.data?.toString() ?? ''); // TODO 解密? return super.onResponse(response); } @override Future onError(DioError err) { // Logger.error(err); return super.onError(err); } }