axios封装及使用说明
开始之前
在前后端分离
的大环境下,webApi
是两者之间通信的桥梁,在传统的前端开发中,发送 http
请求是没有统一管理的,这样会导致每个请求的地方都会加很多重复冗余的业务代码,所以急需将请求进行统一管理,提升代码质量和开发效率。
讲解只展示了部分重点代码,详细情况请前往源码查看,源码地址:packages-->request
# 如何封装的请求方法
# 1、配置环境变量
一般在我们的开发过程中,最少都会有一个开发develop
环境和一个生产production
环境,两者的baseUrl
(请求的 ip 和前缀都会所差别),如果是每次发布都去手动更新的话,那么会十分的麻烦,也很容易出错,所以就有了环境变量的配置,他会根据当前发布的环境去自动的辨别使用哪一个环境变量。
在我们的 scada 系统中有.env.develop
和.env.production
两个文件,分别代表了开发和生产环境中的一些环境变量,可配置服务名
,ip 地址
等

# 2. 封装 axios 请求
# 2-1. 确定哪些是请求中需要外部传入的
在 bridge 中就定义了哪些数据是需要从外部传入的,比如说apiUrl
,token
,超时处理
、错误处理
等等,这些业务逻辑在具体的项目中都会有所差别。为了封装后的扩展性和复用性,就需要从外部传入。

# 2-2. 封装
- 配置请求头,如 apiUrl、header、timeout 等

- 处理请求前的一些参数和配置,列如处理请求中的
链接
、data
、params
,为 formdata 的时候是如何处理的,等等

- 添加请求拦截器
由于请求头可能会根据各自项目的需求带一些自定义的请求头
,所以在请求前需要对请求头在做一次处理,如添加token
、国际化
等等
/**
* @description: 请求拦截器处理
*/
requestInterceptors: (config, options) => {
const { getLocale } = useLocale()
// 请求之前处理config
const token = context.getTokenFunction?.()
const tenantId = context.getTenantId?.()
;(config as Recordable<any>).headers["TenantId"] = tenantId
;(config as Recordable<any>).headers["Accept-Language"] = getLocale.value // 添加请求头国际化
if (
token &&
(config as Recordable<any>)?.requestOptions?.withToken !== false
) {
;(config as Recordable<any>).headers.Authorization = `Bearer ${token}`
}
return config
},
- 添加请求响处理
在后端返回的数据中,数据结构基本一致,所以可以对响应的数据做统一的处理,也可以对过期的 token 等做统一处理
this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => {
res && axiosCanceler.removePending(res.config)
if (responseInterceptors && isFunction(responseInterceptors)) {
res = responseInterceptors(res)
}
if (res.config.isFileRet) {
let type = res.headers["content-type"]
if (type.indexOf("application/octet-stream") === -1) {
return Promise.reject(res)
} else {
return res
}
}
const response: AxiosResponse<any> = res.data
if (response.statusCode && response.statusCode !== 200) {
if (response.statusCode == 401 || response.statusCode == 1001) {
// 1001 表示当前token过期 acessToken,用refreshToken去请求新的token替换掉过去的
//刷新accessToken并重新请求。
const config = res.config
if (!isRefreshing) {
isRefreshing = true
const refreshTokenFunction = context.refreshTokenFunction
return refreshTokenFunction()
.then(
() => {
let accessToken = context.getTokenFunction
// 这里有个小问题 当在重试中 如果接口报错 就会直接跳转到登录页 需要后端配合
// 已经刷新了accessToken,将所有队列中的请求进行重试
setTimeout(() => {
requests.forEach(cb => cb(accessToken()))
requests = []
}, 200)
return this.axiosInstance(config)
},
() => {}
)
.catch(() => {})
.finally(() => {
isRefreshing = false
})
} else {
// 正在刷新token,将返回一个未执行resolve的promise
// 保存函数 等待执行
// 吧请求都保存起来 等刷新完成后再一个一个调用
return new Promise(resolve => {
if (res.config.url !== context.refreshToken) {
// 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
requests.push(token => {
config.headers["Authorization"] = `Bearer ${token}`
resolve(this.axiosInstance(config))
})
}
})
}
} else if (response.statusCode == 1002) {
// 1002 表示当前refreshToekn过期,需重新登录了
//refleshToken过期,重新登录
context
.confirmFunction("confirm", "你已被登出,请重新登录", "提示")
.then(() => {
context.unauthorizedFunction()
requests = []
})
.catch(() => {})
return Promise.reject(response)
} else {
if (!res.config?.hideerror) {
context.msgFunction(response?.message || "", "error", 5 * 1000)
}
return Promise.reject(new Error(response?.message || "Error"))
}
} else {
if (res.config.responseType === "blob" && res.status === 200) {
return Promise.resolve(response)
}
if (response.status) {
if (response.data === null || response.data === undefined) {
return response
}
return response.data
} else {
context.msgFunction(
response?.data?.message
? response?.data?.message
: response.message,
"error"
)
return Promise.reject(new Error("业务错误"))
}
}
},
- 添加请求响应出错处理器
接口请求出错在系统中也是很重要的一环,会有各种各样的错误出现,但是错误码所代表的意思都是一样的,所以可以对响应错误进行统一的处理
/**
* @description: 响应错误处理
*/
responseInterceptorsCatch: (error: any) => {
const { t } = useI18n()
const { response, code, message, config } = error || {}
const errorMessageMode = config?.requestOptions?.errorMessageMode || "none"
const err: string = error?.toString?.() ?? ""
let errMessage = ""
try {
if (code === "ECONNABORTED" && message.indexOf("timeout") !== -1) {
errMessage = t("api.apiTimeoutMessage") //
}
if (err?.includes("Network Error")) {
errMessage = t("api.networkExceptionMsg") //
}
if (errMessage) {
context.msgFunction(errMessage, "error")
return Promise.reject(error)
}
} catch (error) {
throw new Error(error as unknown as string)
}
checkStatus(response?.status, errorMessageMode)
return Promise.reject(error)
},
}
- 暴露出 get、post、put、delete 等方式供页面使用
get<T = any>(
config: AxiosRequestConfig,
options?: RequestOptions
): Promise<T> {
return this.request({ ...config, method: "GET" }, options)
}
post<T = any>(
config: AxiosRequestConfig,
options?: RequestOptions
): Promise<T> {
return this.request({ ...config, method: "POST" }, options)
}
put<T = any>(
config: AxiosRequestConfig,
options?: RequestOptions
): Promise<T> {
return this.request({ ...config, method: "PUT" }, options)
}
delete<T = any>(
config: AxiosRequestConfig,
options?: RequestOptions
): Promise<T> {
return this.request({ ...config, method: "DELETE" }, options)
}
request<T = any>(
config: AxiosRequestConfig,
options?: RequestOptions
): Promise<T> {
let conf: CreateAxiosOptions = cloneDeep(config)
const transform = this.getTransform()
const { requestOptions } = this.options
const opt: RequestOptions = Object.assign({}, requestOptions, options)
const { beforeRequestHook, requestCatchHook, transformRequestHook } =
transform || {}
if (beforeRequestHook && isFunction(beforeRequestHook)) {
conf = beforeRequestHook(conf, opt)
}
conf.requestOptions = opt
conf = this.supportFormData(conf)
return new Promise((resolve, reject) => {
this.axiosInstance
.request<any, AxiosResponse<RequestResult>>(conf)
.then((res: AxiosResponse<RequestResult>) => {
if (transformRequestHook && isFunction(transformRequestHook)) {
try {
const ret = transformRequestHook(res, opt)
resolve(ret)
} catch (err) {
reject(err || new Error("request error!"))
}
return
}
resolve(res as unknown as Promise<T>)
})
.catch((e: Error | AxiosError) => {
if (requestCatchHook && isFunction(requestCatchHook)) {
reject(requestCatchHook(e, opt))
return
}
if (axios.isAxiosError(e)) {
// rewrite error message from axios in here
}
reject(e)
})
})
}
- 暴露出整个 axios 实例与一些方法,让使用者可以初始化配置、调用该包的一些方法等等
export { initRequest, setMsg, setNoice, setConfirm } from "./bridge";
export { AxiosCanceler as RequestCanceler } from "./src/axiosCancel";
export { request } from "./src/index";
# 页面中怎么使用
# 1. 初始化 axios 配置
在第一点中我们讲到了 bridge 里面定义了一些需要使用者传入的参数,那么我们在使用的时候就需要先初始化
这些配置,列如token
、租户id
、请求超时时间
等
- 从封装好的
request
包里导入initRequest
方法 - 调用
initRequest
方法,传入配置
import { App } from "vue";
import { initRequest } from "@xfe/request";
import useAuths from "@/hooks/useAuth";
import { config } from "@/config";
import { useMessage } from "@/hooks/web/useMessage";
async function initPackages() {
const _initRequest = async () => {
//TODO 路由api前缀统一处理函数
const { base_url, request_timeout, default_headers } = config;
const PATH_URL = base_url[process.env.VUE_APP_BASEPATH];
const {
getToken,
logoutFun,
clearStorage,
getRefreshToken,
refreshToken,
getTenantId,
} = useAuths();
const RefreshToken = `${base_url.homeServerPrefix}/Account/login/refresh_token`;
const { createMessage } = useMessage();
await initRequest(() => {
return {
apiUrl: PATH_URL,
refreshToken: RefreshToken,
timeOut: request_timeout,
headers: default_headers,
getTokenFunction: () => {
return getToken();
},
getTenantId: () => {
return getTenantId();
},
getRefreshTokenFunction: () => {
return getRefreshToken();
},
refreshTokenFunction: () => {
return Promise.resolve(refreshToken());
},
errorFunction: null,
noticeFunction: null,
errorModalFunction: null,
timeoutFunction: () => {
clearStorage();
logoutFun();
},
unauthorizedFunction: () => {
createMessage("非法访问,即将登出,请联系管理员!", "error");
clearStorage();
// logoutFun()
},
handleErrorFunction: (msg: string) => {
createMessage(msg, "error");
},
};
});
};
await Promise.all([_initRequest()]);
}
export async function initApplication() {
// ! Need to pay attention to the timing of execution
// ! 需要注意调用时机
await initPackages();
}
# 2. 使用 get、post 等方法
# 2-1. ① 返回为 json 结构的数据
- 引入
request
请求方法 - 使用具体的请求方法,如
request.get
,requset.post
等 - 3)传入请求的地址、需要的参数等等,
get
方法为params
,其余的基本就是data
(放在请求的 body 中),也可以加 params,具体情况具体分析
//统一请求封装
import { request } from "@xfe/request";
import { config } from "@/config";
const homeServerPrefix = config.base_url.homeServerPrefix;
export function modifySysNameAndLogo(data) {
return request.post({
url: `${homeServerPrefix}/BaseSysConfig/ModifySysNameAndLogo`,
data,
});
}
export function getLogStatisticsPies(params, type) {
return request.get({
url: `${homeServerPrefix}/Statistics/pie/${type}/server-name`,
params,
});
}
# 2. 处理特殊的返回(文件流),如文件的导入导出
此处需要对请求头传入特定的参数
去标识需要什么样的返回和传入了什么样格式的数据
// 报警日志导出
export function exportWarnLog(params) {
return request.get({
url: `${homeServerPrefix}/Log/ExportWarnLog`,
headers: { "Content-Type": "application/x-download" },
responseType: "blob",
params,
});
}
/**
* 导入交互配置表
*/
export function importExcel(data) {
return request.post({
url: `${homeServerPrefix}/ProductDataSetting/ImportExcel`,
headers: { "Content-Type": "multipart/form-data" },
data,
});
}