Flutter网络请求-dio

1,427 阅读2分钟

Dio

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

添加依赖

dependencies:
  dio: ^4.0.6

网络请求

网络请求方法

Get请求

请求指定的页面信息,并返回实体主体

  static Future doGet() async {
    String url = 'https://www.xxx.com/api/index/getInfo';
    Response response;
    Dio dio = Dio();
    response = await dio.get('$url?device=android&idcode=123456');
    print(response.data.toString());
    // 请求参数也可以通过对象传递,上面的代码等同于
    // response = await dio
    //     .get(url, queryParameters: {'device': 'android', "idcode": "123456"});
    // print(response.data.toString());
  }

Post请求

向指定资源提交数据进行处理请求(例如提交表单或者上传文件)

  static Future doPost() async {
    Response response;
    Dio dio = Dio();
    // 表单提交操作
    // FormData formData = FormData.fromMap({
    //   'device': 'android',
    //   'idcode': '123456',
    //   'type': '1'
    // });
    response = await dio.post(
        'https://www.xxx.com/api/house/dialogRemind',
        data: {'device': 'android', "idcode": "123456", 'type': '1'});
    print(response.data.toString());
  }

Put请求

从客户端向服务器传送的数据取代指定的文档内容

  static Future doPut(
    String path, {
    data,
    Map<String, dynamic> params,
    Options options,
    CancelToken cancelToken,
  }) async {
    Response response;
    Dio dio = Dio();
    Options requestOptions = options ?? Options();
    response = await dio.put(path,
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken);
    print(response.data.toString());
  }

Patch请求

与PUT类似,发送一个修改数据的请求,区别在于PATCH代表部分更新

  static Future doPatch(
    String path, {
    data,
    Map<String, dynamic> params,
    Options options,
    CancelToken cancelToken,
  }) async {
    Response response;
    Dio dio = Dio();
    Options requestOptions = options ?? Options();
    response = await dio.patch(path,
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken);
    return response.data;
  }

Delete请求

请求服务器删除指定内容

  static Future doDelete(
    String path, {
    data,
    Map<String, dynamic> params,
    Options options,
    CancelToken cancelToken,
  }) async {
    Response response;
    Dio dio = Dio();
    Options requestOptions = options ?? Options();
    response = await dio.delete(path,
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken);
    return response.data;
  }

网络请求配置

并发请求

  // 发起多个并发请求
  static Future complicating() async {
    Dio dio = Dio();
    List<Response> list = await Future.wait([
      dio.get(
          'https://www.xxx.com/api/index/getInfo?device=android&idcode=123456'),
      dio.post('https://www.xxx.com/api/house/dialogRemind',
          data: {'device': 'android', "idcode": "123456", 'type': '1'})
    ]);
    for (Response response in list) {
      // 结果依次返回
      print(response.data.toString());
    }
  }

使用代理

  (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
          (client) {
        client.findProxy = (uri) {
          return 'PROXY ${Config.proxyIp}:${Config.proxyPort}';
        };
        //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
        client.badCertificateCallback =
            (X509Certificate cert, String host, int port) => true;
      };

添加cookie管理

# 添加依赖
path_provider: ^2.0.9
dio_cookie_manager: ^2.0.0
  /// 加载cookie
  _addCookie() async {
    Directory appDocDir = await getApplicationDocumentsDirectory();
    PersistCookieJar _cookieJar =
        PersistCookieJar(storage: FileStorage(appDocDir.path + '/.cookies/'));
    _dio.interceptors.add(CookieManager(_cookieJar));
  }

添加日志拦截器

      // 添加日志输出拦截器
      _dio.interceptors
          .add(LogInterceptor(requestBody: true, responseBody: true));
      // 添加错误处理拦截器(自定义ErrorInterceptor类)
      _dio.interceptors.add(ErrorInterceptor());

添加缓存拦截器

// 自定义NetCacheInterceptor缓存拦截器
dio.interceptors.add(NetCacheInterceptor());

网络封装

BaseParam类

统一携带固定的请求参数,域名debug自动切换

import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter_common/config/config.dart';
import 'package:flutter_common/config/sp_key.dart';
import 'package:flutter_common/config/url.dart';
import 'package:flutter_common/res/string.dart';
import 'package:flutter_common/util/device_info_util.dart';
import 'package:flutter_common/util/package_info_util.dart';
import 'package:flutter_common/util/sp_util.dart';

/// 基础参数
class BaseParam {
  /// 获取参数(用于自动带上必传参数)
  static Map<String, dynamic> getBaseMap() {
    String device = Config.unknown;
    if (Platform.isAndroid) {
      device = Config.android;
    } else if (Platform.isIOS || Platform.isMacOS) {
      device = Config.ios;
    } else if (kIsWeb) {
      device = Config.web;
    }
    /**
     * device:区别端
     */
    Map<String, dynamic> map = {
      'device': device,
      'idcode': DeviceInfoUtil().appDeviceId,
      'app_id': DeviceInfoUtil().appDeviceId,
      'version': PackageInfoUtil().versionName,
      'project': 'flutter'
    };
    // 语言版本
    String language = SPUtil()
        .getString(SpKey.appLanguage, defaultValue: StringModel.languageCN);
    if (language == StringModel.languageEN) {
      map['lang'] = 'en-us';
    }
    return map;
  }

  /// 构建URL(用于环境切换)
  static String buildUrl(url) {
    if (Config.isDebug) {
      // 切换成调试模式
      bool isDebug =
          SPUtil().getBoolean(SpKey.appDebugModel, defaultValue: false);
      if (isDebug) {
        // 切换成调试模式
        if (url.contains(Url.hostLumen)) {
          url = url.replaceFirst(Url.hostLumen, Url.hostLumenDebug);
        } else if (url.contains(Url.hostService)) {
          url = url.replaceFirst(Url.hostService, Url.hostServiceDebug);
        } else if (url.contains(Url.hostNews)) {
          url = url.replaceFirst(Url.hostNews, Url.hostNewsDebug);
        } else if (url.contains(Url.hostHYPerf)) {
          url = url.replaceFirst(Url.hostHYPerf, Url.hostHYPerfDebug);
        } else if (url.contains(Url.hostApi)) {
          url = url.replaceFirst(Url.hostApi, Url.hostApiDebug);
        } else {
          url = url.replaceFirst(Url.host, Url.hostDebug);
        }
      }
    }
    return url;
  }
}

HttpRequest类

执行http请求,主要支持get和post请求(日志拦截器/错误信息处理/cookie管理/代理抓包/网络缓存)

// ignore_for_file: empty_catches

import 'dart:io';

import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:flutter_common/config/config.dart';
import 'package:flutter_common/config/http_key.dart';
import 'package:flutter_common/http/base/base_param.dart';
import 'package:flutter_common/http/interceptor/error_interceptor.dart';
import 'package:flutter_common/http/interceptor/net_cache_interceptor.dart';
import 'package:path_provider/path_provider.dart';

/// 网络请求
class HttpRequest {
  late Dio _dio;

  get dio => _dio;

  final CancelToken _cancelToken = CancelToken();

  factory HttpRequest() => _instance;
  static final HttpRequest _instance = HttpRequest._init();

  HttpRequest._init() {
    // BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
    BaseOptions options = BaseOptions(
      headers: {'Content-Type': Headers.jsonContentType},
      // 连接服务器超时时间,单位是毫秒.
      connectTimeout: 30 * 1000,
      // 响应流上前后两次接受到数据的间隔,单位为毫秒。
      receiveTimeout: 30 * 1000,
      // 请求的Content-Type,默认值是"application/json; charset=utf-8",Headers.formUrlEncodedContentType会自动编码请求体.
      contentType: Headers.jsonContentType,
      // 表示期望以那种格式(方式)接受响应数据。接受4种类型 `json`, `stream`, `plain`, `bytes`. 默认值是 `json`,
      responseType: ResponseType.json,
    );
    _dio = Dio(options);
    // 添加cookie
    _addCookie();
    // 添加缓存拦截器
    dio.interceptors.add(NetCacheInterceptor());
    // 在调试模式下
    if (Config.isDebug) {
      // 添加日志输出拦截器
      _dio.interceptors
          .add(LogInterceptor(requestBody: true, responseBody: true));
      // 添加错误处理拦截器
      _dio.interceptors.add(ErrorInterceptor());
      // 在调试模式下需要抓包调试,所以我们使用代理,并禁用HTTPS证书校验
      if (Config.proxyEnable) {
        (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
            (client) {
          client.findProxy = (uri) {
            return 'PROXY ${Config.proxyIp}:${Config.proxyPort}';
          };
          //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
          client.badCertificateCallback =
              (X509Certificate cert, String host, int port) => true;
        };
      }
    }
  }

  /// 加载cookie
  _addCookie() async {
    Directory appDocDir = await getApplicationDocumentsDirectory();
    PersistCookieJar _cookieJar =
        PersistCookieJar(storage: FileStorage(appDocDir.path + '/.cookies/'));
    _dio.interceptors.add(CookieManager(_cookieJar));
  }

  /// get请求
  Future get(url,
      {Map<String, dynamic>? queryParameters,
      Options? options,
      CancelToken? cancelToken}) async {
    url = BaseParam.buildUrl(url);
    Map<String, dynamic> map = BaseParam.getBaseMap();
    if (null != queryParameters) {
      map.addAll(queryParameters);
    }
    Response? response;
    try {
      response = await _dio.get(url,
          queryParameters: map,
          options: options ?? Options(extra: {HttpKey.keyCacheEnable: false}),
          cancelToken: cancelToken ?? _cancelToken);
    } on DioError catch (_) {}
    return response;
  }

  /// post请求
  Future post(url,
      {data,
      Map<String, dynamic>? queryParameters,
      Options? options,
      CancelToken? cancelToken}) async {
    url = BaseParam.buildUrl(url);
    Map<String, dynamic> map = BaseParam.getBaseMap();
    if (null != data) {
      map.addAll(data);
    }
    Response? response;
    try {
      response = await _dio.post(url,
          data: map,
          queryParameters: queryParameters,
          options: options,
          cancelToken: cancelToken ?? _cancelToken);
    } on DioError catch (_) {}
    return response;
  }

  /// post请求(表单提交操作)
  Future postForm(url,
      {Map<String, dynamic>? data,
      Options? options,
      CancelToken? cancelToken}) async {
    url = BaseParam.buildUrl(url);
    Map<String, dynamic> map = BaseParam.getBaseMap();
    if (null != data) {
      map.addAll(data);
    }
    Response? response;
    try {
      response = await _dio.post(url,
          data: FormData.fromMap(map),
          options: options,
          cancelToken: cancelToken ?? _cancelToken);
    } on DioError catch (_) {}
    return response;
  }

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

ErrorInterceptor类

错误信息拦截,并通过日志打印输出(仅debug模式下生效)

import 'package:dio/dio.dart';
import 'package:flutter_common/util/log_util.dart';

/// 错误处理拦截器
class ErrorInterceptor extends Interceptor {
  @override
  void onError(DioError err, ErrorInterceptorHandler handler) {
    AppException appException = AppException.create(err);
    // 错误日志输出
    LogUtil.log(appException.toString());
    err.error = appException;
    super.onError(err, handler);
  }
}

/// 自定义异常
class AppException implements Exception {
  final int code;
  final String message;

  AppException({
    required this.code,
    required this.message,
  });

  @override
  String toString() {
    return '错误代码:$code\n错误信息:$message';
  }

  factory AppException.create(DioError error) {
    switch (error.type) {
      case DioErrorType.cancel:
        {
          return _BadRequestException(-10, '请求取消');
        }
      case DioErrorType.connectTimeout:
        {
          return _BadRequestException(-11, '连接超时');
        }
      case DioErrorType.sendTimeout:
        {
          return _BadRequestException(-12, '请求超时');
        }
      case DioErrorType.receiveTimeout:
        {
          return _BadRequestException(-13, '响应超时');
        }
      case DioErrorType.response:
        {
          try {
            int? errCode = error.response?.statusCode;
            if (null != errCode) {
              switch (errCode) {
                case 400:
                  {
                    return _BadRequestException(errCode, '请求语法错误');
                  }
                case 401:
                  {
                    return _UnauthorisedException(errCode, '没有权限');
                  }
                case 403:
                  {
                    return _UnauthorisedException(errCode, '服务器拒绝执行');
                  }
                case 404:
                  {
                    return _UnauthorisedException(errCode, '无法连接服务器');
                  }
                case 405:
                  {
                    return _UnauthorisedException(errCode, '请求方法被禁止');
                  }
                case 500:
                  {
                    return _UnauthorisedException(errCode, '服务器内部错误');
                  }
                case 502:
                  {
                    return _UnauthorisedException(errCode, '无效的请求');
                  }
                case 503:
                  {
                    return _UnauthorisedException(errCode, '服务器挂了');
                  }
                case 505:
                  {
                    return _UnauthorisedException(errCode, '不支持HTTP协议请求');
                  }
                default:
                  {
                    return AppException(
                        code: errCode,
                        message: '${error.response?.statusMessage}');
                  }
              }
            } else {
              return AppException(code: -1, message: '未知错误');
            }
          } on Exception catch (_) {
            return AppException(code: -2, message: '未知错误');
          }
        }
      default:
        {
          return AppException(code: -3, message: error.message);
        }
    }
  }
}

/// 请求错误
class _BadRequestException extends AppException {
  _BadRequestException(int code, String message)
      : super(code: code, message: message);
}

/// 未认证异常
class _UnauthorisedException extends AppException {
  _UnauthorisedException(int code, String message)
      : super(code: code, message: message);
}

NetCacheInterceptor类

网络缓存拦截器,默认不使用,可指定接口启用该功能。若开启磁盘缓存,会优先使用内存缓存

import 'dart:collection';

import 'package:dio/dio.dart';
import 'package:flutter_common/config/http_key.dart';
import 'package:flutter_common/util/date_util.dart';
import 'package:flutter_common/util/sp_util.dart';

// 缓存最大数量
const int _cacheMaxCount = 1000;
// 缓存最大默认时间(内存缓存)
const int _cacheMaxAge = 60 * 60 * 2;

/// 网络缓存拦截器
class NetCacheInterceptor extends Interceptor {
  // 为确保迭代器顺序和对象插入时间一致顺序一致,我们使用LinkedHashMap
  LinkedHashMap cacheLinkedHashMap = LinkedHashMap<String, CacheObject>();

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // 是否开启缓存(默认不开启)
    bool cacheEnable = options.extra[HttpKey.keyCacheEnable] ?? false;
    if (!cacheEnable) {
      return super.onRequest(options, handler);
    }
    // refresh标记是否是刷新缓存(默认不刷新)
    bool refresh = options.extra[HttpKey.keyRefresh] ?? false;
    // 是否磁盘缓存(默认是不缓存)
    bool cacheDisk = options.extra[HttpKey.keyCacheDiskEnable] ?? false;
    // 如果刷新,先删除相关缓存
    if (refresh) {
      // 删除uri相同的内存缓存
      delete(options.uri.toString());
      // 删除磁盘缓存
      if (cacheDisk) {
        SPUtil().removeKey(options.uri.toString());
      }
      // 继续执行网络请求
      return super.onRequest(options, handler);
    }
    // 如果是 get 请求,策略:内存缓存优先,然后才是磁盘缓存
    if (options.method.toLowerCase() == 'get') {
      String key = options.uri.toString();
      // 1 内存缓存
      var ob = cacheLinkedHashMap[key];
      if (ob != null) {
        // 若缓存未过期,则返回缓存内容
        int cacheMaxAge = options.extra[HttpKey.keyCacheMaxAge] ?? _cacheMaxAge;
        if ((DateUtil.currentTimeMillis() - ob.timeStamp) / 1000 <
            cacheMaxAge) {
          CacheObject cacheObject = cacheLinkedHashMap[key];
          // 解析自定义数据(返回内存缓存数据)
          return handler.resolve(cacheObject.response);
        } else {
          // 若已过期则删除缓存,继续向服务器请求
          cacheLinkedHashMap.remove(key);
        }
      } else if (cacheDisk) {
        // 磁盘缓存(有效期:永久)
        String cacheData = SPUtil().getString(key);
        if (cacheData.isNotEmpty) {
          // 解析自定义数据(返回磁盘缓存数据)
          return handler.resolve(Response(
              statusCode: 200, data: cacheData, requestOptions: options));
        }
      }
    }
    return super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    // 如果启用缓存,将返回结果保存到缓存
    RequestOptions options = response.requestOptions;
    bool cacheEnable = options.extra[HttpKey.keyCacheEnable] ?? false;
    if (cacheEnable) {
      _saveCache(response);
    }
    return super.onResponse(response, handler);
  }

  /// 保持缓存
  Future<void> _saveCache(Response response) async {
    RequestOptions options = response.requestOptions;
    // 只缓存 get 的请求,策略:内存、磁盘都写缓存
    if (options.method.toLowerCase() == 'get') {
      // 缓存key
      String key = options.uri.toString();
      // 磁盘缓存
      if (options.extra[HttpKey.keyCacheDiskEnable] == true) {
        SPUtil().put(key, response.data);
      }
      // 内存缓存,如果缓存数量超过最大数量限制,则先移除最早的一条记录
      if (cacheLinkedHashMap.length == _cacheMaxCount) {
        cacheLinkedHashMap
            .remove(cacheLinkedHashMap[cacheLinkedHashMap.keys.first]);
      }
      cacheLinkedHashMap[key] = CacheObject(response);
    }
  }

  void delete(String key) {
    cacheLinkedHashMap.remove(key);
  }
}

/// 缓存类
class CacheObject {
  int timeStamp;
  Response response;

  CacheObject(this.response)
      : timeStamp = DateTime.now().millisecondsSinceEpoch;

  @override
  bool operator ==(other) {
    return response.hashCode == other.hashCode;
  }

  @override
  int get hashCode => response.realUri.hashCode;
}