|
|
|
@ -1,8 +1,10 @@ |
|
|
|
import fetch from 'dva/fetch'; |
|
|
|
/** |
|
|
|
* request 网络请求工具 |
|
|
|
* 更详细的api文档: https://bigfish.alipay.com/doc/api#request
|
|
|
|
*/ |
|
|
|
import { extend } from 'umi-request'; |
|
|
|
import { notification } from 'antd'; |
|
|
|
import router from 'umi/router'; |
|
|
|
import hash from 'hash.js'; |
|
|
|
import { isAntdPro } from './utils'; |
|
|
|
|
|
|
|
const codeMessage = { |
|
|
|
200: '服务器成功返回请求的数据。', |
|
|
|
@ -22,134 +24,50 @@ const codeMessage = { |
|
|
|
504: '网关超时。', |
|
|
|
}; |
|
|
|
|
|
|
|
const checkStatus = response => { |
|
|
|
if (response.status >= 200 && response.status < 300) { |
|
|
|
return response; |
|
|
|
} |
|
|
|
/** |
|
|
|
* 异常处理程序 |
|
|
|
*/ |
|
|
|
const errorHandler = error => { |
|
|
|
const { response = {} } = error; |
|
|
|
const errortext = codeMessage[response.status] || response.statusText; |
|
|
|
const { status, url } = response; |
|
|
|
|
|
|
|
notification.error({ |
|
|
|
message: `请求错误 ${response.status}: ${response.url}`, |
|
|
|
message: `请求错误 ${status}: ${url}`, |
|
|
|
description: errortext, |
|
|
|
}); |
|
|
|
const error = new Error(errortext); |
|
|
|
error.name = response.status; |
|
|
|
error.response = response; |
|
|
|
throw error; |
|
|
|
}; |
|
|
|
|
|
|
|
const cachedSave = (response, hashcode) => { |
|
|
|
/** |
|
|
|
* Clone a response data and store it in sessionStorage |
|
|
|
* Does not support data other than json, Cache only json |
|
|
|
*/ |
|
|
|
const contentType = response.headers.get('Content-Type'); |
|
|
|
if (contentType && contentType.match(/application\/json/i)) { |
|
|
|
// All data is saved as text
|
|
|
|
response |
|
|
|
.clone() |
|
|
|
.text() |
|
|
|
.then(content => { |
|
|
|
sessionStorage.setItem(hashcode, content); |
|
|
|
sessionStorage.setItem(`${hashcode}:timestamp`, Date.now()); |
|
|
|
}); |
|
|
|
if (status === 401) { |
|
|
|
notification.error({ |
|
|
|
message: '未登录或登录已过期,请重新登录。', |
|
|
|
}); |
|
|
|
// @HACK
|
|
|
|
/* eslint-disable no-underscore-dangle */ |
|
|
|
window.g_app._store.dispatch({ |
|
|
|
type: 'login/logout', |
|
|
|
}); |
|
|
|
return; |
|
|
|
} |
|
|
|
// environment should not be used
|
|
|
|
if (status === 403) { |
|
|
|
router.push('/exception/403'); |
|
|
|
return; |
|
|
|
} |
|
|
|
if (status <= 504 && status >= 500) { |
|
|
|
router.push('/exception/500'); |
|
|
|
return; |
|
|
|
} |
|
|
|
if (status >= 404 && status < 422) { |
|
|
|
router.push('/exception/404'); |
|
|
|
} |
|
|
|
return response; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Requests a URL, returning a promise. |
|
|
|
* |
|
|
|
* @param {string} url The URL we want to request |
|
|
|
* @param {object} [option] The options we want to pass to "fetch" |
|
|
|
* @return {object} An object containing either "data" or "err" |
|
|
|
* 配置request请求时的默认参数 |
|
|
|
*/ |
|
|
|
export default function request(url, option) { |
|
|
|
const options = { |
|
|
|
expirys: isAntdPro(), |
|
|
|
...option, |
|
|
|
}; |
|
|
|
/** |
|
|
|
* Produce fingerprints based on url and parameters |
|
|
|
* Maybe url has the same parameters |
|
|
|
*/ |
|
|
|
const fingerprint = url + (options.body ? JSON.stringify(options.body) : ''); |
|
|
|
const hashcode = hash |
|
|
|
.sha256() |
|
|
|
.update(fingerprint) |
|
|
|
.digest('hex'); |
|
|
|
const request = extend({ |
|
|
|
errorHandler, // 默认错误处理
|
|
|
|
credentials: 'include', // 默认请求是否带上cookie
|
|
|
|
}); |
|
|
|
|
|
|
|
const defaultOptions = { |
|
|
|
credentials: 'include', |
|
|
|
}; |
|
|
|
const newOptions = { ...defaultOptions, ...options }; |
|
|
|
if ( |
|
|
|
newOptions.method === 'POST' || |
|
|
|
newOptions.method === 'PUT' || |
|
|
|
newOptions.method === 'DELETE' |
|
|
|
) { |
|
|
|
if (!(newOptions.body instanceof FormData)) { |
|
|
|
newOptions.headers = { |
|
|
|
Accept: 'application/json', |
|
|
|
'Content-Type': 'application/json; charset=utf-8', |
|
|
|
...newOptions.headers, |
|
|
|
}; |
|
|
|
newOptions.body = JSON.stringify(newOptions.body); |
|
|
|
} else { |
|
|
|
// newOptions.body is FormData
|
|
|
|
newOptions.headers = { |
|
|
|
Accept: 'application/json', |
|
|
|
...newOptions.headers, |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const expirys = options.expirys && 60; |
|
|
|
// options.expirys !== false, return the cache,
|
|
|
|
if (options.expirys !== false) { |
|
|
|
const cached = sessionStorage.getItem(hashcode); |
|
|
|
const whenCached = sessionStorage.getItem(`${hashcode}:timestamp`); |
|
|
|
if (cached !== null && whenCached !== null) { |
|
|
|
const age = (Date.now() - whenCached) / 1000; |
|
|
|
if (age < expirys) { |
|
|
|
const response = new Response(new Blob([cached])); |
|
|
|
return response.json(); |
|
|
|
} |
|
|
|
sessionStorage.removeItem(hashcode); |
|
|
|
sessionStorage.removeItem(`${hashcode}:timestamp`); |
|
|
|
} |
|
|
|
} |
|
|
|
return fetch(url, newOptions) |
|
|
|
.then(checkStatus) |
|
|
|
.then(response => cachedSave(response, hashcode)) |
|
|
|
.then(response => { |
|
|
|
// DELETE and 204 do not return data by default
|
|
|
|
// using .json will report an error.
|
|
|
|
if (newOptions.method === 'DELETE' || response.status === 204) { |
|
|
|
return response.text(); |
|
|
|
} |
|
|
|
return response.json(); |
|
|
|
}) |
|
|
|
.catch(e => { |
|
|
|
const status = e.name; |
|
|
|
if (status === 401) { |
|
|
|
// @HACK
|
|
|
|
/* eslint-disable no-underscore-dangle */ |
|
|
|
window.g_app._store.dispatch({ |
|
|
|
type: 'login/logout', |
|
|
|
}); |
|
|
|
return; |
|
|
|
} |
|
|
|
// environment should not be used
|
|
|
|
if (status === 403) { |
|
|
|
router.push('/exception/403'); |
|
|
|
return; |
|
|
|
} |
|
|
|
if (status <= 504 && status >= 500) { |
|
|
|
router.push('/exception/500'); |
|
|
|
return; |
|
|
|
} |
|
|
|
if (status >= 404 && status < 422) { |
|
|
|
router.push('/exception/404'); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
export default request; |
|
|
|
|