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…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...
