包
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("发送请求"), ), ], ), ), ); } }