request.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /**
  2. * 和服务端进行交互
  3. * 它封装了全局 request拦截器、respone拦截器、统一的错误处理、统一做了超时,baseURL设置等
  4. * <pre>
  5. * 作者:hugh zhuang
  6. * 邮箱:3378340995@qq.com
  7. * 日期:2015-11-02-下午3:29:34
  8. * 版权:广州流辰信息技术有限公司
  9. * </pre>
  10. */
  11. import axios from 'axios'
  12. import { Message, MessageBox } from 'element-ui'
  13. import store from '@/store'
  14. import router from '@/router'
  15. // Internationalization 国际化
  16. import I18n from '@/utils/i18n'
  17. import Utils from '@/utils/util'
  18. import Ids from 'ids'
  19. import setting from '@/setting.js'
  20. import { encryptByAes } from '@/utils/encrypt'
  21. // 验权
  22. import { getToken, updateToken, removeRefreshToken } from '@/utils/auth'
  23. import { refreshAccessToken } from '@/api/oauth2/user'
  24. import { showFullScreenLoading, tryHideFullScreenLoading } from './loading'
  25. import requestState from '@/constants/state'
  26. import { BASE_API, BASE_GATEWAY_API } from '@/api/baseUrl'
  27. import { HEADER_TOKEN_KEY, HEADER_SYSTEM_ID, HEADER_TENANT_ID, MULTIPLE_DOMAIN, API_DOMAIN_NAMES, ENCRYPT_GET_PARAMS } from '@/constant'
  28. // 请求超时(timeout)时间
  29. const TIMEOUT = setting.requestTimeout
  30. /**
  31. * 创建axios实例
  32. */
  33. const service = axios.create({
  34. // request timeout
  35. timeout: TIMEOUT,
  36. // 跨域安全策略
  37. withCredentials: true,
  38. headers: {
  39. 'Cache-Control': 'no-cache',
  40. 'Content-Type': 'application/json;charset=utf-8'
  41. }
  42. })
  43. // 是否正在刷新的标记
  44. let isRefreshing = false
  45. // 重试队列,每一项将是一个待执行的函数形式
  46. let requests = []
  47. // 取消请求
  48. let cancelRequest = false
  49. let requestCount = 0
  50. /**
  51. * 请求(request)拦截器
  52. *
  53. * get请求,统一参数放在params里面,后台对应只有@RequestParam
  54. * `params`是即将与请求一起发送的 URL 参数,必须是一个无格式对象(plain object)或 URLSearchParams 对象
  55. *
  56. * post请求,统一参数放在data里面——json格式,后台对应@RequestBody ,其他 后台对应@RequestParam
  57. * `data` 是作为请求主体被发送的数据
  58. * 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
  59. * 在没有设置 `transformRequest` 时,必须是以下类型之一:
  60. * - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  61. * - 浏览器专属:FormData, File, Blob
  62. * - Node 专属: Stream
  63. * post 请求 `params` 这个同get 但要注意 后台对应@RequestParam 请求的`Content-Type`是 application/x-www-form-urlencoded 用 qs.stringify 去构造数据
  64. */
  65. service.interceptors.request.use(async config => {
  66. if (!config.baseURL) {
  67. config.baseURL = BASE_API(parseInt(requestCount / 5, 10))
  68. if (MULTIPLE_DOMAIN) {
  69. requestCount >= ((API_DOMAIN_NAMES.length - 1) * 5) ? requestCount = 0 : requestCount++
  70. }
  71. }
  72. if (config.gateway) {
  73. config.baseURL = BASE_GATEWAY_API()
  74. }
  75. config.isLoading = config.isLoading !== undefined && config.isLoading !== null ? config.isLoading : false
  76. if (config.isLoading) {
  77. showFullScreenLoading(config.loading)
  78. }
  79. // 如果超时不对还原默认超时时间
  80. if (config.timeout !== TIMEOUT) {
  81. config.timeout = TIMEOUT
  82. }
  83. // 只设置当前的时间设置以设置为准
  84. if (Utils.isNotEmpty(config.overtime)) {
  85. config.timeout = config.overtime
  86. }
  87. // 防止缓存,参数加密
  88. if (config.method.toUpperCase() === 'GET') {
  89. if (ENCRYPT_GET_PARAMS) {
  90. config.params = {
  91. _p: Utils.isNotEmpty(config.params) ? encryptByAes(JSON.stringify(config.params), 'get') : undefined,
  92. _t: new Ids([32, 36, 1]).next()
  93. }
  94. } else {
  95. config.params = {
  96. ...config.params,
  97. _t: new Ids([32, 36, 1]).next()
  98. }
  99. }
  100. }
  101. // 判断是否需要token
  102. if (setting.whiteRequestList.indexOf(config.url) !== -1) {
  103. return config
  104. }
  105. config.headers[HEADER_TOKEN_KEY] = getToken()
  106. // 系统ID
  107. if (store && store.getters.systemid) {
  108. config.headers[HEADER_SYSTEM_ID] = store.getters.systemid
  109. }
  110. // 租户ID
  111. if (store && store.getters.tenantid) {
  112. config.headers[HEADER_TENANT_ID] = store.getters.designTenantid ? store.getters.designTenantid : store.getters.tenantid
  113. }
  114. // 租户模式
  115. if (store && store.getters.isTenantOpen) {
  116. config.headers[HEADER_TENANT_ID] = store.getters.designTenantid ? store.getters.designTenantid : store.getters.tenantid
  117. }
  118. return config
  119. }, error => {
  120. tryHideFullScreenLoading()
  121. // Do something with request error
  122. // for debug
  123. Utils.log.error('request' + error)
  124. Promise.reject(error)
  125. })
  126. /**
  127. * 响应(respone)拦截器
  128. */
  129. service.interceptors.response.use(response => {
  130. // 临时关闭了保存等待的取消功能
  131. tryHideFullScreenLoading()
  132. const dataAxios = response.data
  133. const { state, message, cause } = dataAxios
  134. // 获取接口保留参数,用于部分接口的后续请求
  135. const { retainData } = response.config
  136. if (response.config.responseType === 'arraybuffer') {
  137. // 刷新tonken
  138. return response
  139. }
  140. // 如果没有 state 代表这不是项目后端开发的接口 比如可能是请求最新版本,或者是请求的数据,或者是
  141. if (state === undefined) {
  142. const msg = '接口异常,没有返回[state]参数</br>' + response.config.url
  143. Message.closeAll()
  144. Message({
  145. message: `${msg}`,
  146. type: 'error',
  147. showClose: true,
  148. dangerouslyUseHTMLString: true,
  149. duration: 10000
  150. })
  151. return
  152. }
  153. // state为200是正确的请求 或者 验证码问题,或者警告类型的错误 自行处理
  154. if (state === requestState.SUCCESS || state === requestState.UNSUPORT || state === requestState.WARNING || state === requestState.WARN) {
  155. return { ...dataAxios, retainData }
  156. }
  157. // 处理刷新tonken问题,说明token过期了,刷新token
  158. if (state === requestState.TOKEN_EXPIRED) {
  159. const config = response.config
  160. if (!isRefreshing) {
  161. isRefreshing = true
  162. return refreshAccessToken().then(res => {
  163. const data = res.data
  164. updateToken(data)
  165. const token = getToken()
  166. config.headers[HEADER_TOKEN_KEY] = token
  167. // 已经刷新了token,将所有队列中的请求进行重试
  168. requests.forEach(cb => cb(token))
  169. requests = []
  170. return service(config)
  171. }).catch(res => {
  172. console.error('refreshtoken error =>', res)
  173. removeRefreshToken()
  174. window.location.href = this.$baseUrl
  175. }).finally(() => {
  176. isRefreshing = false
  177. })
  178. } else {
  179. // 正在刷新token,将返回一个未执行resolve的promise
  180. return new Promise((resolve) => {
  181. // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
  182. requests.push((token) => {
  183. config.headers[HEADER_TOKEN_KEY] = token
  184. resolve(service(config))
  185. })
  186. })
  187. }
  188. // 6020201:非法的token;6020202:其他客户端登录了;6020301:Token 过期了;
  189. } else if (state === requestState.ILLEGAL_TOKEN || state === requestState.OTHER_CLIENTS) {
  190. // if (!cancelRequest) {
  191. cancelRequest = false
  192. MessageBox.confirm(I18n.t('error.logout.message'), I18n.t('error.logout.title'), {
  193. confirmButtonText: I18n.t('error.logout.confirmButtonText'),
  194. cancelButtonText: I18n.t('error.logout.cancelButtonText'),
  195. type: 'warning'
  196. }).then(() => {
  197. store.dispatch('ibps/account/fedLogout').then(() => {
  198. // 终止所有请求
  199. cancelRequest = true
  200. router.push({
  201. name: 'login'
  202. })
  203. }).catch(() => {
  204. cancelRequest = false
  205. })
  206. }).finally(() => {
  207. cancelRequest = false
  208. })
  209. // }
  210. return Promise.reject(new Error(message))
  211. } else { // 错误处理
  212. let errorMsg = ''
  213. if (Utils.isNotEmpty(message)) { // 有错误消息
  214. errorMsg = Utils.isNotEmpty(dataAxios.cause) ? I18n.t('error.messageCause', {
  215. message,
  216. cause: dataAxios.cause
  217. }) : I18n.t('error.message', {
  218. message
  219. })
  220. } else if (Utils.isNotEmpty(cause)) { // 只有错误原因
  221. errorMsg = I18n.t('error.cause', {
  222. cause
  223. })
  224. } else if (I18n.te('error.status.' + state)) { // 有错误编码
  225. errorMsg = I18n.t('error.status.' + state)
  226. } else { // 未知
  227. errorMsg = message || I18n.t('error.unknown', {
  228. state
  229. })
  230. }
  231. Message.closeAll()
  232. Message({
  233. message: `${errorMsg}`,
  234. type: 'error',
  235. showClose: true,
  236. dangerouslyUseHTMLString: true,
  237. duration: 5 * 1000
  238. })
  239. const err = new Error(errorMsg)
  240. err.state = state
  241. err.cause = cause
  242. return Promise.reject(err)
  243. }
  244. }, error => {
  245. // 异常处理
  246. tryHideFullScreenLoading()
  247. // for debug
  248. console.error('request-error', error)
  249. if (error && error.response) {
  250. error.message = I18n.t('error.status.' + error.response.status, {
  251. url: error.response.config.url
  252. })
  253. } else {
  254. error.state = 500
  255. // '服务器君开小差了,请稍后再试'
  256. error.message = I18n.t('error.network')
  257. }
  258. Message.closeAll()
  259. Message({
  260. message: error.message || I18n.t('error.network'),
  261. type: 'error',
  262. showClose: true,
  263. duration: 5 * 1000
  264. })
  265. return Promise.reject(error)
  266. })
  267. export default service