若依源码对Axios封装和实现请求拦截,内置下载功能

 1-安装Axios

npm install axios

 2-封装request.js文件

为什么要创建Axios实例?

1. ​​隔离配置,避免全局污染​

  • ​场景​​:当项目需要与多个后端服务交互时,不同服务可能有不同的baseURLheaderstimeout设置。
  • ​解决方案​​:通过创建实例,每个实例可以独立配置参数(如baseURL: "https://api.service.com/v1"),避免修改默认实例的全局配置,防止不同服务间的配置冲突。
  • ​​示例​​:
    
    const userApi = axios.create({ baseURL: "https://user.service.com" });
     const productApi = axios.create({ baseURL: "https://product.service.com" });

2. ​​独立的拦截器管理​

  • ​场景​​:某些API需要身份验证(如添加Authorization头),而其他API可能是公开的。
  • ​解决方案​​:为不同实例绑定独立的请求/响应拦截器,例如认证逻辑仅作用于需要登录的实例,避免在拦截器中编写冗余的条件判断。
  • // 需要认证的实例 
    const authApi = axios.create({ baseURL: "https://secure.api.com" }); 
    
    authApi.interceptors.request.use(config => { config.headers.Authorization = `Bearer ${getToken()}`; return config; });

3. ​​模块化与可维护性​

  • ​场景​​:按功能模块划分API(如用户模块、订单模块),提升代码组织性。
  • ​解决方案​​:通过命名实例(如userServiceorderService)明确职责,使代码更易维护和协作。
  • // userService.js
     export const userService = axios.create({
     baseURL: "/user", timeout: 5000, 
    });

4. ​​灵活应对不同场景需求​

  • ​场景​​:某些API需要更长的超时时间,或不同的Content-Type(如上传文件的multipart/form-data)。
  • ​解决方案​​:通过实例化快速生成适配不同场景的客户端,无需每次请求重复配置。
  • const uploadApi = axios.create({ 
    baseURL: "/upload",
     headers: { "Content-Type": "multipart/form-data" },
     timeout: 30000, 
    // 更长超时时间 });

创建Axios实例的核心目的是通过​​配置隔离​​和​​逻辑解耦​​,提升代码的健壮性和可维护性。这种方式使得不同服务或模块的API调用互不干扰,同时支持更细粒度的控制和扩展,是复杂项目中的最佳实践。

const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: import.meta.env.VITE_APP_BASE_API,
  // 超时
  timeout: 1000000
})

创建步骤:

1. 创建实例

创建 Axios 实例​​
const service = axios.create({
  baseURL: import.meta.env.VITE_APP_BASE_API, // 根据环境变量设置后端 API 地址
  timeout: 1000000 // 请求超时时间(约 16 分钟)
});
配置所有请求的基础 URL 和超时时间。


2. 自动身份验证

// 自动添加JWT Token

if ( getToken ( ) && ! isToken ) {

config . headers [ 'Authorization' ] = 'Bearer ' + getToken ( )

}

3. 统一错误处理

  • ​二进制响应​​: 直接返回数据供下载方法处理。
  • ​错误码处理​​:
    • 401 未授权: 弹出重新登录确认框,确认后调用退出登录逻辑。
    • 其他错误: 显示通知提示。
  • ​网络错误​​: 统一处理超时、连接异常等,显示错误消息。


4. 请求/响应拦截

// request拦截器
service.interceptors.request.use(config => {
  // 判断是否需要携带 Token
  const isToken = (config.headers || {}).isToken === false;
  if (getToken() && !isToken) {
    config.headers['Authorization'] = 'Bearer ' + getToken();
  }

  // GET 请求参数处理
  if (config.method === 'get' && config.params) {
    config.url += '?' + tansParams(config.params);
    config.params = {}; // 清空 params,避免重复拼接
  }

  // 防止重复提交(仅 POST/PUT)
  const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
  if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
    const requestObj = { url: config.url, data: config.data, time: new Date().getTime() };
    const requestSize = JSON.stringify(requestObj).length;
    if (requestSize >= 5 * 1024 * 1024) {
      console.warn('请求数据过大,跳过防重复提交验证');
    }
    // 实际项目可能需要在此处添加缓存校验逻辑
  }

  return config;
}, error => {
  return Promise.reject(error);
});

// 响应拦截器
service.interceptors.response.use(
  res => {
    const code = res.data.code || 200;
    const msg = errorCode[code] || res.data.msg || '未知错误';

    // 处理二进制响应(如文件下载)
    if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
      return res.data;
    }

    // 错误处理
    if (code !== 200) {
      if (code === 401) {
        if (!isRelogin.show) {
          isRelogin.show = true;
          ElMessageBox.confirm('登录状态已过期,请重新登录', '提示', { 
            confirmButtonText: '重新登录',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            isRelogin.show = false;
            useUserStore().logout(); // 调用 Pinia 退出登录
          }).catch(() => {
            isRelogin.show = false;
          });
        }
        return Promise.reject('无效会话,请重新登录');
      } else {
        ElNotification.error({ title: msg });
        return Promise.reject(res.data);
      }
    } else {
      return res.data;
    }
  },
  error => {
    // 网络错误处理
    let { message } = error;
    if (message === 'Network Error') {
      message = '后端接口连接异常';
    } else if (message.includes('timeout')) {
      message = '请求超时';
    } else if (message.includes('Request failed with status code')) {
      message = `接口 ${message.slice(-3)} 异常`;
    }
    ElMessage.error(message);
    return Promise.reject(error);
  }
);

  5. 通用下载

// 通用下载方法
export function download(url, params, filename, config) {
  downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })
  return service.post(url, params, {
    transformRequest: [(params) => { return tansParams(params) }],
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    responseType: 'blob',
    ...config
  }).then(async (data) => {
    const isBlob = blobValidate(data);
    if (isBlob) {
      const blob = new Blob([data])
      saveAs(blob, filename)
    } else {
      const resText = await data.text();
      const rspObj = JSON.parse(resText);
      const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
      ElMessage.error(errMsg);
    }
    downloadLoadingInstance.close();
  }).catch((r) => {
    console.error(r)
    ElMessage.error('下载文件出现错误,请联系管理员!')
    downloadLoadingInstance.close();
  })
}


6. 维护性提升


- 修改配置只需调整封装文件
- 新成员快速上手
- 减少重复代码


使用

在使用时,只需导入对应的request.js文件

import request from '@/utils/request'

//查询设备故障记录列表
export function getRowData(data) {
  return request({
    url: '/qc-api/getRowData',
    method: 'post',
    data: data
  })
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值