Dio的封装

作者: A_si | 来源:发表于2020-06-14 15:31 被阅读0次

    dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等..

    基本使用

    添加依赖

    dependencies:
      dio: ^3.x.x  // 请使用pub上3.0.0分支的最新版本
    

    发起一个 GET 请求 :

    Response response;
    Dio dio = Dio();
    response = await dio.get("/test?id=12&name=wendu")
    print(response.data.toString());
    // 请求参数也可以通过对象传递,上面的代码等同于:
    response = await dio.get("/test", queryParameters: {"id": 12, "name": "wendu"});
    print(response.data.toString());
    

    发起一个 POST 请求:

    response = await dio.post("/test", data: {"id": 12, "name": "wendu"});
    
    

    发起多个并发请求:

    response = await Future.wait([dio.post("/info"), dio.get("/token")]);
    

    下载文件:

    response = await dio.download("https://www.google.com/", "./xx.html");
    

    发送 FormData:

    FormData formData = FormData.from({
        "name": "wendux",
        "age": 25,
      });
    response = await dio.post("/info", data: formData);
    

    通过FormData上传多个文件:

    FormData.fromMap({
        "name": "wendux",
        "age": 25,
        "file": await MultipartFile.fromFile("./text.txt",filename: "upload.txt"),
        "files": [
          await MultipartFile.fromFile("./text1.txt", filename: "text1.txt"),
          await MultipartFile.fromFile("./text2.txt", filename: "text2.txt"),
        ]
     });
    response = await dio.post("/info", data: formData);
    

    封装Dio

    为什么要封装 dio

    上面看了dio的api,非常灵活和简单,那么为什么还要封装呢?因为我们开发需要统一的配置场景。比如:

    • 全局token验证
    • 自定义拦截器
    • 缓存处理
    • 统一封装业务错误逻辑
    • 代理配置
    • 重试机制
    • log输出

    代理配置:

    flutter抓包需要配置dio代理,所以我们实现一个代理配置,proxy.dart:

    // 是否启用代理
    const PROXY_ENABLE = false;
    
    /// 代理服务IP
    // const PROXY_IP = '192.168.1.105';
    const PROXY_IP = '172.16.43.74';
    
    /// 代理服务端口
    const PROXY_PORT = 8866;
    

    错误处理:

    一般错误分为网络错误、请求错误、认证错误、服务器错误,所以实现统一的错误处理,认证错误需要登录等认证,所以单独一个类型,请求错误也单独设置一个类型,方便我们定位错误,app_exceptions.dart:

    import 'package:dio/dio.dart';
    
    /// 自定义异常
    class AppException implements Exception {
      final String _message;
      final int _code;
    
      AppException([
        this._code,
        this._message,
      ]);
    
      String toString() {
        return "$_code$_message";
      }
    
      factory AppException.create(DioError error) {
        switch (error.type) {
          case DioErrorType.CANCEL:
            {
              return BadRequestException(-1, "请求取消");
            }
            break;
          case DioErrorType.CONNECT_TIMEOUT:
            {
              return BadRequestException(-1, "连接超时");
            }
            break;
          case DioErrorType.SEND_TIMEOUT:
            {
              return BadRequestException(-1, "请求超时");
            }
            break;
          case DioErrorType.RECEIVE_TIMEOUT:
            {
              return BadRequestException(-1, "响应超时");
            }
            break;
          case DioErrorType.RESPONSE:
            {
              try {
                int errCode = error.response.statusCode;
                // String errMsg = error.response.statusMessage;
                // return ErrorEntity(code: errCode, message: errMsg);
                switch (errCode) {
                  case 400:
                    {
                      return BadRequestException(errCode, "请求语法错误");
                    }
                    break;
                  case 401:
                    {
                      return UnauthorisedException(errCode, "没有权限");
                    }
                    break;
                  case 403:
                    {
                      return UnauthorisedException(errCode, "服务器拒绝执行");
                    }
                    break;
                  case 404:
                    {
                      return UnauthorisedException(errCode, "无法连接服务器");
                    }
                    break;
                  case 405:
                    {
                      return UnauthorisedException(errCode, "请求方法被禁止");
                    }
                    break;
                  case 500:
                    {
                      return UnauthorisedException(errCode, "服务器内部错误");
                    }
                    break;
                  case 502:
                    {
                      return UnauthorisedException(errCode, "无效的请求");
                    }
                    break;
                  case 503:
                    {
                      return UnauthorisedException(errCode, "服务器挂了");
                    }
                    break;
                  case 505:
                    {
                      return UnauthorisedException(errCode, "不支持HTTP协议请求");
                    }
                    break;
                  default:
                    {
                      // return ErrorEntity(code: errCode, message: "未知错误");
                      return AppException(errCode, error.response.statusMessage);
                    }
                }
              } on Exception catch (_) {
                return AppException(-1, "未知错误");
              }
            }
            break;
          default:
            {
              return AppException(-1, error.message);
            }
        }
      }
    }
    
    /// 请求错误
    class BadRequestException extends AppException {
      BadRequestException([int code, String message]) : super(code, message);
    }
    
    /// 未认证异常
    class UnauthorisedException extends AppException {
      UnauthorisedException([int code, String message]) : super(code, message);
    }
    
    

    Error拦截器:

    有了上面的异常类型,我们要把DioError变成自己定义的异常:

    import 'package:dio/dio.dart';
    import 'package:flutter/material.dart';
    
    import 'app_exceptions.dart';
    
    /// 错误处理拦截器
    class ErrorInterceptor extends Interceptor {
      @override
      Future onError(DioError err) {
        // error统一处理
        AppException appException = AppException.create(err);
        // 错误提示
        debugPrint('DioError===: ${appException.toString()}');
        err.error = appException;
        return super.onError(err);
      }
    }
    

    http单例操作类:

    利用单利和配置,实现一个dio的封装。

    class Http {
      ///超时时间
      static const int CONNECT_TIMEOUT = 30000;
      static const int RECEIVE_TIMEOUT = 30000;
    
      static Http _instance = Http._internal();
      factory Http() => _instance;
    
      Dio dio;
      CancelToken _cancelToken = new CancelToken();
    
      Http._internal() {
        if (dio == null) {
          // BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
          BaseOptions options = new BaseOptions(
            connectTimeout: CONNECT_TIMEOUT,
    
            // 响应流上前后两次接受到数据的间隔,单位为毫秒。
            receiveTimeout: RECEIVE_TIMEOUT,
    
            // Http请求头.
            headers: {},
          );
    
          dio = new Dio(options);
    
          // 添加error拦截器
          dio.interceptors
              .add(ErrorInterceptor());
    
          // 在调试模式下需要抓包调试,所以我们使用代理,并禁用HTTPS证书校验
          if (PROXY_ENABLE) {
            (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
                (client) {
              client.findProxy = (uri) {
                return "PROXY $PROXY_IP:$PROXY_PORT";
              };
              //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
              client.badCertificateCallback =
                  (X509Certificate cert, String host, int port) => true;
            };
          }
        }
      }
    
        ///初始化公共属性
      ///
      /// [baseUrl] 地址前缀
      /// [connectTimeout] 连接超时赶时间
      /// [receiveTimeout] 接收超时赶时间
      /// [interceptors] 基础拦截器
      void init(
          {String baseUrl,
          int connectTimeout,
          int receiveTimeout,
          List<Interceptor> interceptors}) {
        dio.options = dio.options.merge(
          baseUrl: baseUrl,
          connectTimeout: connectTimeout,
          receiveTimeout: receiveTimeout,
        );
        if (interceptors != null && interceptors.isNotEmpty) {
          dio.interceptors..addAll(interceptors);
        }
      }
    }
    

    增加取消功能:

     /*
       * 取消请求
       *
       * 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。
       * 所以参数可选
       */
      void cancelRequests({CancelToken token}) {
        token ?? _cancelToken.cancel("cancelled");
      }
    

    增加认证header:

    /// 读取本地配置
      Map<String, dynamic> getAuthorizationHeader() {
        var headers;
        String accessToken = Global.accessToken;
        if (accessToken != null) {
          headers = {
            'Authorization': 'Bearer $accessToken',
          };
        }
        return headers;
      }
    

    添加cookie和cache:

    添加cookie管理:

      cookie_jar: ^1.0.1
      dio_cookie_manager: ^1.0.0
    
        // Cookie管理
        CookieJar cookieJar = CookieJar();
        dio.interceptors.add(CookieManager(cookieJar));
         // 加内存缓存
        dio.interceptors.add(NetCache());
    

    利用sp做磁盘缓存:

      shared_preferences: ^0.5.6+3
    

    编写cache类,net_cache.dart:

    class CacheObject {
      CacheObject(this.response)
          : timeStamp = DateTime.now().millisecondsSinceEpoch;
      Response response;
      int timeStamp;
    
      @override
      bool operator ==(other) {
        return response.hashCode == other.hashCode;
      }
    
      @override
      int get hashCode => response.realUri.hashCode;
    }
    
    class NetCacheInterceptor extends Interceptor {
      // 为确保迭代器顺序和对象插入时间一致顺序一致,我们使用LinkedHashMap
      var cache = LinkedHashMap<String, CacheObject>();
    
      @override
      onRequest(RequestOptions options) async {
        if (!CACHE_ENABLE) return options;
    
        // refresh标记是否是刷新缓存
        bool refresh = options.extra["refresh"] == true;
    
        // 是否磁盘缓存
        bool cacheDisk = options.extra["cacheDisk"] == true;
    
        // 如果刷新,先删除相关缓存
        if (refresh) {
          // 删除uri相同的内存缓存
          delete(options.uri.toString());
    
          // 删除磁盘缓存
          if (cacheDisk) {
            await SpUtil().remove(options.uri.toString());
          }
    
          return options;
        }
    
        // get 请求,开启缓存
        if (options.extra["noCache"] != true &&
            options.method.toLowerCase() == 'get') {
          String key = options.extra["cacheKey"] ?? options.uri.toString();
    
          // 策略 1 内存缓存优先,2 然后才是磁盘缓存
    
          // 1 内存缓存
          var ob = cache[key];
          if (ob != null) {
            //若缓存未过期,则返回缓存内容
            if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <
                CACHE_MAXAGE) {
              return cache[key].response;
            } else {
              //若已过期则删除缓存,继续向服务器请求
              cache.remove(key);
            }
          }
    
          // 2 磁盘缓存
          if (cacheDisk) {
            var cacheData = SpUtil().getJSON(key);
            if (cacheData != null) {
              return Response(
                statusCode: 200,
                data: cacheData,
              );
            }
          }
        }
      }
    
      @override
      onError(DioError err) async {
        // 错误状态不缓存
      }
    
      @override
      onResponse(Response response) async {
        // 如果启用缓存,将返回结果保存到缓存
        if (CACHE_ENABLE) {
          await _saveCache(response);
        }
      }
    
      Future<void> _saveCache(Response object) async {
        RequestOptions options = object.request;
    
        // 只缓存 get 的请求
        if (options.extra["noCache"] != true &&
            options.method.toLowerCase() == "get") {
          // 策略:内存、磁盘都写缓存
    
          // 缓存key
          String key = options.extra["cacheKey"] ?? options.uri.toString();
    
          // 磁盘缓存
          if (options.extra["cacheDisk"] == true) {
            await SpUtil().setJSON(key, object.data);
          }
    
          // 内存缓存
          // 如果缓存数量超过最大数量限制,则先移除最早的一条记录
          if (cache.length == CACHE_MAXCOUNT) {
            cache.remove(cache[cache.keys.first]);
          }
    
          cache[key] = CacheObject(object);
        }
      }
    
      void delete(String key) {
        cache.remove(key);
      }
    }
    
    

    dio加入缓存:

       // 加内存缓存
          dio.interceptors.add(NetCacheInterceptor());
    

    重试拦截器:

    在网络断开的时候,监听网络,等重连的时候重试:

    import 'dart:io';
    
    import 'package:dio/dio.dart';
    import 'package:flutter/cupertino.dart';
    
    import 'connectivity_request_retrier.dart';
    
    class RetryOnConnectionChangeInterceptor extends Interceptor {
      final DioConnectivityRequestRetrier requestRetrier;
    
      RetryOnConnectionChangeInterceptor({
        @required this.requestRetrier,
      });
    
      @override
      Future onError(DioError err) async {
        if (_shouldRetry(err)) {
          try {
            return requestRetrier.scheduleRequestRetry(err.request);
          } catch (e) {
            return e;
          }
        }
        return err;
      }
    
      bool _shouldRetry(DioError err) {
        return err.type == DioErrorType.DEFAULT &&
            err.error != null &&
            err.error is SocketException;
      }
    }
    
    
    import 'dart:async';
    
    import 'package:connectivity/connectivity.dart';
    import 'package:dio/dio.dart';
    import 'package:flutter/material.dart';
    
    class DioConnectivityRequestRetrier {
      final Dio dio;
      final Connectivity connectivity;
    
      DioConnectivityRequestRetrier({
        @required this.dio,
        @required this.connectivity,
      });
    
      Future<Response> scheduleRequestRetry(RequestOptions requestOptions) async {
        StreamSubscription streamSubscription;
        final responseCompleter = Completer<Response>();
    
        streamSubscription = connectivity.onConnectivityChanged.listen(
          (connectivityResult) {
            if (connectivityResult != ConnectivityResult.none) {
              streamSubscription.cancel();
              responseCompleter.complete(
                dio.request(
                  requestOptions.path,
                  cancelToken: requestOptions.cancelToken,
                  data: requestOptions.data,
                  onReceiveProgress: requestOptions.onReceiveProgress,
                  onSendProgress: requestOptions.onSendProgress,
                  queryParameters: requestOptions.queryParameters,
                  options: requestOptions,
                ),
              );
            }
          },
        );
    
        return responseCompleter.future;
      }
    }
    

    添加重试拦截器:

          if (Global.retryEnable) {
            dio.interceptors.add(
              RetryOnConnectionChangeInterceptor(
                requestRetrier: DioConnectivityRequestRetrier(
                  dio: Dio(),
                  connectivity: Connectivity(),
                ),
              ),
            );
          }
    

    restful请求:

     /// restful get 操作
      Future get(
        String path, {
        Map<String, dynamic> params,
        Options options,
        CancelToken cancelToken,
        bool refresh = false,
        bool noCache = !CACHE_ENABLE,
        String cacheKey,
        bool cacheDisk = false,
      }) async {
        Options requestOptions = options ?? Options();
        requestOptions = requestOptions.merge(extra: {
          "refresh": refresh,
          "noCache": noCache,
          "cacheKey": cacheKey,
          "cacheDisk": cacheDisk,
        });
        Map<String, dynamic> _authorization = getAuthorizationHeader();
        if (_authorization != null) {
          requestOptions = requestOptions.merge(headers: _authorization);
        }
        Response response;
        response = await dio.get(path,
            queryParameters: params,
            options: requestOptions,
            cancelToken: cancelToken ?? _cancelToken);
        return response.data;
      }
    
      /// restful post 操作
      Future post(
        String path, {
        Map<String, dynamic> params,
        data,
        Options options,
        CancelToken cancelToken,
      }) async {
        Options requestOptions = options ?? Options();
        Map<String, dynamic> _authorization = getAuthorizationHeader();
        if (_authorization != null) {
          requestOptions = requestOptions.merge(headers: _authorization);
        }
        var response = await dio.post(path,
            data: data,
            queryParameters: params,
            options: requestOptions,
            cancelToken: cancelToken ?? _cancelToken);
        return response.data;
      }
    
      /// restful put 操作
      Future put(
        String path, {
        data,
        Map<String, dynamic> params,
        Options options,
        CancelToken cancelToken,
      }) async {
        Options requestOptions = options ?? Options();
    
        Map<String, dynamic> _authorization = getAuthorizationHeader();
        if (_authorization != null) {
          requestOptions = requestOptions.merge(headers: _authorization);
        }
        var response = await dio.put(path,
            data: data,
            queryParameters: params,
            options: requestOptions,
            cancelToken: cancelToken ?? _cancelToken);
        return response.data;
      }
    
      /// restful patch 操作
      Future patch(
        String path, {
        data,
        Map<String, dynamic> params,
        Options options,
        CancelToken cancelToken,
      }) async {
        Options requestOptions = options ?? Options();
        Map<String, dynamic> _authorization = getAuthorizationHeader();
        if (_authorization != null) {
          requestOptions = requestOptions.merge(headers: _authorization);
        }
        var response = await dio.patch(path,
            data: data,
            queryParameters: params,
            options: requestOptions,
            cancelToken: cancelToken ?? _cancelToken);
        return response.data;
      }
    
      /// restful delete 操作
      Future delete(
        String path, {
        data,
        Map<String, dynamic> params,
        Options options,
        CancelToken cancelToken,
      }) async {
        Options requestOptions = options ?? Options();
    
        Map<String, dynamic> _authorization = getAuthorizationHeader();
        if (_authorization != null) {
          requestOptions = requestOptions.merge(headers: _authorization);
        }
        var response = await dio.delete(path,
            data: data,
            queryParameters: params,
            options: requestOptions,
            cancelToken: cancelToken ?? _cancelToken);
        return response.data;
      }
    
      /// restful post form 表单提交操作
      Future postForm(
        String path, {
        Map<String, dynamic> params,
        Options options,
        CancelToken cancelToken,
      }) async {
        Options requestOptions = options ?? Options();
        Map<String, dynamic> _authorization = getAuthorizationHeader();
        if (_authorization != null) {
          requestOptions = requestOptions.merge(headers: _authorization);
        }
        var response = await dio.post(path,
            data: FormData.fromMap(params),
            options: requestOptions,
            cancelToken: cancelToken ?? _cancelToken);
        return response.data;
      }
    

    通常我们喜欢静态方法调用,所以新建一个类:

    import 'package:dio/dio.dart';
    import 'package:flutter_dio/http/http.dart';
    
    import 'app_exceptions.dart';
    import 'cache.dart';
    
    class HttpUtils {
      static void init(
          {String baseUrl,
          int connectTimeout,
          int receiveTimeout,
          List<Interceptor> interceptors}) {
        Http().init(
            baseUrl: baseUrl,
            connectTimeout: connectTimeout,
            receiveTimeout: receiveTimeout,
            interceptors: interceptors);
      }
    
      static void setHeaders(Map<String, dynamic> map) {
        Http().setHeaders(map);
      }
    
      static void cancelRequests({CancelToken token}) {
        Http().cancelRequests(token: token);
      }
    
      static Future get(
        String path, {
        Map<String, dynamic> params,
        Options options,
        CancelToken cancelToken,
        bool refresh = false,
        bool noCache = !CACHE_ENABLE,
        String cacheKey,
        bool cacheDisk = false,
      }) async {
        return await Http().get(
          path,
          params: params,
          options: options,
          cancelToken: cancelToken,
          refresh: refresh,
          noCache: noCache,
          cacheKey: cacheKey,
        );
      }
    
      static Future post(
        String path, {
        data,
        Map<String, dynamic> params,
        Options options,
        CancelToken cancelToken,
      }) async {
        return await Http().post(
          path,
          data: data,
          params: params,
          options: options,
          cancelToken: cancelToken,
        );
      }
    
      static Future put(
        String path, {
        data,
        Map<String, dynamic> params,
        Options options,
        CancelToken cancelToken,
      }) async {
        return await Http().put(
          path,
          data: data,
          params: params,
          options: options,
          cancelToken: cancelToken,
        );
      }
    
      static Future patch(
        String path, {
        data,
        Map<String, dynamic> params,
        Options options,
        CancelToken cancelToken,
      }) async {
        return await Http().patch(
          path,
          data: data,
          params: params,
          options: options,
          cancelToken: cancelToken,
        );
      }
    
      static Future delete(
        String path, {
        data,
        Map<String, dynamic> params,
        Options options,
        CancelToken cancelToken,
      }) async {
        return await Http().delete(
          path,
          data: data,
          params: params,
          options: options,
          cancelToken: cancelToken,
        );
      }
    
      static Future postForm(
        String path, {
        Map<String, dynamic> params,
        Options options,
        CancelToken cancelToken,
      }) async {
        return await Http().postForm(
          path,
          params: params,
          options: options,
          cancelToken: cancelToken,
        );
      }
    }
    
    

    使用:

    初始化:

    void main() {
      HttpUtils.init(
        baseUrl: "https://gan.io/",
      );
      runApp(MyApp());
    }
    

    在model里写接口请求:

      static Future<ApiResponse<CategoryEntity>> getCategories() async {
        try {
          final response = await HttpUtils.get(categories);
          var data = CategoryEntity.fromJson(response);
          return ApiResponse.completed(data);
        } on DioError catch (e) {
          return ApiResponse.error(e.error);
        }
      }
    

    调用:

    void getCategories() async {
      ApiResponse<CategoryEntity> entity = await GanRepository.getCategories();
      print(entity.data.data.length);
    }
    

    代码地址

    相关文章

      网友评论

        本文标题:Dio的封装

        本文链接:https://www.haomeiwen.com/subject/yorbtktx.html