首页
归档
笔记
树洞
搜索
友言

文章详情

Interesting People Record Interesting.

/ JavaScript / 文章详情

axios封装 可请求不同地址

Sonder
2023-05-23
8598字
21分钟
浏览 (1.1k)

实现功能:针对不同请求设置token、code不为成功时的信息提示、code异常情况下的异常处理、屏蔽某些接口、简洁的数据结构响应。同时修改baseURL可以请求不同地址,让接口不再限制于一个链接。

需要下载vantjs-cookie,主要是为做移动端封装的。

接口返回格式是:

复制代码
{
 data: {
   body:[]
 },
 errno: 0,
 error:"",
}

request.js

复制代码
import axios from 'axios'
import {Toast} from 'vant'
import {config} from '@/config'
import cookie from "js-cookie";

// export interface RequestOptions {
//     baseURL?: string;// 后端接口地址,适配接口多域名情况
//     isToken?: boolean;// 是否携带token
//     repeatRequestCancel?: boolean;// 是否开启取消重复请求
//     loading?: boolean;// 是否开启loading层效果
//     reductDataFormat?: boolean;// 是否开启简洁的数据结构响应
//     errorMessageShow?: boolean;// 是否开启接口错误信息展示
//     codeMessageShow?: boolean;// 是否开启code不为0时的信息提示
// }

const pendingMap = new Map()

const loadingInstance = {
  _target: null,
  _count: 0
}

function setBaseURL(name, prefix) {
  return config(prefix)[name]
}

function getCookieKey() {
  return config().COOKIE_KEY
}

function request(
  axiosConfig,
  customOptions = {},
  loadingOptions = {}
) {
  const service = axios.create({
    timeout: 30000 // 设置统一的超时时长
  })

  // 自定义配置
  const custom_options = Object.assign({
    baseURL: null, // 后端接口地址,适配接口多域名情况
    isToken: true, // 是否携带token
    repeatRequestCancel: false, // 是否开启取消重复请求
    loading: true, // 是否开启loading层效果
    reductDataFormat: true, // 是否开启简洁的数据结构响应
    errorMessageShow: true, // 是否开启接口错误信息展示
    codeMessageShow: true // 是否开启code不为0时的信息提示
  }, customOptions)
  // 请求拦截
  service.interceptors.request.use(
    config => {
      config.baseURL = custom_options.baseURL || setBaseURL('BASE_URL', customOptions.prefix)
      removePending(config)
      custom_options.repeatRequestCancel && addPending(config)
      // 创建loading实例
      if (custom_options.loading) {
        loadingInstance._count++
        if (loadingInstance._count === 1) {
          if (loadingOptions?.message) {
            loadingInstance._target = Toast(loadingOptions)
          } else {
            loadingInstance._target = Toast.loading({
              duration: 0,
              forbidClick: true
            })
          }
        }
      }
      // 自动携带token
      if (custom_options.isToken) {
        if (config && config.headers) { // 多一步判断
          config.headers['Authorization'] = `${cookie.get(getCookieKey()) || ''}`; // 请求携带自定义token 请根据实际情况自行修改
        }
      }

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

  // 响应拦截
  service.interceptors.response.use(
    response => {
      removePending(response.config)
      custom_options.loading && closeLoading(custom_options) // 关闭loading
      const {errno, error} = response.data
      // 关于code码的判断自行修改
      if (errno === '401') {
        let redirect_uri = encodeURIComponent(window.location.href);
        window.location.replace('/login?redirect_uri=' + redirect_uri)
        return Promise.reject(response.data)
      } else if (errno === '402') { // openid没有绑定
        return Promise.reject(response.data)
      } else if (custom_options.codeMessageShow && response.data && +errno !== 0) {
        Toast({
          type: 'fail',
          message: error
        })
        return Promise.reject(response.data)
      } else {
        return custom_options.reductDataFormat ? response.data : response
      }
    },
    error => {
      error.config && removePending(error.config)
      custom_options.loading && closeLoading(custom_options) // 关闭loading
      custom_options.errorMessageShow && httpErrorStatusHandle(error) // 处理错误状态码
      return Promise.reject(error) // 错误继续返回给到具体页面
    }
  )

  return service(axiosConfig)
}

export default {
  request,
  get: (url,
        customOptions = {},
        loadingOptions = {}) => request({
    url,
    method: 'get',
  }, customOptions, loadingOptions),
  post: (url,
         data,
         customOptions = {},
         loadingOptions = {}) => request({
    url,
    method: 'post',
    data
  }, customOptions, loadingOptions)
}

/**
 * 处理异常
 * @param {*} error
 */
function httpErrorStatusHandle(error) {
  // 处理被取消的请求
  if (axios.isCancel(error)) return console.error('请求的重复请求:' + error.message)
  let message = ''
  if (error && error.response) {
    switch (error.response.status) {
      case 302:
        message = '接口重定向了!';
        break
      case 400:
        message = '参数不正确!';
        break
      case 401:
        message = '您未登录,或者登录已经超时,请先登录!';
        break
      case 403:
        message = '您没有权限操作!';
        break
      case 404:
        message = `请求地址出错: ${error.response.config.url}`;
        break // 在正确域名下
      case 408:
        message = '请求超时!';
        break
      case 409:
        message = '系统已存在相同数据!';
        break
      case 500:
        message = '服务器内部错误!';
        break
      case 501:
        message = '服务未实现!';
        break
      case 502:
        message = '网关错误!';
        break
      case 503:
        message = '服务不可用!';
        break
      case 504:
        message = '服务暂时无法访问,请稍后再试!';
        break
      case 505:
        message = 'HTTP版本不受支持!';
        break
      default:
        message = '异常问题,请联系管理员!';
        break
    }
  }
  if (error.message.includes('timeout')) message = '网络请求超时!'
  if (error.message.includes('Network')) message = window.navigator.onLine ? '服务端异常!' : '您断网了!'

  Toast({
    type: 'fail',
    message
  })
}

/**
 * 关闭Loading层实例
 * @param {*} options
 */
function closeLoading(options) {
  if (options.loading && loadingInstance._count > 0) loadingInstance._count--
  if (loadingInstance._count === 0) {
    loadingInstance._target.close()
    loadingInstance._target = null
  }
}

/**
 * 储存每个请求的唯一cancel回调, 以此为标识
 * @param {*} config
 */
function addPending(config) {
  const pendingKey = getPendingKey(config)
  config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
    if (!pendingMap.has(pendingKey)) {
      pendingMap.set(pendingKey, cancel)
    }
  })
}

/**
 * 删除重复的请求
 * @param {*} config
 */
function removePending(config) {
  const pendingKey = getPendingKey(config)
  if (pendingMap.has(pendingKey)) {
    const cancelToken = pendingMap.get(pendingKey)
    // 如你不明白此处为什么需要传递pendingKey可以看文章下方的补丁解释
    cancelToken(pendingKey)
    pendingMap.delete(pendingKey)
  }
}

/**
 * 生成唯一的每个请求的唯一key
 * @param {*} config
 * @returns
 */
function getPendingKey(config) {
  let {data} = config
  const {url, method, params} = config
  if (typeof data === 'string') data = JSON.parse(data) // response里面返回的config.data是个字符串对象
  return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&')
}

config.js

复制代码
export const production = "prod.xxxx.com"
export const development = "dev.xxxx.com"

export const isProd = window.location.host.includes(production);
export const isDevelopment = () => {
    return !isProd
}

const prefix = "https://"
const ROUTE = "/api/home"
// const ROUTE = "/api/admin"

export const config = (routePrefix = "") => {
    const COOKIE_KEY = isDevelopment() ? "COOKIE_KEY_dev" : "COOKIE_KEY_prod"
    const FIELD_PREFIX = isDevelopment() ? "dev" : "prod"
    routePrefix = routePrefix || ROUTE
    if (isProd) {
        return {
            BASE_URL: `${prefix}${production}${routePrefix}`,
            COOKIE_KEY,
            FIELD_PREFIX
        };
    }
    return {
        BASE_URL: `${prefix}${development}${routePrefix}`,
        COOKIE_KEY,
        FIELD_PREFIX
    };
};

使用方法

复制代码
import request from '@/utils/request';
import {config} from '@/config';

// dev.xxxx.com/v/wx/config?id=1
export const test1 = (id) => request.get(`/v/wx/config?id=${id}`, {
    baseURL: null, // 后端接口地址,适配接口多域名情况
    isToken: true, // 是否携带token
    rawaitepeatRequestCancel: false, // 是否开启取消重复请求
    loading: true, // 是否开启loading层效果
    reductDataFormat: true, // 是否开启简洁的数据结构响应
    errorMessageShow: true, // 是否开启接口错误信息展示
    codeMessageShow: true // 是否开启code不为0时的信息提示
})
// index.xxx.com/api/wx/test2
export const test2 = (data) => request.post(`/wx/test2`, data, {
    baseURL:'index.xxx.com/api'
})
// dev.xxxx.com/test/user/wx/config
export const getConfig = (data) => request.post(`/wx/config`, data, {
    loading: false, // 是否开启loading层效果
    isToken: false, // 是否携带token
    baseURL:config("/test/user").BASE_URL
})
下一篇 / 微信内置浏览器下载pdf的时候标题为null?

🎯 相关文章

💡 推荐文章

🕵️‍♂️ 评论 (0)