Dio网络请求

发布时间 2023-12-22 15:04:30作者: 鲤斌

  dio: ^4.0.0
  http: ^0.13.3
  dio_cookie_manager: ^2.0.0
  cookie_jar: ^3.0.1
  dio_http2_adapter: ^2.0.0
  shared_preferences: ^2.0.7

dio_util.dart

//使用单例模式进行Dio封装

//因为我们的应用程序在每个页面中都会用到网络请求,
//那么如果我们每次请求的时候都去实例化一个Dio,
//无非是增加了系统不必要的开销,
//而使用单例模式对象一旦创建每次访问都是同一个对象,
//不需要再次实例化该类的对象。

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:tomato/page/dio/dio_cache_interceptors.dart';
import 'package:tomato/page/dio/dio_interceptors.dart';
import 'package:tomato/page/dio/dio_method.dart';
import 'package:tomato/page/dio/dio_token_interceptors.dart';
import 'package:tomato/page/dio/dio_transformer.dart';

class DioUtil {
  /// 连接超时时间
  static const int CONNECT_TIMEOUT = 6 * 1000;

  /// 响应超时时间
  static const int RECEIVE_TIMEOUT = 6 * 1000;

  /// 请求的URL前缀
  static String BASE_URL = "http://39.108.223.110:9199";

  /// 是否开启网络缓存,默认false
  static bool CACHE_ENABLE = false;

  /// 最大缓存时间(按秒), 默认缓存七天,可自行调节
  static int MAX_CACHE_AGE = 7 * 24 * 60 * 60;

  /// 最大缓存条数(默认一百条)
  static int MAX_CACHE_COUNT = 100;

  static DioUtil? _instance;
  static Dio _dio = Dio();
  Dio get dio => _dio;

  DioUtil._internal() {
    _instance = this;
    _instance!._init();
  }

  factory DioUtil() => _instance ?? DioUtil._internal();

  static DioUtil? getInstance() {
    _instance ?? DioUtil._internal();
    return _instance;
  }

  /// 取消请求token
  CancelToken _cancelToken = CancelToken();

  /// cookie
  CookieJar cookieJar = CookieJar();

  _init() {
    /// 初始化基本选项
    BaseOptions options = BaseOptions(
        baseUrl: BASE_URL,
        connectTimeout: CONNECT_TIMEOUT,
        receiveTimeout: RECEIVE_TIMEOUT);

    /// 初始化dio
    _dio = Dio(options);

    /// 添加拦截器
    _dio.interceptors.add(DioInterceptors());

    /// 添加转换器
    _dio.transformer = DioTransformer();

    /// 添加cookie管理器
    _dio.interceptors.add(CookieManager(cookieJar));

    /// 刷新token拦截器(lock/unlock)
    // _dio.interceptors.add(DioTokenInterceptors());

    /// 添加缓存拦截器
    _dio.interceptors.add(DioCacheInterceptors());
  }

  /// 设置Http代理(设置即开启)
  void setProxy({String? proxyAddress, bool enable = false}) {
    if (enable) {
      (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
          (HttpClient client) {
        client.findProxy = (uri) {
          return proxyAddress ?? "";
        };
        client.badCertificateCallback =
            (X509Certificate cert, String host, int port) => true;
      };
    }
  }

  /// 设置https证书校验
  void setHttpsCertificateVerification({String? pem, bool enable = false}) {
    if (enable) {
      (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
          (client) {
        client.badCertificateCallback =
            (X509Certificate cert, String host, int port) {
          if (cert.pem == pem) {
            // 验证证书
            return true;
          }
          return false;
        };
      };
    }
  }

  /// 开启日志打印
  void openLog() {
    //true  开启日志
    _dio.interceptors.add(LogInterceptor(responseBody: true));
  }

  /// 请求类
  Future<T> request<T>(
    String path, {
    DioMethod method = DioMethod.get,
    Map<String, dynamic>? params,
    data,
    CancelToken? cancelToken,
    Options? options,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    const _methodValues = {
      DioMethod.get: 'get',
      DioMethod.post: 'post',
      DioMethod.put: 'put',
      DioMethod.delete: 'delete',
      DioMethod.patch: 'patch',
      DioMethod.head: 'head'
    };

    options ??= Options(method: _methodValues[method]);
    try {
      Response response;
      response = await _dio.request(path,
          data: data,
          queryParameters: params,
          cancelToken: cancelToken ?? _cancelToken,
          options: options,
          onSendProgress: onSendProgress,
          onReceiveProgress: onReceiveProgress);
      return response.data;
    } on DioError catch (e) {
      throw e;
    }
  }

  /// 取消网络请求
  void cancelRequests({CancelToken? token}) {
    token ?? _cancelToken.cancel("cancelled");
  }
}

dio_transformer.dart

import 'dart:async';
import 'package:dio/dio.dart';

class DioTransformer extends DefaultTransformer {
  @override
  Future<String> transformRequest(RequestOptions options) async {
    // 如果请求的数据接口是List<String>那我们直接抛出异常
    if (options.data is List<String>) {
      throw DioError(
        error: "你不能直接发送List数据到服务器",
        requestOptions: options,
      );
    } else {
      return super.transformRequest(options);
    }
  }

  @override
  Future transformResponse(
      RequestOptions options, ResponseBody response) async {
    // 例如我们响应选项里面没有自定义某些头部数据,那我们就可以自行添加
    options.extra['myHeader'] = 'abc';
    return super.transformResponse(options, response);
  }
}

dio_token_interceptors.dart

import 'package:dio/dio.dart';
import 'package:tomato/page/dio/dio_util.dart';

class DioTokenInterceptors extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    if (options.headers['refreshToken'] == null) {
      DioUtil.getInstance()?.dio.lock();
      Dio _tokenDio = Dio();
      _tokenDio
        ..get("http://localhost:8080/getRefreshToken").then((d) {
          options.headers['refreshToken'] = d;
          handler.next(options);
        }).catchError((error, stackTrace) {
          handler.reject(error, true);
        }).whenComplete(() {
          DioUtil.getInstance()?.dio.unlock();
        }); // unlock the dio
    } else {
      options.headers['refreshToken'] = options.headers['refreshToken'];
      handler.next(options);
    }
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) async {
    // 响应前需要做刷新token的操作

    super.onResponse(response, handler);
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) {
    super.onError(err, handler);
  }
}

dio_response.dart

class DioResponse<T> {
  /// 消息(例如成功消息文字/错误消息文字)
  final String? message;

  /// 自定义code(可根据内部定义方式)
  final int? code;

  /// 接口返回的数据
  final T? data;

  /// 需要添加更多
  /// .........

  DioResponse({
    this.message,
    this.data,
    this.code,
  });

  @override
  String toString() {
    StringBuffer sb = StringBuffer('{');
    sb.write("\"message\":\"$message\"");
    sb.write(",\"errorMsg\":\"$code\"");
    sb.write(",\"data\":\"$data\"");
    sb.write('}');
    return sb.toString();
  }
}

class DioResponseCode {
  /// 成功
  static const int SUCCESS = 0;

  /// 错误
  static const int ERROR = 1;

  /// 更多
}

dio_method.dart

enum DioMethod {
  get,
  post,
  put,
  delete,
  patch,
  head,
}

dio_interceptors.dart

import 'package:dio/dio.dart';
import 'package:tomato/page/dio/dio_response.dart';

class DioInterceptors extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // 对非open的接口的请求参数全部增加userId
    if (!options.path.contains("open")) {
      options.queryParameters["userId"] = "xxx";
    }

    // 头部添加token
    options.headers["token"] = "xxx";

    // 更多业务需求

    handler.next(options);

    // super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) async {
    // 请求成功是对数据做基本处理
    if (response.statusCode == 200) {
      response.data =
          DioResponse(code: 0, message: "请求成功啦", data: response.data);
    } else {
      response.data =
          DioResponse(code: 1, message: "请求失败啦", data: response.data);
    }

    // 对某些单独的url返回数据做特殊处理
    if (response.requestOptions.baseUrl.contains("???????")) {
      //....
    }

    // 根据公司的业务需求进行定制化处理

    // 重点
    handler.next(response);
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) {
    switch (err.type) {
      // 连接服务器超时
      case DioErrorType.connectTimeout:
        {
          // 根据自己的业务需求来设定该如何操作,可以是弹出框提示/或者做一些路由跳转处理
        }
        break;
      // 响应超时
      case DioErrorType.receiveTimeout:
        {
          // 根据自己的业务需求来设定该如何操作,可以是弹出框提示/或者做一些路由跳转处理
        }
        break;
      // 发送超时
      case DioErrorType.sendTimeout:
        {
          // 根据自己的业务需求来设定该如何操作,可以是弹出框提示/或者做一些路由跳转处理
        }
        break;
      // 请求取消
      case DioErrorType.cancel:
        {
          // 根据自己的业务需求来设定该如何操作,可以是弹出框提示/或者做一些路由跳转处理
        }
        break;
      // 404/503错误
      case DioErrorType.response:
        {
          // 根据自己的业务需求来设定该如何操作,可以是弹出框提示/或者做一些路由跳转处理
        }
        break;
      // other 其他错误类型
      case DioErrorType.other:
        {}
        break;
    }
    super.onError(err, handler);
  }
}

dio_cache_interceptors.dart

import 'dart:collection';
import 'package:dio/dio.dart';
import 'package:tomato/page/dio/dio_util.dart';
import 'package:shared_preferences/shared_preferences.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 DioCacheInterceptors extends Interceptor {
  // 为确保迭代器顺序和对象插入时间一致顺序一致,我们使用LinkedHashMap
  var cache = LinkedHashMap<String, CacheObject>();
  // sp
  SharedPreferences? preferences;

  @override
  void onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    if (!DioUtil.CACHE_ENABLE) return super.onRequest(options, handler);
    // 是否刷新缓存
    bool refresh = options.extra["refresh"] == true;

    if (refresh) {
      // 删除本地缓存
      delete(options.uri.toString());
    }
    // 只有get请求才开启缓存
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == 'get') {
      String key = options.extra["cacheKey"] ?? options.uri.toString();
      var ob = cache[key];
      if (ob != null) {
        // 内存缓存
        if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <
            DioUtil.MAX_CACHE_AGE) {
          return handler.resolve(cache[key]!.response);
        } else {
          //若已过期则删除缓存,继续向服务器请求
          cache.remove(key);
        }

        // 磁盘缓存
      }
    }
    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    // 把响应的数据保存到缓存
    if (DioUtil.CACHE_ENABLE) {
      _saveCache(response);
    }

    super.onResponse(response, handler);
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) {
    // TODO: implement onError
    super.onError(err, handler);
  }

  _saveCache(Response object) {
    RequestOptions options = object.requestOptions;
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == "get") {
      // 如果缓存数量超过最大数量限制,则先移除最早的一条记录
      if (cache.length == DioUtil.MAX_CACHE_COUNT) {
        cache.remove(cache[cache.keys.first]);
      }
      String key = options.extra["cacheKey"] ?? options.uri.toString();
      cache[key] = CacheObject(object);
    }
  }

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

使用方法

//
import 'package:dio/dio.dart';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:tomato/page/dio/dio_method.dart';
import 'package:tomato/page/dio/dio_response.dart';
import 'package:tomato/page/dio/dio_util.dart';

class DioUtilExample extends StatefulWidget {
  @override
  _DioUtilExampleState createState() => _DioUtilExampleState();
}

class _DioUtilExampleState extends State<DioUtilExample> {
  CancelToken _cancelToken = CancelToken();

  void _handleLogin() async {
    // 模拟用户退出页面
    const _timeout = Duration(milliseconds: 2000);
    Timer.periodic(_timeout, (timer) {
      DioUtil().cancelRequests(token: _cancelToken); //定时取消请求
    });

    DioUtil().openLog(); //开启 DioUtil 内部日志输出
    DioUtil.getInstance()?.openLog(); //获取单例并开启日志输出(如果单例不存在则返回 null)
    DioUtil.CACHE_ENABLE = true; //开启缓存功能(默认关闭)

    //不发送参数
    // DioUtil().setProxy(proxyAddress: "https://www.baidu.com", enable: true);  //Http代理
    // DioResponse result = await DioUtil().request("/book/book/getBookType", method: DioMethod.get, cancelToken: _cancelToken);
    //发送这种的参数?letterId=&bookId=1
    DioResponse result = await DioUtil().request(
        //发送 GET 请求
        "/Vocabulary/Vocabulary/getvocabularyType", //请求的接口路径
        method: DioMethod.get, //请求的 HTTP 方法
        params: {"letterId": 2, "bookId": 2}, //请求的参数
        cancelToken: _cancelToken); ////请求的取消 Token

    // DioResponse result = await DioUtil().request(
    //     "/Vocabulary/Vocabulary/getvocabularyType",
    //     method: DioMethod.get,
    //     params: {"letterId": 2, "bookId": 2},
    //     cancelToken: _cancelToken);
    print("^^^1^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1^^^");
    print(result);
    print("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("DioUtilExample"),
      ),
      body: Center(
        child: Column(
          children: [
            TextButton(
              onPressed: _handleLogin,
              child: Text("发送请求"),
            ),
          ],
        ),
      ),
    );
  }
}