目录
- 前后端交互约定
- 安装
- 创建Axios实例
- 拦截器
- 封装请求方法
- 业务异常处理
axios 是一个易用、简洁且高效的http库
axios 中文文档:http://www.axios-js.com/zh-cn/docs/
前后端交互约定
在本项目中,前后端交互统一使用 application/json;charset=UTF-8
的请求方式,后端返回对象统一为如下格式
export interface ResponseBody<T = any> {
status: boolean, // 业务处理状态,true表示正常,false表示异常
code: string // 业务处理状态码
message: string, // 提示信息
data?: T // 业务处理返回数据
}
安装
yarn add axios
创建Axios实例
在
src
目录下创建http
目录,http
请求相关的文件都放置于该目录
创建 axios.ts
文件,用于定义 axios
// axios.ts
const instance: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 60000,
headers: { 'Content-Type': 'application/json;charset=UTF-8' },
})
其中,import.meta.env.VITE_APP_BASE_API
是 .env
中配置的环境变量,不同环境使用不同的请求地址
拦截器
通过 instance.interceptors.request.use
实现前置拦截器,发起请求前执行,用于对请求对象进行加工处理。
下面这段代码主要做的事情就是将 token
设置到请求头中。
// axios.ts
async function requestHandler(config: InternalAxiosRequestConfig & RequestConfigExtra): Promise<InternalAxiosRequestConfig> {
if (config.modulePrefix) {
config.url = config.modulePrefix + config.url
}
const token = useAuthorization()
if (token.value && config.token !== false) {
config.headers.set("Authorization", token.value)
}
console.log("execute http request:" + config.url)
return config
}
instance.interceptors.request.use(requestHandler)
RequestConfigExtra
为自定义参数,在本项目中定义如下,可根据需要自行扩展。
export interface RequestConfigExtra {
// 模块前缀
modulePrefix?: string,
// 发起请求时,是否需要在请求头中附加 token
token?: boolean,
// 成功处理函数,默认为 false,如果为 false,则什么都不做,如果为 true,则自动提示成功信息,如果为 Function,则自定义处理结果
success?: boolean | ((response: ResponseBody<any>) => void),
// 失败处理函数,默认为 true,如果为 false,则什么都不做,如果为 true,则自动提示失败信息,如果为 Function,则自定义处理结果
error?: boolean | ((response: ResponseBody<any>) => void),
}
通过 instance.interceptors.response.use
实现后置拦截器,对后端返回数据进行处理。
function responseHandler(response: any): ResponseBody<any> | AxiosResponse<any> | Promise<any> | any {
return response.data
}
function errorHandler(errorInfo: AxiosError): Promise<any> {
if (errorInfo.response) {
const { data, status, statusText } = errorInfo.response as AxiosResponse<ResponseBody>
if (status === 401) {
const token = useAuthorization()
token.value = null
message.error(data?.message || statusText)
router.push({path: '/login', query: {
redirect: router.currentRoute.value.fullPath
}})
} else {
message.error(data?.message || statusText)
}
}
return Promise.reject(errorInfo)
}
instance.interceptors.response.use(responseHandler, errorHandler)
errorHandler
方法对系统异常进行了统一处理,如果后端返回的 status
不是 200,则会执行该处理方法,如果返回 401,表示没有通过登录鉴权,自动跳转登录页,如果是其它异常码,则提示错误信息。
封装请求方法
这里对 restful
常用的四种请求方式进行进一步的封装
// axios.ts
function instancePromise<R = any, T = any>(options: AxiosRequestConfig<T> & RequestConfigExtra): Promise<ResponseBody<R>> {
return new Promise((resolve, reject) => {
instance.request<any, ResponseBody<R>>(options)
.then((res) => {
try {
resolve(responseBodyHandle(res, options))
} catch (err) {
reject(err || new Error('response handle error!'))
}
})
.catch((e: Error | AxiosError) => {
reject(e)
})
})
}
export function doGet<R = any, T = any>(url: string, params?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
params,
method: RequestEnum.GET,
...config,
}
return instancePromise<R, T>(options)
}
export function doPost<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
data,
method: RequestEnum.POST,
...config,
}
return instancePromise<R, T>(options)
}
export function doPut<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
data,
method: RequestEnum.PUT,
...config,
}
return instancePromise<R, T>(options)
}
export function doDelete<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
data,
method: RequestEnum.DELETE,
...config,
}
return instancePromise<R, T>(options)
}
业务异常处理
在拦截器中,我们对系统异常进行了统一处理,在实际项目中,更多的情况是前端请求没有通过后端的业务校验,后端返回错误信息,前端进行提示。
创建一个业务异常类
export class ResponseBodyError extends Error {
code: string
message: string
cause: any
constructor({ code, message, cause }: { code: string, message: string, cause?: any }) {
super();
this.code = code;
this.message = message;
this.cause = cause
}
}
实现业务异常统一处理,通过在请求时定义 RequestConfigExtra
中的参数来实现对后端返回结果的提示。
比如:查询请求,设置为 {success:false, error: true}
,如果请求失败,提示错误信息,如果请求成功,不作处理。业务请求,设置为 {success: true, error: true}
,如果请求失败,提示错误信息,如果处理成功,提示操作成功。
function responseBodyHandle<R = any, T = any>(response: ResponseBody<R>, options: AxiosRequestConfig<T> & RequestConfigExtra): any {
const { status, message:msg, code, data } = response
const { success, error } = options
if (status === true) {
if (success === true) {
message.success(msg ?? "操作成功")
} else if (isFunction(success)) {
success(response)
}
return response
} else {
if (isFunction(error)) {
error(response)
} else if (error !== false) {
message.error(msg ?? "操作失败")
}
throw new ResponseBodyError({ code, msg })
}
}
完整代码如下:
// axios.ts
/**
* 创建 Axios 实例
*/
const instance: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 60000,
headers: { 'Content-Type': 'application/json;charset=UTF-8' },
})
/**
* 前置拦截器
*/
async function requestHandler(config: InternalAxiosRequestConfig & RequestConfigExtra): Promise<InternalAxiosRequestConfig> {
if (config.modulePrefix) {
config.url = config.modulePrefix + config.url
}
const token = useAuthorization()
if (token.value && config.token !== false) {
config.headers.set(authorizationHeader, authorizationValue())
}
console.log("execute http request:" + config.url)
return config
}
/**
* 后置拦截器
*/
function responseHandler(response: any): ResponseBody<any> | AxiosResponse<any> | Promise<any> | any {
return response.data
}
/**
* 系统异常统一处理函数
*/
function errorHandler(errorInfo: AxiosError): Promise<any> {
if (errorInfo.response) {
const { data, status, statusText } = errorInfo.response as AxiosResponse<ResponseBody>
if (status === 401) {
const token = useAuthorization()
token.value = null
message.error(data?.message || statusText)
router.push({path: '/login', query: {
redirect: router.currentRoute.value.fullPath
}})
} else {
message.error(data?.message || statusText)
}
}
return Promise.reject(errorInfo)
}
instance.interceptors.request.use(requestHandler)
instance.interceptors.response.use(responseHandler, errorHandler)
/**
* 业务异常统一处理函数
*/
function responseBodyHandle<R = any, T = any>(response: ResponseBody<R>, options: AxiosRequestConfig<T> & RequestConfigExtra): any {
const { status, message:msg, code, data } = response
const { success, error } = options
if (status === true) {
if (success === true) {
message.success(msg ?? "操作成功")
} else if (isFunction(success)) {
success(response)
}
return response
} else {
if (isFunction(error)) {
error(response)
} else if (error !== false) {
message.error(msg ?? "操作失败")
}
throw new ResponseBodyError({ code, msg })
}
}
function instancePromise<R = any, T = any>(options: AxiosRequestConfig<T> & RequestConfigExtra): Promise<ResponseBody<R>> {
return new Promise((resolve, reject) => {
instance.request<any, ResponseBody<R>>(options)
.then((res) => {
try {
resolve(responseBodyHandle(res, options))
} catch (err) {
reject(err || new Error('response handle error!'))
}
})
.catch((e: Error | AxiosError) => {
reject(e)
})
})
}
export function doGet<R = any, T = any>(url: string, params?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
params,
method: RequestEnum.GET,
...config,
}
return instancePromise<R, T>(options)
}
export function doPost<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
data,
method: RequestEnum.POST,
...config,
}
return instancePromise<R, T>(options)
}
export function doPut<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
data,
method: RequestEnum.PUT,
...config,
}
return instancePromise<R, T>(options)
}
export function doDelete<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
data,
method: RequestEnum.DELETE,
...config,
}
return instancePromise<R, T>(options)
}