123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
- import { showFullScreenLoading, tryHideFullScreenLoading } from '@/components/Loading/fullScreen'
- import { LOGIN_URL } from '@/config'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { ResultData } from '@/api/interface'
- import { ResultEnum } from '@/enums/httpEnum'
- import { checkStatus } from './helper/checkStatus'
- import { AxiosCanceler } from './helper/axiosCancel'
- import { useUserStore } from '@/stores/modules/user'
- import router from '@/routers'
- import { getToken } from '@/utils/token'
- import { tansParams } from '@/utils/common'
- import { nanoid } from 'nanoid'
- import cache from '@/plugins/cache'
- import { encryptBase64, encryptWithAes, generateAesKey, decryptWithAes, decryptBase64 } from '@/utils/crypto'
- import { encrypt, decrypt } from '@/utils/jsEncrypt'
- export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
- loading?: boolean
- cancel?: boolean
- isEncrypt?: boolean
- }
- const config = {
- // 默认地址请求地址,可在 .env.** 文件中修改
- baseURL: import.meta.env.VITE_API_URL as string,
- // 设置超时时间
- timeout: ResultEnum.TIMEOUT as number,
- // 跨域时候允许携带凭证
- withCredentials: true
- }
- const encryptHeader = 'encrypt-key'
- // 是否显示重新登录
- export let isReLogin = { show: false }
- // export const globalHeaders = () => {
- // return {
- // Authorization: 'Bearer ' + getToken(),
- // clientid: import.meta.env.VITE_APP_CLIENT_ID
- // }
- // }
- axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
- axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID
- const axiosCanceler = new AxiosCanceler()
- class RequestHttp {
- service: AxiosInstance
- public constructor(config: AxiosRequestConfig) {
- // instantiation
- this.service = axios.create(config)
- /**
- * @description 请求拦截器
- * 客户端发送请求 -> [请求拦截器] -> 服务器
- * token校验(JWT) : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中
- */
- this.service.interceptors.request.use(
- (config: CustomAxiosRequestConfig) => {
- // 是否需要加密
- const isEncrypt = config.isEncrypt ?? (config.isEncrypt = false)
- // 重复请求不需要取消,在 api 服务中通过指定的第三个参数: { cancel: false } 来控制
- config.cancel ?? (config.cancel = true)
- config.cancel && axiosCanceler.addPending(config)
- // 当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { loading: false } 来控制
- config.loading ?? (config.loading = true)
- config.loading && showFullScreenLoading()
- // 是否需要设置 token
- const isToken = (config.headers || {}).isToken === false
- // 是否需要防止数据重复提交
- const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
- const requestId = nanoid()
- const userStore = useUserStore()
- if (getToken() && !isToken) {
- // 请求加上随机ID
- config.headers.set('RequestId', requestId)
- if (config.headers && typeof config.headers.set === 'function') {
- config.headers.set('Authorization', 'Bearer ' + userStore.token)
- }
- }
- // get请求映射params参数
- if (config.method === 'get' && config.params) {
- let url = config.url + '?' + tansParams(config.params)
- url = url.slice(0, -1)
- config.params = {}
- config.url = url
- }
- if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
- const requestObj = {
- url: config.url,
- data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
- time: new Date().getTime()
- }
- const sessionObj = cache.session.getJSON('sessionObj')
- if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
- cache.session.setJSON('sessionObj', requestObj)
- } else {
- const s_url = sessionObj.url // 请求地址
- const s_data = sessionObj.data // 请求数据
- const s_time = sessionObj.time // 请求时间
- const interval = 1000 // 间隔时间(ms),小于此时间视为重复提交
- if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
- const message = '数据正在处理,请勿重复提交'
- console.warn(`[${s_url}]: ` + message)
- return Promise.reject(new Error(message))
- } else {
- cache.session.setJSON('sessionObj', requestObj)
- }
- }
- }
- // 当开启参数加密
- if (isEncrypt && (config.method === 'post' || config.method === 'put')) {
- // 生成一个 AES 密钥
- const aesKey = generateAesKey()
- config.headers[encryptHeader] = encrypt(encryptBase64(aesKey))
- config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey)
- }
- return config
- },
- (error: AxiosError) => {
- console.log(error)
- return Promise.reject(error)
- }
- )
- /**
- * @description 响应拦截器
- * 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
- */
- this.service.interceptors.response.use(
- (response: AxiosResponse) => {
- // 加密后的 AES 秘钥
- const keyStr = response.headers[encryptHeader]
- // 加密
- if (keyStr != null && keyStr != '') {
- const data = response.data
- // 请求体 AES 解密
- const base64Str = decrypt(keyStr)
- // base64 解码 得到请求头的 AES 秘钥
- const aesKey = decryptBase64(base64Str.toString())
- // aesKey 解码 data
- const decryptData = decryptWithAes(data, aesKey)
- // 将结果 (得到的是 JSON 字符串) 转为 JSON
- response.data = JSON.parse(decryptData)
- }
- const { data, config, request } = response
- axiosCanceler.removePending(config)
- tryHideFullScreenLoading()
- if (request.responseType === 'blob' || request.responseType === 'arraybuffer') {
- return data
- }
- // 登录失效
- if (data.code == ResultEnum.OVERDUE) {
- const userStore = useUserStore()
- if (!isReLogin.show) {
- isReLogin.show = true
- ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
- confirmButtonText: '重新登录',
- cancelButtonText: '取消',
- type: 'warning'
- })
- .then(() => {
- isReLogin.show = false
- userStore.logOut().then(() => {
- router.replace(LOGIN_URL)
- })
- })
- .catch(() => {
- isReLogin.show = false
- })
- }
- return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
- }
- // 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
- if (data.code && data.code == ResultEnum.ERROR) {
- ElMessage.error(data.msg)
- return Promise.reject(data)
- }
- // 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑)
- return data
- },
- async (error: AxiosError) => {
- const { response } = error
- tryHideFullScreenLoading()
- // 请求超时 && 网络错误单独判断,没有 response
- if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时!请您稍后重试')
- if (error.message.indexOf('Network Error') !== -1) ElMessage.error('网络错误!请您稍后重试')
- // 根据服务器响应的错误状态码,做不同的处理
- if (response) checkStatus(response.status)
- // 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
- if (!window.navigator.onLine) router.replace('/500')
- return Promise.reject(error)
- }
- )
- }
- /**
- * @description 常用请求方法封装
- */
- get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
- return this.service.get(url, { params, ..._object })
- }
- post<T>(url: string, params?: object | string, _object = {}): Promise<ResultData<T>> {
- return this.service.post(url, params, _object)
- }
- put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
- return this.service.put(url, params, _object)
- }
- delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
- return this.service.delete(url, { params, ..._object })
- }
- downloadGet(url: string, params?: object, _object = {}): Promise<BlobPart> {
- return this.service.get(url, { ..._object, responseType: 'blob' })
- }
- downloadPost(url: string, params?: object, _object = {}): Promise<BlobPart> {
- return this.service.post(url, params, { ..._object, responseType: 'blob' })
- }
- }
- export default new RequestHttp(config)
|