Flutter 学习 之 DIO4.0 的封装

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

一.引入插件

在 pubspec.yaml 文件下新增 dio(注意空格问题)

dependencies:
  dio: ^4.0.6

二. 封装DIO

1.创建DioClient单例模式,实现访问方法

// 必须是顶层函数
_parseAndDecode(String response) {
  return jsonDecode(response);
}

parseJson(String text) {
  return compute(_parseAndDecode, text);
}
  //继承DioForNavigator(详情见官方文档) 
class DioClient extends DioForNative {
  //单例模式
  static DioClient? _instance;
  factory DioClient() => _instance ??= DioClient._init();
    //初始化方法
  DioClient._init() {
    (transformer as DefaultTransformer).jsonDecodeCallback = parseJson;
    options = BaseOptions(
    //设定一些基础的东西
      connectTimeout: 60*1000,//连接超时间
      receiveTimeout: 60*1000,//接收超时时间
      //除了在这里定义还可以到拦截器中定义
    );
    //处理访问前的拦截器
    interceptors.add(OptionInterceptor());
    //处理回来的数据
    interceptors.add(RequestInterceptor());
    //代理抓包(开发阶段可能用到,正式上线建议关闭)
    proxy();
  }

  ///get请求
  doGet(path, {queryParameters, options, cancelToken, onReceiveProgress}) {
    return get(path,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onReceiveProgress: onReceiveProgress);
  }

  ///post请求 为了不和继承的DioMixin里面的post方法名冲突所以起名叫doPost
  doPost(path,
      {queryParameters,
      options,
      cancelToken,
      onSendProgress,
      onReceiveProgress}) {
    return post(path,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress);
  }
  ///上传文件
  uploadFile(formData) {
    var uploadOptions = Options(contentType: "multipart/form-data");
    return doPost(Api.uploadURL, options: uploadOptions, data: formData);
  }
  ///代理抓包测试用
  void proxy() {
    if (NetworkConfig.proxyEnable) {
      if (NetworkConfig.caughtAddress != "") {
        (httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
            (client) {
          client.findProxy = (Uri uri) {
            return 'PROXY ' + NetworkConfig.caughtAddress;
          };
          client.badCertificateCallback =
              (X509Certificate cert, String host, int port) {
            return true;
          };
        };
      }
    }
  }
}

2.封装拦截器

dio的请求流程是 请求拦截器 >> 请求转换器 >> 发起请求 >> 响应转换器 >> 响应拦截器 >> 最终结果。

请求拦截器

//Option拦截器可以用来统一处理Option信息 可以在这里添加
class OptionInterceptor extends InterceptorsWrapper {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    //在请求发起前修改头部
    // options.headers["token"] = "11111";
  ///请求的Content-Type,默认值是"application/json; charset=utf-8".
  /// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据,
    options.contentType=Headers.formUrlEncodedContentType;
///如果你的headers是固定的你可以在BaseOption中设置,如果不固定可以在这里进行根据条件设置
    options.headers["apiToken"] = "111222154546";
    options.headers["user-token"]=CacheUtil().getJson(SPName.userInfo)!["userToken"];
    String? mainUrl = CacheUtil().get(SPName.mainUrl);
    //修改地址
    //如果需要改变主地址可以在这里设置
    if (StringUtil.isNotEmpty(mainUrl)) {
      options.baseUrl = mainUrl!;
    } else {
      options.baseUrl = NetworkConfig.baseUrl;
    }
    //开发阶段可以把地址带参数打印出来方便校验结果
    debugPrint(
        "request:${options.method}\t url-->${options.baseUrl}${options.path}?${FormatUtil.formattedUrl(options.queryParameters)}");

    if (options.queryParameters["hideLoading"] != true) {
      EasyLoading.show();
    }
// 一定要加上这句话 否则进入不了下一步
    return handler.next(options);
  }
}

  ///格式化url,将post和get请求以get链接输出
  static String formattedUrl(params) {
    var urlParamsStr = "";
    if (params?.isNotEmpty ?? false) {
      var tempArr = [];
      params.forEach((k, v) {
        tempArr.add(k + '=' + v.toString());
      });
      urlParamsStr = tempArr.join('&');
    }
    return urlParamsStr;
  }

响应拦截器

这一部分需要和实际相结合,根据每个后端返回的数据不同灵活配置

///拦截器 数据初步处理
class RequestInterceptor extends InterceptorsWrapper {
  //请求后 成功走这里
  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    EasyLoading.dismiss();
    if (response.statusCode == 200) {
      //访问正确有返回值的情况
      if (response.data is Map) {
        //将数据脱壳需要返回自己的数据
        ResponseData responseData = ResponseData.fromJson(response.data);
        if (responseData.success) {
          response.data = responseData.data;
          response.statusCode = responseData.respCode;
          response.statusMessage = responseData.respDesc;
          return handler.resolve(response);
        }
        return handler.resolve(response);
      } else if (response.data is String) {
        //  {"respCode":403,"respDesc":"非法访问"}
        ResponseError model = ResponseError.fromJson(jsonDecode(response.data));
        response.statusCode = model.respCode;
        if (model.respCode == 403 || model.respCode == 402) {
          //做些什么
          throwUnauthorizedError(response);
        }else{
          throwError(response);
        }
      } else {
        throwError(response);
      }
    } else {
      throwError(response);
    }
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) {
    EasyLoading.dismiss();

    throw DioError(
        requestOptions: err.requestOptions,
        type: err.type,
        error: err,
        response: err.response);
  }

  ///抛出异常 留给baseModel去统一处理
  void throwError(Response response) {
    throw DioError(
        requestOptions: response.requestOptions,
        error: ResponseException(errCode: response.statusCode));
  }
}
  ///鉴权错误
  void throwUnauthorizedError(Response response) {
    throw DioError(
        requestOptions: response.requestOptions,
        error: UnauthorizedError(errCode: response.statusCode));
  }

上述中用到的类

abstract class BaseResponseData{
  int? respCode;
  String? respDesc;
  dynamic attribute;
  dynamic data;
  bool get success;

  BaseResponseData({this.respCode, this.respDesc, this.attribute, this.data});

  @override
  String toString() {
    return 'BaseRespData{code: $respCode, message: $respDesc, data: $attribute}';
  }
}

class ResponseData extends BaseResponseData {
  @override
  bool get success => respCode != null || data != null;

  ResponseData.fromJson(Map json) {
    if (json['respCode'] != null && json['respCode'] is String) {
      json['respCode'] = int.parse(json['respCode']);
    }
    respCode = json['respCode'] ?? json['code'];
    respDesc = json['respDesc'] ?? json['message'] ?? json['msg'];
    attribute = json['attribute'] ?? json["data"];
    if (attribute != null) {
      if (attribute is Map && attribute.containsKey("data")) {
        data = attribute['data'];
      } else {
        data = attribute;
      }
    } else {
      data = json;
    }
  }
}


class ResponseError extends BaseResponseData {

  ResponseError.fromJson(Map json) {
    respDesc = json["respDesc"];
    respCode = json["respCode"];
  }

  Map toJson() {
    Map data = {};
    data["respDesc"] = respDesc;
    data["respCode"] = respCode;
    return data;
  }

  @override
  // TODO: implement success
  bool get success => false;
}

class ResponseException implements Exception {
  int? errCode;
  String? errMsg;

  ResponseException({this.errCode});

  int? get errorCode => errCode;

//statusCode==200时候返回的data中存在的respCode
  String? get errorMessage {
    String msg = errMsg ?? "";
    switch (errCode) {
      default:
    }
    return msg;
  }
  @override
  String toString() {
    return 'RequestException{errorCode: $errorCode, errorMessage: $errorMessage}';
  }
}

捕获错误并提示

DioErrorType 分六种 connectTimeout,sendTimeout,receiveTimeout,response,cancel,other,
其实加上刚才我们自定义type 总共可以分成四类 超时的 返回错误的 取消的 和其他

  ///格式化Dio返回的Error
  ///[e] catch到的error
  static ErrorMessageModel dioErrorFormat(e) {
    int? errorCode;
    StateErrorType errorType = StateErrorType.defaultError;
    String errMsg = "网络离家出走了~";
    //判断一下抛出的异常是不是DIO包裹的异常
    if (e is DioError) {
      //是不是各种超时
      if (e.type == DioErrorType.receiveTimeout ||
          e.type == DioErrorType.sendTimeout ||
          e.type == DioErrorType.receiveTimeout) {
        errorType = StateErrorType.networkTimeoutError;
        errMsg = "连接超时了";
      } else if (e.type == DioErrorType.response) {
        //访问出的各种错 访问中statusCode是400/500代码都会走到这里 如果想详细展示具体是什么错误可以继续细分
        errorType = StateErrorType.responseException;
        errMsg = _getNumberMeans(e);
      } else if (e.type == DioErrorType.cancel) {
        //如果是取消访问了走这里
        errMsg = e.message;
      } else {
        //这里是刚才DIOerror包裹的自定义错误
       // 这里由于没有定义error.type所以用error的类型判断
        dynamic otherError = e.error;
        dynamic otherE;
        if (otherError is DioError) {
          otherE = otherError.error;
        }
        if (otherE is ResponseException) {
          errorType = StateErrorType.responseException;
          errMsg = otherE.errorMessage ?? "";
          errorCode = otherE.errorCode;
        } else if (otherE is SocketException) {
          errorType = StateErrorType.networkTimeoutError;
          errMsg = "网络无连接,请检查网络设置";
        } else {
          errorType = StateErrorType.defaultError;
          errMsg = "网络无连接,请检查网络设置";
        }
      }
    } else {
      errorType = StateErrorType.defaultError;
      errMsg = "出问题了~~~";
    }
    return ErrorMessageModel(
        errorType: errorType, message: errMsg, errorCode: errorCode);
  }

将获取到的状态码转成中文提示

  ///获取到的数值转换成文字
  static String _getNumberMeans(DioError e) {
    String str;
    if (e.response?.statusCode != null) {
      switch (e.response?.statusCode) {
        case 400:
          str = "[${e.response?.statusCode}] 参数有误";
          break;
        case 402:
          str = "[${e.response?.statusCode}] 啊 这是一个非法请求呢";
          break;
        case 403:
          str = "[${e.response?.statusCode}] 服务器拒绝请求";
          break;
        case 404:
          str = "[${e.response?.statusCode}] 访问地址不存在";
          break;
        case 405:
          str = "[${e.response?.statusCode}] 请求方式错误";
          break;
        case 500:
          str = "[${e.response?.statusCode}] 服务器内部出错了";
          break;
        case 502:
          str = "[${e.response?.statusCode}] 无效的请求哦";
          break;
        case 503:
          str = "[${e.response?.statusCode}] 服务器说他在忙";
          break;
        case 505:
          str = "[${e.response?.statusCode}] 不支持的HTTP协议";
          break;
        default:
          str = "[${e.response?.statusCode}] 未知错误";
          break;
      }
    } else {
      str = e.message;
    }
    return str;
  }

上面用到的一个类


class ErrorMessageModel {
  StateErrorType? errorType;
  String? message;
  int? errorCode;

  ErrorMessageModel({
    this.errorType = StateErrorType.defaultError,
    this.message = "出错啦,请稍后重试~",
    this.errorCode,
  });

  ErrorMessageModel.fromJson(Map json) {
    errorType = json['errorType'];
    message = json['message'];
    errorCode = json['errorCode'];
  }

  Map toJson() {
    final Map data = {};
    data['errorType'] = errorType;
    data['message'] = message;
    data['errorCode'] = errorCode;
    return data;
  }
}

配合provider 根据错误不同提示不同页面即可

三 使用

   Future login(Map param) async {
    Response response =
        await DioClient().doPost(Api.login, queryParameters: param);
   LoginModel model =LoginModel.fromJson(response.data);
   return model;
  }

你可能感兴趣的:(Flutter 学习 之 DIO4.0 的封装)