昨天剛剛完成了小程式介面的合併優化,順便還對攔截器改造了一番,由之前的在響應攔截器對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,一定要做到真正理解其實現原理,這樣我們才能自己寫出更好更優解的程式碼來!
如果有發現問題的話,歡迎指正!如果有更優解的話,也歡迎討論!非常感謝!