刚接触小程序那会,一个接一个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情况。
后续有更新会及时补充。
前端小白,抛砖引玉...