将 Dio 拦截器添加到 Flutter Riverpod

问题描述

我正在向服务器发出请求,但一段时间后 access token 过期。我正在使用 dio,最近遇到了拦截器。当返回 access token 时,如何向所有调用添加拦截器并使用 refresh token 获取 401。 我将我的 tokens 存储在 Shared Preferences 中。这是我的代码



class AuthenticationService {
  final dio _dio;
  final LocalDBService _prefs;

  AuthenticationService(this._dio,this._prefs);

  Future<User?> fetchUser() async {
    try {
      Tokens _tokens = await _prefs.getTokens();
      if (_tokens.accesstoken == '') {
        return null;
      }
      final result = await _dio.get(
        CURRENT_USER,options: Options(headers: {
          "Authorization": "Bearer ${_tokens.accesstoken}",'Accept': "application/json",}),);
      User _user = User.fromJson(result.data);
      return _user;
    } catch (e) {
      print("fetcherror");
      print(e);
      return null;
    }
  }
}

final authenticationServiceProvider = Provider.autodispose<AuthenticationService>((ref) {
  final _prefs = ref.read(localDBProvider);
  final _dio = dio(); // Need to add interceptors to this
  return AuthenticationService(_dio,_prefs);
});

final userProvider = FutureProvider.autodispose<User?>((ref) {
  ref.maintainState = true;
  return ref.read(authenticationServiceProvider).fetchUser();
});

如何以及在哪里添加拦截器。还想将 dio() 实例移动到它自己的类

解决方法

我会推荐 2 个 dio 实例,一个用于日志记录 (authProvider),另一个用于应用程序的其余部分,因此您可以锁定一个并使用您的 authProvider 和它自己的 dio 实例来首先刷新令牌

class AuthenticatorInterceptor extends Interceptor {
  final AuthenticationService authService;
  final Dio dioReference;

  AuthenticatorInterceptor(this.authService,this.dioReference);

  @override
  void onRequest(
      RequestOptions options,RequestInterceptorHandler handler) async {
    Tokens _tokens = await authService.token;
    /// get the token from your authService and do some logic with it:
    /// check if its still valid (expirationTime)
    /// refresh it if its not,etc.
    if (_token.refreshToken != null && _token.hasExpired) { // check somehow if the token is invalid and try to refresh it
    try {
        dioReference.lock(); // lock the current dio instance
        authService.refresh(); //try to get a new access token
        _tokens = await authService.token; //get the new token if the refresh was succesful
      } on DioError catch (error) { // there was an error refreshing,report it or do some logic
        dioReference.clear();
        return handler.reject(error);
      } finally {
        dioReference.unlock(); //unlock the instance and move on
      }
    }
    // if there was no error you can move on with your request with the new token (if any)
    options.headers
        .putIfAbsent(HttpHeaders.authorizationHeader,() => "Bearer ${_tokens.accessToken}");
    return handler.next(options);
  }

  @override
  void onError(DioError error,ErrorInterceptorHandler handler) async {
    if (error.type == DioErrorType.response &&
        error.response!.statusCode == 401) {
      // If you still receive 401 then maybe you should logout and report the user that the refresh token is invalid (maybe the server removed it)
    }
    return super.onError(error,handler);
  }
}

现在创建一个带有 Dio 实例的提供程序,您将在您的应用程序中使用该实例(这与您将与 AuthService 一起使用的实例不同,因此您可以在请求令牌时锁定它)

final dioProvider = Provider<Dio>((ref) {
  final authenticationService = ref.watch(authenticationServiceProvider);
  final Dio dio = Dio();

  ref.onDispose(dio.close);

  return dio
    ..interceptors.addAll([
      AuthenticatorInterceptor(authenticationService,dio),//this interceptor holds a reference of your authService and the dio instance that uses it
    ]);
},name: 'Dio');

现在像这样使用它

final userProvider = FutureProvider.autoDispose<User?>((ref) {
      final dio = ref.watch(dioProvider);
      final result = await dio.get(
        CURRENT_USER,// I have no idea where you get this
      );
      User _user = User.fromJson(result.data);
      ref.maintainState = true; // the request was succesful,you can mantain the state now
      return _user;
});

当然后台还有很多逻辑要做,你的 AuthService 在做请求之前必须至少有一个令牌(如果你知道用户没有登录,就没有目的进行调用)但这应该给如何处理拦截器的总体思路