axios封装终极版实现token无感刷新及全局loading

前言

关于axios全局loading的封装博主已经发过一次了,这次是在其基础上增加了token的无感刷新。

token无感刷新流程

  • 首次登录的时候会获取到两个token(AccessToken,RefreshToken)
  • 持久化保存起来(localStorage方案)
  • 正常请求业务接口的时候携带AccessToken
  • 当接口口返回401权限错误时,使用RefreshToken请求接口获取新的AccessToken
  • 替换原有旧的AccessToken,并保存
  • 继续未完成的请求,携带AccessToken
  • RefreshToken也过期了,跳转回登录页面,重新登录

后端设计

这里采用node简单实现的后台接口服务

  • 后端存有两个字段,分别保存长短token,并且每一段时间更新他们
  • 短token过期,返回 returncode:104;长token过期,返回 returncode: 108;请求成功返回returncode: 0;
  • 请求头中pass用来接收客户端长token,请求头中authorization用来接收客户端短token

1、创建一个新文件夹,通过vscode打开,运行:

npm init -y 

2、安装koa

npm i koa -s 

3、安装nodemon

npm i nodemon -g 

4、使用路由中间件

npm i koa-router -S 

5、跨域处理

npm i koa2-cors 

6、新建routes/index.js

const router = require("koa-router")();
let accessToken = "init_s_token"; //短token
let refreshToken = "init_l_token"; //长token/* 5s刷新一次短token */
setInterval(() => {accessToken = "s_tk" + Math.random();
}, 5000);/* 一小时刷新一次长token */
setInterval(() => {refreshToken = "l_tk" + Math.random();
}, 600000);/* 登录接口获取长短token */
router.get("/login", async (ctx) => {ctx.body = {returncode: 0,accessToken,refreshToken,};
});/* 获取短token */
router.get("/refresh", async (ctx) => {//接收的请求头字段都是小写的let { pass } = ctx.headers;if (pass !== refreshToken) {ctx.body = {returncode: 108,info: "长token过期,重新登录",};} else {ctx.body = {returncode: 0,accessToken,};}
});/* 获取应用数据1 */
router.get("/getData", async (ctx) => {let { authorization } = ctx.headers;if (authorization !== accessToken) {ctx.body = {returncode: 104,info: "token过期",};} else {ctx.body = {code: 200,returncode: 0,data: { id: Math.random() },};}
});/* 获取应用数据2 */
router.get("/getData2", async (ctx) => {let { authorization } = ctx.headers;if (authorization !== accessToken) {ctx.body = {returncode: 104,info: "token过期",};} else {ctx.body = {code: 200,returncode: 0,data: { id: Math.random() },};}
});module.exports = router; 

7、创建index.js文件

const Koa = require('koa')
const app = new Koa();
const index = require('./routes/index')const cors = require('koa2-cors');app.use(cors());app.use(index.routes(),index.allowedMethods())app.listen(4000,() => {console.log('server is listening on port 4000')
}) 

8、`配置package.json

"dev":"nodemon index.js", 

9、运行 npm run dev,这时服务端已准备好

npm run dev

前端源码

interceptors.ts

/** axios封装* 请求拦截、相应拦截、错误统一处理*/
import Axios from "axios";
import { ElMessage, ElLoading } from "element-plus";
import _ from "lodash";
import router from "@/router";
import BaseRequest from "@/request/request";
const axios = Axios.create({//baseURL: localStorage.getItem("address")?.toString(), // url = base url + request url// timeout: 50000 // request timeout
});
// loading对象
let loadingInstance: { close: () => void } | null;
// 变量isRefreshing
let isRefreshing = false;
// 后续的请求队列
let requestList: ((newToken: any) => void)[] = [];
// 请求合并只出现一次loading
// 当前正在请求的数量
let loadingRequestCount = 0;
// post请求头
axios.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
// request interceptoraxios.interceptors.request.use((config: any) => {let loadingTarget = "body";if (config.headers.loadingTarget) {loadingTarget = config.headers.loadingTarget;}const isShowLoading = config.headers.isShowLoading;const target = document.querySelector(loadingTarget);if (target && !isShowLoading) {// 请求拦截进来调用显示loading效果showLoading(loadingTarget);}// do something before request is sent// if (sessionStorage.getItem("token")) {//   config.headers.Authorization =//     "Bearer " + sessionStorage.getItem("token"); // 让每个请求携带自定义 token 请根据实际情况自行修改// }if (config.url) {// 此处为 Refresh Token 专用接口,请求头使用 Refresh Tokenif (config.url.indexOf("/refresh") >= 0) {config.headers.Authorization = localStorage.getItem("RefreshToken");} else if (!(config.url.indexOf("/login") !== -1)) {// 其他接口,请求头使用 Access Tokenconfig.headers.Authorization = localStorage.getItem("accessToken");}}return config;},(error) => {// do something with request errorconsole.log(error); // for debugreturn Promise.reject(error);}
);
// http response 拦截器
axios.interceptors.response.use(async (response) => {setTimeout(() => {hideLoading();}, 200);const data = response.data;if (data.code == "401") {// 控制是否在刷新token的状态if (!isRefreshing) {// 修改isRefreshing状态isRefreshing = true;// 这里是获取新token的接口,方法在这里省略了。const url = `/refresh`;const BaseRequestFun = new BaseRequest(url, "");BaseRequestFun.get().then(async (res) => {if (res && res.accessToken) {console.log("a");// 新tokenconst newToken = res.accessToken;// 保存新的accessTokenlocalStorage.setItem("accessToken", newToken);// 替换新accessTokenresponse.config.headers.Authorization = newToken;// token 刷新后将数组里的请求队列方法重新执行requestList.forEach((cb) => cb(newToken));// 重新请求完清空requestList = [];// 继续未完成的请求const resp = await axios.request(response.config);// 重置状态isRefreshing = false;// 返回请求结果return resp;} else {// 清除tokenlocalStorage.clear();// 重置状态isRefreshing = false;// 跳转到登录页router.replace("/");}});} else {// 后面的请求走这里排队// 返回未执行 resolve 的 Promisereturn new Promise((resolve) => {// 用函数形式将 resolve 存入,等待获取新token后再执行requestList.push((newToken) => {response.config.headers.Authorization = newToken;resolve(axios(response.config));});});}}return data;},(err) => {setTimeout(() => {hideLoading();}, 200);// 返回状态码不为200时候的错误处理ElMessage({message: err.toString(),type: "error",duration: 5 * 1000,});return Promise.resolve(err);}
);
// 显示loading的函数 并且记录请求次数 ++
const showLoading = (target: any) => {if (loadingRequestCount === 0) {loadingInstance = ElLoading.service({lock: true,text: "加载中...",target: target,background: "rgba(255,255,255,0.5)",});}loadingRequestCount++;
};// 隐藏loading的函数,并且记录请求次数
const hideLoading = () => {if (loadingRequestCount <= 0) return;loadingRequestCount--;if (loadingRequestCount === 0) {toHideLoading();}
};// 防抖:将 300ms 间隔内的关闭 loading 便合并为一次. 防止连续请求时, loading闪烁的问题。
const toHideLoading = _.debounce(() => {// eslint-disable-next-line @typescript-eslint/ban-ts-comment// @ts-ignoreloadingInstance.close();loadingInstance = null;
}, 300);export default axios;

request.ts

import instance from "./interceptors";
import { ElMessage } from "element-plus";export default class baseRequest {private url: any;private params: any;constructor(url: any, params: any) {this.url = url;this.params = typeof params === "undefined" ? {} : params;}get(...params: any[]) {return instance.get(this.url, {params: this.params,headers: {loadingTarget: params[0],isShowLoading: params[1] === undefined ? true : params[1],},}).then((res: any) => {if (res.code === 200) {return Promise.resolve(res);} else {ElMessage({message: res.entitys[Object.keys(res.entitys)[0]],type: "error",duration: 5 * 1000,});return Promise.resolve(false);}}).catch((e) => {ElMessage({message: e,type: "error",duration: 5 * 1000,});Promise.resolve(false);});}post(...params: any[]) {return instance.post(this.url, this.params, {headers: {loadingTarget: params[0],isShowLoading: params[1] === undefined ? true : params[1],},}).then((res: any) => {if (res.code === "200") {return Promise.resolve(res.entitys);} else {ElMessage({message: res.entitys[Object.keys(res.entitys)[0]],type: "error",duration: 5 * 1000,});Promise.resolve(false);}}).catch((e) => {ElMessage({message: e,type: "error",duration: 5 * 1000,});Promise.resolve(false);});}put(...params: any[]) {return instance.put(this.url, this.params, {headers: {loadingTarget: params[0],isShowLoading: params[1] === undefined ? true : params[1],},}).then((res: any) => {if (res.code === "200") {return Promise.resolve(res.entitys);} else {ElMessage({message: res.entitys[Object.keys(res.entitys)[0]],type: "error",duration: 5 * 1000,});Promise.resolve(false);}}).catch((e) => {ElMessage({message: e,type: "error",duration: 5 * 1000,});Promise.resolve(false);});}delete(...params: any[]) {return instance.delete(this.url, {params: this.params,headers: {loadingTarget: params[0],isShowLoading: params[1] === undefined ? true : params[1],},}).then((res: any) => {if (res.code === "200") {return Promise.resolve(res.entitys);} else {ElMessage({message: res.entitys[Object.keys(res.entitys)[0]],type: "error",duration: 5 * 1000,});Promise.resolve(false);}}).catch((e) => {ElMessage({message: e,type: "error",duration: 5 * 1000,});Promise.resolve(false);});}upfile(...params: any[]) {return instance.post(this.url, this.params, {headers: {"Content-Type": "multipart/form-data","X-Requested-With": "XMLHttpRequest",loadingTarget: params[0],isShowLoading: params[1] === undefined ? true : params[1],},}).then((res: any) => {if (res.code === "200") {return Promise.resolve(res.entitys);} else {ElMessage({message: res.entitys[Object.keys(res.entitys)[0]],type: "error",duration: 5 * 1000,});Promise.resolve(false);}}).catch((e) => {ElMessage({message: e,type: "error",duration: 5 * 1000,});Promise.resolve(false);});}downfile(...params: any[]) {return instance.post(this.url, this.params, { responseType: "blob" }).then((res: any) => {const fileReader = new FileReader();fileReader.onload = function (e: any) {try {const jsonData = JSON.parse(e.target.result); // 说明是普通对象数据,后台转换失败if (jsonData.code) {ElMessage({message: jsonData.message,type: "error",duration: 5 * 1000,});Promise.resolve(false);}} catch (err) {// 解析成对象失败,说明是正常的文件流const url = window.URL.createObjectURL(res);const eleLink = document.createElement("a");eleLink.href = url;eleLink.download = params[2];// eleLink.download = "1.xls";document.body.appendChild(eleLink);eleLink.click();window.URL.revokeObjectURL(url);}};fileReader.readAsText(res);}).catch((e) => {ElMessage({message: e,type: "error",duration: 5 * 1000,});Promise.resolve(false);});}icd9Export() {return instance.post(this.url, this.params, { responseType: "blob" }).then((res: any) => {const fileReader = new FileReader();fileReader.onload = function (e: any) {try {const jsonData = JSON.parse(e.target.result); // 说明是普通对象数据,后台转换失败if (jsonData.code) {ElMessage({message: jsonData.message,type: "error",duration: 5 * 1000,});Promise.resolve(false);}} catch (err) {// 解析成对象失败,说明是正常的文件流const url = window.URL.createObjectURL(res);const eleLink = document.createElement("a");eleLink.href = url;eleLink.download = "icd9.xls";document.body.appendChild(eleLink);eleLink.click();window.URL.revokeObjectURL(url);}};fileReader.readAsText(res);}).catch((e) => {ElMessage({message: e,type: "error",duration: 5 * 1000,});Promise.resolve(false);});}icd10Export() {return instance.post(this.url, this.params, { responseType: "blob" }).then((res: any) => {const fileReader = new FileReader();fileReader.onload = function (e: any) {try {const jsonData = JSON.parse(e.target.result); // 说明是普通对象数据,后台转换失败if (jsonData.code) {ElMessage({message: jsonData.message,type: "error",duration: 5 * 1000,});Promise.resolve(false);}} catch (err) {// 解析成对象失败,说明是正常的文件流const url = window.URL.createObjectURL(res);const eleLink = document.createElement("a");eleLink.href = url;eleLink.download = "icd10.xls";document.body.appendChild(eleLink);eleLink.click();window.URL.revokeObjectURL(url);}};fileReader.readAsText(res);}).catch((e) => {ElMessage({message: e,type: "error",duration: 5 * 1000,});Promise.resolve(false);});}
}

测试vue

<template><div><el-button type="primary" @click="login()">登录</el-button><el-button type="primary" @click="getData()">接口一</el-button><el-button type="primary" @click="getData2()">接口二</el-button></div>
</template><script lang="ts" setup>
import BaseRequest from "@/request/request";
const login = () => {const url = `/login`;const BaseRequestFun = new BaseRequest(url, "");BaseRequestFun.get().then((res) => {if (res) {console.log();localStorage.setItem("accessToken", res.accessToken);localStorage.setItem("RefreshToken", res.refreshToken);}});
};
const getData = () => {const url = `/getData`;const BaseRequestFun = new BaseRequest(url, "");BaseRequestFun.get().then((res) => {if (res) {console.log(res);}});
};
const getData2 = () => {const url = `/getData2`;const BaseRequestFun = new BaseRequest(url, "");BaseRequestFun.get().then((res) => {if (res) {console.log(res);}});
};
</script><style lang="scss"></style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/691318.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iOS 使用Image I/O 实现超大图片降采样

一个图片解码成未压缩的位图的时候&#xff0c;占用的内存和图片的文件大小没有关系&#xff0c;和图片的尺寸大小有关系&#xff0c;尺寸越大&#xff0c;所需要的像素点越多&#xff0c;所以超大图片&#xff0c;我们需要进行优化处理 这里直接上降采样代码 给UIImage 添加…

Emlog博客网站快速搭建并结合内网穿透实现远程访问本地站点

文章目录 前言1. 网站搭建1.1 Emolog网页下载和安装1.2 网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 3. 公网访问测试总结 前言 博客作为使…

【C++之语法篇003】

C学习笔记---003 C知识开篇1、内联函数1.1、什么是内联函数?1.2、解决外部头文件&#xff0c;重复定义问题1.3、内联函数的总结 2、auto关键字2.1、auto的作用2.2、auto的总结 3、范围for3.1、什么是范围for&#xff1f;3.2、范围for的循环应用 4、指针空值关键字nullptr4.1、…

nginx的底层理解

Nginx是一个高性能的Web服务器&#xff0c;同时也是一个反向代理服务器、负载均衡器和HTTP缓存器。它的底层设计注重效率和性能&#xff0c;能够支持高并发连接数&#xff0c;最大可以支持约50000个并发连接。以下是Nginx的一些关键特性和底层理解&#xff1a; 事件驱动模型&a…

机器人十大前沿技术(2023-2024年)

2023-2024年机器人十大前沿技术 1. 具身智能与垂直大模型 具身智能是指拥有自主感知、交互和行动能力的智能体&#xff0c;能够与环境进行实时互动&#xff0c;从而实现对环境的理解和适应。 “大模型”是指在深度学习和人工智能领域中&#xff0c;使用大量参数和数据进行训…

【PHP】web服务器支持PHP_环境配置

一、PHP运行目前为止主要有4方式 &#xff08;1&#xff09;以模块加载的方式运行&#xff0c;初学者可能不容易理解&#xff0c;其实就是将PHP集成到Apache服务器&#xff0c; 以同一个进程运行。 &#xff08;2&#xff09;以CGI的方式运行&#xff0c;CGI英文叫…

HarmonyOS4.0系列——08、整合UI常用组件

HarmonyOS4.0 系列——08、UI 组件 Blank Blank 组件在横竖屏占满空余空间效果 // xxx.ets Entry Component struct BlankExample {build() {Column() {Row() {Text(Button).fontSize(18)Blank()Toggle({type: ToggleType.Switch}).margin({top: 14,bottom: 14,left: 6,righ…

go-redis 使用 redis 6.0.14 版本错误: consider implementing encoding.BinaryMarshaler

使用方法 err : bp.data.redis.Get(ctx, policyKey).Scan(&result)起初在 redis 5.x.x 版本并没有遇到错误&#xff0c;但是在切换 redis 实例之后就出现了错误&#xff08;他们之间只是版本不同&#xff09;。 修复方法 看错误日志的描述&#xff0c;大概含义就是需要我们…

【Spring Boot 3】【JPA】一对一双向关联

【Spring Boot 3】【JPA】一对一双向关联 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习新技术总是要花…

Windows 使设置更改立即生效——并行发送广播消息

目录 前言 1 遍历窗口句柄列表 2 使用 SendMessageTimeout 发送延时消息 3 并行发送消息实现模拟广播消息 4 修改 UIPI 消息过滤器设置 5 托盘图标刷新的处理 6 完整代码和测试 本文属于原创文章&#xff0c;转载请注明出处&#xff1a; https://blog.csdn.net/qq_5907…

effective c++ 笔记 条款18-25

条款18&#xff1a;让接口容易被正确使用&#xff0c;不易误使用 使用外覆类型&#xff08;wrapper&#xff09;提醒调用者传参错误检查&#xff0c;将参数的附加条件限制在类型本身 Data::Data(int month, int day, int year) { ... }三个参数类型相同的函数容易造成误用 Da…

全球首个AI管理体系标准:ISO/IEC 42001《信息技术–人工智能–管理系统》

ISO/IEC 42001《信息技术–人工智能–管理系统》是全球首个针对人工智能&#xff08;AI&#xff09;管理体系的国际标准。该标准旨在帮助组织在开发、实施和维护AI技术时建立有效的管理体系&#xff0c;以确保AI系统的可靠性、安全性和合规性。 ISO/IEC 42001标准涵盖了多个方…

Java学习24--异常

异常 软件运行过程中的各种意料之外叫做Exception&#xff0c;比如要读取的文件找不到&#xff0c;准备联网发现没网&#xff0c;等着int参数来了个String 注意Error和exception不一样&#xff0c;error错的比较猛&#xff0c;一般是直接把JAVA整个搞崩了&#xff0c;比如内存…

洛谷C++简单题小练习day15—计算阶乘小程序(不用循环)

day15--计算阶乘小程序--2.19 习题概述 题目描述 求 n!&#xff0c;也就是 123⋯n。 挑战&#xff1a;尝试不使用循环语句&#xff08;for、while&#xff09;完成这个任务。 输入格式 第一行输入一个正整数 n。 输出格式 输出一个正整数&#xff0c;表示 n! 代码部分 …

瑞芯微RK3588驱动配置之DVP摄像头1

发现很多人不会RK3588DVP摄像头的驱动配置&#xff0c;这里简单分享一下&#xff0c;各位老铁点个赞&#xff0c;一键三连&#xff0c;给点动力。 这篇主要讲一下DTS设备树的配置&#xff0c;下篇写具体的驱动设计。 因为瑞芯微的大部分芯片都支持DVP摄像头&#xff0c;配置都大…

Android内存分析

1.分析某个进程的内存情况 1.1 生成.hprof文件 可以在运行的设备上&#xff0c;使用adb命令生成 .hprof文件&#xff0c;表示在执行命令的那一刻&#xff0c;该进程的内存情况。如下&#xff1a; am dumpheap com.example.demo /data/local/tmp/demo.hprof这里就是对包名为c…

【力扣白嫖日记】1890.2020年最后一次登录

前言 练习sql语句&#xff0c;所有题目来自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免费数据库练习题。 今日题目&#xff1a; 1890.2020年最后一次登录 表&#xff1a;Logins 列名类型user_idinttime_stampvarchar (user_id, time_sta…

Unity UGUI的DrawCall优化

Unity UGUI是一种强大的用户界面设计工具&#xff0c;它可以帮助开发者快速创建各种界面元素&#xff0c;从按钮和文本到滑块和面板等。然而&#xff0c;在使用UGUI时&#xff0c;一个常见的性能瓶颈就是DrawCall过多导致的性能下降。在本文中&#xff0c;我们将深入探讨UGUI的…

搜索算法(算法竞赛、蓝桥杯)--BFS八数码难题、抓住那头牛、魔板问题

1、B站视频链接&#xff1a;B14 BFS 八数码难题_哔哩哔哩_bilibili 题目链接&#xff1a;八数码难题 - 洛谷 #include <bits/stdc.h> using namespace std; char c; string str; unordered_map<string,int> d;//记录步数 queue<string> q; int dx[4]{-1,0,1…

微服务—RabbitMQ高级(业务在各方面的可靠性)

本博客为个人学习笔记&#xff0c;学习网站&#xff1a;2023黑马程序员RabbitMQ入门到实战教程 高级篇章节 目录 生产者可靠性 生产者重连机制 生产者确认机制 介绍 实现 总结与建议 MQ可靠性 数据持久化 LazyQueue 消费者可靠性 消费者确认机制 失败重试机制 失…