import type { AxiosInstance, AxiosRequestConfig, CreateAxiosDefaults } from 'axios';
|
import axios from 'axios';
|
import { ElMessage } from 'element-plus';
|
import { NO_AUTH_API_LIST } from '../api/ai/chat';
|
import { LOGIN_URL, TEL_LOGIN_URL } from '../api/ai/user';
|
import { Logger } from '../model/logger/Logger';
|
import emitter from './mitt';
|
import { debounce, decodeFormData } from './util';
|
import { AUTH_URL, MAIN_URL, SECONDARY_URL } from '/@/constants';
|
import { Local, LoginInfo, Session } from '/@/utils/storage';
|
import { isSharePage } from '../stores/chatRoom';
|
// import JSONbig from 'json-bigint';
|
|
//#region ====================== 后端 res.err_code ======================
|
export const enum ErrorCode {
|
/** @description 权限验证失败 */
|
Message = 'MESSAGE',
|
/** @description 内部错误 */
|
Exception = 'EXCEPTION',
|
/** @description 无权使用 */
|
Auth = 'AUTH',
|
}
|
//#endregion
|
|
const emitNoAuth = () => {
|
emitter.emit('logout');
|
emitter.emit('openLoginDlg');
|
};
|
|
export const handleNormalAuth = () => {
|
const accessSession = Local.get(accessSessionKey);
|
if (!accessSession) {
|
emitter.emit('logout');
|
emitter.emit('openLoginDlg');
|
}
|
return !!accessSession;
|
};
|
|
export const handleNoAuth = debounce(() => {
|
emitNoAuth();
|
});
|
const initRequestInterceptor = (request: AxiosInstance) => {
|
// 添加请求拦截器
|
request.interceptors.request.use(
|
(config) => {
|
// 获取本地的 token
|
const accessSession = Local.get(accessSessionKey);
|
if (accessSession) {
|
// 将 token 添加到请求报文头中
|
config.headers['hswatersession'] = accessSession;
|
}
|
if (!NO_AUTH_API_LIST.includes(config.url) && !isSharePage.value) {
|
if (!accessSession && config.url !== LOGIN_URL && config.url !== TEL_LOGIN_URL) {
|
handleNoAuth(config.url);
|
throw '权限验证失败';
|
}
|
}
|
|
return config;
|
},
|
(error) => {
|
// 对请求错误做些什么
|
return Promise.reject(error);
|
}
|
);
|
|
// 添加响应拦截器
|
request.interceptors.response.use(
|
(res) => {
|
// 获取状态码和返回数据
|
const status = res.status;
|
const serveData = res.data;
|
if (!serveData) {
|
ElMessage.error('请求失败');
|
throw new Error('请求失败');
|
}
|
|
// 处理 401
|
if (status === 401) {
|
clearAccessTokens();
|
}
|
|
// 处理未进行规范化处理的
|
if (status >= 400) {
|
throw new Error(res.statusText || 'Request Error.');
|
}
|
|
// 响应拦截及自定义处理
|
if (!serveData.json_ok) {
|
switch (serveData?.err_code) {
|
case ErrorCode.Auth:
|
if (res.config.url !== LOGIN_URL && res.config.url !== TEL_LOGIN_URL && !isSharePage.value) {
|
handleNoAuth();
|
throw '权限验证失败';
|
}
|
break;
|
case ErrorCode.Exception:
|
const param = res.config.data ? `\n 请求参数:${JSON.stringify(decodeFormData(res.config.data))}\n` : '';
|
ElMessage.error('内部错误!');
|
Logger.error(`${res.config.url} 响应失败${param}`, serveData?.json_msg && new Error(serveData?.json_msg));
|
return res.data;
|
}
|
}
|
return res.data;
|
},
|
(error) => {
|
if (typeof error === 'string') {
|
// ElMessage.error(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 = (option: Partial<CreateAxiosDefaults<any>> = {}) => {
|
return axios.create({
|
baseURL: MAIN_URL,
|
timeout: 1200000,
|
headers: {
|
'Content-Type': 'application/x-www-form-urlencoded',
|
},
|
...option,
|
});
|
};
|
|
const service = createAxiosInstance();
|
export const mainRequest = service;
|
|
//#region ====================== 流响应数据 ======================
|
const streamInstance = createAxiosInstance({
|
adapter: 'fetch',
|
responseType: 'stream',
|
});
|
const decoder = new TextDecoder();
|
const readStream = async (stream: ReadableStream, cb: (value) => void): Promise<any> => {
|
const reader = stream.getReader();
|
let lastValue = '';
|
const p = new Promise(async (resolve, reject) => {
|
let fullValue = '';
|
while (1) {
|
const { done, value } = await reader.read();
|
if (done) {
|
break;
|
}
|
// const txt = decoder.decode(Uint8Array.from([...lastValue, ...value]));
|
const txt = decoder.decode(value);
|
const txtArr = txt.split('\n');
|
txtArr[0] = lastValue + txtArr[0];
|
txtArr.forEach((value, index, array) => {
|
// 一般不会出现连续换行,只可能最后一个是换行
|
if (index === array.length - 1) {
|
lastValue = value;
|
} else {
|
const decodeValue = decodeURIComponent(value);
|
fullValue += decodeValue;
|
cb(decodeValue);
|
}
|
});
|
}
|
resolve(fullValue);
|
});
|
return p;
|
};
|
|
export const streamReq = async (config: AxiosRequestConfig<any>, callback: (value) => void) => {
|
const response = await streamInstance(config);
|
const stream = response as unknown as ReadableStream;
|
return readStream(stream, (value) => {
|
const jsonValue = JSON.parse(value);
|
callback(jsonValue);
|
});
|
};
|
//#endregion
|
|
initRequestInterceptor(service);
|
initRequestInterceptor(streamInstance);
|
|
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;
|
};
|
|
export const domainPrefix = getDomainPrefix(window);
|
|
// token 键定义
|
export const sessionName = 'access-session';
|
export const userName = 'userName';
|
|
export const getSessionKey = (win: Window) => {
|
return sessionName;
|
};
|
export const getUserNameKey = (win: Window) => {
|
return userName;
|
};
|
|
export const accessSessionKey = getSessionKey(window);
|
export const userNameKey = getUserNameKey(window);
|
|
export const refreshAccessTokenKey = `x-${accessSessionKey}`;
|
|
// userInfo键定义
|
export const userInfoKey ='userInfo';
|
// 获取 token
|
export const getSession = () => {
|
return Local.get(accessSessionKey);
|
};
|
|
// 清除 token
|
export const clearAccessTokens = async () => {
|
// Local.remove(accessSessionKey);
|
LoginInfo.remove();
|
// 清除用户信息(每次刷新都需要利用用户信息去请求对应权限菜单)
|
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;
|