小程序这两年可谓是风风火火,已经涉及到了行业的方方面面,做为一枚前端码农,为了紧跟时代的步伐,知足市场的须要,楼主也步入了小程序的坑中,一探究竟,从最初的原生开发,过渡到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
对于我来讲,也算是属于初探,这篇文章也是但愿能给跟我同样萌新的小伙伴一个抛砖引玉的做用,若是有哪里写的不对的地方,还请批评斧正~