分析需求
为什么要封装?
全局token验证
自定义拦截器
缓存处理
统一封装业务错误逻辑
代理配置
重试机制
log输出
自定义解析,数据脱壳
要初始化哪些配置?
- 域名
 - 代理地址
 - cookie本地缓存地址
 - 超时时间
 - 自定义拦截器
 
定义一个配置信息类去初始化这些配置:
// dio 配置项
class HttpConfig {
  final String? baseUrl;
  final String? proxy;
  final String? cookiesPath;
  final List<Interceptor>? interceptors;
  final int connectTimeout;
  final int sendTimeout;
  final int receiveTimeout;
  HttpConfig({
    this.baseUrl,
    this.proxy,
    this.cookiesPath,
    this.interceptors,
    this.connectTimeout = Duration.millisecondsPerMinute,
    this.sendTimeout = Duration.millisecondsPerMinute,
    this.receiveTimeout = Duration.millisecondsPerMinute,
  });
  // static DioConfig of() => Get.find<DioConfig>();
}
请求差异化有哪些配置?
- 
解析策略
许多公司接口规范经历过变更,有多个返回类型,那么就需要针对不同的数据类型,做不同的解析。
比如旧版本:
// 旧版本 { "code": 1, "data": {}, "state": true } // 新版本 { "code": 1, "data": { "data": {}, "hasmore":false }, "message": “success” }要做到脱壳,拿到解析后的
data,就需要两种解析策略。所以需要根据不同接口动态配置解析策略。 path
参数
cancelToken
- 
dio 的常用参数
Dio 的请求参数已经很全面的包括了分析出的配置参数,只需要另添加一个解析策略即可。
遵守 SOLID 原则定义一个抽象解析策略:
/// Response 解析 abstract class HttpTransformer { HttpResponse parse(Response response); }根据实际需求默认实现:
class DefaultHttpTransformer extends HttpTransformer { // 假设接口返回类型 // { // "code": 100, // "data": {}, // "message": "success" // } @override HttpResponse parse(Response response) { // if (response.data["code"] == 100) { // return HttpResponse.success(response.data["data"]); // } else { // return HttpResponse.failure(errorMsg:response.data["message"],errorCode: response.data["code"]); // } return HttpResponse.success(response.data["data"]); } /// 单例对象 static DefaultHttpTransformer _instance = DefaultHttpTransformer._internal(); /// 内部构造方法,可避免外部暴露构造函数,进行实例化 DefaultHttpTransformer._internal(); /// 工厂构造方法,这里使用命名构造函数方式进行声明 factory DefaultHttpTransformer.getInstance() => _instance; }单例模式是为了避免多次创建实例。方便下一步使用。
 
异常处理
异常大体分为以下几种:
- 网络异常
 - 客户端请求异常
 - 服务端异常
 
客户端异常又可拆分两种常见的异常:请求参数或路径错误,鉴权失败/token失效
异常归档后创建异常:
class HttpException implements Exception {
  final String? _message;
  String get message => _message ?? this.runtimeType.toString();
  final int? _code;
  int get code => _code ?? -1;
  HttpException([this._message, this._code]);
  String toString() {
    return "code:$code--message=$message";
  }
}
/// 客户端请求错误
class BadRequestException extends HttpException {
  BadRequestException({String? message, int? code}) : super(message, code);
}
/// 服务端响应错误
class BadServiceException extends HttpException {
  BadServiceException({String? message, int? code}) : super(message, code);
}
class UnknownException extends HttpException {
  UnknownException([String? message]) : super(message);
}
class CancelException extends HttpException {
  CancelException([String? message]) : super(message);
}
class NetworkException extends HttpException {
  NetworkException({String? message, int? code}) : super(message, code);
}
/// 401
class UnauthorisedException extends HttpException {
  UnauthorisedException({String? message, int? code = 401}) : super(message);
}
class BadResponseException extends HttpException {
  dynamic? data;
  BadResponseException([this.data]) : super();
}
返回数据类型
返回的数据类型,需要有成功或是失败的标识,还需要脱壳后的数据,如果失败了,也需要失败的信息,定义几个工厂方法方便创建实例:
class HttpResponse {
  late bool ok;
  dynamic? data;
  HttpException? error;
  HttpResponse._internal({this.ok = false});
  HttpResponse.success(this.data) {
    this.ok = true;
  }
  HttpResponse.failure({String? errorMsg, int? errorCode}) {
    this.error = BadRequestException(message: errorMsg, code: errorCode);
    this.ok = false;
  }
  HttpResponse.failureFormResponse({dynamic? data}) {
    this.error = BadResponseException(data);
    this.ok = false;
  }
  HttpResponse.failureFromError([HttpException? error]) {
    this.error = error ?? UnknownException();
    this.ok = false;
  }
}
开始封装
配置 Dio
Dio 配置组装,需要我们定义一个初始化类,用于把请求的初始化配置添加进去。一般可以定义一个单例类,init方法里去初始化一个 Dio ,也可以采用实现 Dio 的方式:
class AppDio with DioMixin implements Dio {
  AppDio({BaseOptions? options, HttpConfig? dioConfig}) {
    options ??= BaseOptions(
      baseUrl: dioConfig?.baseUrl ?? "",
      contentType: 'application/json',
      connectTimeout: dioConfig?.connectTimeout,
      sendTimeout: dioConfig?.sendTimeout,
      receiveTimeout: dioConfig?.receiveTimeout,
    );
    this.options = options;
    // DioCacheManager
    final cacheOptions = CacheOptions(
      // A default store is required for interceptor.
      store: MemCacheStore(),
      // Optional. Returns a cached response on error but for statuses 401 & 403.
      hitCacheOnErrorExcept: [401, 403],
      // Optional. Overrides any HTTP directive to delete entry past this duration.
      maxStale: const Duration(days: 7),
    );
    interceptors.add(DioCacheInterceptor(options: cacheOptions));
    // Cookie管理
    if (dioConfig?.cookiesPath?.isNotEmpty ?? false) {
      interceptors.add(CookieManager(
          PersistCookieJar(storage: FileStorage(dioConfig!.cookiesPath))));
    }
    if (kDebugMode) {
      interceptors.add(LogInterceptor(
          responseBody: true,
          error: true,
          requestHeader: false,
          responseHeader: false,
          request: false,
          requestBody: true));
    }
    if (dioConfig?.interceptors?.isNotEmpty ?? false) {
      interceptors.addAll(interceptors);
    }
    httpClientAdapter = DefaultHttpClientAdapter();
    if (dioConfig?.proxy?.isNotEmpty ?? false) {
      setProxy(dioConfig!.proxy!);
    }
  }
  setProxy(String proxy) {
    (httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
        (client) {
      // config the http client
      client.findProxy = (uri) {
        // proxy all request to localhost:8888
        return "PROXY $proxy";
      };
      // you can also create a HttpClient to dio
      // return HttpClient();
    };
  }
}
Restful请求
采用 Restful 标准,创建对应的请求方法:
class HttpClient {
  late AppDio _dio;
  HttpClient({BaseOptions? options, HttpConfig? dioConfig})
      : _dio = AppDio(options: options, dioConfig: dioConfig);
  Future<HttpResponse> get(String uri,
      {Map<String, dynamic>? queryParameters,
      Options? options,
      CancelToken? cancelToken,
      ProgressCallback? onReceiveProgress,
      HttpTransformer? httpTransformer}) async {
    try {
      var response = await _dio.get(
        uri,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onReceiveProgress: onReceiveProgress,
      );
      return handleResponse(response, httpTransformer: httpTransformer);
    } on Exception catch (e) {
      return handleException(e);
    }
  }
  Future<HttpResponse> post(String uri,
      {data,
      Map<String, dynamic>? queryParameters,
      Options? options,
      CancelToken? cancelToken,
      ProgressCallback? onSendProgress,
      ProgressCallback? onReceiveProgress,
      HttpTransformer? httpTransformer}) async {
    try {
      var response = await _dio.post(
        uri,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress,
      );
      return handleResponse(response, httpTransformer: httpTransformer);
    } on Exception catch (e) {
      return handleException(e);
    }
  }
  Future<HttpResponse> patch(String uri,
      {data,
      Map<String, dynamic>? queryParameters,
      Options? options,
      CancelToken? cancelToken,
      ProgressCallback? onSendProgress,
      ProgressCallback? onReceiveProgress,
      HttpTransformer? httpTransformer}) async {
    try {
      var response = await _dio.patch(
        uri,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress,
      );
      return handleResponse(response, httpTransformer: httpTransformer);
    } on Exception catch (e) {
      return handleException(e);
    }
  }
  Future<HttpResponse> delete(String uri,
      {data,
      Map<String, dynamic>? queryParameters,
      Options? options,
      CancelToken? cancelToken,
      HttpTransformer? httpTransformer}) async {
    try {
      var response = await _dio.delete(
        uri,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
      );
      return handleResponse(response, httpTransformer: httpTransformer);
    } on Exception catch (e) {
      return handleException(e);
    }
  }
  Future<HttpResponse> put(String uri,
      {data,
      Map<String, dynamic>? queryParameters,
      Options? options,
      CancelToken? cancelToken,
      HttpTransformer? httpTransformer}) async {
    try {
      var response = await _dio.put(
        uri,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
      );
      return handleResponse(response, httpTransformer: httpTransformer);
    } on Exception catch (e) {
      return handleException(e);
    }
  }
  Future<Response> download(String urlPath, savePath,
      {ProgressCallback? onReceiveProgress,
      Map<String, dynamic>? queryParameters,
      CancelToken? cancelToken,
      bool deleteOnError = true,
      String lengthHeader = Headers.contentLengthHeader,
      data,
      Options? options,
      HttpTransformer? httpTransformer}) async {
    try {
      var response = await _dio.download(
        urlPath,
        savePath,
        onReceiveProgress: onReceiveProgress,
        queryParameters: queryParameters,
        cancelToken: cancelToken,
        deleteOnError: deleteOnError,
        lengthHeader: lengthHeader,
        data: data,
        options: data,
      );
      return response;
    } catch (e) {
      throw e;
    }
  }
}
响应解析
得到请求数据后,解析为定义的通用返回数据类型,需要首先判断是否取得返回值,然后判断网络请求成功,网络请求成功之后,采取判断是否接口返回期望的数据,还是因为请求参数错误或者服务器错误返回了错误信息。如果错误了,把错误信息格式化为定义的异常:
HttpResponse handleResponse(Response? response,
    {HttpTransformer? httpTransformer}) {
  httpTransformer ??= DefaultHttpTransformer.getInstance();
  // 返回值异常
  if (response == null) {
    return HttpResponse.failureFromError();
  }
  // token失效
  if (_isTokenTimeout(response.statusCode)) {
    return HttpResponse.failureFromError(
        UnauthorisedException(message: "没有权限", code: response.statusCode));
  }
  // 接口调用成功
  if (_isRequestSuccess(response.statusCode)) {
    return httpTransformer.parse(response);
  } else {
    // 接口调用失败
    return HttpResponse.failure(
        errorMsg: response.statusMessage, errorCode: response.statusCode);
  }
}
HttpResponse handleException(Exception exception) {
  var parseException = _parseException(exception);
  return HttpResponse.failureFromError(parseException);
}
/// 鉴权失败
bool _isTokenTimeout(int? code) {
  return code == 401;
}
/// 请求成功
bool _isRequestSuccess(int? statusCode) {
  return (statusCode != null && statusCode >= 200 && statusCode < 300);
}
HttpException _parseException(Exception error) {
  if (error is DioError) {
    switch (error.type) {
      case DioErrorType.connectTimeout:
      case DioErrorType.receiveTimeout:
      case DioErrorType.sendTimeout:
        return NetworkException(message: error.error.message);
      case DioErrorType.cancel:
        return CancelException(error.error.message);
      case DioErrorType.response:
        try {
          int? errCode = error.response?.statusCode;
          switch (errCode) {
            case 400:
              return BadRequestException(message: "请求语法错误", code: errCode);
            case 401:
              return UnauthorisedException(message: "没有权限", code: errCode);
            case 403:
              return BadRequestException(message: "服务器拒绝执行", code: errCode);
            case 404:
              return BadRequestException(message: "无法连接服务器", code: errCode);
            case 405:
              return BadRequestException(message: "请求方法被禁止", code: errCode);
            case 500:
              return BadServiceException(message: "服务器内部错误", code: errCode);
            case 502:
              return BadServiceException(message: "无效的请求", code: errCode);
            case 503:
              return BadServiceException(message: "服务器挂了", code: errCode);
            case 505:
              return UnauthorisedException(
                  message: "不支持HTTP协议请求", code: errCode);
            default:
              return UnknownException(error.error.message);
          }
        } on Exception catch (_) {
          return UnknownException(error.error.message);
        }
      case DioErrorType.other:
        if (error.error is SocketException) {
          return NetworkException(message: error.message);
        } else {
          return UnknownException(error.message);
}
      default:
        return UnknownException(error.message);
    }
  } else {
    return UnknownException(error.toString());
  }
}
缓存、重试、401拦截
默认的通用拦截器在 AppDio里直接定义,如果需要额外配置的拦截器,从HttpConfig里传入。
这些拦截器的创建,可以参考上一篇强大的dio封装,可能满足你的一切需要,这里就不再赘述。
使用
第一步,全局配置并初始化:
  HttpConfig dioConfig =
      HttpConfig(baseUrl: "https://gank.io/", proxy: "192.168.2.249:8888");
  HttpClient client = HttpClient(dioConfig: dioConfig);
  Get.put<HttpClient>(client);
请求:
  void get() async {
    HttpResponse appResponse = await dio.get("api/v2/banners");
    if (appResponse.ok) {
      debugPrint("====" + appResponse.data.toString());
    } else {
      debugPrint("====" + appResponse.error.toString());
    }
  }
附上开发环境:
[✓] Flutter (Channel stable, 2.0.5, on Mac OS X 10.15.7 19H15 darwin-x64, locale zh-Hans-CN)










