request.js 9.7 KB

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