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…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...
