Flutter中使用Queued Interceptor实现Dio请求锁定与解锁
用Dio QueuedInterceptor处理401过期令牌问题
一、核心实现:QueuedInterceptor拦截401并刷新令牌
QueuedInterceptor自带请求队列管理能力,刚好可以替代原有的lock/unlock逻辑,核心思路是:遇到401时暂停队列,完成令牌刷新后更新请求头并重试原请求,最后恢复队列,同时要避免重复触发刷新操作。
以下是完整代码示例:
import 'package:dio/dio.dart'; class AuthInterceptor extends QueuedInterceptor { final Dio _dio; bool _isRefreshing = false; // 存储等待刷新完成后重试的请求 final List<RequestInterceptorHandler> _pendingRequests = []; AuthInterceptor(this._dio); @override void onError(DioException err, ErrorInterceptorHandler handler) async { // 过滤出401错误,且排除刷新令牌的请求本身,避免无限循环 if (err.response?.statusCode == 401 && err.requestOptions.path != '/auth/refresh-token') { final requestHandler = handler; final options = err.requestOptions; if (!_isRefreshing) { _isRefreshing = true; try { // 从本地获取存储的刷新令牌 final refreshToken = await _getLocalRefreshToken(); // 调用刷新令牌接口 final refreshResponse = await _dio.post( '/auth/refresh-token', data: {'refresh_token': refreshToken}, ); // 保存新的令牌到本地 final newAccessToken = refreshResponse.data['access_token']; final newRefreshToken = refreshResponse.data['refresh_token']; await _saveLocalTokens(newAccessToken, newRefreshToken); // 更新Dio全局请求头和当前请求的头 _dio.options.headers['Authorization'] = 'Bearer $newAccessToken'; options.headers['Authorization'] = 'Bearer $newAccessToken'; // 重试原请求 final response = await _dio.fetch(options); requestHandler.resolve(response); // 批量处理所有等待中的请求 _pendingRequests.forEach((handler) { handler.next(options); }); _pendingRequests.clear(); } catch (e) { // 刷新失败时,标记所有等待请求为失败 _pendingRequests.forEach((handler) { handler.reject(DioException(requestOptions: options, error: '令牌刷新失败')); }); _pendingRequests.clear(); // 触发刷新令牌过期的处理逻辑 _handleRefreshTokenExpired(); } finally { _isRefreshing = false; } } else { // 若正在刷新令牌,将当前请求加入等待队列 _pendingRequests.add(requestHandler); } } else { // 非401错误直接抛出 handler.reject(err); } } // 模拟从本地获取刷新令牌(实际用SharedPreferences等存储) Future<String> _getLocalRefreshToken() async { return 'your_local_refresh_token'; } // 模拟保存新令牌到本地 Future<void> _saveLocalTokens(String accessToken, String refreshToken) async { // 实际项目中实现持久化存储逻辑 } // 处理刷新令牌过期的逻辑 void _handleRefreshTokenExpired() { // 1. 清空本地存储的所有令牌 // 2. 跳转到登录页面(根据项目路由框架实现,如Navigator.pushReplacement) // 3. 移除Dio全局请求头中的Authorization字段 _dio.options.headers.remove('Authorization'); } }
二、处理刷新令牌本身过期的情况
当刷新令牌请求返回401时,说明用户登录状态彻底失效,此时需要:
- 清空本地存储的accessToken和refreshToken
- 立即跳转至登录页面,强制用户重新登录
- 清空队列中所有等待的请求,避免无效重试
- 重置Dio全局请求头,移除过期的授权信息
上面代码中的_handleRefreshTokenExpired()方法就是负责这个逻辑,你可以根据项目的路由框架(如Flutter Navigator、GetX等)实现页面跳转。
注意事项
- 必须排除刷新令牌接口本身的401错误,防止无限循环
- 用
_isRefreshing标记控制刷新操作的唯一性,避免多个401请求同时触发多次刷新 - 重试原请求时,要确保请求的method、data、headers等参数完全复用
内容的提问来源于stack exchange,提问作者Common Sense
相关产品推荐
相关产品推荐

