import type { AxiosInstance, AxiosRequestConfig } from 'axios';
|
import axios from 'axios';
|
import { ElLoadingService, ElMessage } from 'element-plus';
|
import router from '../router';
|
import { debounce } from './util';
|
import { AUTH_URL, MAIN_URL, SECONDARY_URL } from '/@/constants';
|
import { Local, Session } from '/@/utils/storage';
|
// import JSONbig from 'json-bigint';
|
export type ExtraConfig = {
|
loading?: boolean;
|
noAuth?: boolean;
|
handleFail?: boolean;
|
};
|
export const checkAuth = () => {
|
const session = Local.get(accessSessionKey);
|
if (!session) {
|
// 既然用户需要重新进行登录,就需要把之前用户存储的信息(过期的 token) 进行清除
|
clearAccessTokens();
|
router.push({
|
name: 'login',
|
});
|
}
|
return !!session;
|
};
|
|
//#region ====================== 后端 res.err_code ======================
|
export const enum ErrorCode {
|
/** @description 权限验证失败 */
|
Message = 'MESSAGE',
|
/** @description 内部错误 */
|
Exception = 'EXCEPTION',
|
/** @description 无权使用 */
|
Auth = 'AUTH',
|
}
|
//#endregion
|
export const handleNoAuth = debounce(() => {
|
clearAccessTokens();
|
window.location.reload();
|
});
|
|
let requestNum = 0;
|
let loadingInstance: ReturnType<typeof ElLoadingService>;
|
const addLoading = () => {
|
// 增加loading 如果pending请求数量等于1,弹出loading, 防止重复弹出
|
requestNum++;
|
if (requestNum == 1) {
|
loadingInstance = ElLoadingService({
|
text: '加载中...',
|
target: '.layout-parent',
|
});
|
}
|
};
|
|
const cancelLoading = () => {
|
// 取消loading 如果pending请求数量等于0,关闭loading
|
requestNum--;
|
if (requestNum === 0) loadingInstance?.close();
|
};
|
|
const initRequestInterceptor = (request: AxiosInstance) => {
|
// 添加请求拦截器
|
request.interceptors.request.use(
|
(config) => {
|
// 获取本地的 token
|
const accessSession = Local.get(accessSessionKey);
|
const { loading = true, noAuth = false, handleFail = true } = config as ExtraConfig;
|
if (loading) {
|
addLoading();
|
}
|
if (accessSession) {
|
// 将 token 添加到请求报文头中
|
config.headers['hswatersession'] = accessSession;
|
}
|
if (!noAuth) {
|
if (!accessSession ) {
|
handleNoAuth();
|
}
|
}
|
|
return config;
|
},
|
(error) => {
|
// 直接关闭 loading
|
loadingInstance?.close();
|
// 对请求错误做些什么
|
return Promise.reject(error);
|
}
|
);
|
|
// 添加响应拦截器
|
request.interceptors.response.use(
|
(res) => {
|
// 获取状态码和返回数据
|
const status = res.status;
|
const serveData = res.data;
|
const { loading = true, noAuth = false, handleFail = true } = res.config as ExtraConfig;
|
if (loading) cancelLoading();
|
|
if (!serveData) {
|
ElMessage.error('请求失败');
|
throw new Error('请求失败');
|
}
|
|
// 处理 401
|
if (status === 401) {
|
clearAccessTokens();
|
}
|
|
// 响应拦截及自定义处理
|
if (!serveData.json_ok) {
|
switch (serveData?.err_code) {
|
case ErrorCode.Auth:
|
handleNoAuth();
|
throw '权限验证失败';
|
break;
|
case ErrorCode.Exception:
|
ElMessage.error('内部错误!');
|
throw '内部错误';
|
}
|
// 非 message error,且 handleFail 为 true
|
// message error 不需要处理
|
if (handleFail && serveData?.err_code !== ErrorCode.Message) {
|
const errorText = serveData?.json_msg || '响应失败!';
|
ElMessage.error(errorText);
|
throw errorText;
|
}
|
}
|
return res.data;
|
},
|
(error) => {
|
// 直接关闭 loading
|
loadingInstance?.close();
|
if (typeof error === 'string') {
|
// showFailToast(error);
|
return Promise.reject(error);
|
}
|
// 处理响应错误
|
if (error.response) {
|
if (error.response.status === 401) {
|
clearAccessTokens();
|
}
|
}
|
|
// 对响应错误做点什么
|
if (error.message.indexOf('timeout') != -1) {
|
ElMessage.error('网络超时');
|
} else if (error.message == 'Network Error') {
|
ElMessage.error('网络连接错误');
|
} else {
|
if (error.response.data) ElMessage.error(error.response.statusText);
|
else ElMessage.error('接口路径找不到');
|
}
|
|
return Promise.reject(error);
|
}
|
);
|
};
|
// 配置新建一个 axios 实例
|
const createAxiosInstance = () => {
|
return axios.create({
|
baseURL: MAIN_URL,
|
timeout: 50000,
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
});
|
};
|
const service = createAxiosInstance();
|
|
export const mainRequest = service;
|
|
initRequestInterceptor(service);
|
|
export function secondaryRequest(config: AxiosRequestConfig<any>) {
|
return service({
|
...config,
|
baseURL: SECONDARY_URL,
|
});
|
}
|
|
/**
|
* 用于访问登录接口
|
*/
|
export function authRequest(config: AxiosRequestConfig<any>) {
|
return service({
|
...config,
|
baseURL: AUTH_URL,
|
});
|
}
|
|
/**
|
* @description 域名前缀
|
* 防止类似于 http://sqi.beng35.com/airp 和 http://sqi.beng35.com/test 公用同一个 token 或 userInfo
|
*/
|
export const getDomainPrefix = (win: Window = window) => {
|
const subDomainName = win.location.pathname
|
.split('/')
|
.filter((item) => !!item)
|
.join('-');
|
const domainPrefix = subDomainName ? `${subDomainName}-` : '';
|
return domainPrefix;
|
};
|
|
// token 键定义
|
const sessionName = 'access-session';
|
const userInfoName = 'userInfo';
|
export type UserInfo = {
|
userName: string;
|
phoneNumber?: string;
|
};
|
|
const getDomainKey = (suffix: string) => getDomainPrefix() + suffix;
|
|
export const accessSessionKey = getDomainKey(sessionName);
|
export const userInfoKey = getDomainKey(userInfoName);
|
|
export const refreshAccessTokenKey = `x-${accessSessionKey}`;
|
|
// 获取 token
|
export const getSession = () => {
|
return Local.get(accessSessionKey);
|
};
|
|
export const getUserInfo = () => {
|
return Local.get(userInfoKey) as UserInfo;
|
};
|
|
// 清除 token
|
export const clearAccessTokens = async () => {
|
Local.remove(accessSessionKey);
|
Local.remove(refreshAccessTokenKey);
|
// 清除用户信息(每次刷新都需要利用用户信息去请求对应权限菜单)
|
Local.remove(userInfoKey);
|
// 清除其他
|
Session.clear();
|
};
|
|
// axios 默认实例
|
export const axiosInstance: AxiosInstance = axios;
|
|
/**
|
* 参数处理
|
* @param {*} params 参数
|
*/
|
export function tansParams(params: any) {
|
let result = '';
|
for (const propName of Object.keys(params)) {
|
const value = params[propName];
|
const part = encodeURIComponent(propName) + '=';
|
if (value !== null && value !== '' && typeof value !== 'undefined') {
|
if (typeof value === 'object') {
|
for (const key of Object.keys(value)) {
|
if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
|
const params = propName + '[' + key + ']';
|
const subPart = encodeURIComponent(params) + '=';
|
result += subPart + encodeURIComponent(value[key]) + '&';
|
}
|
}
|
} else {
|
result += part + encodeURIComponent(value) + '&';
|
}
|
}
|
}
|
return result;
|
}
|
|
/**
|
* 解密 JWT token 的信息
|
* @param token jwt token 字符串
|
* @returns <any>object
|
*/
|
export function decryptJWT(token: string): any {
|
token = token.replace(/_/g, '/').replace(/-/g, '+');
|
const json = decodeURIComponent(escape(window.atob(token.split('.')[1])));
|
return JSON.parse(json);
|
}
|
|
/**
|
* 将 JWT 时间戳转换成 Date
|
* @description 主要针对 `exp`,`iat`,`nbf`
|
* @param timestamp 时间戳
|
* @returns Date 对象
|
*/
|
export function getJWTDate(timestamp: number): Date {
|
return new Date(timestamp * 1000);
|
}
|
|
// 导出 axios 实例
|
export default service;
|