我正在像这样调用多个API
await Future.wait([profile, calendar, cities]);
我有一个拦截器类,它扩展了DioQueuedInterceptor。
在配置文件 api 上说,我的访问 token 过期并且 onError
被调用。
所以我点击了refresh_token api,重试当前请求,成功后我调用handler.resolve(response);
。但其余的 api 仍然调用拦截器 onError
方法,并且我的refresh_token api 总共被命中了 3 次。
可能是什么问题?
Future<void> onError(DioError err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
var tokenDio = Dio();
try {
Response refreshResponse = await tokenDio.post('/${endPoint.path}',
data: endPoint.requestBody);
if (refreshResponse.statusCode == 201) {
var newAccessToken = refreshResponse.data['access_token'];
var newHeaders = err.requestOptions.headers;
newHeaders['Authorization'] = 'Bearer $newAccessToken';
var response = await dio.request(
err.requestOptions.path,
data: err.requestOptions.data,
queryParameters: err.requestOptions.queryParameters,
options: Options(
method: err.requestOptions.method,
headers: newHeaders,
),
);
return handler.resolve(response);
}
else {
return handler.reject(err);
}
} catch (error) {
return handler.reject(err);
}
}
else {
return handler.next(err);
}
}
最佳答案
这是一个无需在拦截器中扩展 QueuedInterceptor
类的解决方案:
- 扩展
Interceptor
类而不是QueuedInterceptor
类。 - 创建一个 bool 值
isRefreshing
,在第一个请求失败时将其设置为 true,并发送refreshToken
请求以刷新您的访问 token 。 - 创建失败请求列表,让所有请求都失败,并且只要
isRefreshing
为 true,就不断将失败请求添加到此列表中。 - 刷新 token (第 2 步)后,通过循环访问
failedRequests
列表来重试所有失败的请求。
if (!isRefreshing) {
debugPrint("ACCESS TOKEN EXPIRED, GETTING NEW TOKEN PAIR");
isRefreshing = true;
await refreshToken(err, handler);
} else {
debugPrint("ADDING ERRRORED REQUEST TO FAILED QUEUE");
failedRequests.add({'err': err, 'handler': handler});
}
拦截器的完整代码:
class MyHttpInterceptor extends Interceptor {
List<Map<dynamic, dynamic>> failedRequests = [];
bool isRefreshing = false;
MyHttpInterceptor();
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
debugPrint('REQUEST[${options.method}] => PATH: ${options.path}');
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
debugPrint(
'RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
handler.next(response);
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) async {
debugPrint(
'ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}, IS REFRESHING: ${isRefreshing.toString()}');
if (err.response?.statusCode == 401) {
if (getIt<AuthProvider>().refreshToken.isEmpty) {
debugPrint("LOGGING OUT: NO REFRESH TOKEN FOUND");
// perform logout
return handler.reject(err);
}
if (!isRefreshing) {
debugPrint("ACCESS TOKEN EXPIRED, GETTING NEW TOKEN PAIR");
isRefreshing = true;
await refreshToken(err, handler);
} else {
debugPrint("ADDING ERRRORED REQUEST TO FAILED QUEUE");
failedRequests.add({'err': err, 'handler': handler});
}
} else {
return handler.next(err);
}
}
FutureOr refreshToken(DioError err, ErrorInterceptorHandler handler) async {
Dio retryDio = Dio(
BaseOptions(
baseUrl: baseURI,
headers: <String, String>{
'Content-Type': 'application/json',
'Authorization': 'Bearer ${getIt<AuthProvider>().refreshToken}'
},
),
);
// handle refresh token
var response = await retryDio.get('/user/get-at-by-rt');
var parsedResponse = response.data;
if (response.statusCode == 401 || response.statusCode == 403) {
// handle logout
debugPrint("LOGGING OUT: EXPIRED REFRESH TOKEN");
return handler.reject(err);
}
// handle setting tokens in your store for future requests
isRefreshing = false;
failedRequests.add({'err': err, 'handler': handler});
debugPrint("RETRYING ${failedRequests.length} FAILED REQUEST(s)");
retryRequests(parsedResponse['data']['access_token']);
}
Future retryRequests(token) async {
Dio retryDio = Dio(
BaseOptions(
baseUrl: baseURI,
),
);
for (var i = 0; i < failedRequests.length; i++) {
debugPrint(
'RETRYING[$i] => PATH: ${failedRequests[i]['err'].requestOptions.path}');
RequestOptions requestOptions =
failedRequests[i]['err'].requestOptions as RequestOptions;
requestOptions.headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json'
};
await retryDio.fetch(requestOptions).then(
failedRequests[i]['handler'].resolve,
onError: (error) =>
failedRequests[i]['handler'].reject(error as DioError),
);
}
isRefreshing = false;
failedRequests = [];
}
}
陷阱:
- 确保您跟踪每个失败请求的
DioError
和ErrorInterceptorHandler
,并使用它们执行重试请求。 - 请确保在重试所有失败的请求后将
isRefreshing
设置为 false。 - 完成后,请确保清空
failedRequests
列表。
希望这个解决方案适合您!
关于flutter - Dio - QueuedInterceptor 处理具有多个请求的刷新 token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76228296/