Flutter 入門與實戰(三十二):小夥子,你買票了嗎?

島上碼農發表於2021-07-21

前言

話說那時候還是學生,有一年春運,冒著風雪在北京北站買回家的票。悲催的是,特快車的票沒買著,只買到了綠皮火車,L 打頭的臨時車。更悲催的是,原本33個小時的車程(特快不到20個小時),結果……走走停停超過了40多個小時,那煎熬,感覺永無盡頭!

image.png image.png

換個別的方式吧!於是我提前下車了,下車到了出站口,車站檢票人員瞄了一眼我的車牌,眼神有點奇怪,估計是在想這小夥子是不是傻。還好我的票到站點是更遠的站,不需要補票。其他沒票的,就被嚷嚷著補票了——那個時候為了能回家,多少人是偷偷混上車的啊! 以前是一把心酸一把淚,現在回憶起來那都是年少時難得的經歷啊!

image.png

火車票與會話

火車票就好像我們後臺的會話 Session一樣,坐火車的這個過程就是我們的會話過程。我們需要先買票才能上車,下車出站票就失效了。本篇我們就來介紹Dio如何使用會話Session保持登入狀態。首先請更新後臺程式碼:gitee.com/island-code…,涉及到的介面如下:

先拿 Postman 走一遍流程做一下介面自測(我們的口號是真正的全棧:產品->設計->後端->前端->測試->運維一條龍服務)。過程見下圖:

螢幕錄製2021-07-21 下午9.32.35.gif 可以看到整個會話過程如下:

  • 登入成功後,後端會返回 Cookie,對應的是一個 sessionId 欄位(欄位名由後端定),這裡就是登入人的會話資訊。
  • 每次請求相同域名的介面時候,Postman就會攜帶這個 Cookie 到後端,從而實現了會話過程的免登入驗證。
  • 如果退出登入,後端會清除會話資訊,此時攜帶原有的會話資訊請求會被後端認為是無效的。

這個過程對於我們使用 Postman 是無感知的,就好比我們用瀏覽器訪問網頁一樣。但是對於 App 來說就不一樣了。App本身是不會自動把登入會話資訊攜帶到其他介面的,也就是說如果我們登入成功後不做會話處理,那麼其他涉及到需要會話資訊的介面全部會失敗!這就好比我們買了張火車票,但是弄丟了,傻乎乎地跑到檢票口才發現進不了站!!!

攜帶會話資訊

怎麼辦?首先是在買票環節我們要把票儲存好,在我們的 HttpUtil 中找一個嚴實的口袋儲存車票,這個口袋就是 Dio 的 options.headers。當我們往 options 裡存放資訊時,每次請求都會攜帶這些資訊到後端(除非請求本身將其引數覆蓋)。

static void setCookie(String cookie) {
  _dioInstance.options.headers['Cookie'] = cookie;
}
複製程式碼

options 是 Dio 的預設請求配置,是一個BaseOptions物件,包括了很多屬性,例如請求方法、響應型別、請求內容型別,連線超時時間、預設查詢引數、請求頭等等,通過設定 options 我們可以設定很多預設的引數,從而避免到處設定。常見的設定有:

  • 連線超時:connectTimeout,可以根據需要設定超時時長。
  • 請求內容型別:contentType,是一個字串,預設是:application/json; charset=utf-8。可以在 headers 裡設定也可以指定。
  • 響應型別:responseType,是一個列舉,預設是 json
  • 預設查詢引數:queryParameters,一個Map 物件,假設介面有預設的請求引數(如終端型別,版本號這類)可以加入到這裡。
  • 請求頭:headers,也就是我們今天的主角,可以設定請求的 cookie 資訊或者其他引數(比如 JWT 的 token,後端要求傳遞的其他通用引數)。其中 cookie 固定使用 Cookie 欄位儲存,當然如果後端要自定義別的欄位也是可以的。

找著了口袋,我們買完票後就需要把票存放起來,也就是登入成功後要獲取到後端設定的 cookie,這個是在響應頭裡,除錯的時候可以列印出來整個 headers 檢視,如下所示:

image.png

實際 cookie 存在response.headers.map['set-cookie']中,注意這是一個陣列,我們這裡因為只有一個 cookie,因此取第一個元素再呼叫 HttpUtilsetCookie即可。登入處理業務邏輯如下:

_handleSubmit(String username, String password) async {
  EasyLoading.showInfo('請稍候...');
  var response = await AuthService.login(username, password);
  if (response != null && response.statusCode == 200) {
    EasyLoading.showSuccess('登入成功');
    if (response.headers.map['set-cookie'] != null) {
      HttpUtil.setCookie(response.headers.map['set-cookie'][0]);
    }
    Navigator.of(context).pop();
  } else {
    EasyLoading.showInfo(response.statusMessage);
  }
  EasyLoading.dismiss();
}
複製程式碼

這樣,主要我們不退出登入,我們就可以攜帶會話資訊與後端友好地互動了——碰到查票的你也不用心慌了!

image.png

清除會話資訊

下車了,以前票預設是要回收的,可千萬別隻是返回到登入頁面哦!退出登入時 呼叫後端的退出登入介面,成功後需要清除掉本地的儲存的sessionclearCookie方法很簡單,只是將 headersCookie 設定為 null 即可。

void _logout() async {
  var response = await AuthService.logout();
  if (response != null && response.statusCode == 200) {
    HttpUtil.clearCookie();
    EasyLoading.showSuccess('已退出登入');
  } else {
    print('logout Failed');
  }
}
複製程式碼
static void clearCookie() {
  _dioInstance.options.headers['Cookie'] = null;
}
複製程式碼

驗票

有了這個,我們就可以像乘務員那樣驗票了。驗票這裡只是呼叫了後端的一個需要會話資訊的介面來驗證。

void _checkSession() async {
  var response = await AuthService.checkSession();
  if (response != null && response.statusCode == 200) {
    print(response.data);
    EasyLoading.showSuccess('驗票通過,持票人:' + response.data['loginUser']);
  } else {
    print('Request Failed');
  }
}
複製程式碼

執行效果如下圖:

螢幕錄製2021-07-21 下午10.18.33.gif

總結

本篇介紹了 Dio如何在登入後攜帶會話資訊,以避免其他需要登入鑑權的介面請求失敗。需要注意的是,會話資訊在退出登入後需要及時清除,同時,會話資訊可能還攜帶失效時間資訊,可以根據失效時間來判斷是否需要重新登入。另外,Dio官方推薦的 Cookie 管理外掛是dio_cookie_managerdio_cookie_manager使用攔截器來管理 Cookie,支援使用 CookieJar 來在記憶體存放 Cookie,或使用 PersistCookieJar持久化Cookie。

相關文章