昨天刚刚完成了小程序接口的合并优化,顺便还对拦截器改造了一番,由之前的在响应拦截器对cookie过期进行处理,改为了在请求拦截器里就先处理一番,这种修改一定程度上可以减少接口调用所需的时间,虽然可能减少的时间很少,大约200,100甚至几十毫秒?但是总归是有减少的,对于老板提出的让用户尽快地看到我们的商品列表的优化需求,多少还是有点作用的!下面我们就一起来看看怎么实现吧~
一.前期准备
关于mpvue项目的目录,可以参考这篇文章;至于请求库flyio(即Fly.js),大伙可以去该库的文档去一探究竟,文档里其实有关于拦截器的基础的讲解,我的这个拦截器就是在该文档的基础上实现的,我觉得flyio还是挺好用的,我自己并没有在项目里使用微信小程序原生的wx.request()。
该实现的文件主要有两个,一个是config.js,即配置文件,里面放一些通用的配置,另一个就是今天的主角ajax.js,即我们要实现的拦截器。
二.中心思想
首先来看下怎么实现在请求发起前在本地判断cookie是否过期,这点需要用到本地存储wx.setStorageSync,实现核心逻辑如下:
if(本地无cookie){
将登录接口responseHeader里的date和set-Cookie字段的值存储在本地
return
}
(后续请求发起前)根据storage里的date与现在的时间进行对比
if(cookie已过期){
锁定fly实例
使用新的fly实例调用登录接口更新cookie (异步)
解锁fly实例,重新发起过期时调用的请求
return
}
// 若无过期则正常处理
...
...
三.具体代码
没什么好隐藏的,不啰嗦直接上干货,各位看官仔细看🧐 ⬇️
src/api/config.js:
const config = {
Host: {
production: 'https://xxx.xxx.cn',
development: 'http://test-xxx.xxx.cn',
test: 'http://test-xxx.xxx.cn'
},
// 不需要检查cookie的名单
urlNotNeedSession: [
'/xxx/xxx.xx.xxxx/xx1', // 我是该接口注释
'/xxx/xxx.xx.xxxx/xx2', // 我是该接口注释
'/xxx/xxx.xx.xxxx/xx3', // 我是该接口注释
'/xxx/xxx.xx.xxxx/xx4', // 我是该接口注释
'/xxx/xxx.xx.xxxx/xx5' // 我是该接口注释
],
// cookie信息
COOKIE_VALID_DURATION: 1000 * 60 * 60 * 24 * 30 // 有效期30天
}
module.exports = config
src/api/ajax.js:
/**
* http请求拦截器
*/
const Fly = require('flyio/dist/npm/wx')
const config = require('./config')
const log = function (msg) {
console.log(msg)
}
// 以下为生成简易随机的x-request-id的函数
const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split
/**
* 随机生成x-request-id
*/
let getXRequestId = function () {
let chars = CHARS
let uuid = new Array(36)
let rnd = 0
let r
for (let i = 0; i < 36; i++) {
if (i === 8 || i === 13 || i === 18 || i === 23) {
uuid[i] = '-'
} else if (i === 14) {
uuid[i] = '4'
} else {
if (rnd <= 0x02) rnd = 0x2222222 + (Math.random() * 0x1222222) | 0
r = rnd & 0xf
rnd = rnd >> 4
uuid[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r]
}
}
return uuid.join('')
}
// 不同环境下的请求baseurl
const ajaxUrl =
process.env.NODE_ENV === 'development'
? config.Host.development
: process.env.NODE_ENV === 'production'
? config.Host.production
: config.Host.test
/**
* 定义两个flyio实例
*/
let fly = new Fly()
let loginFly = new Fly()
// 实例级配置
// 定义公共headers
const headers = {
...
'x-request-id': getXRequestId()
}
Object.assign(fly.config, {
headers: headers,
baseURL: ajaxUrl,
timeout: 10000,
withCredentials: true
})
loginFly.config = fly.config
/**
* 本地重新登录
*/
const login = () => {
return new Promise((resolve) => {
wx.login({
success: res => {
wx.getUserInfo({
success: infoRes => {
let loginParams = {
saleChannel: 5,
userInfo: infoRes.userInfo,
code: res.code,
encryptedData: infoRes.encryptedData,
iv: infoRes.iv
}
wx.showLoading({
title: '正在重新登录'
})
// 记住要使用新的fly实例loginFly,因为旧的fly 实例已被锁定
loginFly.post('/xxxx/xxxxx.xxxx.xxx.xxxxxxx/xxxx', loginParams)
.then(d => {
if (!d.data.hasOwnProperty('success')) {
wx.showLoading({
title: '重新登录已完成'
})
if (d.headers && typeof d.headers['set-cookie'][0] !== 'undefined') {
// 当前时间戳
let timeStampExpired = new Date().getTime()
if (typeof d.headers['date'][0] !== 'undefined') {
//将respondHeader里的date转化为时间戳
timeStampExpired = new Date(d.headers['date'][0]).getTime() + config.COOKIE_VALID_DURATION
} else {
timeStampExpired += config.COOKIE_VALID_DURATION
}
// 将cookie信息存储在本地
wx.setStorageSync('COOKIE_EXPIRES', {
cookieValue: d.headers['set-cookie'][0],
expiredTimeStamp: timeStampExpired
})
}
// 返回新的cookie值
resolve({
newCookie: d.headers['set-cookie'][0]
})
} else {
wx.showToast({
title: '本地登录失败',
icon: 'none',
duration: 2000
})
}
})
}
})
},
fail: res => {
console.error(res.errMsg)
},
complete: res => {}
})
})
}
/**
* 检测本地cookie信息是否存在以及过期
*/
const _checkCookieExpires = () => {
return new Promise((resolve, reject) => {
const nowTimeStamp = new Date().getTime()
const cookieExpiresInfo = wx.getStorageSync('COOKIE_EXPIRES')
// console.log(cookieExpiresInfo)
// 本地不存在cookie
if (!cookieExpiresInfo) {
resolve({
isCookieExpired: true,
cookieValue: null
})
}
// 本地存储的cookie有问题
if (!cookieExpiresInfo.hasOwnProperty('expiredTimeStamp')) {
reject(new Error('本地数据有问题'))
}
// 本地cookie未过期
if (nowTimeStamp < cookieExpiresInfo.expiredTimeStamp) {
resolve({
isCookieExpired: false,
cookieValue: cookieExpiresInfo.cookieValue
})
} else {
// 本地cookie已过期
resolve({
isCookieExpired: true,
cookieValue: null
})
}
})
}
/**
* 请求拦截器
*/
fly.interceptors.request.use(request => {
// log(request)
// 白名单内的url不需要检查cookie
if (config.urlNotNeedSession.includes(request.url)) {
return request
} else {
// 如果在本地存储中没有COOKIE_EXPIRES 或者 COOKIE_EXPIRES的EXPIRES_TIME已过期,则重新请求cookie
return _checkCookieExpires()
.then(res => {
// log(res)
if (res.isCookieExpired) {
wx.showLoading({
title: '本地登录已失效'
})
log('已有cookie失效,即将请求新的cookie')
// 锁定当前fly实例
fly.lock()
return login()
.then((loginRes) => {
wx.hideLoading()
log('cookie已更新')
// log(`重新请求:path:${response.request.url},baseURL:${response.request.baseURL}`)
// 将header中的cookie置为新获取的cookie
request.headers.cookie = loginRes.newCookie
return request
})
.finally(() => {
// 解锁当前fly实例
fly.unlock()
})
} else {
request.headers.cookie = res.cookieValue
return request
}
})
.catch(errInfo => {
log(errInfo)
})
}
})
/**
* 响应拦截器
*/
fly.interceptors.response.use(
response => {
// session已经失效,需要重新登录小程序
if (response.data.errCode === 100009) {
wx.showLoading({
title: '本地登录已失效'
})
log('本地已有cookie失效,即将请求新的cookie')
// 锁定当前fly实例
fly.lock()
return login()
.then(() => {
wx.hideLoading()
log('cookie已更新')
})
.finally(() => {
// 解锁当前fly实例
fly.unlock()
})
.then(() => {
log(`重新请求:path:${response.request.url},baseURL:${response.request.baseURL}`)
return fly.request(response.request)
})
} else {
return response.data
}
},
err => {
if (err.status) {
wx.showModal({
title: '温馨提示',
content: 'blablablaaaaa',
showCancel: true,
cancelText: '取消',
success: (res) => {
if (res.confirm) {
...
...
}
if (res.cancel) {
log('error-interceptor', err)
}
}
})
} else {
wx.showModal({
title: '温馨提示',
content: '无法获取数据,请检查您的设备网络',
showCancel: false,
success: (res) => {
if (res.confirm) {
log(res)
}
}
})
}
}
)
export default fly
四.总结
之前在响应拦截器中设置的根据后端返回cookie过期信息后更新cookie的代码并没有删除,一切为了防止意外情况的发生。
总的来讲就我们目前的需求来讲,整套下来覆盖的已经比较全面了,如果你正在做mpvue项目的小程序并且准备采用同样的策略,那么这个拦截器据对适合你,代码稍作修改即可~如果你在做小程序之外的其他种类的项目,或者用其他框架开发的小程序,那也无所谓,因为不管框架/项目如何变,思想总是可以通用的,你同样可以在这边文章里找到你想要的!但是呢,如果有用的话,千万不能只ctrl+c/ctrl+v,一定要做到真正理解其实现原理,这样我们才能自己写出更好更优解的代码来!
如果有发现问题的话,欢迎指正!如果有更优解的话,也欢迎讨论!非常感谢!