刚接触小程序那会,一个接一个web的方法、APi不支持,难受的只能敲着代码,流着眼泪。

oauth2.0鉴权,一个access_token,一个refresh_token,一个expires_in。

在pc端我们可以使用cookie来轻松处理access_token过期,自动刷token。

然而我们在小程序中是不支持cookie的,我们只能通过缓存机制来保存token,这个时候是不能监听到token是否过期,只有通过接口调用的返回结果才知道token是否过期。

1.Token1.0

记得第一次为了赶时间,就急急忙忙写了个请求方法:

getAccessToken(callback) {
        let that = this
        console.log('→获取token')
        var date = new Date();
        var dt = date.getTime();
        var dd = 0;
        var expires_in = wx.getStorageSync('expires_in');
        if (dt >= expires_in || isNaN(expires_in)) {
            if (wx.getStorageSync('access_token') != 'wait') {
                wx.setStorageSync('access_token', 'wait')
                console.log('→token过期,刷新token')
                var refresh_token = wx.getStorageSync('refresh_token');
                wx.request({
                    url: API.refreshToken + refresh_token,
                    method: 'POST',
                    header: {
                        'Content-Type': 'application/json;charset=UTF-8',
                        "Authorization": "Basic bGl6LXJlZHBhY2thZ2Utd3g6c2VjcmV0" //base64加密liz-youli-wx:secret
                    },
                    success: function(res) {
                        util.waitHide();
                        if (res.data.error == 'invalid_grant' || res.data.error == 'invalid_token') {
                            console.log('→refresh_token失效')
                            var unionid = wx.getStorageSync('unionid', unionid); //unionid  
                            // that.getToken(API.getToken + 'unionid_' + unionid + '_type_2');
                            //重新拿token
                                let url=API.getToken + 'unionid_' + unionid + '_type_2'
                                util.waitShow();
                                let that = this
                                console.log('→项目开始获取token开始/n', url);
                                wx.request({
                                    url: url,
                                    method: 'POST',
                                    data: {},
                                    header: {
                                        'Content-Type': 'application/json;charset=UTF-8',
                                        "Authorization": "Basic bGl6LWxpbWEtd3g6c2VjcmV0" //base64加密liz-youli-wx:secret
                                    },
                                    success(res) {
                                        util.waitHide();
                                        console.log(res, '→数据')
                                        util.delayed30s(res.data.access_token, res.data.refresh_token, res.data.expires_in);
                                        callback(res.data.access_token);
                                        console.log('→项目开始获取token结束', url);
                                    },
                                    fail(e) {
                                        console.log(e, '→获取token失败');
                                    }
                                });

                        } else {
                            console.log('→刷新token成功')
                            util.delayed30s(res.data.access_token, res.data.refresh_token, res.data.expires_in);
                            callback(wx.getStorageSync('access_token'));
                        }
                    },
                    fail: function(e) {
                        util.waitHide();
                    }
                })
            } else {
                setTimeout(() => {
                    callback(wx.getStorageSync('access_token'));
                }, 2000)
            }
        } else {
            console.log('→token未过期');
            callback(wx.getStorageSync('access_token'));
        }

    }, 

fetchToken(url, method, data, callback, hideLoading) {
        // util.waitShow();
        wx.showNavigationBarLoading()
        if (hideLoading == '1') {
            console.log('hideLoading:' + hideLoading)
            // util.waitHide();
            wx.hideNavigationBarLoading()
        }
        let self = this
        self.getAccessToken((res) => {
            console.log(res, "→获取token成功")
            wx.request({
                url: url,
                method: method,
                data: data,
                header: {
                    'Content-Type': 'application/json;charset=UTF-8',
                    "Authorization": "bearer " + res
                },
                success(res) {
                    // util.waitHide();
                    wx.hideNavigationBarLoading()
                    console.log('→返回数据', res.data);
                    if (res.data.error == 'invalid_token') {
                        console.log('invalid_token')
                        var unionid = wx.getStorageSync('unionid', unionid); //unionid  
                        self.getToken(API.getToken + 'unionid_' + unionid + '_type_2');
                        callback(null, res.data);
                    } else {
                        callback(null, res.data);
                    }
                    console.log('→请求结束', url, data);
                },
                fail(e) {
                    console.log(e)
                    // util.waitHide();
                    wx.hideNavigationBarLoading()
                    util.showModal('加载失败', '网络不好,稍后再试试咯~')
                    callback(e);
                }
            });
        })
    }

虽然这个已经满足基本开发要求。也摆脱了在token过期时,同时几个请求发出时token刷新几次的情况,但是还是感觉会有事故发生。

于是就想着封装一下token的存储、删除、刷新及获取。

2.Token2.0

通过promise来进行第一次封装,也在线上版本使用,效果看起来还不错,具体代码是这样的:

/**
 * AuthProvider.js
 */
const wxRequest = require('./wxRequest')
const API = require('./api')
const Promise = require('./es6-promise');

function onLogin() {
    let url = API.getToken + 'unionid_' + wx.getStorageSync('unionid') + '_type_2';
    let token = API.SECRET;
    return wxRequest.fetch(url, { type: 'Basic', value: token }, '', "POST").then((res) => {
        saveTokens(res.data.access_token, res.data.refresh_token, res.data.expires_in);
        return res.data.access_token
    }).catch((req) => {
        return 'error'
    })
}
/*刷新时删除已有的access_token防止再次刷新*/
function setWait() {
    wx.removeStorageSync('access_token');
}
function saveTokens(access_token, refresh_token, expires_in) {
    wx.setStorageSync('access_token', access_token);
    wx.setStorageSync('refresh_token', refresh_token);
    var exp = new Date();
    var expires_ins = exp.getTime() + expires_in * 1000 - 30000;
    wx.setStorageSync('expires_in', expires_ins);
}
function onRefreshToken() {
    setWait();
    let token = API.SECRET;
    var url = API.refreshToken + wx.getStorageSync('refresh_token');
    return wxRequest.fetch(url, { type: 'Basic', value: token }, '', 'POST').then((res) => {
        if (res.data.access_token) {
            saveTokens(res.data.access_token, res.data.refresh_token, res.data.expires_in);
            return res.data.access_token;
        } else {
            return onLogin().then(res => {
                return res
            });
        }
    }).catch(req => {
        if (wx.getStorageSync('refresh_token') != null) {
            return onLogin().then(res => {
                return res
            });
        }
    })
}
function getAccessToken() {
    var date = new Date();
    var dt = date.getTime();
    var expires_in = wx.getStorageSync('expires_in');
        if ((!expires_in || dt >= expires_in) && wx.getStorageSync('access_token')) {
            return onRefreshToken();
        } else if (!wx.getStorageSync('access_token')) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(wx.getStorageSync('access_token'))
                }, 2000)
            })
        } else {
            return new Promise((resolve, reject) => {
                resolve(wx.getStorageSync('access_token'))
            })
        }
}
module.exports = {
    onLogin: onLogin,
    getAccessToken: getAccessToken
}

token单独封装出来使用,借助promise的异步处理,是处理思路明确了一点。有一点不友好的是,引入了一个es6_promise包,增加了小程序整体的大小,这个虽然说是无可厚非的,但是为了token的使用就去用一个包很不划算。

于是又开始捯饬了些时间:

3.Token3.0

通过事件订阅的方式去做了token的封装,用设计模式去处理token的发布:

手写了一个小小的发布订阅方法,使得代码量又减少了一点。

/**
 * MessageCenter.js
 */

function MessageCenter() {
    let message = {};
    this.register = function (messageType) {
        if (typeof message[messageType] == 'undefined') {
            message[messageType] = [];
        } else {
            console.log("消息已被注册");
        }
    }
    this.subscribe = function (messageType, func) {
        if (typeof message[messageType] != 'undefined') {
            message[messageType].push(func);
        } else {
            console.log("消息未注册,不能进行订阅");
        }
    }
    this.fire = function (messageType, args) {
        if (typeof message[messageType] == 'undefined') {
            console.log("消息未注册,不能进行订阅");
            return false;
        }
        let events = {
            type: messageType,
            args: args || {}
        }

        message[messageType].forEach(function (item) {
            item(events);
        })
    }
}
module.exports={
    MessageCenter:MessageCenter
}
/**
 *AuthProvider.js
 */
import {MessageCenter} from './MessageCenter';


import {getToken, refreshToken} from './token';

const API = require('./config');
const Promise = require('./es6-promise');

let message = new MessageCenter();
message.register('token');
let params = {
    method: 'POST',
    token: {
        type: 'Basic',
        value: API.SECRET
    }
}

function onLogin() {
    params.unionId = wx.getStorageSync('unionid');
    return getToken(params).then((res) => {
        saveTokens(res.access_token, res.refresh_token, res.expires_in);
        message.fire('token', res.access_token);
        return res.access_token
    }).catch((req) => {
        return 'error'
    })
}

function setWait() {
    wx.removeStorageSync('access_token');
}

function saveTokens(access_token, refresh_token, expires_in) {
    wx.setStorageSync('access_token', access_token);
    wx.setStorageSync('refresh_token', refresh_token);
    let exp = Date.now();
    let expires_ins = exp + expires_in * 1000 - 30000;
    wx.setStorageSync('expires_in', expires_ins);
}

function onRefreshToken() {
    setWait();
    params.refresh_token = wx.getStorageSync('refresh_token');
    return refreshToken(params).then((res) => {
        if (res.access_token) {
            saveTokens(res.access_token, res.refresh_token, res.expires_in);
            message.fire('token', res.access_token);
            return res.access_token;
        } else {
            return onLogin().then(res => {
                return res
            });
        }
    }).catch(req => {
        if (wx.getStorageSync('refresh_token') != null) {
            return onLogin().then(res => {
                return res
            });
        }
    })
}

function getAccessToken() {
    let date = Date.now();
    let expires_in = wx.getStorageSync('expires_in');
    if ((!expires_in || date >= expires_in) && wx.getStorageSync('access_token')) {
        return onRefreshToken()
    } else if ((!expires_in || date >= expires_in) && !wx.getStorageSync('access_token')) {
        return new Promise((resolve, reject) => {
            message.subscribe("token", (event) => {
                resolve(event.args)
            });
        })
    } else {
        return new Promise((resolve, reject) => {
            resolve(wx.getStorageSync('access_token'))
        })
    }

}

module.exports = {
    onLogin: onLogin,
    getAccessToken: getAccessToken
}

当然中间调试的版本有很多啦,因为开发的小程序很多,目前线上使用的就这三个,看起来效果都还可以,没有出现同时多次刷新token情况。

后续有更新会及时补充。

前端小白,抛砖引玉...