后台管理系统-axios网络请求的封装

embedded/2025/1/14 19:32:07/

此博客是针对开源项目:vue3-element-admin 的学习记录,为了帮助自己理清开发这个系统的逻辑.

安装依赖

javascript">npm install axios , qs

Axios实例封装

javascript">// 创建 axios 实例 ,同时给出一些预设配置,比如baseURL,超时时间等等
const service = axios.create({baseURL: import.meta.env.VITE_APP_BASE_API,timeout: 10000,headers: { 'Content-Type': 'application/json;charset=utf-8' },//序列化`params`paramsSerializer: (params) => qs.stringify(params),
})

其中 paramsSerializer: (params) => qs.stringify(params)是把发送网络请求时传递的params参数序列化为url查询字符串,拼接在URL之后,发送网络请求。

请求拦截器

javascript">// 请求拦截器,如果有token,就添加
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {const accessToken = getToken() //获取访问tokenif (config.headers.Authorization !== 'no-auth' && accessToken) {config.headers.Authorization = accessToken}else {delete config.headers.Authorization}return config},(error) => {return Promise.reject(error)},
)

请求拦截器,在我们的网络请求发送之前,拦截它们,这里我们需要加上token,有些页面是必须有token才能访问的,所以我们这里统一拦截,统一添加。

其中
const accessToken = getToken()
是在获取访问token:accessToken
getToken()的代码如下:

javascript">//获取到的token格式如  Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx 
function getToken(): string {return localStorage.getItem(ACCESS_TOKEN_KEY) || ''
}

设置token

javascript">if (config.headers.Authorization !== 'no-auth' && accessToken) {
//把获取到的token设置上config.headers.Authorization = accessToken
}
else {delete config.headers.Authorization
}

这段代码的目的是在请求发送之前判断是否需要携带 Token。如果请求头 Authorizationno-auth,或者没有设置请求头 Authorization字段,则不会添加 Token,即删除该字段。
反之则会根据 accessToken 动态地设置 Authorization 头。

在进行登录时,会调用登录方法,成功登录之后,会调用setToken的方法,保存tokenlocalStorage中:

javascript">  // 登录操作,这是首先在登录.vue组件中调用的方法function login(loginData: LoginData) {return new Promise((resolve, reject) => {AuthAPI.login(loginData).then((data) => {const { tokenType, accessToken, refreshToken } = data// 这里在成功登录以后,保存token,这样之后的网络请求都可以获取到token。setToken(tokenType + ' ' + accessToken) // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx,存储到localStorage中setRefreshToken(refreshToken)resolve(data)}).catch((err) => {reject(err)})})}

响应拦截器

javascript">// 响应拦截器
service.interceptors.response.use((response: AxiosResponse) => {// 如果响应是二进制流,则直接返回,用于下载文件、Excel 导出等if (response.config.responseType === "blob") {return response;}const { code, data, msg } = response.data;if (code === ResultEnum.SUCCESS) {console.log("response SUCCESS", response);return data;}ElMessage.error(msg || "系统出错");return Promise.reject(new Error(msg || "Error"));},async (error: any) => {// 非 2xx 状态码处理 401、403、500 等const { config, response } = error;if (response) {const { code, msg } = response.data;if (code === ResultEnum.ACCESS_TOKEN_INVALID) {// Token 过期,刷新 Tokenreturn handleTokenRefresh(config);} else if (code === ResultEnum.REFRESH_TOKEN_INVALID) {return Promise.reject(new Error(msg || "Error"));} else {ElMessage.error(msg || "系统出错");}}return Promise.reject(error.message);}
);

service.interceptors.response.use( onFulfilled ,onRejected ) 函数接收两个函数作为参数。

onFulfilled函数是响应成功时执行的回调:所谓响应成功是当 axios 收到一个 2xx 状态码 的响应时,它会认为响应成功,并进入 onFulfilled 函数。此时的 response 对象中包含了以下内容:

response.data:服务器返回的响应数据(通常是你需要的内容)。
response.status:响应的 HTTP状态码,如 200, 201 等。
response.statusText:状态信息文本,如 “OK”,“Created” 等。
response.headers:响应头信息。
response.config:原始请求配置对象。

axios收到一个 非 2xx 状态码 的响应时,或者请求失败(如网络错误、超时等),它会进入 onRejected 函数。在 onRejected 函数中,错误对象 error 会包含以下内容:

error.response: 当请求得到了响应但状态码不在 2xx 范围时(如 4xx 或 5xx 错误)才存在的属性。它包含了来自服务器的响应信息。
error.config:包含了请求时的配置信息
等等…

对于请求失败的情况,我们获取到 const { config, response } = error;
通过response.data获取到服务器返回的错误消息或错误数据,比如codemsg等信息。
如果code代表访问token失效,则需要重新获取一次访问token,这里调用handleTokenRefresh()函数
如果code代表刷新token失效,中止请求流程。
这个系统里的分为refreshTokenaccessToken,通常refreshToken的有效期是更久的,同时每次accessToken更新时也会把refreshToken一起更新,正常来说,基本不会同时出现两个Token同时过期(根据其它代码的推测)。

现在重点关注handleTokenRefresh函数,相关代码如下:

javascript">// 刷新 Token 的锁
let isRefreshing = false;
// 因 Token 过期导致失败的请求队列
let requestsQueue: Array<() => void> = [];// 刷新 Token 处理
async function handleTokenRefresh(config: InternalAxiosRequestConfig) {return new Promise((resolve) => {const requestCallback = () => {config.headers.Authorization = getToken();resolve(service(config));};requestsQueue.push(requestCallback);if (!isRefreshing) {isRefreshing = true;// 刷新 TokenuseUserStoreHook().refreshToken().then(() => {// Token 刷新成功,执行请求队列requestsQueue.forEach((callback) => callback());requestsQueue = [];}).catch((error) => {console.log("handleTokenRefresh error", error);// Token 刷新失败,清除用户数据并跳转到登录ElNotification({title: "提示",message: "您的会话已过期,请重新登录",type: "info",});useUserStoreHook().clearUserData().then(() => {router.push("/login");});}).finally(() => {isRefreshing = false;});}});
}

首先有一个刷新 Token 的锁isRefreshing ,因为可能多个网络请求再发送时,token过期了,这时候需要更新token,但是我们只需要更新一次就可以了,所有有一个锁。对于后续的网络请求,发现当前正在更新token时,就不用再次更新token了。

首先定义了一个网络请求的回调函数:

javascript">const requestCallback = () => {config.headers.Authorization = getToken();resolve(service(config));};
//推入数组中
requestsQueue.push(requestCallback);

这段代码是为了保存那些再token失效时发送的网络请求,等到后面刷新token以后,在统一调用。
service(config):是使用之前创建的axios实例对象,config则是本次请求的配置信息,因为token失效,axios请求失败,通过error对象获取到本次的configresponse

javascript"> const { config, response } = error;

之后:

javascript">//如果当前没有进行更新tokenif (!isRefreshing) {//则进行更新token,同时上锁isRefreshing = true;// 刷新 Token,调用相关的API更新useUserStoreHook().refreshToken().then(() => {// Token 刷新成功,执行请求队列,即重新发送之前因为token失效而失败的网络请求requestsQueue.forEach((callback) => callback());requestsQueue = [];})//如果更新token出错,直接清除数据,跳转到登录页..catch((error) => {console.log("handleTokenRefresh error", error);// Token 刷新失败,清除用户数据并跳转到登录ElNotification({title: "提示",message: "您的会话已过期,请重新登录",type: "info",});useUserStoreHook().clearUserData().then(() => {router.push("/login");});}).finally(() => {isRefreshing = false;});}

完整代码

javascript">import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from 'axios'
import qs from 'qs'
import { ElNotification } from 'element-plus'
import { useUserStore } from '@/stores/modules/user' //用户信息的store
import { getToken } from '@/utils/auth'
import { ResultEnum } from '@/enums/ResultEnum'
import router from '@/router'// 创建 axios 实例 ,针对网络请求的封装
const service = axios.create({baseURL: import.meta.env.VITE_APP_BASE_API,timeout: 10000,headers: { 'Content-Type': 'application/json;charset=utf-8' },paramsSerializer: (params) => qs.stringify(params),
})// 请求拦截器,如果有token,就添加
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {const accessToken = getToken() //获取访问tokenif (config.headers.Authorization !== 'no-auth' && accessToken) {config.headers.Authorization = accessToken}else {delete config.headers.Authorization}return config},(error) => {return Promise.reject(error)},
)// 响应拦截器
service.interceptors.response.use((response: AxiosResponse) => {// 如果响应是二进制流,则直接返回,用于下载文件、Excel 导出等// 这种类型不能return response.dataif (response.config.responseType === 'blob') {return response}// json数据,成功状态const { code, data, msg } = response.dataif (code === ResultEnum.SUCCESS) {return data}// @ts-ignoreElMessage.error(msg || '系统出错')return Promise.reject(new Error(msg || 'Error'))},// 响应错误拦截器,处理不同的错误状态(error: any) => {// 非 2xx 状态码处理 401、403、500 等const { config, response } = errorif (response) {const { code, msg } = response.data// 访问令牌无效或过期,重新获取一次,但是可能刷新令牌也过期if (code === ResultEnum.ACCESS_TOKEN_INVALID) {console.log('token过期,重新获取')return handleTokenRefresh(config)// return 'AccessTokenInvalid'}// 刷新令牌无效或过期,应该是跳转登录页面else if (code === ResultEnum.REFRESH_TOKEN_INVALID) {return Promise.reject(new Error(msg || 'Error'))} else {// @ts-ignoreElMessage.error(msg || '系统出错')}}return Promise.reject(error.message)},
)// 导出 axios 实例
export default service// 刷新 Token 的锁,表示是否正在刷新token
let isRefreshing = false
// 因 Token 过期导致失败的请求队列
let requestsQueue: Array<() => void> = []async function handleTokenRefresh(config: InternalAxiosRequestConfig) {return new Promise((resolve) => {const requestCallback = () => {config.headers.Authorization = getToken()resolve(service(config))}requestsQueue.push(requestCallback)if (!isRefreshing) {isRefreshing = true// 刷新 TokenuseUserStore().refreshToken().then(() => {// Token 刷新成功,执行请求队列requestsQueue.forEach((callback) => callback())requestsQueue = []}).catch((error: any) => {console.log('handleTokenRefresh error', error)// Token 刷新失败,清除用户数据并跳转到登录ElNotification({title: '提示',message: '您的会话已过期,请重新登录',type: 'info',})useUserStore().clearUserData().then(() => {router.push('/login')})}).finally(() => {isRefreshing = false})}})
}

http://www.ppmy.cn/embedded/153916.html

相关文章

idea快捷键

IDEA常见快捷键 Ctrl A 全写 Ctrl C 粘贴 Ctrl V 复制 Ctrl F 搜索 Ctrl R 替换 Ctrl Z 撤销 Ctrl D 复制行 Ctrl X 删除行&#xff0c;并且被删除的行复制到剪贴板中 Ctrl Y 删除一行 Ctrl Shift Z 反撤销 IDEA重要快捷键 Ctrl / 单行注释&…

汇总统计数据--SQL中聚集函数的使用

目录 1、为什么需要汇总数据 2、聚集函数 &#xff08;1&#xff09;AVG函数 &#xff08;2&#xff09;COUNT函数 &#xff08;3&#xff09;MAX和MIN函数 &#xff08;4&#xff09;SUM函数 3、聚集不同值--DISTINCT 4、组合聚集函数 5、小结 博主用的是mysql8 DBMS…

vue 中的 v-model

v-model 是 vue 的主要特性&#xff0c;双向绑定是响应式变量的核心。v-model 的简单原理就是数据监听加UI通知&#xff0c;如何在我们自己的组件中实现 v-model 呢&#xff1f;数据变更监听加父组件事件通知&#xff0c;如下&#xff0c;来自官网的一个例子 <script setup&…

机器学习与人工智能的关系

机器学习与人工智能的关系 一、人工智能二、机器学习2.1 机器学习与人工智能的关系2.2 机器学习的本质 三、其他玩艺 曾几何时&#xff0c;人工智能还是个科幻名词&#xff0c;仿佛只属于未来世界。如今&#xff0c;它已经渗透到了我们生活的方方面面&#xff0c;成为顶流。我们…

详解用大模型超拟人语音做桌面AI宠物/机器人的个性化能力

前言 本文基于前面已经落地的CSK6大模型语音视觉开发板的配套示例功能来进行讲解&#xff0c;超拟人交互效果可以参考视频&#xff1a; 超拟人语音极速回复演示视频 目前聆思平台的超拟人模板实现了快速响应、声纹识别、知识库问答、兜底闲聊、超拟人TTS等功能&#xff0c;具体…

Open FPV VTX开源之默认MAVLink设置

Open FPV VTX开源之默认MAVLink设置 1. 源由2. 准备3. 连接4. 安装5. 配置6. 测试6.1 启动wfb-ng服务6.2 启动wfb-ng监测6.3 启动QGroundControl6.4 观察测试结果 7. 总结8. 参考资料9. 补充9.1 telemetry_tx异常9.2 DEBUG串口部分乱码9.3 PixelPilot软件问题 1. 源由 飞控图传…

机器学习算法(一): 基于逻辑回归的分类预测

1 逻辑回归的介绍和应用 1.1 逻辑回归的介绍 逻辑回归&#xff08;Logistic regression&#xff0c;简称LR&#xff09;虽然其中带有"回归"两个字&#xff0c;但逻辑回归其实是一个分类模型&#xff0c;并且广泛应用于各个领域之中。虽然现在深度学习相对于这些传统…

可以进行重复测量的方差分析的AI agent

可以进行重复测量的方差分析的AI agent 前几天做了机器学习的AI agent&#xff0c;把一个糖尿病机器学习模型采用API的形式接入到LLM模型中&#xff0c;结合LLM的智能性和机器学习模型的准确性&#xff0c;利用两者的有点&#xff0c;有可以避免两者的缺点&#xff0c;是一条合…