小程序這兩年可謂是風風火火,已經涉及到了行業的方方面面,作為一枚前端碼農,為了緊跟時代的步伐,滿足市場的需要,樓主也步入了小程序的坑中,一探究竟,從最初的原生開發,過渡到WePY的引入,又轉到了mpvue的框架,最近又準備踩踩Taro的坑 了~
之所以從mpvue
框架遷移到Taro
框架,一是由于該框架是京東凹凸實驗室開發維護的,目前來看比較活躍,更新迭代速度比較快,質量上有一定的保障;二是該框架支持多端統一,一勞永逸,為后面拓展H5等其他業務奠定基礎~
因業務需求,我們采用JWT
進行身份鑒權,所以才有了這篇JWT
授權登錄 解(cai)決(keng)方(zhi)案(nan),如果對JWT
還不是很了解的小伙伴,請猛戳這里~
ok,不廢話了,直接進入你們最想看到代碼環節~
關于Taro
的安裝的使用,我這里就不再贅述了,具體的步驟可以參考官網給出的教程,我們直接來看看授權登錄這一塊~
FBI WARNING
: 本文相關的代碼是跟樓主目前公司的有些業務場景有關,如有不同,請自行更改即可~
初始化Taro
項目時,我們需要選擇使用Redux
模版,進行狀態管理,然后默默等待依賴安裝完成,通過npm run dev:weapp
命令就可以啟動我們的小程序服務了~
接下來跟著我的步驟走下去~
首先,我們需要使用到Redux
定義一些app
相關的數據,方便我們后面的判斷和使用~
Redux
三步走,reducer
、constant
、action
我這里就直接貼代碼了~
constants
目錄//constants app.js //更改app登錄狀態 export const CHANGE_APP_ON_LAUNCH = 'change_app_on_launch'; //寫入授權信息 export const INSERT_AUTHORIZE = 'insert_authorize'; 復制代碼
actions
目錄// actions app.js import * as constants from '../constants/app' //更改登錄狀態 export const changeAppOnLaunch = () => ({ type : constants.CHANGE_APP_ON_LAUNCH }) //寫入請求token export const insertToken = (authorize) => ({ type : constants.INSERT_AUTHORIZE , authorize }) 復制代碼
recuders
目錄import * as constants from '../constants/app' const INITIAL_STATE ={ //請求接口域名地址 baseURL : 'https://xx.xxx.xx/' , //應用首次加載 appOnLaunch : true , //請求token authorize : '' } export default function app( state = INITIAL_STATE , action ){ switch (action.type){ case constants.CHANGE_APP_ON_LAUNCH : return { ...state , appOnLaunch : false }; case constants.INSERT_AUTHORIZE : return { ...state , authorize : action.authorize }; default : return state; } } 復制代碼
最后不要忘記將其通過combineReducers
方法合并到Redux
里面去~
為了方便后面的使用,這里我在入口文件app.js
中將store
直接掛在到了Taro
對象上,這樣我們也就可以在后面通過Taro.$store
這種方式直接獲取進行操作處理,即:
//app.js componentDidMount() { //將redux狀態掛載到 Taro 對象上,方便使用 Taro.$store = store; } 復制代碼
我們在src
創建一個utils
文件夾,用來存放我們一些公用的方法之類的文件~
由于微信小程序的限制,我們無法得知用戶的登錄狀態是否過期,所以我們必須要通過wx.checkSession
來接口檢測當前用戶登錄態是否有效,做對應的邏輯處理~
因此,我們創建一個auth.js
文件,用來微信登錄,獲取獲取授權信息,其流程可以參考官方的這張時序圖!
我們的JWT
就是上面圖所提到的自定義登錄狀態,它一般是長這樣的
其中exp
是令牌過期時間,iat
令牌簽發時間,nbf
令牌生效時間,token
是用戶狀態,包含用戶的身份信息,我們主要會用到exp
和token
兩個字段,用來判斷令牌是否過期和發送請求攜帶用戶身份~
因此,我的做法是,在用戶進入小程序的時候通過Taro.checkSession()
,檢查session_key
是否過期,如果過期需要通過Taro.login()
重新登錄進行授權,獲取JWT
授權信息,否則就判斷用戶的令牌是否過期,如果過期就要重新刷新令牌,重新獲取,其代碼如下~
// auth.js import Taro from '@tarojs/taro' import { insertToken , changeAppOnLaunch } from '../actions/app' //獲取數據 export default class Auth { //app授權 static appCheckAuth(){ return new Promise(function (resolve) { const state = Taro.$store.getState(); //如果有授權信息 if( Auth.checkAuth() && !state.app.appOnLaunch ){ //直接返回 resolve(true); }else{ //判斷session_key是否過期 Taro.checkSession().then(async ()=>{ //未過期檢查token是否有效 if( !Auth.checkAuth() ){ //判斷是否 token 請求成功 let flag = await getAuthToken(); if( flag ) { //更新app狀態 Taro.$store.dispatch(changeAppOnLaunch()); resolve(true); }else{ //提示 Taro.showToast({ title : '獲取授權信息失敗' , icon : 'none' , mask : true }) } }else{ //更新app狀態 Taro.$store.dispatch(changeAppOnLaunch()); //token 沒有過期,直接返回 resolve(true); } }).catch(async (err)=> { console.log(err); let flag = await getAuthToken(); //判斷是否 token 請求成功 if( flag ) { //更新app狀態 Taro.$store.dispatch(changeAppOnLaunch()); resolve(true); }else{ //提示 Taro.showToast({ title : '獲取授權信息失敗' , icon : 'none' , mask : true }) } }) } }) } // 檢查令牌是否有效 true--> 有效 false--> 無效 static checkAuth(){ const state = Taro.$store.getState(); //從緩存讀取授權信息 let authorize = state.authorize || Taro.getStorageSync('authorize') || {}, expiryTime = 0, nowTime = ~~(Date.now() / 1000); if (authorize.exp) { expiryTime = authorize.exp; } return expiryTime - nowTime > 300; } //獲取token static getToken(){ const state = Taro.$store.getState(); let authorize = state.authorize || Taro.getStorageSync('authorize'); return authorize.token; } } //授權用戶 token async function getAuthToken(){ const state = Taro.$store.getState(); //login let res = await Taro.login(); //獲取token let response = await Taro.request({ url : `${state.app.baseURL}api/xxx/xxx` , data : { code : res.code } , method : 'POST' }) //判斷是否成功 if( response.data.data && response.data.data.authorize ){ //寫入token let authorize = response.data.data.authorize; saveAuthToken(authorize); return true; }else{ console.log('獲取token失敗'); return false; } } //寫入信息 function saveAuthToken (authorize) { //寫入狀態管理 Taro.$store.dispatch(insertToken(authorize)); //寫入緩存 Taro.setStorageSync('authorize',authorize) } 復制代碼
上面總共定義不少方法,我都有注釋,相信大家也都能看懂,其主要思想就是,如果用戶首次進入小程序,通過login
進行登錄,并將令牌存入本地,后面通過getToken
方法進行獲取請求的令牌,以后用戶在進入小程序的時候,就進行授權信息和登錄狀態檢測,如果登錄過期或者令牌過期則通過getAuthToken
方法進行請求,獲取最新的身份令牌!
上面之所以要觸發一次changeAppOnLaunch
是因為用戶每次進入需要進行一次checkSeesion
檢測,如果用戶本次session_key
未過期,一般來說本次小程序生命周期內就就不需要在進行checkSession
檢測了,因此我們用一個狀態來標識用戶是否是重新進入小程序~
假定我們的頁面的初始化請求都寫在componentDidMount
生命周期內,那么現在我們要做的就是要在頁面接口請求之前去判斷是否已經獲取到token
,如果有token
則進行正常的接口請求,否則就通過上面的登錄獲取token
,不然就會導致接口未攜帶令牌而發生錯誤~
那么問題來了,我們如何阻塞componentDidMount
而保證每次接口請求之前都已經拿到了token
呢,這里我的做法就是對Component
進行二次封裝,采用的是ES7
提供的裝飾器,關于裝飾器的使用,可以瞄一眼這篇文章~
通過裝飾器,我們可以將Component
進行二次封裝,可以根據業務情況將授權、分享等全部進行歸一化處理,這里我們創建一個pageInit.js
,來實現這個功能~
//pageInit.js import Taro from '@tarojs/taro' import auth from '../utils/auth' function pageInit() { return function Component(Component) { return class extends Component { constructor (props){ super(props); } //onLoad componentWillMount(){ //初始分享信息 initShareMenu(this.state); } //阻塞 didMount , 鑒權 async componentDidMount() { let result = await auth.appCheckAuth(); //授權成功 if( result ){ //調用父組件的函數 super.componentDidMount && super.componentDidMount(); }else{ //授權失敗 Taro.showToast({ title : '授權失敗' , icon : 'none' , mask : true }) } } //重寫分享 onShareAppMessage(){ let shareOptions = super.onShareAppMessage(); //如果當前頁面配置分享使用配置的 if( shareOptions ) return shareOptions; //默認分享 return { title : '默認分享內容' } } //重新下拉刷新 onPullDownRefresh(){ if(super.onPullDownRefresh){ super.onPullDownRefresh(); setTimeout(() => { Taro.stopPullDownRefresh(); }, 1500) } } } }; } /** * 初始化分享信息 */ function initShareMenu (state) { // 初始化頁面分享信息 if ( state && state.canShare ) { Taro.showShareMenu({ withShareTicket: false }) } else { Taro.hideShareMenu(); } } export default pageInit; 復制代碼
核心思想是,通過async
和await
阻塞,讓其先執行自身的componentDidMount
方法,拿到授權信息后,在調用父類的componentDidMount
方法,這樣就可以保證授權信息獲取到再進行接口請求了~
我們要做的就是在頁面內通過@pageInit()
進行裝飾即可~
這里我做了個測試,在清空全部授權信息后,在頁面進行一次接口請求,可以看到先執行的是lite
獲取用戶令牌,然后進行頁面的接口請求,保證了執行順序不會錯亂~
ok,到此為止,我們基本上已經算是完成了整套體系,接下來就是在接口請求中加上token
就好了,這里我就補不貼代碼了~ 完成的代碼可去倉庫自行查看~
Taro
對于我來說,也算是屬于初探,這篇文章也是希望能給跟我一樣萌新的小伙伴一個拋磚引玉的作用,如果有哪里寫的不對的地方,還請批評斧正~