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>
相关文章:
axios封装终极版实现token无感刷新及全局loading
前言 关于axios全局loading的封装博主已经发过一次了,这次是在其基础上增加了token的无感刷新。 token无感刷新流程 首次登录的时候会获取到两个token(AccessToken,RefreshToken)持久化保存起来(localStorage方案&a…...

推荐一个内网穿透工具,支持Windows桌面、Linux、Arm平台客户端
神卓互联是一款常用的内网穿透工具,它可以将本地服务器映射到公网上,并提供域名或子域名给外部访问。神卓互联具有简单易用、高速稳定的特点,支持Windows桌面版、Linux版、Arm版客户端,以及硬件等。 神卓互联内网穿透技术简介 企…...
【linux】vim多行操作命令
文章目录 1. vim多行同时修改2. vim复制/移动多行3. vim删除多行4. vim设置缩进空格 回顾:vi/vim常用命令 1. vim多行同时修改 (1) ctrl v (2) 按 下箭头,选择多行 (3) shift i,…...

vue-router钩子函数有哪些?都有哪些参数?
Vue.js是一款流行的JavaScript框架,它提供了大量的工具和特性,使得web前端开发更加高效和灵活。其中之一就是Vue-router,它是Vue.js官方路由插件,可以实现前端路由的管理和控制。在使用Vue-router时,我们可以利用钩子函…...

基于JavaWeb开发的小区车辆登记系统计算机毕设[附源码]
基于JavaWeb开发的小区车辆登记系统计算机毕设[附源码] 🍅 作者主页 央顺技术团队 🍅 欢迎点赞 👍 收藏 ⭐留言 📝 🍅 文末获取源码联系方式 📝 🍅 查看下方微信号获取联系方式 承接各种定制系统…...

【开源】SpringBoot框架开发高校宿舍调配管理系统
目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能需求2.1 学生端2.2 宿管2.3 老师端 三、系统展示四、核心代码4.1 查询单条个人习惯4.2 查询我的室友4.3 查询宿舍4.4 查询指定性别全部宿舍4.5 初次分配宿舍 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的…...

高压开关柜实现无线测温监测的关键点
一、概述 近年来,电厂自动化、信息化飞速发展,加快了对高压开关柜内的温度检测技术的研究。一系列的开关柜的无线测温监测技术也因此应运而生,并且发挥着越来越重要的作用。高压开关柜是发电厂、变电站、动力车间最重要的电气设备,…...

在线图片生成工具:定制化占位图片的利器
title: 在线图片生成工具:定制化占位图片的利器 date: 2024/2/20 14:08:16 updated: 2024/2/20 14:08:16 tags: 占位图片网页布局样式展示性能测试响应式设计在线生成开发工具 在现代的网页设计和开发中,占位图片扮演着重要的角色。占位图片是指在开发过…...
闭包----闭包的理解、优点
1、闭包的理解 闭包就是能够读取其他函数内部变量的函数。 由于在 javascript 中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理 解成 “ 定义在一个函数内部的函数 “ 。 所以,在本质上,闭包是将…...

jenkins的nmp install命令无法下载包
问题:在jenkin的流水线脚本中执行到:npm install命令后无法下载前端依赖包 1、进到jenkins的工作目录,一般在底层为/var/lib/jenkins/workspace/任务名称 cd /var/lib/jenkins/workspace/xkc处理方式: # 查看镜像源 npm config …...

Collection集合体系(ArrayList,LinekdList,HashSet,LinkedHashSet,TreeSet,Collections)
目录 一.Collection 二.List集合 三.ArrayList集合 四.LinkedList集合 五.Set集合 六.hashSet集合 七.LinkedHashSet集合 八.TreeSet集合 九.集合工具类Collections 集合体系概述 单列集合:Collection代表单列集合,每个元素&#…...

Job 和 DaemonSet
一、Job 1、Job 背景问题 K8s 里,最小的调度单元是 Pod,如果直接通过 Pod 来运行任务进程,会产生以下几种问题: ① 如何保证 Pod 内进程正确的结束? ② 如何保证进程运行失败后重试? ③ 如何管理多个任…...

C++ 二维前缀和 子矩阵的和
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2 ,表示一个子矩阵的左上角坐标和右下角坐标。 对于每个询问输出子矩阵中所有数的和。 输入格式 第一行包含三个整数 n,m,q 。 接下…...

第六届计算机科学与技术在教育中的应用国际会议(CSTE 2024)
2024年第六届计算机科学与技术在教育中的应用国际会议(CSTE 2024)将于4月19-21日在中国西安举行。此次会议由陕西师范大学主办,陕西师范大学教育学部承办。在前五届成功举办的基础上,CSTE 2024将继续关注计算机科学与技术在教育领…...

Vue3学习——标签的ref属性
在HTML标签上,可以使用相同的ref名称,得到DOM元素ref放在组件上时,拿到的是组件实例(组件defineExpose暴露谁,ref才可以看到谁) <script setup lang"ts"> import RefPractice from /compo…...

数字化转型导师坚鹏:政府数字化转型之数字化技术
政府数字化转型之数字化技术 ——物联网、云计算、大数据、人工智能、虚拟现实、区块链、数字孪生、元宇宙等综合解析及应用 课程背景: 数字化背景下,很多政府存在以下问题: 不清楚新技术的发展现状? 不清楚新技术的重要应…...
go build
go build 作用:将Go语言程序和相关依赖编译成可执行文件 go build 无参数编译 生成当前目录名的可执行文件并放置于当前目录下,如: go build go build文件列表 编译同目录的多个源码文件时,可以在 go build 的后面提供多个文件…...

力扣238和169
一:238. 除自身以外数组的乘积 1.1题目 1.2思路 1.3代码 //左右乘表 int* productExceptSelf(int* nums, int numsSize, int* returnSize) {int* answer (int*)malloc(numsSize*sizeof(int));int i 0;int left[numsSize],right[numsSize];left[0] 1;for(i 1;…...

Android 基础技术——Framework
笔者希望做一个系列,整理 Android 基础技术,本章是关于 Framework 简述 Android 系统启动流程 当按电源键触发开机,首先会从 ROM 中预定义的地方加载引导程序 BootLoader 到 RAM 中,并执行 BootLoader 程序启动 Linux Kernel&…...
JavaWeb 中的静态资源访问
文章目录 JavaWeb 中的静态资源访问1. Tomcat 中的两个默认 ServletJSPServletDefaultServlet配置引起的 bug情况一情况二情况三 2. 总结3. 如何允许静态资源访问 JavaWeb 中的静态资源访问 1. Tomcat 中的两个默认 Servlet Tomcat 有两个默认的 Servlet,你的 Web…...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7
在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤: 第一步: 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为: // 改为 v…...

Java后端检查空条件查询
通过抛出运行异常:throw new RuntimeException("请输入查询条件!");BranchWarehouseServiceImpl.java // 查询试剂交易(入库/出库)记录Overridepublic List<BranchWarehouseTransactions> queryForReagent(Branch…...
WEB3全栈开发——面试专业技能点P4数据库
一、mysql2 原生驱动及其连接机制 概念介绍 mysql2 是 Node.js 环境中广泛使用的 MySQL 客户端库,基于 mysql 库改进而来,具有更好的性能、Promise 支持、流式查询、二进制数据处理能力等。 主要特点: 支持 Promise / async-await…...

[拓扑优化] 1.概述
常见的拓扑优化方法有:均匀化法、变密度法、渐进结构优化法、水平集法、移动可变形组件法等。 常见的数值计算方法有:有限元法、有限差分法、边界元法、离散元法、无网格法、扩展有限元法、等几何分析等。 将上述数值计算方法与拓扑优化方法结合&#…...