import 'dart:io'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:get/get.dart' hide FormData; import 'package:news_getx/config/cache.dart'; import 'package:news_getx/config/proxy.dart'; import 'package:news_getx/config/server.dart'; import 'package:cookie_jar/cookie_jar.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:news_getx/data/services/config.dart'; import 'package:news_getx/data/services/user.dart'; import 'package:news_getx/utils/loading.dart'; import 'package:news_getx/utils/net_cache.dart'; /* * http 操作类 * * 手册 * https://github.com/cfug/dio/blob/main/dio/README-ZH.md * * 从 4 升级到 5 * https://github.com/cfug/dio/blob/main/dio/migration_guide.md */ class HttpUtil { late Dio dio; CancelToken cancelToken = CancelToken(); // 每次调用都会重新生成一个dio对象 static HttpUtil _instance = HttpUtil._internal(); factory HttpUtil() => _instance; HttpUtil._internal() { // BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数 BaseOptions options = BaseOptions( // 请求基地址,可以包含子路径 baseUrl: ServerApiUrl, //连接服务器超时时间,单位是毫秒. connectTimeout: Duration(seconds: 10), // 响应流上前后两次接受到数据的间隔,单位为毫秒。 receiveTimeout: Duration(seconds: 5), // Http请求头. headers: {}, /// 请求的Content-Type,默认值是"application/json; charset=utf-8". /// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据, /// 可以设置此选项为 `Headers.formUrlEncodedContentType`, 这样[Dio] /// 就会自动编码请求体. contentType: "application/json; charset=utf-8", /// [responseType] 表示期望以那种格式(方式)接受响应数据。 /// 目前 [ResponseType] 接受三种类型 `JSON`, `STREAM`, `PLAIN`. /// /// 默认值是 `JSON`, 当响应头中content-type为"application/json"时,dio 会自动将响应内容转化为json对象。 /// 如果想以二进制方式接受响应数据,如下载一个二进制文件,那么可以使用 `STREAM`. /// /// 如果想以文本(字符串)格式接收响应数据,请使用 `PLAIN`. responseType: ResponseType.json, ); dio = Dio(options); // 代理设置 if (!ConfigService.to.isRelease && ProxyEnable) { dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { final client = HttpClient(); client.findProxy = (uri) { return "PROXY $ProxyIP:$ProxyPort"; }; // 代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验 client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; return client; } ); } // Cookie管理 CookieJar cookieJar = CookieJar(); // 增加拦截器CookieManager dio.interceptors.add(CookieManager(cookieJar)); // 添加内存缓存 if (CacheEnable) { dio.interceptors.add(NetCache()); } // 添加拦截器 dio.interceptors.add(InterceptorsWrapper( onRequest: (RequestOptions options, RequestInterceptorHandler handler) { // Do something before request is sent return handler.next(options); // 如果你想完成请求并返回一些自定义数据,你可以resolve一个Response对象 `handler.resolve(response)`。 // 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义response. // // 如果你想终止请求并触发一个错误,你可以返回一个`DioException`对象,如`handler.reject(error)`, // 这样请求将被中止并触发异常,上层catchError会被调用。 }, onResponse: (response, handler) { // Do something with response data return handler.next(response); // 如果你想终止请求并触发一个错误,你可以 reject 一个`DioException`对象,如`handler.reject(error)`, // 这样请求将被中止并触发异常,上层catchError会被调用。 }, onError: (DioException err, ErrorInterceptorHandler handler) { // Do something with response error Loading.dismiss(); ErrorEntity entity = createErrorEntity(err); onError(entity); return handler.next(err); // 如果你想完成请求并返回一些自定义数据,可以resolve 一个`Response`,如`handler.resolve(response)`。 // 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义response. }, )); } /// 统一错误处理 void onError(ErrorEntity errorEntity) { print( 'error.code -> ${errorEntity.code}, error.message -> ${errorEntity.message}'); switch (errorEntity.code) { case 401: // 会话过期 返回到登录页 UserService.to.onLogout(); EasyLoading.showError(errorEntity.message); break; default: EasyLoading.showError('未知错误'); break; } } // 错误信息 ErrorEntity createErrorEntity(DioException error) { switch (error.type) { case DioExceptionType.cancel: return ErrorEntity(code: -1, message: "请求取消"); case DioExceptionType.connectionTimeout: return ErrorEntity(code: -1, message: "连接超时"); case DioExceptionType.sendTimeout: return ErrorEntity(code: -1, message: "请求超时"); case DioExceptionType.receiveTimeout: return ErrorEntity(code: -1, message: "响应超时"); case DioExceptionType.badResponse: { try { int errCode = error.response != null ? error.response!.statusCode! : -1; // String errMsg = error.response.statusMessage; // return ErrorEntity(code: errCode, message: errMsg); switch (errCode) { case 400: return ErrorEntity(code: errCode, message: "请求语法错误"); case 401: return ErrorEntity(code: errCode, message: "没有权限"); case 403: return ErrorEntity(code: errCode, message: "服务器拒绝执行"); case 404: return ErrorEntity(code: errCode, message: "无法连接服务器"); case 405: return ErrorEntity(code: errCode, message: "请求方法被禁止"); case 500: return ErrorEntity(code: errCode, message: "服务器内部错误"); case 502: return ErrorEntity(code: errCode, message: "无效的请求"); case 503: return ErrorEntity(code: errCode, message: "服务器挂了"); case 505: return ErrorEntity(code: errCode, message: "不支持HTTP协议请求"); default: { // return ErrorEntity(code: errCode, message: "未知错误"); return ErrorEntity( code: errCode, message: error.response != null ? error.response!.statusMessage! : "", ); } } } on Exception catch (_) { return ErrorEntity(code: -1, message: "未知错误"); } } default: { return ErrorEntity(code: -1, message: error.message ?? "未定义错误"); } } } /// 取消请求 /// 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。 /// 所以参数可选 void cancelRequests(CancelToken token) { token.cancel("cancelled"); } /// 读取本地配置 Map? getAuthorizationHeader() { var headers = {}; if (Get.isRegistered() && UserService.to.hasToken) { headers['Authorization'] = 'Bearer ${UserService.to.token}'; } return headers; } /// restful get 操作 /// refresh 是否下拉刷新 默认 false /// noCache 是否不缓存 默认 true /// list 是否列表 默认 false /// cacheKey 缓存key /// cacheDisk 是否磁盘缓存 Future get( String path, { Map? queryParameters, Options? options, bool refresh = false, bool noCache = !CacheEnable, bool list = false, String cacheKey = '', bool cacheDisk = false, }) async { Options requestOptions = options ?? Options(); // 设置自定义字段 用于后续使用 requestOptions.extra ??= { "refresh": refresh, "noCache": noCache, "list": list, "cacheKey": cacheKey, "cacheDisk": cacheDisk, }; // 设置头信息 requestOptions.headers ??= {}; Map? authorization = getAuthorizationHeader(); if (authorization != null) { requestOptions.headers!.addAll(authorization); } var response = await dio.get( path, queryParameters: queryParameters, options: options, cancelToken: cancelToken, ); return response.data; } /// restful post 操作 Future post( String path, { dynamic data, Map? queryParameters, Options? options, }) async { Options requestOptions = options ?? Options(); requestOptions.headers = requestOptions.headers ?? {}; Map? authorization = getAuthorizationHeader(); if (authorization != null) { requestOptions.headers!.addAll(authorization); } var response = await dio.post( path, data: data, queryParameters: queryParameters, options: requestOptions, cancelToken: cancelToken, ); return response.data; } /// restful put 操作 Future put( String path, { dynamic data, Map? queryParameters, Options? options, }) async { Options requestOptions = options ?? Options(); requestOptions.headers = requestOptions.headers ?? {}; Map? authorization = getAuthorizationHeader(); if (authorization != null) { requestOptions.headers!.addAll(authorization); } var response = await dio.put( path, data: data, queryParameters: queryParameters, options: requestOptions, cancelToken: cancelToken, ); return response.data; } /// restful patch 操作 Future patch( String path, { dynamic data, Map? queryParameters, Options? options, }) async { Options requestOptions = options ?? Options(); requestOptions.headers = requestOptions.headers ?? {}; Map? authorization = getAuthorizationHeader(); if (authorization != null) { requestOptions.headers!.addAll(authorization); } var response = await dio.patch( path, data: data, queryParameters: queryParameters, options: requestOptions, cancelToken: cancelToken, ); return response.data; } /// restful delete 操作 Future delete( String path, { dynamic data, Map? queryParameters, Options? options, }) async { Options requestOptions = options ?? Options(); requestOptions.headers = requestOptions.headers ?? {}; Map? authorization = getAuthorizationHeader(); if (authorization != null) { requestOptions.headers!.addAll(authorization); } var response = await dio.delete( path, data: data, queryParameters: queryParameters, options: requestOptions, cancelToken: cancelToken, ); return response.data; } /// restful post form 表单提交操作 Future postForm( String path, { required Map data, Map? queryParameters, Options? options, }) async { Options requestOptions = options ?? Options(); requestOptions.headers = requestOptions.headers ?? {}; Map? authorization = getAuthorizationHeader(); if (authorization != null) { requestOptions.headers!.addAll(authorization); } var response = await dio.post( path, data: FormData.fromMap(data), queryParameters: queryParameters, options: requestOptions, cancelToken: cancelToken, ); return response.data; } /// restful post Stream 流数据 Future postStream( String path, { dynamic data, int dataLength = 0, Map? queryParameters, Options? options, }) async { Options requestOptions = options ?? Options(); requestOptions.headers = requestOptions.headers ?? {}; Map? authorization = getAuthorizationHeader(); if (authorization != null) { requestOptions.headers!.addAll(authorization); } requestOptions.headers!.addAll({ Headers.contentLengthHeader: dataLength.toString(), }); var response = await dio.post( path, data: Stream.fromIterable(data.map((e) => [e])), queryParameters: queryParameters, options: requestOptions, cancelToken: cancelToken, ); return response.data; } } class ErrorEntity implements Exception { int code = -1; String message = ""; ErrorEntity({required this.code, required this.message}); @override String toString() { if (message.isEmpty) return "Exception"; return "Exception: code $code, $message"; } }