// 服务层 , 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