flutetr dio 攔截器實現 token 失效重新整理

jethroHuang發表於2019-03-02

網上查了一圈,沒發現有人分享,一路吃坑吃過來,終於實現了自動重新整理 token 並重發請求的攔截器。

然後就想著分享一下,也許分享出來可以幫助小夥伴快速實現自己的token重新整理機制,關於Dio的攔截器,不過多介紹,直接前往 Dio 的文件,即可找到攔截器的介紹。我只說一下使用上需要注意的點,以及我寫的程式碼。不多,也就70行。

攔截器文件傳送門:Dio中文文件

先上攔截器類的整體程式碼,然後簡單講解一下

import 'dart:async';

import 'package:college_circle/data/events/login_Invalid_event.dart';
import 'package:college_circle/data/models/auth.dart';
import 'package:college_circle/data/net/dio_util.dart';
import 'package:college_circle/util/config.dart';
import 'package:college_circle/util/event_manager.dart';
import 'package:dio/dio.dart';

class RefreshTokenInterceptor extends Interceptor {
  @override
  onError(DioError err) async {
    if (err.response != null && err.response.statusCode == 401 && Auth.isLogin) {
      Dio dio = DioUtil().getDio(); //獲取dio單例
      dio.lock();
      Auth.token = await getToken(); //獲取新token
      Auth.save();
      DioUtil().setToken(Auth.token);
      dio.unlock();

      var request = err.response.request; //千萬不要呼叫 err.request
      try {
        var response = await dio.request(request.path,
            data: request.data,
            queryParameters: request.queryParameters,
            cancelToken: request.cancelToken,
            options: request,
            onReceiveProgress:request.onReceiveProgress //TODO 差一個onSendProgress
            );
        return response;
      } on DioError catch (e) {
        return e;
      }
    }
    super.onError(err);
  }

  ///獲取新token
  Future<String> getToken() async {
    String token = Auth.token; //獲取當前token
    Dio ndio = DioUtil.createNewDio(); //建立新dio例項
    ndio.options.baseUrl = AppConfig.DOMAIN_NAME; //配置baseUrl
    ndio.options.headers['Authorization'] = 'JWT ${token}'; //設定當前token
    try {
      var response = await ndio.get('/auth/fresh');
      token = response.data['data']['access_token']; //獲取返回的新token
      print('newToken:$token');
    } on DioError catch (e) {
      if (e.response == null) {
        print('DioError:${e.message}');
      } else {
        if (e.response.statusCode == 422) {
          print('422Error:${e.response.data['msg']}');
          //422狀態碼代表異地登入,token失效,傳送登入失效事件,以便app彈出登入頁面
          EventManager.get(EventGroup.app).fire(LoginInvalidEvent());
        }
      }
    }
    return token;
  }
}

複製程式碼

這裡分成兩個函式講解:

getToken

我們後端的 token 驗證是檢查請求頭裡的 Authorization 欄位。然後能用到期的 token 直接請求新的 token,然後再來看 getToken 函式,這是個非同步函式,很簡單,就是拿到當前的 token,然後建立一個新的 dio 物件(這裡注意一定要建立新的 dio 物件,不要用新增了攔截器的那個 dio 物件,不然會發生死鎖,這一點官方文件也有提到)。

然後用新的 dio 物件去請求新的 token,然後把新 token 返回給呼叫者。這裡有個異常處理,處理 token 重新整理失敗的情況是否為異地登入,然後傳送登入失效的事件,這個事件傳送是用的 event_bus 外掛。超級好用。

onError

這裡挺麻煩的,先獲取 err 物件的 response 物件,判斷狀態碼是否為 401 ,為 401 則是 token 失效了,需要重新整理。在重新整理之前,要給攔截器上個鎖,這裡給 requestLock 給鎖上,鎖 requestLock 目的是為了防止更多的 401 錯誤,並且導致重複重新整理 token。

然後呼叫 getToken 獲取新 token 然後把新 token 儲存起來,再給 DioUtil單例物件的 dio 設定上token,這個單例是平時請求用的。然後把攔截器的鎖給解開,這樣後面的請求就可以攜帶上新的token去伺服器拿資料了。

接下來是容易出錯的請求重發環節,這裡要拿到 token 失效的那個請求。留意一段程式碼var request = err.response.request;

我一開始是呼叫的 err.request ,結果後續用到 request 變數的地方就報錯了,空物件錯誤,折騰了一會兒才發現要拿到發生錯誤的請求要用 err.response.request

然後就是重新發起一個請求獲取資料了

      try {
        var response = await dio.request(request.path,
            data: request.data,
            queryParameters: request.queryParameters,
            cancelToken: request.cancelToken,
            options: request,
            onReceiveProgress:
                request.onReceiveProgress //TODO 差一個onSendProgress
            );
        return response;
      } on DioError catch (e) {
        return e;
      }
     
複製程式碼

這裡就可以用平時發請求的 dio 物件了,最好用 try...catch 捕獲錯誤,如果發生錯誤就把錯誤拋給下一個攔截器。

最後把請求到的結果返回一下就好了。新請求的引數都從之前獲取的 request 物件裡拿。

程式碼還有一點瑕疵,但是能正常的執行了,我覺得重發請求那一塊還有優化的空間。

相關文章