1、先写拦截器,我这里分开写的
请求拦截器
import 'package:dio/dio.dart';
/// 错误处理拦截器
class RequestInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
options.copyWith(
headers: {
"Content-Type": "application/json",
// 'Authorization': 'Bearer $accessToken',
},
);
return super.onRequest(options, handler);
}
}
错误拦截器
// ignore_for_file: overridden_fields, avoid_renaming_method_parameters
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import '../app_exceptions.dart';
// 异常
class MyDioSocketException extends SocketException {
@override
late String message;
MyDioSocketException(
message, {
osError,
address,
port,
}) : super(
message,
osError: osError,
address: address,
port: port,
);
}
/// 错误处理拦截器
class ErrorInterceptor extends Interceptor {
// 是否有网
Future<bool> isConnected() async {
var connectivityResult = await (Connectivity().checkConnectivity());
return connectivityResult != ConnectivityResult.none;
}
@override
Future<void> onError(DioError err, ErrorInterceptorHandler errCb) async {
// 自定义一个socket实例,因为dio原生的实例,message属于是只读的
if (err.error is SocketException) {
err.error = MyDioSocketException(
err.message,
osError: err.error?.osError,
address: err.error?.address,
port: err.error?.port,
);
}
if (err.type == DioErrorType.other) {
bool isConnectNetWork = await isConnected();
if (!isConnectNetWork && err.error is MyDioSocketException) {
err.error.message = "当前网络不可用,请检查您的网络";
}
}
// error统一处理
AppException appException = AppException.create(err);
// 错误提示
debugPrint('DioError===: ${appException.toString()}');
err.error = appException;
return super.onError(err, errCb);
}
}
缓存拦截器
// ignore_for_file: constant_identifier_names, avoid_renaming_method_parameters
import 'dart:collection';
import 'package:dio/dio.dart';
import 'package:fluttergetdemo/app/utils/storage.dart';
const int CACHE_MAXAGE = 86400000;
const int CACHE_MAXCOUNT = 1000;
const bool CACHE_ENABLE = false;
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 NetCacheInterceptor extends Interceptor {
// 为确保迭代器顺序和对象插入时间一致顺序一致,我们使用LinkedHashMap
// ignore: prefer_collection_literals
var cache = LinkedHashMap<String, CacheObject>();
@override
void onRequest(
RequestOptions options,
RequestInterceptorHandler requestCb,
) async {
if (!CACHE_ENABLE) {
return super.onRequest(options, requestCb);
}
// refresh标记是否是刷新缓存
bool refresh = options.extra["refresh"] == true;
// 是否磁盘缓存
bool cacheDisk = options.extra["cacheDisk"] == true;
// 如果刷新,先删除相关缓存
if (refresh) {
// 删除uri相同的内存缓存
delete(options.uri.toString());
// 删除磁盘缓存
if (cacheDisk) {
await SpUtil().remove(options.uri.toString());
}
return;
}
// get 请求,开启缓存
if (options.extra["noCache"] != true &&
options.method.toLowerCase() == 'get') {
String key = options.extra["cacheKey"] ?? options.uri.toString();
// 策略 1 内存缓存优先,2 然后才是磁盘缓存
// 1 内存缓存
var ob = cache[key];
if (ob != null) {
//若缓存未过期,则返回缓存内容
if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <
CACHE_MAXAGE) {
return;
} else {
//若已过期则删除缓存,继续向服务器请求
cache.remove(key);
}
}
// 2 磁盘缓存
if (cacheDisk) {
var cacheData = SpUtil().getJSON(key);
if (cacheData != null) {
return;
}
}
}
return super.onRequest(options, requestCb);
}
@override
void onResponse(
Response response, ResponseInterceptorHandler responseCb) async {
// 如果启用缓存,将返回结果保存到缓存
if (CACHE_ENABLE) {
await _saveCache(response);
}
return super.onResponse(response, responseCb);
}
Future<void> _saveCache(Response object) async {
RequestOptions options = object.requestOptions;
// 只缓存 get 的请求
if (options.extra["noCache"] != true &&
options.method.toLowerCase() == "get") {
// 策略:内存、磁盘都写缓存
// 缓存key
String key = options.extra["cacheKey"] ?? options.uri.toString();
// 磁盘缓存
if (options.extra["cacheDisk"] == true) {
await SpUtil().setJSON(key, object.data);
}
// 内存缓存
// 如果缓存数量超过最大数量限制,则先移除最早的一条记录
if (cache.length == CACHE_MAXCOUNT) {
cache.remove(cache[cache.keys.first]);
}
cache[key] = CacheObject(object);
}
}
void delete(String key) {
cache.remove(key);
}
}
代理拦截器
// ignore_for_file: avoid_renaming_method_parameters
import 'dart:async';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
class DioConnectivityRequestRetrier {
final Dio dio;
final Connectivity connectivity;
DioConnectivityRequestRetrier({
required this.dio,
required this.connectivity,
});
Future<Response> scheduleRequestRetry(RequestOptions requestOptions) async {
late StreamSubscription streamSubscription;
final responseCompleter = Completer<Response>();
Options options = Options(
method: requestOptions.method,
sendTimeout: requestOptions.sendTimeout,
receiveTimeout: requestOptions.receiveTimeout,
extra: requestOptions.extra,
headers: requestOptions.headers,
responseType: requestOptions.responseType,
contentType: requestOptions.contentType,
validateStatus: requestOptions.validateStatus,
receiveDataWhenStatusError: requestOptions.receiveDataWhenStatusError,
followRedirects: requestOptions.followRedirects,
maxRedirects: requestOptions.maxRedirects,
requestEncoder: requestOptions.requestEncoder,
responseDecoder: requestOptions.responseDecoder,
listFormat: requestOptions.listFormat,
);
streamSubscription = connectivity.onConnectivityChanged.listen(
(connectivityResult) {
if (connectivityResult != ConnectivityResult.none) {
streamSubscription.cancel();
responseCompleter.complete(
dio.request(
requestOptions.path,
cancelToken: requestOptions.cancelToken,
data: requestOptions.data,
onReceiveProgress: requestOptions.onReceiveProgress,
onSendProgress: requestOptions.onSendProgress,
queryParameters: requestOptions.queryParameters,
options: options,
),
);
}
},
);
return responseCompleter.future;
}
}
class RetryOnConnectionChangeInterceptor extends Interceptor {
final DioConnectivityRequestRetrier requestRetrier;
RetryOnConnectionChangeInterceptor({
required this.requestRetrier,
});
@override
dynamic onError(DioError err, ErrorInterceptorHandler errFn) async {
if (_shouldRetry(err)) {
try {
return requestRetrier.scheduleRequestRetry(err.requestOptions);
} catch (e) {
return e;
}
}
return super.onError(err, errFn);
}
bool _shouldRetry(DioError err) {
return err.error != null && err.error is SocketException;
}
}
2、封装一个工具类
import 'package:dio/dio.dart';
import 'http_request.dart';
import 'interceptor/cache.dart';
class HttpUtils {
static void init({
required String baseUrl,
int connectTimeout = 15000,
int receiveTimeout = 15000,
List<Interceptor>? interceptors,
}) {
Http().init(
baseUrl: baseUrl,
connectTimeout: connectTimeout,
receiveTimeout: receiveTimeout,
interceptors: interceptors,
);
}
static void cancelRequests({required CancelToken token}) {
Http().cancelRequests(token: token);
}
static Future get(
String path, {
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
bool refresh = false,
bool noCache = !CACHE_ENABLE,
String? cacheKey,
bool cacheDisk = false,
}) async {
return await Http().get(
path,
params: params,
options: options,
cancelToken: cancelToken,
refresh: refresh,
noCache: noCache,
cacheKey: cacheKey,
);
}
static Future post(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().post(
path,
data: data,
params: params,
options: options,
cancelToken: cancelToken,
);
}
static Future put(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().put(
path,
data: data,
params: params,
options: options,
cancelToken: cancelToken,
);
}
static Future patch(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().patch(
path,
data: data,
params: params,
options: options,
cancelToken: cancelToken,
);
}
static Future delete(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().delete(
path,
data: data,
params: params,
options: options,
cancelToken: cancelToken,
);
}
}
3、封装一个返回类
// ignore_for_file: constant_identifier_names
import 'app_exceptions.dart';
class ApiResponse<T> {
Status status;
T? data;
AppException? exception;
ApiResponse.completed(this.data) : status = Status.COMPLETED;
ApiResponse.error(this.exception) : status = Status.ERROR;
@override
String toString() {
return "Status : $status \n Message : $exception \n Data : $data";
}
}
enum Status { COMPLETED, ERROR }
4、自定义一个异常类
import 'package:dio/dio.dart';
/// 自定义异常
class AppException implements Exception {
final String _message;
final int _code;
AppException(
this._code,
this._message,
);
@override
String toString() {
return "$_code$_message";
}
String getMessage() {
return _message;
}
factory AppException.create(DioError error) {
switch (error.type) {
case DioErrorType.cancel:
{
return BadRequestException(-1, "请求取消");
}
case DioErrorType.connectTimeout:
{
return BadRequestException(-1, "连接超时");
}
case DioErrorType.sendTimeout:
{
return BadRequestException(-1, "请求超时");
}
case DioErrorType.receiveTimeout:
{
return BadRequestException(-1, "响应超时");
}
case DioErrorType.response:
{
try {
int? errCode = error.response!.statusCode;
// String errMsg = error.response.statusMessage;
// return ErrorEntity(code: errCode, message: errMsg);
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 ErrorEntity(code: errCode, message: "未知错误");
return AppException(errCode!, error.response!.statusMessage!);
}
}
} on Exception catch (_) {
return AppException(-1, "未知错误");
}
}
default:
{
return AppException(-1, error.error.message);
}
}
}
}
/// 请求错误
class BadRequestException extends AppException {
BadRequestException(int code, String message) : super(code, message);
}
/// 未认证异常
class UnauthorisedException extends AppException {
UnauthorisedException(int code, String message) : super(code, message);
}
5、封装请求类,这里是一个单例类
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'interceptor/cache.dart';
import 'interceptor/error.dart';
import 'interceptor/request.dart';
import 'interceptor/retry.dart';
class Http {
static final Http _instance = Http._internal();
factory Http() => _instance;
static late final Dio dio;
final CancelToken _cancelToken = CancelToken();
Http._internal() {
// BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
BaseOptions options = BaseOptions();
dio = Dio(options);
// 添加request拦截器
dio.interceptors.add(RequestInterceptor());
// 添加error拦截器
dio.interceptors.add(ErrorInterceptor());
// // 添加cache拦截器
dio.interceptors.add(NetCacheInterceptor());
// // 添加retry拦截器
dio.interceptors.add(
RetryOnConnectionChangeInterceptor(
requestRetrier: DioConnectivityRequestRetrier(
dio: dio,
connectivity: Connectivity(),
),
),
);
// 在调试模式下需要抓包调试,所以我们使用代理,并禁用HTTPS证书校验
// if (PROXY_ENABLE) {
// (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
// (client) {
// client.findProxy = (uri) {
// return "PROXY $PROXY_IP:$PROXY_PORT";
// };
// //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
// client.badCertificateCallback =
// (X509Certificate cert, String host, int port) => true;
// };
// }
}
///初始化公共属性
///
/// [baseUrl] 地址前缀
/// [connectTimeout] 连接超时赶时间
/// [receiveTimeout] 接收超时赶时间
/// [interceptors] 基础拦截器
void init({
String? baseUrl,
int connectTimeout = 15000,
int receiveTimeout = 15000,
Map<String, String>? headers,
List<Interceptor>? interceptors,
}) {
dio.options = dio.options.copyWith(
baseUrl: baseUrl,
connectTimeout: connectTimeout,
receiveTimeout: receiveTimeout,
headers: headers ?? const {},
);
if (interceptors != null && interceptors.isNotEmpty) {
dio.interceptors.addAll(interceptors);
}
}
// 关闭dio
void cancelRequests({required CancelToken token}) {
_cancelToken.cancel("cancelled");
}
// 添加认证
// 读取本地配置
Map<String, dynamic>? getAuthorizationHeader() {
Map<String, dynamic>? headers;
// 从getx或者sputils中获取
// String accessToken = Global.accessToken;
// String accessToken = "";
// if (accessToken != null) {
// headers = {
// 'Authorization': 'Bearer $accessToken',
// };
// }
return headers;
}
Future get(
String path, {
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
bool refresh = false,
bool noCache = !CACHE_ENABLE,
String? cacheKey,
bool cacheDisk = false,
}) async {
Options requestOptions = options ?? Options();
requestOptions = requestOptions.copyWith(
extra: {
"refresh": refresh,
"noCache": noCache,
"cacheKey": cacheKey,
"cacheDisk": cacheDisk,
},
);
Map<String, dynamic>? _authorization = getAuthorizationHeader();
if (_authorization != null) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
Response response;
response = await dio.get(
path,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken,
);
return response.data;
}
Future post(
String path, {
Map<String, dynamic>? params,
data,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String, dynamic>? _authorization = getAuthorizationHeader();
if (_authorization != null) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio.post(
path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken,
);
return response.data;
}
Future put(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String, dynamic>? _authorization = getAuthorizationHeader();
if (_authorization != null) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio.put(
path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken,
);
return response.data;
}
Future patch(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String, dynamic>? _authorization = getAuthorizationHeader();
if (_authorization != null) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio.patch(
path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken,
);
return response.data;
}
Future delete(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String, dynamic>? _authorization = getAuthorizationHeader();
if (_authorization != null) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio.delete(
path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken,
);
return response.data;
}
}