You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 03:22:31