import axios, { AxiosRequestConfig, AxiosResponse } from 'axios' import { CustomConfigType } from './types' import { customConfigDefault } from './config' import { emptyObj, handleErr, handleLoading, handleRepeat, addPendingRequest, removePendingRequest, setLruCache, getLruCache } from './utils/index' import MD5 from 'md5' let instance let cacheSetting let cacheJson let delArr = ['user_req_id'] let setTime = 24*60*60*1000 let setWebTime = 24*60*60*1000 /***************************************************** 传递axios 中间件拦截 目前用于web端 *********************************************/ // 请求拦截去重 export const webRequestInterceptor = async () => { try { cacheJson = await fetchWebCacheJson() } catch (error) { console.log(error) } } const isJsonFn = (str) => { if (isNaN(Number(str))) { try { JSON.parse(str); return true; } catch (e) { return false; } } return false; } const cacheHandler = (apiMiddlewareContext:any , cacheFile: any, flag:boolean = true) => { try { // 接口Url const url = apiMiddlewareContext.config.url const cacheList = cacheFile?.cacheList.filter(ele=> url.indexOf(ele.cacheUrl) > 0 && url.split('/')[url.split('/').length-1] == ele.cacheUrl.split('/')[ele.cacheUrl.split('/').length-1]) const config = apiMiddlewareContext.config || {} if(cacheList?.length){ const cacheItem = cacheList[0] let tokenStr = apiMiddlewareContext.config?.headers?.Authorization || '' // 参数Data let paramItem:any = {} if(config.method=='get'){ if(config.params){ paramItem = config.params || {} const isJson = isJsonFn(paramItem) if(isJson){ paramItem = JSON.parse(paramItem) } delArr.forEach(ele=>{ if(paramItem && Object.keys(paramItem).length){ if(paramItem[ele]){ delete paramItem[ele] } } }) }else{ if(url.indexOf('?')>0){ let paramUrlObj:any = queryParse(url) || {} delArr.forEach(ele=>{ if(paramUrlObj && Object.keys(paramUrlObj).length){ if(paramUrlObj[ele]){ delete paramUrlObj[ele] } } }) paramItem = paramUrlObj } } }else{ paramItem = config.data || {} const isJson = isJsonFn(paramItem) if(isJson){ paramItem = JSON.parse(paramItem) } if(Array.isArray(paramItem)){ paramItem.forEach(ele=>{ Object.keys(ele).forEach(key=>{ if(delArr.includes(key)){ delete ele[key] } }) }) }else{ delArr.forEach(ele=>{ if(Object.keys(paramItem).length){ if(paramItem[ele]){ delete paramItem[ele] } } }) } } const paramStr = JSON.stringify(paramItem) const urlHref = window.location.href?.split('?')[0] || '' if(tokenStr.length > 16){ tokenStr = tokenStr.slice(tokenStr.length-16, tokenStr.length) } // 帐套信息 方法 路由 参数 token 后16位 const requestKey = `${urlHref}_${config.method}_${cacheItem.cacheUrl}_${paramStr}_${tokenStr}` const abstract = MD5(requestKey) if(flag){ // 网络检查 if (!window.navigator.onLine) { return null } // 处理重复请求并记录 if (cacheFile.isNeedFullRepeat && handleRepeat(requestKey)){ return {isRepeat: true} } // 不走缓存 if(cacheItem?.notNeedCache){ // oss配置参数 return null } if(Array.isArray(paramItem)){ // 接口属性参数 const list = paramItem.filter(ele=> ele?.flushCache) if(list.length){ return null } }else{ if(paramItem?.flushCache){ return null } } if(cacheItem.cacheUrl.indexOf('/graphql') > -1){ // GQL属性参数 if(!paramItem?.variables?.graphqlQueryCache){ return null } } } // 走缓存 return {abstract , cacheItem, requestKey} } } catch (error) { return '' } } // 获取缓存 export const fetchCache = async (apiMiddlewareContext:any) => { try { if(apiMiddlewareContext && Object.keys(apiMiddlewareContext)?.length){ if(Array.isArray(cacheJson?.result?.cacheList) && cacheJson?.result?.cacheList?.length){ const tempItem:any = cacheHandler(apiMiddlewareContext, cacheJson.result) if(tempItem?.abstract){ // 获取缓存值 const cacheRes = getLruCache(tempItem.abstract) if(cacheRes){ const cacheTime = cacheRes.time const curTime = Date.now() if((curTime > cacheTime+Number(tempItem.cacheItem.cacheTime)*1000)){ return null }else{ if(tempItem?.requestKey){ // 处理重复请求 handleRepeat(tempItem.requestKey, false) } return cacheRes } }else{ return null } }else if(tempItem?.isRepeat){ //重复请求 return {isRepeat: true} } return null } } } catch (error) { return '' } } // 设置缓存 export const setCache = async (apiMiddlewareContext:any) => { try { if(apiMiddlewareContext && Object.keys(apiMiddlewareContext)?.length){ if(Array.isArray(cacheJson?.result?.cacheList) && cacheJson?.result?.cacheList?.length){ const tempItem:any = cacheHandler(apiMiddlewareContext, cacheJson.result, false) if(tempItem?.abstract){ const { data, status, statusText } = apiMiddlewareContext.response if(status > -1 && status < 300){ const caches = { time: Date.now(), data, status, statusText } // 设置缓存 setLruCache(tempItem.abstract, caches) } } if(tempItem?.requestKey){ // 处理重复请求 handleRepeat(tempItem.requestKey, false) } } } } catch (error) { return '' } } // 获取缓存值 const fetchWebCacheJson = async () => { // 获取缓存值 const cacheRes = getLruCache('cacheWebConfig') if(cacheRes){ const cacheTime = cacheRes.time const curTime = Date.now() if(curTime > cacheTime+setWebTime){ // 15分钟缓存失效 const res = await fetchWebOssOption() return res } else { return cacheRes } }else{ const res = await fetchWebOssOption() return res } } const fetchWebOssOption = async () => { try { let result:any = { } const json = await fetch("https://mallsource.static.chanjet.com/WebCacheFile.json?"+Math.random()).then(res => res.text()) result = JSON.parse(json) setWebTime = result?.jsonCacheTime || setWebTime const caches = { time: Date.now(), result } setLruCache('cacheWebConfig', caches) return caches } catch (error) { return '' } } /******************************************************** 提供axios 实列 目前用于h5移动端 *********************************************/ const h5CacheHandler = (apiMiddlewareContext:any , h5CacheJson: any, delList?: string[]) => { try { // 接口Url const url = apiMiddlewareContext.url || apiMiddlewareContext?.config?.url const cacheList = h5CacheJson.cacheList.filter(ele=> url.indexOf(ele.cacheUrl) > 0 && url.split('/')[url.split('/').length-1] == ele.cacheUrl.split('/')[ele.cacheUrl.split('/').length-1]) const config = apiMiddlewareContext || apiMiddlewareContext.config || {} if(delList?.length){ delArr = delList } if(cacheList?.length){ const cacheItem = cacheList[0] let tokenStr = apiMiddlewareContext?.headers?.Authorization || '' let paramItem:any = {} if(config.method=='get'){ if(config.params){ paramItem = config.params || {} const isJson = isJsonFn(paramItem) if(isJson){ paramItem = JSON.parse(paramItem) } delArr.forEach(ele=>{ if(paramItem && Object.keys(paramItem).length){ if(paramItem[ele]){ delete paramItem[ele] } } }) }else{ if(url.indexOf('?')>0){ let paramUrlObj:any = queryParse(url) || {} delArr.forEach(ele=>{ if(paramUrlObj && Object.keys(paramUrlObj).length){ if(paramUrlObj[ele]){ delete paramUrlObj[ele] } } }) paramItem = paramUrlObj } } }else{ paramItem = config.data || {} const isJson = isJsonFn(paramItem) if(isJson){ paramItem = JSON.parse(paramItem) } if(Array.isArray(paramItem)){ paramItem.forEach(ele=>{ Object.keys(ele).forEach(key=>{ if(delArr.includes(key)){ delete ele[key] } }) }) }else{ delArr.forEach(ele=>{ if(Object.keys(paramItem).length){ if(paramItem[ele]){ delete paramItem[ele] } } }) } } const paramStr = JSON.stringify(paramItem) const urlHref = window.location.href?.split('?')[0] || '' if(tokenStr.length > 16){ tokenStr = tokenStr.slice(tokenStr.length-16, tokenStr.length) } // 路由 地址 方法 参数 const requestKey = `${urlHref}_${config.method}_${cacheItem.cacheUrl}_${paramStr}_${tokenStr}` const abstract = MD5(requestKey) // 不走缓存 if(cacheItem?.notNeedCache){ // oss配置参数 return null } if(Array.isArray(paramItem)){ // 接口属性参数 const list = paramItem.filter(ele=> ele?.flushCache) if(list.length){ return null } }else{ if(paramItem?.flushCache){ return null } } if(cacheItem.cacheUrl.indexOf('/graphql') > -1){ // GQL属性参数 if(!paramItem?.variables?.graphqlQueryCache){ return null } } // 走缓存 return {abstract , cacheItem} } } catch (error) { return '' } } // 请求拦截去重 export const requestInterceptor = async (axiosInstance, delList?: string[]) => { cacheSetting = await fetchCacheJson() // 获取缓存值 axiosInstance.interceptors.request.use( async (config) => { if(cacheSetting && Object.keys(cacheSetting)?.length){ const cacheResult = cacheSetting?.result if(cacheResult.isNeedFullRepeat){ // 全局去重 removePendingRequest(config, cacheResult, delList); addPendingRequest(config, cacheResult, delList); } if(cacheResult.isNeedFullCache){ // 读取缓存 const tempItem:any = h5CacheHandler(config, cacheResult, delList) if(tempItem?.abstract){ // 获取缓存值 let cacheRes = getLruCache(tempItem.abstract) if(cacheRes){ const cacheData = JSON.parse(cacheRes) const cacheTime = cacheData.time const curTime = Date.now() if(curTime <= cacheTime+Number(tempItem.cacheItem.cacheTime)*1000){ config.cancelToken = new axios.CancelToken(cancel => { cancel(cacheData) }) } } } } } // 请求缓存 return config }, (error) => { return Promise.reject(error) } ) // 响应缓存 axiosInstance.interceptors.response.use( (response) => { if(cacheSetting && Object.keys(cacheSetting)?.length){ const cacheResult = cacheSetting?.result if(cacheResult.isNeedFullRepeat){ removePendingRequest(response.config, cacheResult, delList); } if(cacheResult.isNeedFullCache){ // 设置缓存 const tempItem:any = h5CacheHandler(response.config, cacheResult, delList) if(tempItem?.abstract){ const { data, status, statusText } = response if(status > -1 && status < 300){ const caches = { time: Date.now(), data, status, statusText } // 设置缓存 setLruCache(tempItem.abstract, JSON.stringify(caches)) } } } } return response }, (error) => { const config = error.config; if(config){ removePendingRequest(config, cacheSetting.result, delList); } // 请求缓存处理方式 if (axios.isCancel(error) && cacheSetting.result && error.message) { const cancle = error.message if(cancle){ return Promise.resolve(cancle) // 返回结果数据 } } return Promise.reject(error) } ) } const fetchCacheJson = async () => { // 获取缓存值 const cacheRes = getLruCache('cacheConfig') if(cacheRes){ const cacheTime = cacheRes.time const curTime = Date.now() if(curTime > cacheTime+setTime){ // 60分钟缓存失效 const res = await fetchOssOption() return res } else { return cacheRes } }else{ const res = await fetchOssOption() return res } } const fetchOssOption = async () => { try { let result:any = { } const json = await fetch("https://mallsource.static.chanjet.com/CacheFile.json?"+Math.random()).then(res => res.text()) result = JSON.parse(json) setTime = result?.jsonCacheTime || setTime const caches = { time: Date.now(), result } setLruCache('cacheConfig', caches) return caches } catch (error) { return '' } } /**************************************************** 暴露和axios 一样的方法 需要替换引用此库axios 目前未用于实战 *********************************************/ export const interfaceType = axios export const create = (config: AxiosRequestConfig, delList?: string[]) => { // 初始化 instance = axios.create(config) if(delList){ delArr = delList } return instance } /** * 设置axios头的accessToken */ export const setAccessToken = (accessToken) => { instance.defaults.headers.accessToken = accessToken; axios.defaults.headers.common['accessToken'] = accessToken; }; export const setPassPort = (passport) => { instance.defaults.headers.Authorization = 'Bearer ' + passport; axios.defaults.headers.common['Authorization'] = 'Bearer ' + passport; }; // 后端通过这个响应头来确定是否允许用户登录或调用api export const setRequestAuthAppId = (RequestAuthAppId) => { instance.defaults.headers['request-auth-app-id'] = RequestAuthAppId axios.defaults.headers.common['request-auth-app-id'] = RequestAuthAppId } // 解析 const queryParse = query => { query = query.substring(query.indexOf('?') + 1) let arr = query.split('&') let params = {} arr.forEach(item => { let key = item.substring(0, item.indexOf('=')) let val = item.substring(item.indexOf('=') + 1) params[key] = val }) return params } // 封装请求H5 const _request = async (config: AxiosRequestConfig, interceptor?: any): Promise => { return new Promise((resolve, reject) => { instance(config) .then((resp) => { resolve(resp); }) .catch((err) => { if (interceptor) { interceptor(err); reject(err); return; } else { reject(err); } }); }); } const requestHttp = async (config: AxiosRequestConfig, customConfig?: CustomConfigType) => { const _customConfig = { ...customConfigDefault, ...customConfig, } // 格式化 url let headUrl = config.url?.split('?')[0] let paramUrl:any = queryParse(config.url) || {} delArr.forEach(ele=>{ if(paramUrl && Object.keys(paramUrl).length){ if(paramUrl[ele]){ delete paramUrl[ele] } } }) paramUrl = JSON.stringify(paramUrl) // 参数Data let paramItem:any = config.data || {} delArr.forEach(ele=>{ if(paramItem && Object.keys(paramItem).length){ if(paramItem[ele]){ delete paramItem[ele] } } }) paramItem = JSON.stringify(paramItem) // 路由 地址 方法 参数 const requestKey = `${headUrl}_${config.method}_${paramUrl}_${paramItem}` const abstract = MD5(requestKey) // 网络检查 if (!window.navigator.onLine) { return null } // 处理重复请求并记录 if (handleRepeat(requestKey)){ return null } const request = async () => { try { // 请求发起 const res:any = await _request(config) const { data, status, statusText } = res const caches = { time: Date.now(), data, status, statusText } // 设置缓存 if (_customConfig.isNeedCache) { setLruCache(abstract, caches) } return res } catch (error:any) { const { response: { status = 0 } = {} } = error // 处理重复请求 handleRepeat(requestKey, false) // 处理错误 handleError(_customConfig, error) // 抛出错误 return Promise.reject(error) } finally { handleAfterRequest(_customConfig, requestKey) } } if (_customConfig.isNeedCache) { try { const cacheRes = getLruCache(abstract) if(cacheRes){ const cacheTime = cacheRes.time const curTime = Date.now() if(curTime > cacheTime+Number(_customConfig.cacheTime)*1000){ console.log('1不走缓存') return request() }else{ console.log('走缓存') return cacheRes } }else{ console.log('2不走缓存') return request() } } catch (error) { return Promise.reject(error) } } else { return request() } } const handleAfterRequest = ( customConfig: CustomConfigType, requestKey: string ) => { const { isNeedLoading = false, showLoadingFn, delayLoading } = customConfig // 处理重复请求 handleRepeat(requestKey, false) // 处理 Loading isNeedLoading && handleLoading(false, requestKey, delayLoading, showLoadingFn) } const handleError = (customConfig: CustomConfigType, error) => { const { isNeedError = false, showErrorFn } = customConfig // 处理错误 if (isNeedError) handleErr(error, showErrorFn) } export const httGet = async (config: AxiosRequestConfig = emptyObj(), customConfig: CustomConfigType = emptyObj()) => { return requestHttp({ ...config, method: 'get' }, customConfig) } export const httPost = async (config: AxiosRequestConfig = emptyObj(), customConfig: CustomConfigType = emptyObj()) => { return requestHttp({ ...config, method: 'post' }, customConfig) } export const httPut = async (config: AxiosRequestConfig = emptyObj(), customConfig: CustomConfigType = emptyObj()) => { return requestHttp({ ...config, method: 'put' }, customConfig) } export const requestCom = async (config: AxiosRequestConfig = emptyObj(), customConfig: CustomConfigType = emptyObj()) => { return requestHttp({ ...config }, customConfig) } const CjetAxios = { create, httGet, httPost, httPut, requestHttp, requestCom, fetchCache, setCache, requestInterceptor, webRequestInterceptor } export default CjetAxios