uniapp请求封装-token无感刷新

当前是vue3+ts版本的封装

vue3+js版本请求封装可参考 https://www.cnblogs.com/lovejielive/p/14343619.html

token无感刷新,可自行删除 requset.ts 中 ts 相关数据恢复vue2版本

先在目录下创建 utils 和 common 这2个文件夹

utils 是存放工具类的,common 用来放置常用方法的

之后在utils 中创建 requset.ts 用来放置 uni.request 的请求方法,无感刷新。

1.common 文件创建 operate.ts + api.ts

主要用来放置 ,请求接口地址,一些全局请求数据,判断是否登录。

配置全局消息提示框,模拟对话框方法,方便调用

operate.ts 代码如下:

复制代码
import store from '@/store/index'

export default {
    //接口
    api: function () {
        let url = ''
        // #ifdef MP-WEIXIN || MP-ALIPAY
        let version = uni.getAccountInfoSync().miniProgram.envVersion;
        switch (version) {
            case "develop": //开发预览版
                url = ''
                break;
            case 'trial': //体验版
                url = ''
                break;
            case 'release': //正式版
                url = ''
                break;
            default: //未知,默认调用正式版
                url = ''
                break;
        }
        // #endif
        // #ifdef H5 || APP-PLUS
        if (process.env.NODE_ENV === 'development') {
            // console.log('开发环境')
            url = ''
        } else {
            // console.log('生产环境')
            url = ''
        }
        // #endif

        return url
    },

    //共同请求参数
    commonBeg: function () {
        return {
            Authorization: this.isToken(),
        }
    },

    //是否已注册(登录状态)
    isLogin: function () {
        return store.state.user.hasLodin;
    },

    //获取用户token
    isToken: function () {
        if (store.state.user.accessToken != '') {
            return 'Bearer ' + store.state.user.accessToken;
        }
        return '';
    },

    //消息提示框
    toast: function (options : any) {
        uni.showToast({
            title: options.title,
            duration: options.duration || 2000,
            icon: options.icon || "none"
        });
    },

    // 模拟对话框
    showModal: function (matter : any) {
        return new Promise((resolve, _reject) => {
            uni.showModal({
                title: matter.title || '',
                content: matter.content || '',
                // 是否显示取消按钮,默认为 true
                showCancel: matter.showCancel,
                // 取消按钮的文字,默认为"取消"
                cancelText: matter.cancelText || "取消",
                // 取消按钮的文字颜色,默认为"#000000"
                cancelColor: matter.cancelColor || "#000000",
                // 确定按钮的文字,默认为"确定"
                confirmText: matter.confirmText || "确定",
                /*
                确定按钮的文字颜色,H5平台默认为"#007aff",
                    微信小程序平台默认为"#576B95",
                    百度小程序平台默认为 "#3c76ff"
                */
                // confirmColor: matter.confirmColor || '#576B95',
                success: (res) => {
                    if (res.confirm) {
                        // console.log('用户点击确定');
                        resolve(res)
                    }
                    // if (res.cancel) {
                    // console.log('用户点击取消');
                    // reject(res.cancel)
                    // }
                }
            })
        })
    }
}

operate.ts

api.ts 代码如下

复制代码
import {
    request
} from '@/utils/requset'

// 手机密码登录
export const text = function (data : any) {
    return request({
        url: "/pai/api/pai",
        method: "POST",
        hideLoading: true,
        data: data,
    })
}

/*
使用方法: 在请求页面中调用

    1.先导入本页面
        import {text} from '@/common/api'
        
    2.在methods 中 调用:
        text().then((res) => {
            console.log(res);
        })

*/

api.ts

2.utils 中创建 requset.ts

配置 uni.request 统一请求, uni.getNetworkType 判断当前网络状态

通过 uni.addInterceptor 拦截器,实现请求前后的数据监听(该方法只写了监听,具体逻辑项目没用到)

import route from '@/utils/routeBlocker' 路由封装-方法链接

无感刷新 token 配置,具体代码如下

复制代码
import operate from "@/common/operate"
import store from '@/store-ts/index'
import route from '@/utils/routeBlocker'

/*
 解决: 类型"string | AnyObject | ArrayBuffer"上不存在属性"code"。
        类型"string"上不存在属性"code"。
  */
interface Codeable {
    data : Object | String | ArrayBuffer,
    code : Number,
}

//请求对列 / 请求状态
let requestQueue = [],
    isRefreshing = false;

export const request = function (param : any) {
    //请求参数
    let url = param.url,
        method = param.method,
        header = {},
        data = param.data || {},
        hideLoading = param.hideLoading || false;

    //拼接完整请求地址
    let requestUrl = operate.api() + url;

    //跨域解决
    // let requestUrl =  url;
    // console.log(requestUrl)

    //请求方式:GET或POST
    if (method) {
        method = method.toUpperCase(); //小写改为大写
        if (method == "POST") {
            header = {
                // 'content-type': 'application/x-www-form-urlencoded',
                'content-type': "application/json",
            };
        } else {
            header = {
                'content-type': "application/json",
            };
        }
    }
    // 拼接header 登录参数
    let jointHeader = Object.assign({}, header, operate.commonBeg());

    //用户交互:加载圈
    if (!hideLoading) {
        uni.showLoading({
            title: '加载中...',
            mask: true
        });
    }

    // 请求-拦截器
    // requestBlocker()

    //开始请求
    return new Promise((resolve, reject) => {
        // 判断有无网络验证
        noneNetwork().then(() => {
            //请求放到promise队列,等待更新token后重新调用。
            addRequestQueue(url, method, param.data, jointHeader)

            //更新 token
            flushToken().then(() => {
                // 执行等待的请求
                onRefreshed().then(resolve).catch(reject);
            })
        }).catch(() => {
            //隐藏加载
            if (!hideLoading) {
                uni.hideLoading();
            }
        })

        //开始请求
        uni.request({
            url: requestUrl,
            data: data,
            method: method,
            header: jointHeader,
            success(res) {
                let data = res.data as Codeable

                // code判断: 刷新令牌
                if (data.code == 401) {
                    // 处理token刷新
                    if (!isRefreshing) {
                        isRefreshing = true
                        //请求放到promise队列,等待更新token后重新调用。
                        addRequestQueue(url, method, param.data, jointHeader)

                        //更新 token
                        flushToken().then(() => {
                            // 执行等待的请求
                            onRefreshed().then(resolve).catch(reject);
                        })
                    }
                    return;
                }

                // code判断: 重新登录
                if (data.code == 403) {
                    restartLogin()
                    return;
                }

                // 将结果抛出
                resolve(data)
            },
            //请求失败
            fail: (err) => {
                operate.toast({
                    title: '网络连接错误',
                    icon: 'loading'
                })
                // 将结果抛出
                reject(err)
                /*
                .catch(err=>{
                          console.log(err)
                      })
                */
            },
            //请求完成
            complete() {
                //隐藏加载
                if (!hideLoading) {
                    uni.hideLoading();
                }
            }
        })
    })
}

// 执行等待的请求
const onRefreshed = () => {
    return new Promise((resolve, reject) => {
        let item = requestQueue.shift();
        // console.warn('执行等待的请求', item);
        request({
            url: item.url,
            method: item.method,
            hideLoading: true,
            data: item.data,
        }).then(resolve).catch(reject)
    });
}

// 添加请求到队列
const addRequestQueue = (url : string, method : object, data : object, header : object) => {
    requestQueue.push({
        url,
        method,
        data,
        header
    })
}

/**
 * @description:  登录刷新 token 请求接口
 * @return
 */
export const flushToken = function () {
    return new Promise((resolve, errs) => {
        uni.request({
            url: operate.api() + '/app-api/refresh-token-刷新接口地址',
            method: 'POST',
            header: {
                'Content-Type': 'application/json'
            },
            data: {
                refreshToken: store.getters['flushToken'],
            },
            success(res) {
                // console.warn('刷新令牌', res.data);
                let data = res.data as any

                if (data.code == 0) {
                    //登录刷新
                    store.commit("user/REFRESH_TOKEN", {
                        accessToken: data.data.accessToken,
                        refreshToken: data.data.refreshToken
                    });

                    resolve('刷新令牌成功')
                    return
                }

                //登录 失效
                if (data.code == 401) {
                    operate.showModal({
                        title: '您的登陆已过期',
                        confirmText: '重新登录',
                        showCancel: false,
                    }).then((_res) => {
                        //清除登录信息
                        store.commit('user/LOG_OUT');
                        //去登录页
                        restartLogin(false)
                    })
                }
            },
            fail: (err) => {
                console.error('刷新令牌失败', err);
                errs(err)
            },
            complete() {
                isRefreshing = false
            }
        })
    })
}

/**
 * @description: 请求-拦截器
 * @return
 * 通过拦截器,实现请求前后的数据监听
 */
// const requestBlocker = function () {
//     uni.addInterceptor('request', {
//         invoke(args) {
//             console.log("求前后的数据监听",args);
//         }
//     })
// }


/**
 * @description: 判断有无网络验证
 * @return
 */
const noneNetwork = function () {
    return new Promise((resolve, reject) => {
        uni.getNetworkType({
            success(res) {
                if (res.networkType == 'none') {
                    uni.showModal({
                        title: '没有网络',
                        content: '请检查您的网络',
                        showCancel: false,
                        success: (_res) => {
                            resolve("无网络确定-返回")
                        }
                    });
                }
            },
            complete() {
                reject('取消-加载圈')
            }
        })
    })
}

/**
 * @description: 重新登录(统一方法)
 * @return
 */
const restartLogin = function (toastShow = true) {
    if (toastShow) {
        operate.toast({
            title: "登录超时!请重新登录"
        })
    }
    setTimeout(() => {
        route({
            url: '/pages/logIn/logIn',
            type: "navigateTo",
            login: false,
        })
    }, 500)
}

requset.ts

缺点:在多个请求同时进行,会出现多次调用(刷新token)的情况。

当前使用 isRefreshing 判断请求刷新转态,没结束不能在调用刷新接口,对于复数请求,还是会多次请求刷新。

下图测试为,同时请求4个接口

项目地址:https://gitee.com/jielov/uni-app-tabbar