index.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
  2. import { showFullScreenLoading, tryHideFullScreenLoading } from '@/components/Loading/fullScreen'
  3. import { LOGIN_URL } from '@/config'
  4. import { ElMessage, ElMessageBox } from 'element-plus'
  5. import { ResultData } from '@/api/interface'
  6. import { ResultEnum } from '@/enums/httpEnum'
  7. import { checkStatus } from './helper/checkStatus'
  8. import { AxiosCanceler } from './helper/axiosCancel'
  9. import { useUserStore } from '@/stores/modules/user'
  10. import router from '@/routers'
  11. import { getToken } from '@/utils/token'
  12. import { tansParams } from '@/utils/common'
  13. import { nanoid } from 'nanoid'
  14. import cache from '@/plugins/cache'
  15. import { encryptBase64, encryptWithAes, generateAesKey, decryptWithAes, decryptBase64 } from '@/utils/crypto'
  16. import { encrypt, decrypt } from '@/utils/jsEncrypt'
  17. export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
  18. loading?: boolean
  19. cancel?: boolean
  20. isEncrypt?: boolean
  21. }
  22. const config = {
  23. // 默认地址请求地址,可在 .env.** 文件中修改
  24. baseURL: import.meta.env.VITE_API_URL as string,
  25. // 设置超时时间
  26. timeout: ResultEnum.TIMEOUT as number,
  27. // 跨域时候允许携带凭证
  28. withCredentials: true
  29. }
  30. const encryptHeader = 'encrypt-key'
  31. // 是否显示重新登录
  32. export let isReLogin = { show: false }
  33. // export const globalHeaders = () => {
  34. // return {
  35. // Authorization: 'Bearer ' + getToken(),
  36. // clientid: import.meta.env.VITE_APP_CLIENT_ID
  37. // }
  38. // }
  39. axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
  40. axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID
  41. const axiosCanceler = new AxiosCanceler()
  42. class RequestHttp {
  43. service: AxiosInstance
  44. public constructor(config: AxiosRequestConfig) {
  45. // instantiation
  46. this.service = axios.create(config)
  47. /**
  48. * @description 请求拦截器
  49. * 客户端发送请求 -> [请求拦截器] -> 服务器
  50. * token校验(JWT) : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中
  51. */
  52. this.service.interceptors.request.use(
  53. (config: CustomAxiosRequestConfig) => {
  54. // 是否需要加密
  55. const isEncrypt = config.isEncrypt ?? (config.isEncrypt = false)
  56. // 重复请求不需要取消,在 api 服务中通过指定的第三个参数: { cancel: false } 来控制
  57. config.cancel ?? (config.cancel = true)
  58. config.cancel && axiosCanceler.addPending(config)
  59. // 当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { loading: false } 来控制
  60. config.loading ?? (config.loading = true)
  61. config.loading && showFullScreenLoading()
  62. // 是否需要设置 token
  63. const isToken = (config.headers || {}).isToken === false
  64. // 是否需要防止数据重复提交
  65. const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
  66. const requestId = nanoid()
  67. const userStore = useUserStore()
  68. if (getToken() && !isToken) {
  69. // 请求加上随机ID
  70. config.headers.set('RequestId', requestId)
  71. if (config.headers && typeof config.headers.set === 'function') {
  72. config.headers.set('Authorization', 'Bearer ' + userStore.token)
  73. }
  74. }
  75. // get请求映射params参数
  76. if (config.method === 'get' && config.params) {
  77. let url = config.url + '?' + tansParams(config.params)
  78. url = url.slice(0, -1)
  79. config.params = {}
  80. config.url = url
  81. }
  82. if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
  83. const requestObj = {
  84. url: config.url,
  85. data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
  86. time: new Date().getTime()
  87. }
  88. const sessionObj = cache.session.getJSON('sessionObj')
  89. if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
  90. cache.session.setJSON('sessionObj', requestObj)
  91. } else {
  92. const s_url = sessionObj.url // 请求地址
  93. const s_data = sessionObj.data // 请求数据
  94. const s_time = sessionObj.time // 请求时间
  95. const interval = 1000 // 间隔时间(ms),小于此时间视为重复提交
  96. if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
  97. const message = '数据正在处理,请勿重复提交'
  98. console.warn(`[${s_url}]: ` + message)
  99. return Promise.reject(new Error(message))
  100. } else {
  101. cache.session.setJSON('sessionObj', requestObj)
  102. }
  103. }
  104. }
  105. // 当开启参数加密
  106. if (isEncrypt && (config.method === 'post' || config.method === 'put')) {
  107. // 生成一个 AES 密钥
  108. const aesKey = generateAesKey()
  109. config.headers[encryptHeader] = encrypt(encryptBase64(aesKey))
  110. config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey)
  111. }
  112. return config
  113. },
  114. (error: AxiosError) => {
  115. console.log(error)
  116. return Promise.reject(error)
  117. }
  118. )
  119. /**
  120. * @description 响应拦截器
  121. * 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
  122. */
  123. this.service.interceptors.response.use(
  124. (response: AxiosResponse) => {
  125. // 加密后的 AES 秘钥
  126. const keyStr = response.headers[encryptHeader]
  127. // 加密
  128. if (keyStr != null && keyStr != '') {
  129. const data = response.data
  130. // 请求体 AES 解密
  131. const base64Str = decrypt(keyStr)
  132. // base64 解码 得到请求头的 AES 秘钥
  133. const aesKey = decryptBase64(base64Str.toString())
  134. // aesKey 解码 data
  135. const decryptData = decryptWithAes(data, aesKey)
  136. // 将结果 (得到的是 JSON 字符串) 转为 JSON
  137. response.data = JSON.parse(decryptData)
  138. }
  139. const { data, config, request } = response
  140. axiosCanceler.removePending(config)
  141. tryHideFullScreenLoading()
  142. if (request.responseType === 'blob' || request.responseType === 'arraybuffer') {
  143. return data
  144. }
  145. // 登录失效
  146. if (data.code == ResultEnum.OVERDUE) {
  147. const userStore = useUserStore()
  148. if (!isReLogin.show) {
  149. isReLogin.show = true
  150. ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
  151. confirmButtonText: '重新登录',
  152. cancelButtonText: '取消',
  153. type: 'warning'
  154. })
  155. .then(() => {
  156. isReLogin.show = false
  157. userStore.logOut().then(() => {
  158. router.replace(LOGIN_URL)
  159. })
  160. })
  161. .catch(() => {
  162. isReLogin.show = false
  163. })
  164. }
  165. return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
  166. }
  167. // 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
  168. if (data.code && data.code == ResultEnum.ERROR) {
  169. ElMessage.error(data.msg)
  170. return Promise.reject(data)
  171. }
  172. // 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑)
  173. return data
  174. },
  175. async (error: AxiosError) => {
  176. const { response } = error
  177. tryHideFullScreenLoading()
  178. // 请求超时 && 网络错误单独判断,没有 response
  179. if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时!请您稍后重试')
  180. if (error.message.indexOf('Network Error') !== -1) ElMessage.error('网络错误!请您稍后重试')
  181. // 根据服务器响应的错误状态码,做不同的处理
  182. if (response) checkStatus(response.status)
  183. // 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
  184. if (!window.navigator.onLine) router.replace('/500')
  185. return Promise.reject(error)
  186. }
  187. )
  188. }
  189. /**
  190. * @description 常用请求方法封装
  191. */
  192. get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
  193. return this.service.get(url, { params, ..._object })
  194. }
  195. post<T>(url: string, params?: object | string, _object = {}): Promise<ResultData<T>> {
  196. return this.service.post(url, params, _object)
  197. }
  198. put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
  199. return this.service.put(url, params, _object)
  200. }
  201. delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
  202. return this.service.delete(url, { params, ..._object })
  203. }
  204. downloadGet(url: string, params?: object, _object = {}): Promise<BlobPart> {
  205. return this.service.get(url, { ..._object, responseType: 'blob' })
  206. }
  207. downloadPost(url: string, params?: object, _object = {}): Promise<BlobPart> {
  208. return this.service.post(url, params, { ..._object, responseType: 'blob' })
  209. }
  210. }
  211. export default new RequestHttp(config)