使用Dio QueuedInterceptor处理多请求时Token刷新重复调用问题排查
Dio并发请求401重复刷新Token的问题分析与解决
问题根源
- 无并发同步机制:三个请求同时触发401错误,各自进入
onError回调后,都会独立执行refresh_token请求,没有互斥逻辑,导致重复调用刷新接口。 - Token未全局生效:第一个请求刷新成功后,仅修改了自身请求的Header并重试,但未更新Dio实例的全局默认Header,另外两个请求的拦截器仍会判定Token过期,继续触发刷新。
- QueuedInterceptor的错误处理逻辑:队列拦截器仅保证请求的发送顺序,但错误回调是每个请求独立触发的,不会自动等待其他请求的错误处理完成。
解决方案
1. 新增全局互斥锁,避免重复刷新
使用Dart的Lock类(需导入sync包),确保同一时间只有一个请求执行Token刷新逻辑,其他请求等待锁释放后直接使用新Token重试。
2. 刷新成功后全局更新Dio的默认Header
将新获取的Token更新到Dio实例的全局headers中,后续所有请求(包括等待的请求)都会自动携带新Token。
3. 统一处理等待的401请求
刷新Token完成后,让所有等待的请求使用新Token重试,无需各自再触发刷新。
修改后的代码示例
import 'package:dio/dio.dart'; import 'package:synchronized/synchronized.dart'; class AuthInterceptor extends QueuedInterceptor { // 全局互斥锁,保证同一时间只有一个刷新操作 final _lock = Lock(); // 标记是否正在刷新Token bool _isRefreshing = false; // 存储等待刷新完成后重试的请求 final List<ErrorInterceptorHandler> _pendingRequests = []; @override Future<void> onError(DioError err, ErrorInterceptorHandler handler) async { if (err.response?.statusCode == 401) { await _lock.synchronized(() async { // 若正在刷新Token,将当前请求加入等待队列 if (_isRefreshing) { _pendingRequests.add(handler); return; } _isRefreshing = true; try { final tokenDio = Dio(); final refreshResponse = await tokenDio.post('/${endPoint.path}', data: endPoint.requestBody); if (refreshResponse.statusCode == 201) { final newAccessToken = refreshResponse.data['access_token']; // 全局更新Dio的默认Header dio.options.headers['Authorization'] = 'Bearer $newAccessToken'; // 重试当前请求 final response = await dio.request( err.requestOptions.path, data: err.requestOptions.data, queryParameters: err.requestOptions.queryParameters, options: Options( method: err.requestOptions.method, headers: err.requestOptions.headers ..['Authorization'] = 'Bearer $newAccessToken', ), ); handler.resolve(response); // 重试所有等待的请求 _processPendingRequests(newAccessToken); } else { // 刷新失败,拒绝所有请求 handler.reject(err); _rejectPendingRequests(err); } } catch (error) { handler.reject(err); _rejectPendingRequests(err); } finally { _isRefreshing = false; } }); } else { handler.next(err); } } // 处理等待的请求,用新Token重试 void _processPendingRequests(String newToken) { for (final handler in _pendingRequests) { final options = handler.requestOptions; options.headers['Authorization'] = 'Bearer $newToken'; dio.request( options.path, data: options.data, queryParameters: options.queryParameters, options: Options(method: options.method, headers: options.headers), ).then((response) { handler.resolve(response); }).catchError((error) { handler.reject(error as DioError); }); } _pendingRequests.clear(); } // 刷新失败时,拒绝所有等待的请求 void _rejectPendingRequests(DioError err) { for (final handler in _pendingRequests) { handler.reject(err); } _pendingRequests.clear(); } }
关键说明
_lock.synchronized确保刷新Token的逻辑同一时间只有一个执行路径。_isRefreshing标记避免重复进入刷新流程。_pendingRequests存储所有等待Token刷新的请求,刷新完成后统一重试,避免每个请求都触发刷新。
内容的提问来源于stack exchange,提问作者hemant
相关产品推荐
相关产品推荐

