axios封装

// 服务层 , import默认会找该目录下index.js的文件,这个可能有小伙伴不知道
// 可以去了解npm的引入和es6引入的理论概念
import axiosPlugin from "./server"; 
Vue.use(axiosPlugin);
对axios的封装(AXIOS:index.js) import axios from
"axios"; import qs from "qs"; import { Message } from "element-ui"; import router from "../router"; const Axios = axios.create({ baseURL: "/", // 因为我本地做了反向代理 timeout: 10000, responseType: "json", withCredentials: true, // 是否允许带cookie这些 headers: { "Content-Type": "application/x-www-form-urlencoded;charset=utf-8" } }); //POST传参序列化(添加请求拦截器) Axios.interceptors.request.use( config => { // 在发送请求之前做某件事 if ( config.method === "post" || config.method === "put" || config.method === "delete" ) { // 序列化 config.data = qs.stringify(config.data); } // 若是有做鉴权token , 就给头部带上token if (localStorage.token) { config.headers.Authorization = localStorage.token; } return config; }, error => { Message({ // 饿了么的消息弹窗组件,类似toast showClose: true, message: error, type: "error.data.error.message" }); return Promise.reject(error.data.error.message); } ); //返回状态判断(添加响应拦截器) Axios.interceptors.response.use( res => { //对响应数据做些事 if (res.data && !res.data.success) { Message({ // 饿了么的消息弹窗组件,类似toast showClose: true, message: res.data.error.message.message ? res.data.error.message.message : res.data.error.message, type: "error" }); return Promise.reject(res.data.error.message); } return res; }, error => { // 用户登录的时候会拿到一个基础信息,比如用户名,token,过期时间戳 // 直接丢localStorage或者sessionStorage if (!window.localStorage.getItem("loginUserBaseInfo")) { // 若是接口访问的时候没有发现有鉴权的基础信息,直接返回登录页 router.push({ path: "/login" }); } else { // 若是有基础信息的情况下,判断时间戳和当前的时间,若是当前的时间大于服务器过期的时间 // 乖乖的返回去登录页重新登录 let lifeTime = JSON.parse(window.localStorage.getItem("loginUserBaseInfo")).lifeTime * 1000; let nowTime = (new Date()).getTime(); // 当前时间的时间戳 if (nowTime > lifeTime) { Message({ showClose: true, message: "登录状态信息过期,请重新登录", type: "error" }); router.push({ path: "/login" }); } } // 下面是接口回调的status ,因为我做了一些错误页面,所以都会指向对应的报错页面 if (error.response.status === 403) { router.push({ path: "/error/403" }); } if (error.response.status === 500) { router.push({ path: "/error/500" }); } if (error.response.status === 502) { router.push({ path: "/error/502" }); } if (error.response.status === 404) { router.push({ path: "/error/404" }); } // 返回 response 里的错误信息 return Promise.reject(error.data.error.message); } ); // 对axios的实例重新封装成一个plugin ,方便 Vue.use(xxxx) export default { install: function(Vue, Option) { Object.defineProperty(Vue.prototype, "$http", { value: Axios }); } }; 路由钩子的调整(Router:index.js) import Vue from "vue"; import Router from "vue-router"; import layout from "@/components/layout/layout"; // 版块有点多,版块独立路由管理,里面都是懒加载引入 import customerManage from "./customerManage"; // 客户管理 import account from "./account"; //登录 import adManage from "./adManage"; // 广告管理 import dataStat from "./dataStat"; // 数据统计 import logger from "./logger"; // 日志 import manager from "./manager"; // 管理者 import putonManage from "./putonManage"; // 投放管理 import error from "./error"; // 服务端错误 import { Message } from "element-ui"; Vue.use(Router); // 请跳过这一段,看下面的 const router = new Router({ hashbang: false, mode: "history", routes: [ { path: "/", redirect: "/adver", component: layout, children: [ ...customerManage, ...adManage, ...dataStat, ...putonManage, ...manager, ...logger ] }, ...account, ...error ] }); // 路由拦截 // 差点忘了说明,不是所有版块都需要鉴权的 // 所以需要鉴权,我都会在路由meta添加添加一个字段requireLogin,设置为true的时候 // 这货就必须走鉴权,像登录页这些不要,是可以直接访问的!!! router.beforeEach((to, from, next) => { if (to.matched.some(res => res.meta.requireLogin)) { // 判断是否需要登录权限 if (window.localStorage.getItem("loginUserBaseInfo")) { // 判断是否登录 let lifeTime = JSON.parse(window.localStorage.getItem("loginUserBaseInfo")).lifeTime * 1000; let nowTime = (new Date()).getTime(); // 当前时间的时间戳 if (nowTime < lifeTime) { next(); } else { Message({ showClose: true, message: "登录状态信息过期,请重新登录", type: "error" }); next({ path: "/login" }); } } else { // 没登录则跳转到登录界面 next({ path: "/login" }); } } else { next(); } }); export default router; axios可配置的一些选项,其他的具体看官网说明哈 export default { // 请求地址 url: "/user", // 请求类型 method: "get", // 请根路径 baseURL: "http://www.mt.com/api", // 请求前的数据处理 transformRequest: [function(data) {}], // 请求后的数据处理 transformResponse: [function(data) {}], // 自定义的请求头 headers: { "x-Requested-With": "XMLHttpRequest" }, // URL查询对象 params: { id: 12 }, // 查询对象序列化函数 paramsSerializer: function(params) {}, // request body data: { key: "aa" }, // 超时设置s timeout: 1000, // 跨域是否带Token withCredentials: false, // 自定义请求处理 adapter: function(resolve, reject, config) {}, // 身份验证信息 auth: { uname: "", pwd: "12" }, // 响应的数据格式 json / blob /document /arraybuffer / text / stream responseType: "json", // xsrf 设置 xsrfCookieName: "XSRF-TOKEN", xsrfHeaderName: "X-XSRF-TOKEN", // 下传和下载进度回调 onUploadProgress: function(progressEvent) { Math.round(progressEvent.loaded * 100 / progressEvent.total); }, onDownloadProgress: function(progressEvent) {}, // 最多转发数,用于node.js maxRedirects: 5, // 最大响应数据大小 maxContentLength: 2000, // 自定义错误状态码范围 validateStatus: function(status) { return status >= 200 && status < 300; }, // 用于node.js httpAgent: new http.Agent({ keepAlive: true }), httpsAgent: new https.Agent({ keepAlive: true }), // 用于设置跨域请求代理 proxy: { host: "127.0.0.1", port: 8080, auth: { username: "aa", password: "2123" } }, // 用于取消请求 cancelToken: new CancelToken(function(cancel) {}) };

 

import axios, { AxiosRequestConfig } from "axios";

const pending = {};

const CancelToken = axios.CancelToken;
const removePending = (key: string, isRequest = false) => {
  if (Reflect.get(pending, key) && isRequest) {
    Reflect.get(pending, key)("取消重复请求");
  }
  Reflect.deleteProperty(pending, key);
};
const getRequestIdentify = (config: AxiosRequestConfig, isReuest = false) => {
  let url = config.url;
  const suburl = config.url?.substring(1, config.url?.length) ?? "";
  if (isReuest) {
    url = config.baseURL + suburl;
  }
  return config.method === "get"
    ? encodeURIComponent(url + JSON.stringify(config.params))
    : encodeURIComponent(config.url + JSON.stringify(config.data));
};

// 创建一个AXIOS实例
const service = axios.create({
  baseURL: import.meta.env.VITE_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 16000, // 请求超时
});

// 请求拦截器
service.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    // 拦截重复请求(即当前正在进行的相同请求)
    const requestData = getRequestIdentify(config, true);
    removePending(requestData, true);

    config.cancelToken = new CancelToken((c: any) => {
      Reflect.set(pending, requestData, c);
    });

    // 请求发送前的预处理(如:获取token等)
    // if (store.getters.token) {
    //   // let each request carry token
    //   // ['X-AUTH-TOKEN'] is a custom headers key
    //   // please modify it according to the actual situation
    //   config.headers['X-AUTH-TOKEN'] = getToken()
    // }
    return config;
  },
  (error: any) => {
    // do something with request error
    console.log(error); // for debug
    return Promise.reject(error);
  }
);

// response interceptor
service.interceptors.response.use(
  (response: { config: AxiosRequestConfig; data: any }) => {
    // 把已经完成的请求从 pending 中移除
    const requestData = getRequestIdentify(response.config);
    removePending(requestData);
    const res = response.data;
    return res;
  },
  (error: {
    message: string;
    config: { showLoading: any };
    response: { status: any };
    request: any;
  }) => {
    console.log(error.message);
    if (error) {
      if (error.response) {
        switch (error.response.status) {
          case 400:
            error.message = "错误请求";
            break;
          case 401:
            error.message = "未授权,请重新登录";
            break;
          default:
            error.message = `连接错误${error.response.status}`;
        }
        const errData = {
          code: error.response.status,
          message: error.message,
        };
        console.log("统一错误处理: ", errData);
      } else if (error.request) {
        console.log("统一错误处理: ", "网络出错,请稍后重试");
      }
    }
    return Promise.reject(error);
  }
);

export default service;

 

import { Dialog } from "vant";
import "vant/es/dialog/style";

import { Toast } from "vant";
import "vant/es/toast/style";

import axios, { AxiosRequestConfig } from "axios";

const pending = {};

const CancelToken = axios.CancelToken;
const removePending = (key: string, isRequest = false) => {
  if (Reflect.get(pending, key) && isRequest) {
    Reflect.get(pending, key)("取消重复请求");
  }
  Reflect.deleteProperty(pending, key);
};
const getRequestIdentify = (config: AxiosRequestConfig, isReuest = false) => {
  let url = config.url;
  const suburl = config.url?.substring(1, config.url?.length) ?? "";
  if (isReuest) {
    url = config.baseURL + suburl;
  }
  return config.method === "get"
    ? encodeURIComponent(url + JSON.stringify(config.params))
    : encodeURIComponent(config.url + JSON.stringify(config.data));
};

// 创建一个AXIOS实例
const service = axios.create({
  baseURL: import.meta.env.VITE_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 16000, // 请求超时
});

// 请求拦截器
service.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    // 拦截重复请求(即当前正在进行的相同请求)
    const requestData = getRequestIdentify(config, true);
    removePending(requestData, true);

    config.cancelToken = new CancelToken((c: any) => {
      Reflect.set(pending, requestData, c);
    });

    // 是否开启loading
    if (config.showLoading) {
      Toast.loading({
        duration: 0,
        mask: true,
        forbidClick: true,
        message: "加载中...",
        loadingType: "spinner",
      });
    }

    // 请求发送前的预处理(如:获取token等)
    // if (store.getters.token) {
    //   // let each request carry token
    //   // ['X-AUTH-TOKEN'] is a custom headers key
    //   // please modify it according to the actual situation
    //   config.headers['X-AUTH-TOKEN'] = getToken()
    // }
    return config;
  },
  (error: any) => {
    // do something with request error
    console.log(error); // for debug
    Toast.loading({
      message: "网络出错,请重试",
      duration: 1500,
      type: "fail",
    });
    return Promise.reject(error);
  }
);

// response interceptor
service.interceptors.response.use(
  (response: { config: AxiosRequestConfig; data: any }) => {
    // 把已经完成的请求从 pending 中移除
    const requestData = getRequestIdentify(response.config);
    removePending(requestData);

    if (response.config.showLoading) {
      Toast.clear();
    }

    const res = response.data;
    return res;
  },
  (error: {
    message: string;
    config: { showLoading: any };
    response: { status: any };
    request: any;
  }) => {
    console.log(error.message);
    if (error) {
      if (error.config && error.config.showLoading) {
        Toast.clear();
      }
      if (error.response) {
        switch (error.response.status) {
          case 400:
            error.message = "错误请求";
            break;
          case 401:
            error.message = "未授权,请重新登录";
            break;
          default:
            error.message = `连接错误${error.response.status}`;
        }
        const errData = {
          code: error.response.status,
          message: error.message,
        };
        console.log("统一错误处理: ", errData);
        Dialog({ title: "提示", message: errData.message || "Error" });
      } else if (error.request) {
        Toast.loading({
          message: "网络出错,请稍后重试",
          duration: 1500,
          type: "fail",
        });
      }
    }
    return Promise.reject(error);
  }
);

export default service;
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { 
  setToken, 
  getToken, 
  getRefreshToken,
  removeToken,
  removeConferenceToken,
  getCompany,
  accountKick,
  isAccountBeKicked
} from '@/utils/auth'
import {
  message
} from 'ant-design-vue';
import { loginApi } from './account';

const instance = axios.create({
  timeout: 60000,
});

const goLogin = ()=>{
    removeToken()
    removeConferenceToken()
}

instance.interceptors.request.use(function (config) {
  if(config.url.indexOf('/sms/send/code') > 0){
    //处理win端跳到忘记密码页面时,如果携带错误token会报错
    config.headers['Authorization'] = '';
  }else{
    config.headers['Authorization'] = getToken() ? `Bearer ${getToken()}` : '';
  }

  // console.error('全局请求拦截器 error', config);
  // 添加 token
  // 在发送请求之前做些什么
  if(config.url.indexOf('/class') > -1) { // class-pro 添加 remote-host 后端要求
    try {
      config.headers['remote-host'] = JSON.parse(getCompany())?.url
    } catch (error) {
      
    }
  }
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 响应拦截器
instance.interceptors.response.use(
  // 因为我们接口的数据都在res.data下,所以我们直接返回res.data
  (res) => {
    

    const code = res.data.code || res.status;
    
    if(code == 200 || code == 0){
      return res.data.data
    }else if( // token 过期
      (code == 15001)
      && (res as any).config.url.indexOf('login/oauth2/token') < 0
    ) {
      return refreshToken(res)

    }else if(code == 401 && !_checkAccountKick((res as any).config.url) || code == 402){ // token 重置 (用户重复登录)
      accountKick();
      return Promise.reject(401);
    }else if(code == 11011 && (res as any).config.url.indexOf('login/oauth2/token') > 0){
      return Promise.reject(res.data);
    }else{
      message.destroy();
      res.data && res.data.msg && message.warning(res.data.msg);
      return Promise.reject(res.data);
    }

  },
  (error: any) => {

    const status = error?.response?.status;
    console.error('全局响应拦截器 error', error);

    const errorJSON = error.toJSON();

    // 登录过期
    if(
      status == 401
      && errorJSON?.config?.url
      && errorJSON.config.url.indexOf('login/oauth2/token') < 0
    ) {

      return refreshToken(errorJSON)
    };

    if (error.message && error.message === 'Network Error') {
      if(isAccountBeKicked()) return;
      message.destroy();
      return message.error('网络连接异常,请检查后重试')
    }
    
    return Promise.reject(error);
  }, 
);

// 不包含互踢检测的API接口
let _checkAccountKick = (url: string)=> {
  let status = false;
  let unIncludeKickApi = [
    'login/oauth2/token',
    'ybshare-signal/share'
  ];

  for (let index = 0; index < unIncludeKickApi.length; index++) {
    const element = unIncludeKickApi[index];
    if(url.includes(element)){
      status = true;
      break
    }
  }

  return status
};

let isRefreshing = false, requests:any = [];
 
function refreshToken (res: AxiosResponse) {
  const config = res.config;
  
  if(!isRefreshing) {
    // 改变标记状态
    isRefreshing = true
    
    const params = {
      refresh_token: getRefreshToken(),
      "grant_type": "refresh_token"
    }

    return loginApi(params)
           .then((loginRes: any) => {

              const code = loginRes.code;
              if(loginRes.accessToken) {
                setToken(loginRes.accessToken)
                
                // 刷新完成 后执行存储的 请求
                setTimeout(() => {
                  requests.forEach((cb:any) => cb());
                  requests = [];
                },100)

                return instance(config)

              } else if(code == 401) {
                goLogin()
                console.error('刷新token失败了---') // TODO: 向上抛 异常 退出登录
                isRefreshing = false
                return Promise.reject(res.data)
              }

            })
            .catch(() => {
              goLogin()
              console.error('刷新token失败了---') // TODO: 向上抛 异常 退出登录
              isRefreshing = false
              return Promise.reject(res.data)
            })
            .finally(() => {
              // 请求回 token 处理完成 一定要改变标记状态 否则会一直重新请求 token 
              isRefreshing = false
            })
            
  } else {
    // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
    return new Promise(resolve => {
      requests.push(() => {
        config.headers['Authorization'] = getToken() ? `Bearer ${getToken()}` : '';
        resolve(instance(config))
      })
    })

  }
  

}
export default instance;
import axios from 'axios'
import qs from 'qs'



// create an axios instance
const Request = axios.create({
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 10000, // request timeout 10000
})

// request interceptor
Request.interceptors.request.use(
  config => {
    const conferenceToken = getConferenceToken()
    const channelToken = getChannelToken()

    let conferenceNo = ''
    const searchStr = window.location.search
    if (searchStr) {
      const parseQuery = qs.parse(searchStr, { ignoreQueryPrefix: true })
      conferenceNo = parseQuery.conferenceNo
    }

    // do something before request is sent
    config.headers['Accept-Language'] = getAcceptLanguage() // 多语言
    config.headers["Authorization"] = getToken() ? `Bearer ${getToken()}` : ''

    config.headers["deviceId"] = getUuid() || ''

    config.headers["X-Conference-Token"] = conferenceToken || ''
    config.headers["X-Channel-Token"] = channelToken || ''
    config.headers["X-Conference-No"] = conferenceNo || ''

    /**
     */
    const url = config.url
    if (url.indexOf('/middle-auth') > -1) {
      config.url = middleServerUrl + url
    } else {
      config.url = meetServerUrl + url
    }

    return config
  },
  error => {
    // do something with request error
    return Promise.reject(error)
  }
)


let isRefreshing = false
let subscribers = []

function onAccessTokenFetched(newToken) {
  subscribers.forEach((callback) => {
    callback(newToken)
  })
  subscribers = []
}

function addSubscriber(callback) {
  subscribers.push(callback)
}

// response interceptor
Request.interceptors.response.use(
  response => {
    const resData = response.data
    const { url, isBlobType } = response.config

    // 优化,没有token的情况
    if (resData.code === 401 && !url.includes('login/oauth2/token')) {

      // console.log('触发了401--------')

      if (!isRefreshing) {
        isRefreshing = true

        // 将刷新token的方法放在vuex中处理了, 可见下面区块代码
        oauthLogin({
          grant_type: 'refresh_token',
          client_id: getClientId(),
          client_secret: getClientSecret(),
          refresh_token: getRefreshToken()
        }).then((res) => {
          // console.log('restoken调用成功了----')
          // console.log(res)

          setToken(res.accessToken)
          onAccessTokenFetched(res.accessToken)
          isRefreshing = false
        }).catch(() => {
          // 刷新token报错了
          console.error('刷新token失败了---')
          isRefreshing = false
        })
      }


      // 将其他接口缓存起来 -- 这个Promise函数很关键
      const retryOriginalRequest = new Promise((resolve) => {
        // 这里是将其他接口缓存起来的关键, 返回Promise并且让其状态一直为等待状态, 
        // 只有当token刷新成功后, 就会调用通过addSubscriber函数添加的缓存接口, 
        // 此时, Promise的状态就会变成resolve
        addSubscriber((newToken) => {
          // console.log('即将重新发起请求---')

          response.config.headers.Authorization = `Bearer ${newToken}`;

          if (url.indexOf('/middle-auth') > -1) {
            response.config.url = response.config.url.replace(middleServerUrl, '')
          } else {
            response.config.url = response.config.url.replace(meetServerUrl, '')
          }
          
          // console.log(response.config)

          // 用重新封装的config去请求, 就会将重新请求后的返回
          resolve(Request(response.config))
        });
      });

      return retryOriginalRequest;
    }

    if (isBlobType) {
      return resData
    }

    if (resData.code === 200) { // 请求成功
      return resData.data
    } else { // 请求失败

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

export default Request

 

posted @ 2017-08-30 18:18  创业男生  阅读(545)  评论(0编辑  收藏  举报