uniapp 无痛刷新token

由于项目中需要用户在token失效时无感知刷新token
遇到问题是如何防止多次刷新token,以及刷新token后再次请求之前的接口
参考链接 : https://segmentfault.com/a/1190000020210980

两个接口几乎同时发起和返回,第一个接口会进入刷新token后重试的流程,而第二个接口需要先存起来,然后等刷新token后再重试。同样,如果同时发起三个请求,此时需要缓存后两个接口,等刷新token后再重试。由于接口都是异步的,处理起来会有点麻烦。
当第二个过期的请求进来,token正在刷新,我们先将这个请求存到一个数组队列中,想办法让这个请求处于等待中,一直等到刷新token后再逐个重试清空请求队列。
那么如何做到让这个请求处于等待中呢?为了解决这个问题,我们得借助Promise。将请求存进队列中后,同时返回一个Promise,让这个Promise一直处于Pending状态(即不调用resolve),此时这个请求就会一直等,只要我们不执行resolve,这个请求就会一直在等待。当刷新请求的接口返回来后,我们再调用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,
    //服务器本地上传文件地址
    fileUrl: base.baseUrl,
    // 服务器上传图片默认url
    defaultUploadUrl: "api/common/v1/upload_image",
    //设置请求头(如果使用报错跨域问题,可能是content-type请求类型和后台那边设置的不一致)
    header: {
        'Content-Type': 'application/json;charset=UTF-8',
        // 'project_token': base.projectToken, //项目token(可删除)
    }
});
// 添加获取七牛云token的方法
$http.getQnToken = function(callback) {
    //该地址需要开发者自行配置(每个后台的接口风格都不一样)
    $http.get("api/common/v1/qn_upload").then(data => {
        /*
         *接口返回参数:
         *visitPrefix:访问文件的域名
         *token:七牛云上传token
         *folderPath:上传的文件夹
         *region: 地区 默认为: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) {
        // 文件最大字节: options.maxSize 可以在调用方法的时候加入参数
        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;
}
//请求结束
$http.requestEnd = function(options) {
    //判断当前接口是否需要加载动画
    if (options.load) {
        // 关闭加载动画
        
    }
}

const code_invalid =401; //token验证失败错误码
//enterpriseList 为token失效后,需要重新登录的接口
const enterpriseList = ["接口1","接口2"]
//所有接口数据处理(此方法需要开发者根据各自的接口返回类型修改,以下只是模板)
$http.dataFactory = async function(res) {
    console.log("接口请求数据222", {
        url: res.url,
        resolve: res.response,
        header: res.header,
        data: res.data,
        method: res.method,
    });
    return res

};

// 是否正在刷新的标记
let isRefreshing = false
// 重试队列,每一项将是一个待执行的函数形式
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, "===无效 无权限=====")
            // 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() {
                                    // 已经刷新了token,将所有队列中的请求进行重试
                                    console.log(requests,'=------接口数量----');
                                    requests.forEach(cb => cb())
                                    // 重试完了别忘了清空这个队列(掘金评论区同学指点)
                                    isRefreshing = false
                                    requests = []
                                    resolve(apiRequest(url, params, opts))
                                })
                            } else {
                                // 其它延时执行
                                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);
            }
            // 返回正确的结果(then接受数据)
            return resolve(httpData.data);
        } else {
            // 返回错误的结果(catch接受数据)
            return reject({
                statusCode: res.response.statusCode,
                // errMsg: "【request】数据工厂验证不通过",
                errMsg: response.message,
                data: res.data
            });
        }
    })
}
// 错误回调
$http.requestError = function(e) {
    console.log(e, "========错误回调函数========")
    // e.statusCode === 0 是参数效验错误抛出的
    if (e.statusCode === 0) {
        throw e;
    } else {
        console.log(e, "=======错误回调========");
        uni.showToast({
            title: "网络错误,请检查一下网络",
            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(() => {
        
    })
}

调用接口
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
}

页面中调用接口 在vue文件中
首先导入api

import {
        request1,
        request2,
    } from '@/config/apiRequest'
export default{
        name:'applyLoan',
        created(){
          //需要参数调用
          /*
             request1({参数:value}).then(res => {
              console.log(res,'----res')
            })
          */
          //不需要参数的调用
          request1().then(res => {
              console.log(res,'----res')
            })
        }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容