包
dio: ^4.0.0 #这是一个强大的 Dart HTTP 客户端库
http: ^0.13.3 #用于进行基本的 HTTP 请求和处理响应
dio_cookie_manager: ^2.0.0 #该包提供了与 Dio 配合使用的 Cookie 管理能力
cookie_jar: ^3.0.1 #这是一个通用的 Dart Cookie 管理库,可以用于设置和管理 Cookie
dio_http2_adapter: ^2.0.0 #包为 Dio 提供了对 HTTP/2 协议的支持
shared_preferences: ^2.2.2 #移动应用程序中存储和获取持久化的键值对数据
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("发送请求"),
),
],
),
),
);
}
}