背景
小程序這兩年可謂是風(fēng)風(fēng)火火伶丐,已經(jīng)涉及到了行業(yè)的方方面面,作為一枚前端碼農(nóng)疯特,為了緊跟時(shí)代的步伐哗魂,滿足市場(chǎng)的需要,樓主也步入了小程序的坑中漓雅,一探究竟录别,從最初的原生開發(fā)朽色,過渡到WePY的引入,又轉(zhuǎn)到了mpvue的框架组题,最近又準(zhǔn)備踩踩Taro的坑 了~
之所以從mpvue
框架遷移到Taro
框架葫男,一是由于該框架是京東凹凸實(shí)驗(yàn)室開發(fā)維護(hù)的,目前來看比較活躍崔列,更新迭代速度比較快梢褐,質(zhì)量上有一定的保障;二是該框架支持多端統(tǒng)一赵讯,一勞永逸盈咳,為后面拓展H5等其他業(yè)務(wù)奠定基礎(chǔ)~
因業(yè)務(wù)需求,我們采用JWT
進(jìn)行身份鑒權(quán)边翼,所以才有了這篇JWT
授權(quán)登錄 解(cai)決(keng)方(zhi)案(nan)鱼响,如果對(duì)JWT
還不是很了解的小伙伴,請(qǐng)猛戳這里~
ok讯私,不廢話了热押,直接進(jìn)入你們最想看到代碼環(huán)節(jié)~
正文
關(guān)于Taro
的安裝的使用西傀,我這里就不再贅述了斤寇,具體的步驟可以參考官網(wǎng)給出的教程,我們直接來看看授權(quán)登錄這一塊~
FBI WARNING
: 本文相關(guān)的代碼是跟樓主目前公司的有些業(yè)務(wù)場(chǎng)景有關(guān)拥褂,如有不同娘锁,請(qǐng)自行更改即可~
初始化Taro
項(xiàng)目時(shí),我們需要選擇使用Redux
模版饺鹃,進(jìn)行狀態(tài)管理莫秆,然后默默等待依賴安裝完成,通過npm run dev:weapp
命令就可以啟動(dòng)我們的小程序服務(wù)了~
接下來跟著我的步驟走下去~
step1
首先悔详,我們需要使用到Redux
定義一些app
相關(guān)的數(shù)據(jù)镊屎,方便我們后面的判斷和使用~
Redux
三步走,reducer
茄螃、constant
缝驳、action
我這里就直接貼代碼了~
-
constants
目錄
//constants app.js
//更改app登錄狀態(tài)
export const CHANGE_APP_ON_LAUNCH = 'change_app_on_launch';
//寫入授權(quán)信息
export const INSERT_AUTHORIZE = 'insert_authorize';
-
actions
目錄
// actions app.js
import * as constants from '../constants/app'
//更改登錄狀態(tài)
export const changeAppOnLaunch = () => ({
type : constants.CHANGE_APP_ON_LAUNCH
})
//寫入請(qǐng)求token
export const insertToken = (authorize) => ({
type : constants.INSERT_AUTHORIZE ,
authorize
})
-
recuders
目錄
import * as constants from '../constants/app'
const INITIAL_STATE ={
//請(qǐng)求接口域名地址
baseURL : 'https://xx.xxx.xx/' ,
//應(yīng)用首次加載
appOnLaunch : true ,
//請(qǐng)求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
對(duì)象上归苍,這樣我們也就可以在后面通過Taro.$store
這種方式直接獲取進(jìn)行操作處理用狱,即:
//app.js
componentDidMount() {
//將redux狀態(tài)掛載到 Taro 對(duì)象上,方便使用
Taro.$store = store;
}
step2
我們?cè)?code>src創(chuàng)建一個(gè)utils
文件夾拼弃,用來存放我們一些公用的方法之類的文件~
由于微信小程序的限制夏伊,我們無法得知用戶的登錄狀態(tài)是否過期,所以我們必須要通過wx.checkSession
來接口檢測(cè)當(dāng)前用戶登錄態(tài)是否有效吻氧,做對(duì)應(yīng)的邏輯處理~
因此溺忧,我們創(chuàng)建一個(gè)auth.js
文件咏连,用來微信登錄,獲取獲取授權(quán)信息鲁森,其流程可以參考官方的這張時(shí)序圖捻勉!
我們的JWT
就是上面圖所提到的自定義登錄狀態(tài),它一般是長這樣的
其中exp
是令牌過期時(shí)間刀森,iat
令牌簽發(fā)時(shí)間踱启,nbf
令牌生效時(shí)間,token
是用戶狀態(tài)研底,包含用戶的身份信息埠偿,我們主要會(huì)用到exp
和 token
兩個(gè)字段,用來判斷令牌是否過期和發(fā)送請(qǐng)求攜帶用戶身份~
因此榜晦,我的做法是冠蒋,在用戶進(jìn)入小程序的時(shí)候通過Taro.checkSession()
,檢查session_key
是否過期乾胶,如果過期需要通過 Taro.login()
重新登錄進(jìn)行授權(quán)抖剿,獲取JWT
授權(quán)信息,否則就判斷用戶的令牌是否過期识窿,如果過期就要重新刷新令牌斩郎,重新獲取,其代碼如下~
// auth.js
import Taro from '@tarojs/taro'
import { insertToken , changeAppOnLaunch } from '../actions/app'
//獲取數(shù)據(jù)
export default class Auth {
//app授權(quán)
static appCheckAuth(){
return new Promise(function (resolve) {
const state = Taro.$store.getState();
//如果有授權(quán)信息
if( Auth.checkAuth() && !state.app.appOnLaunch ){
//直接返回
resolve(true);
}else{
//判斷session_key是否過期
Taro.checkSession().then(async ()=>{
//未過期檢查token是否有效
if( !Auth.checkAuth() ){
//判斷是否 token 請(qǐng)求成功
let flag = await getAuthToken();
if( flag ) {
//更新app狀態(tài)
Taro.$store.dispatch(changeAppOnLaunch());
resolve(true);
}else{
//提示
Taro.showToast({
title : '獲取授權(quán)信息失敗' ,
icon : 'none' ,
mask : true
})
}
}else{
//更新app狀態(tài)
Taro.$store.dispatch(changeAppOnLaunch());
//token 沒有過期喻频,直接返回
resolve(true);
}
}).catch(async (err)=> {
console.log(err);
let flag = await getAuthToken();
//判斷是否 token 請(qǐng)求成功
if( flag ) {
//更新app狀態(tài)
Taro.$store.dispatch(changeAppOnLaunch());
resolve(true);
}else{
//提示
Taro.showToast({
title : '獲取授權(quán)信息失敗' ,
icon : 'none' ,
mask : true
})
}
})
}
})
}
// 檢查令牌是否有效 true--> 有效 false--> 無效
static checkAuth(){
const state = Taro.$store.getState();
//從緩存讀取授權(quán)信息
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;
}
}
//授權(quán)用戶 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) {
//寫入狀態(tài)管理
Taro.$store.dispatch(insertToken(authorize));
//寫入緩存
Taro.setStorageSync('authorize',authorize)
}
上面總共定義不少方法缩宜,我都有注釋,相信大家也都能看懂甥温,其主要思想就是锻煌,如果用戶首次進(jìn)入小程序,通過login
進(jìn)行登錄姻蚓,并將令牌存入本地宋梧,后面通過getToken
方法進(jìn)行獲取請(qǐng)求的令牌,以后用戶在進(jìn)入小程序的時(shí)候狰挡,就進(jìn)行授權(quán)信息和登錄狀態(tài)檢測(cè)捂龄,如果登錄過期或者令牌過期則通過getAuthToken
方法進(jìn)行請(qǐng)求,獲取最新的身份令牌圆兵!
上面之所以要觸發(fā)一次 changeAppOnLaunch
是因?yàn)橛脩裘看芜M(jìn)入需要進(jìn)行一次checkSeesion
檢測(cè)跺讯,如果用戶本次session_key
未過期,一般來說本次小程序生命周期內(nèi)就就不需要在進(jìn)行checkSession
檢測(cè)了殉农,因此我們用一個(gè)狀態(tài)來標(biāo)識(shí)用戶是否是重新進(jìn)入小程序~
step3
假定我們的頁面的初始化請(qǐng)求都寫在componentDidMount
生命周期內(nèi)刀脏,那么現(xiàn)在我們要做的就是要在頁面接口請(qǐng)求之前去判斷是否已經(jīng)獲取到token
,如果有token
則進(jìn)行正常的接口請(qǐng)求超凳,否則就通過上面的登錄獲取token
愈污,不然就會(huì)導(dǎo)致接口未攜帶令牌而發(fā)生錯(cuò)誤~
那么問題來了耀态,我們?nèi)绾巫枞?code>componentDidMount而保證每次接口請(qǐng)求之前都已經(jīng)拿到了token
呢,這里我的做法就是對(duì)Component
進(jìn)行二次封裝暂雹,采用的是ES7
提供的裝飾器首装,關(guān)于裝飾器的使用,可以瞄一眼這篇文章~
通過裝飾器杭跪,我們可以將Component
進(jìn)行二次封裝仙逻,可以根據(jù)業(yè)務(wù)情況將授權(quán)、分享等全部進(jìn)行歸一化處理涧尿,這里我們創(chuàng)建一個(gè)pageInit.js
系奉,來實(shí)現(xiàn)這個(gè)功能~
//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 , 鑒權(quán)
async componentDidMount() {
let result = await auth.appCheckAuth();
//授權(quán)成功
if( result ){
//調(diào)用父組件的函數(shù)
super.componentDidMount && super.componentDidMount();
}else{
//授權(quán)失敗
Taro.showToast({
title : '授權(quán)失敗' ,
icon : 'none' ,
mask : true
})
}
}
//重寫分享
onShareAppMessage(){
let shareOptions = super.onShareAppMessage();
//如果當(dāng)前頁面配置分享使用配置的
if( shareOptions ) return shareOptions;
//默認(rèn)分享
return {
title : '默認(rèn)分享內(nèi)容'
}
}
//重新下拉刷新
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
阻塞缺亮,讓其先執(zhí)行自身的 componentDidMount
方法,拿到授權(quán)信息后桥言,在調(diào)用父類的 componentDidMount
方法萌踱,這樣就可以保證授權(quán)信息獲取到再進(jìn)行接口請(qǐng)求了~
我們要做的就是在頁面內(nèi)通過 @pageInit()
進(jìn)行裝飾即可~
這里我做了個(gè)測(cè)試,在清空全部授權(quán)信息后号阿,在頁面進(jìn)行一次接口請(qǐng)求并鸵,可以看到先執(zhí)行的是 lite
獲取用戶令牌,然后進(jìn)行頁面的接口請(qǐng)求倦西,保證了執(zhí)行順序不會(huì)錯(cuò)亂~
小結(jié)
ok能真,到此為止,我們基本上已經(jīng)算是完成了整套體系扰柠,接下來就是在接口請(qǐng)求中加上token
就好了,這里我就補(bǔ)不貼代碼了~
完成的代碼可去倉庫自行查看~
Taro
對(duì)于我來說疼约,也算是屬于初探卤档,這篇文章也是希望能給跟我一樣萌新的小伙伴一個(gè)拋磚引玉的作用,如果有哪里寫的不對(duì)的地方程剥,還請(qǐng)批評(píng)斧正~