小程序這兩年可謂是風風火火,已經涉及到了行業的方方面面,做爲一枚前端碼農,爲了緊跟時代的步伐,知足市場的須要,樓主也步入了小程序的坑中,一探究竟,從最初的原生開發,過渡到WePY的引入,又轉到了mpvue的框架,最近又準備踩踩Taro的坑 了~html
之因此從mpvue
框架遷移到Taro
框架,一是因爲該框架是京東凹凸實驗室開發維護的,目前來看比較活躍,更新迭代速度比較快,質量上有必定的保障;二是該框架支持多端統一,一勞永逸,爲後面拓展H5等其餘業務奠基基礎~前端
因業務需求,咱們採用JWT
進行身份鑑權,因此纔有了這篇JWT
受權登陸 解(cai)決(keng)方(zhi)案(nan),若是對JWT
還不是很瞭解的小夥伴,請猛戳這裏~vue
ok,不廢話了,直接進入大家最想看到代碼環節~git
關於Taro
的安裝的使用,我這裏就再也不贅述了,具體的步驟能夠參考官網給出的教程,咱們直接來看看受權登陸這一塊~github
FBI WARNING
: 本文相關的代碼是跟樓主目前公司的有些業務場景有關,若有不一樣,請自行更改便可~web
初始化Taro
項目時,咱們須要選擇使用Redux
模版,進行狀態管理,而後默默等待依賴安裝完成,經過npm run dev:weapp
命令就能夠啓動咱們的小程序服務了~npm
接下來跟着個人步驟走下去~json
step1redux
首先,咱們須要使用到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;
}
複製代碼
step2
咱們在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
檢測了,所以咱們用一個狀態來標識用戶是不是從新進入小程序~
step3
假定咱們的頁面的初始化請求都寫在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
對於我來講,也算是屬於初探,這篇文章也是但願能給跟我同樣萌新的小夥伴一個拋磚引玉的做用,若是有哪裏寫的不對的地方,還請批評斧正~