uniapp 無痛刷新token

由于項目中需要用戶在token失效時無感知刷新token
遇到問題是如何防止多次刷新token,以及刷新token后再次請求之前的接口
參考鏈接 : https://segmentfault.com/a/1190000020210980

兩個接口幾乎同時發(fā)起和返回,第一個接口會進(jìn)入刷新token后重試的流程,而第二個接口需要先存起來,然后等刷新token后再重試。同樣,如果同時發(fā)起三個請求,此時需要緩存后兩個接口,等刷新token后再重試。由于接口都是異步的,處理起來會有點麻煩。
當(dāng)?shù)诙€過期的請求進(jìn)來,token正在刷新,我們先將這個請求存到一個數(shù)組隊列中,想辦法讓這個請求處于等待中,一直等到刷新token后再逐個重試清空請求隊列。
那么如何做到讓這個請求處于等待中呢?為了解決這個問題,我們得借助Promise。將請求存進(jìn)隊列中后,同時返回一個Promise,讓這個Promise一直處于Pending狀態(tài)(即不調(diào)用resolve),此時這個請求就會一直等,只要我們不執(zhí)行resolve,這個請求就會一直在等待。當(dāng)刷新請求的接口返回來后,我們再調(diào)用resolve,逐個重試。

下面以zhouWei-request插件為例

定義請求類request.js

import request from "@/plugins/request";
import base from '@/config/baseUrl';
import {
    toSilentAuthorization
} from '@/config/authLogin';
//可以new多個request來支持多個域名請求
let $http = new request({
    //接口請求地址
    baseUrl: base.baseUrl,
    //服務(wù)器本地上傳文件地址
    fileUrl: base.baseUrl,
    // 服務(wù)器上傳圖片默認(rèn)url
    defaultUploadUrl: "api/common/v1/upload_image",
    //設(shè)置請求頭(如果使用報錯跨域問題,可能是content-type請求類型和后臺那邊設(shè)置的不一致)
    header: {
        'Content-Type': 'application/json;charset=UTF-8',
        // 'project_token': base.projectToken, //項目token(可刪除)
    }
});
// 添加獲取七牛云token的方法
$http.getQnToken = function(callback) {
    //該地址需要開發(fā)者自行配置(每個后臺的接口風(fēng)格都不一樣)
    $http.get("api/common/v1/qn_upload").then(data => {
        /*
         *接口返回參數(shù):
         *visitPrefix:訪問文件的域名
         *token:七牛云上傳token
         *folderPath:上傳的文件夾
         *region: 地區(qū) 默認(rèn)為:SCN
         */
        callback({
            visitPrefix: data.visitPrefix,
            token: data.token,
            folderPath: data.folderPath
        });
    });
}
//請求開始攔截器
$http.requestStart = function(options) {
    // console.log("請求開始", options);
    if (options.load) {
        //打開加載動畫
        
    }
    // 圖片上傳大小限制
    if (options.method == "FILE" && options.maxSize) {
        // 文件最大字節(jié): options.maxSize 可以在調(diào)用方法的時候加入?yún)?shù)
        let maxSize = options.maxSize;
        for (let item of options.files) {
            if (item.size > maxSize) {
                setTimeout(() => {
                    uni.showToast({
                        title: "圖片過大,請重新上傳",
                        icon: "none"
                    });
                }, 500);
                return false;
            }
        }
    }
    console.log('-------requestConfig-----');
    options.header['user_token'] = store.state.userInfo.token;
    return options;
}
//請求結(jié)束
$http.requestEnd = function(options) {
    //判斷當(dāng)前接口是否需要加載動畫
    if (options.load) {
        // 關(guān)閉加載動畫
        
    }
}

const code_invalid =401; //token驗證失敗錯誤碼
//enterpriseList 為token失效后,需要重新登錄的接口
const enterpriseList = ["接口1","接口2"]
//所有接口數(shù)據(jù)處理(此方法需要開發(fā)者根據(jù)各自的接口返回類型修改,以下只是模板)
$http.dataFactory = async function(res) {
    console.log("接口請求數(shù)據(jù)222", {
        url: res.url,
        resolve: res.response,
        header: res.header,
        data: res.data,
        method: res.method,
    });
    return res

};

// 是否正在刷新的標(biāo)記
let isRefreshing = false
// 重試隊列,每一項將是一個待執(zhí)行的函數(shù)形式
let requests = []

async function apiRequest(url, params, opts) {

    const res = await $http.request({
        url: url,
        method: opts['method'] || 'GET', // POST、GET、PUT、DELETE、JSONP,具體說明查看官方文檔
        data: params,
    })
    return new Promise((resolve, reject) => {
        if (res.response.data.code && res.response.data.code == 401) {
            console.log(res.response.data.code, "===無效 無權(quán)限=====")
            // token失效 
             //用戶添加token校驗
            //if(!store.state.userInfo.phoneAuth){
                //if (res.response.data.code ==401) {
                    for (let i = 0; i < enterpriseList.length; i++) {
                        if (res.url.indexOf(enterpriseList[i]) > -1) {
                            if (!isRefreshing) {
                                isRefreshing = true
                                return toSilentAuthorization(1, function() {
                                    // 已經(jīng)刷新了token,將所有隊列中的請求進(jìn)行重試
                                    console.log(requests,'=------接口數(shù)量----');
                                    requests.forEach(cb => cb())
                                    // 重試完了別忘了清空這個隊列(掘金評論區(qū)同學(xué)指點)
                                    isRefreshing = false
                                    requests = []
                                    resolve(apiRequest(url, params, opts))
                                })
                            } else {
                                // 其它延時執(zhí)行
                                return requests.push(() => {
                                    resolve(apiRequest(url, params, opts))
                                })
                            }
                        }
                    //}
                //}
            }
        } else if (res.response.data.code && (res.response.data.code == 200)) {
            let httpData = res.response.data;
            if (typeof(httpData) == "string") {
                httpData = JSON.parse(httpData);
            }
            // 返回正確的結(jié)果(then接受數(shù)據(jù))
            return resolve(httpData.data);
        } else {
            // 返回錯誤的結(jié)果(catch接受數(shù)據(jù))
            return reject({
                statusCode: res.response.statusCode,
                // errMsg: "【request】數(shù)據(jù)工廠驗證不通過",
                errMsg: response.message,
                data: res.data
            });
        }
    })
}
// 錯誤回調(diào)
$http.requestError = function(e) {
    console.log(e, "========錯誤回調(diào)函數(shù)========")
    // e.statusCode === 0 是參數(shù)效驗錯誤拋出的
    if (e.statusCode === 0) {
        throw e;
    } else {
        console.log(e, "=======錯誤回調(diào)========");
        uni.showToast({
            title: "網(wǎng)絡(luò)錯誤,請檢查一下網(wǎng)絡(luò)",
            icon: "none"
        });
    }
}
export default apiRequest;

刷新token,authLogin.js

import{refreshToken} from './apiRequest.js'
import store from '@/store';
function toSilentAuthorization(type,callback){
    const _this = this;
    
    apiSilentLogin({
        code:'value值', 
        type:'value值'
    }).then(res => {
        console.log(res,'======res------');
        if(!res) {
            callback('')
            uni.reLaunch({
                url:'回到首頁地址'
            })
            return
        }
        store.commit('setUserInfo', {...res.info,...userInfo});
        console.log(res,'----->getPhone');
        callback(res)
    }).catch(err=>{
        console.log('登錄-----',err);
        uni.reLaunch({
            url:'回到首頁地址'
        })
    }).finally(() => {
        
    })
}

調(diào)用接口
apiRequest.js

import apiRequest from './request.js';
const request1 = (params) => apiRequest('接口地址1', params, { method: 'POST' })
const request2 = (params) => apiRequest('接口地址2', params, { method: 'GET' })
const refreshToken = (params) => apiRequest('接口地址3', params, { method: 'POST' })
export {
    request1,
    request2,
    refreshToken
}

頁面中調(diào)用接口 在vue文件中
首先導(dǎo)入api

import {
        request1,
        request2,
    } from '@/config/apiRequest'
export default{
        name:'applyLoan',
        created(){
          //需要參數(shù)調(diào)用
          /*
             request1({參數(shù):value}).then(res => {
              console.log(res,'----res')
            })
          */
          //不需要參數(shù)的調(diào)用
          request1().then(res => {
              console.log(res,'----res')
            })
        }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容