flutter dio(4.0.0) 处理令牌过期(处理 401)

Posted

技术标签:

【中文标题】flutter dio(4.0.0) 处理令牌过期(处理 401)【英文标题】:flutter dio(4.0.0) handling token expiration (handling 401) 【发布时间】:2021-09-06 20:50:59 【问题描述】:

我已经声明了一个类来使用 Flutter Dio 发出 api 请求,如下所示。

class DioUtil 
  static Dio _instance;

  static Dio getInstance() 
    if (_instance == null) 
      _instance = createDio();
    
    return _instance;
  

  static Dio createDio() 
    var dio = Dio();
    dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) 
      // Do something before request is sent
      return handler.next(options); //continue
    , onResponse: (response, handler) 
      // Do something with response data
      return handler.next(response); // continue
    , onError: (DioError e, handler) async 
      if (e.response != null) 
        if (e.response.statusCode == 401) 
          var dio = DioUtil.getInstance();
          dio.interceptors.requestLock.lock();
          dio.interceptors.responseLock.lock();
          RequestOptions requestOptions = e.requestOptions;

          await refreshToken();
          Repository repository = Repository();
          var accessToken = await repository.readData("accessToken");
          final opts = new Options(
            method: requestOptions.method
          );
          dio.options.headers["Authorization"] = "Bearer " + accessToken;
          dio.interceptors.requestLock.unlock();
          dio.interceptors.responseLock.unlock();
          dio.request(requestOptions.path,
              options: opts,
              data: requestOptions.data,
              queryParameters: requestOptions.queryParameters);
        //TODO: handle else clause
      
    ));
    return dio;
  

  static refreshToken() async 
    Response response;
    Repository repository = Repository();
    var dio = Dio();
    final Uri apiUrl = Uri.parse(BASE_PATH + "auth/reIssueAccessToken");
    var refreshToken = await repository.readData("refreshToken");
    dio.options.headers["Authorization"] = "Bearer " + refreshToken;
    response = await dio.postUri(apiUrl);
    if (response.statusCode == 200) 
      LoginResponse loginResponse =
          LoginResponse.fromJson(jsonDecode(response.toString()));
      repository.addValue('accessToken', loginResponse.data.accessToken);
      repository.addValue('refreshToken', loginResponse.data.refreshToken);
     else 
      print(response.toString());
    
  

我使用flutter bloc模式,我的bloc如下。

class OurClassBloc extends Bloc<OurClassEvent, OurClassState> 
  OurClassBloc(OurClassState initialState) : super(initialState);
  Repository repository = Repository();

  @override
  Stream<OurClassState> mapEventToState(
    OurClassEvent event,
  ) async* 
    if (event is GetClasses) 
      yield* _getClassCategories(event);
    
  

  Stream<OurClassState> _getClassCategories(GetClasses event) async* 
    Response response;
    var dio = DioUtil.getInstance();
    final String apiUrl = (BASE_PATH + "classCategories");
    var accessToken = await repository.readData("accessToken");
    Map<String, dynamic> map = "active": event.active;
    dio.options.headers["Authorization"] = "Bearer " + accessToken;
    dio.options.headers["Accept"] = "*/*";
    try 
      response = await dio.get(apiUrl, queryParameters: map);
      if (response.statusCode == 200) 
        OurClassResponse loginResponse =
            OurClassResponse.fromJson(jsonDecode(response.toString()));
        yield OurClassSuccess(loginResponse);
      
      if (response.statusCode >= 400) 
        yield OurClassFailed();
      
     catch (e) 
      yield OurClassFailed();
    
  

当我使用有效的访问令牌发出请求时,我在 bloc 类中获得 200 个状态代码,并且 api 工作正常。当令牌过期时,dio 类正确获取新令牌,使用新令牌成功进行相同的 api 调用在下面的回调中,我也得到了正确的响应。

onResponse: (response, handler) 
  return handler.next(response);

但响应不涉及到 bloc 类。虽然它通过调用 return handler.next(response); 返回了响应,但它并没有到达 _getClassCategories 方法中的 response 变量。我希望对于这两种情况,bloc 类中的 response 变量都应该得到正确的响应:

    使用有效令牌进行 api 调用。 使用过期令牌进行 api 调用。

但只有场景 1 在我的代码中有效,希望这里有人可以帮助我解决这个问题。

EDIT- 这适用于 dio 以前的版本(3.0.10) - code

【问题讨论】:

【参考方案1】:
          dio.request(requestOptions.path,
              options: opts,
              data: requestOptions.data,
              queryParameters: requestOptions.queryParameters);

这一行创建了一个与原始请求无关的新请求。如果请求成功,则没有代码监听响应。如果您希望原始调用者接收任何内容,则需要将响应转发给原始处理程序:

          try 
              final response = await dio.request(requestOptions.path,
                  options: opts,
                  data: requestOptions.data,
                  queryParameters: requestOptions.queryParameters);
              handler.resolve(response);
           on DioError catch (error) 
              handler.next(error); // or handler.reject(error);
          

此外,请务必在非 401 情况下也将错误转发给处理程序。 Dio 4.0.0 拦截器不会自动转发任何内容。

【讨论】:

以上是关于flutter dio(4.0.0) 处理令牌过期(处理 401)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Dio 在 Flutter 中调用 API?

使用 Dio/bloc Flutter 处理错误

基于dio库封装flutter项目的标准网络框架

flutter 自动刷新token

Dio 没有响应关闭的 API

Dio Interceptor 无法处理同一回调中的第一个请求,仅处理后续调用