see: axios-demo-vue
see: axios-demo-java
直接上代码
1 axios封装
- axios封装
/api/HttpHelper.ts
import axios, { type AxiosRequestConfig, type AxiosResponse, type InternalAxiosRequestConfig, type ResponseType } from "axios";
import { handleGlobalBizError, handleGlobalHttpStatusError } from "./handler";
import type { IHttpApiResponse } from "./types";
const helper = {
isDownload: function (response: AxiosResponse<IHttpApiResponse<any> | Blob>): boolean {
return response.data instanceof Blob;
},
hasBizError: function (response: AxiosResponse<IHttpApiResponse<any> | Blob>): boolean {
if (this.isDownload(response)) {
return response.data.type === "application/json";
}
return (response.data as IHttpApiResponse<any>)?.code !== 200;
},
download: function (response: any, defaultFileName?: string): void {
const url = window.URL.createObjectURL(response.data);
const link = document.createElement("a");
link.href = url;
link.download = this.getFileName(response, defaultFileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
},
getFileName: function (response: any, defaultFileName?: string) {
if (defaultFileName) {
return defaultFileName;
}
let fileName = "download";
const contentDisposition = response.headers["content-disposition"];
if (!contentDisposition) {
return fileName;
}
const rfc5987Match = contentDisposition.match(/filename\*=(.*''.+)/); // Rfc5987
if (rfc5987Match?.length === 2) {
let tmp = rfc5987Match[1].split("''");
if (tmp.length === 1) {
fileName = decodeURI(tmp[0]);
} else if (tmp.length === 2) {
fileName = decodeURI(tmp[1]);
}
}
if (!fileName) {
let match = contentDisposition.match(/filename="(.+)"/); // other
if (match?.length === 2) {
fileName = decodeURI(match[1]);
}
}
return fileName;
},
};
const axiosInstance = axios.create({
// baseURL: import.meta.env.BASE_URL, // 基础请求地址
baseURL: "http://localhost:8080", // 基础请求地址
timeout: 10000, // 请求超时设置
withCredentials: false, // 跨域请求是否需要携带 cookie
});
axiosInstance.interceptors.request.use(
function (config: InternalAxiosRequestConfig) {
// config.headers.set({
// 'Content-Type': 'application/json; charset=utf-8',
// });
// const token = localStorage.getItem('token');
// if (token) {
// config.headers.set('Authorization', `Bearer ${token}`);
// }
return config;
},
function (error: any) {
return Promise.reject(error);
}
);
axiosInstance.interceptors.response.use(
function (response: AxiosResponse<IHttpApiResponse<any> | Blob>) {
// Any status code that lie within the range of 2xx cause this function to trigger
if (response.status !== 200) {
return Promise.reject({
isBizError: false,
data: response,
});
}
if (!helper.hasBizError(response)) {
return response;
}
if (helper.isDownload(response)) {
return new Promise((resolve, reject) => {
let fileReader = new FileReader();
fileReader.onload = function (e) {
// BizError
return reject({
isBizError: true,
data: fileReader.result,
});
};
fileReader.readAsText(response.data as Blob);
});
}
handleGlobalBizError(response.data as IHttpApiResponse<any>);
return Promise.reject({
isBizError: true,
data: response.data,
});
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
handleGlobalHttpStatusError(error.status);
return Promise.reject({
isBizError: false,
data: error.response ?? error,
});
}
);
const HttpHelper = {
get: function <T>(
url: string,
options?: {
config?: AxiosRequestConfig<any>; // request配置
isThrow?: boolean; // 是否使用reject(error)外抛错误。默认为false
}
): Promise<IHttpApiResponse<T>> {
return new Promise((resolve, reject) => {
axiosInstance
.get(url, options?.config)
.then(function (response) {
resolve(response.data);
})
.catch(function (error) {
if (options?.isThrow) {
reject(error);
}
})
.finally(function () {
// always executed
});
});
},
post: function <T>(
url: string,
data?: any,
options?: {
config?: AxiosRequestConfig<any>; // request配置
isThrow?: boolean; // 是否使用reject(error)外抛错误。默认为false
}
): Promise<IHttpApiResponse<T>> {
return new Promise((resolve, reject) => {
axiosInstance
.post(url, data, options?.config)
.then(function (response) {
resolve(response.data);
})
.catch(function (error) {
if (options?.isThrow) {
reject(error);
}
})
.finally(function () {
// always executed
});
});
},
put: function <T>(
url: string,
data?: any,
options?: {
config?: AxiosRequestConfig<any>; // request配置
isThrow?: boolean; // 是否使用reject(error)外抛错误。默认为false
}
): Promise<IHttpApiResponse<T>> {
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data, options?.config)
.then(function (response) {
resolve(response.data);
})
.catch(function (error) {
if (options?.isThrow) {
reject(error);
}
})
.finally(function () {
// always executed
});
});
},
delete: function <T>(
url: string,
options?: {
config?: AxiosRequestConfig<any>; // request配置
isThrow?: boolean; // 是否使用reject(error)外抛错误。默认为false
}
): Promise<IHttpApiResponse<T>> {
return new Promise((resolve, reject) => {
axiosInstance
.delete(url, options?.config)
.then(function (response) {
resolve(response.data);
})
.catch(function (error) {
if (options?.isThrow) {
reject(error);
}
})
.finally(function () {
// always executed
});
});
},
/**
* post JSON数据
*
* @param url url
* @param data JSON格式数据
* @param isThrow 是否使用reject(error)外抛错误。默认为false
* @returns
*/
postJson: function <T>(url: string, data: {}, isThrow?: boolean): Promise<IHttpApiResponse<T>> {
const config = {
headers: {
"Content-Type": "application/json;charset:utf-8;",
},
};
return this.post(url, data, {
config: config,
isThrow: isThrow,
});
},
/**
* post HTML form作为JSON数据
* @param url url
* @param formId form表单的id
* @param isThrow 是否使用reject(error)外抛错误。默认为false
* @returns
*/
postFormAsJson: function <T>(url: string, formId: string, isThrow?: boolean): Promise<IHttpApiResponse<T>> {
const config = {
headers: {
"Content-Type": "application/json;charset:utf-8;",
},
};
const data = document.querySelector("#" + formId);
return this.post(url, data, {
config: config,
isThrow: isThrow,
});
},
/**
* 使用post 'Content-Type': 'application/x-www-form-urlencoded'
*
* @param url url
* @param data JSON格式数据
* @param isThrow 是否使用reject(error)外抛错误。默认为false
* @returns
*/
postUrlencoded: function <T>(url: string, data: {}, isThrow?: boolean): Promise<IHttpApiResponse<T>> {
const config = {
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset:utf-8;",
},
};
return this.post(url, data, {
config: config,
isThrow: isThrow,
});
},
/**
* 使用post 'Content-Type': 'multipart/form-data'
*
* @param url url
* @param data JSON格式数据。包含文件信息<br/>示例
* @param isThrow 是否使用reject(error)外抛错误。默认为false
* {
* userId: 1,
* avatars: document.querySelector('#fileInput').files
* }
*
* input示例:
* <input id="fileInput" type="file" name="avatars" multiple />
* @returns
*/
postMultipart: function <T>(url: string, data: {}, isThrow?: boolean): Promise<IHttpApiResponse<T>> {
const config = {
headers: {
"Content-Type": "multipart/form-data",
},
};
return this.post(url, data, {
config: config,
isThrow: isThrow,
});
},
/**
* 上传多个文件(可包含其他数据字段)
*
* @param url url
* @param data JSON格式数据。包含文件信息<br/>示例
* @param isThrow 是否使用reject(error)外抛错误。默认为false
* {
* userId: 1,
* avatars: document.querySelector('#fileInput').files
* }
*
* input示例:
* <input id="fileInput" type="file" name="avatars" multiple />
* @returns
* @see HttpHelper.postMultipart()
*/
uploadFiles: function <T>(url: string, data: {}, isThrow?: boolean): Promise<IHttpApiResponse<T>> {
return this.postMultipart(url, data, isThrow);
},
/**
* 使用GET请求下载
*
* @param url url
* @param options 其他参数
*/
getDownload: function (
url: string,
options?: {
filename?: string; // 默认文件名
isThrow?: boolean; // 是否使用reject(error)外抛错误。默认为false
}
): Promise<void> {
const config = {
responseType: "blob" as ResponseType,
};
return new Promise((resolve, reject) => {
axiosInstance
.get(url, config)
.then((response) => {
helper.download(response, options?.filename);
resolve();
})
.catch(function (error) {
if (options?.isThrow) {
reject(error);
}
});
});
},
/**
* 使用POST请求下载
*
* @param url url
* @param data 请求参数
* @param options 其他参数
*/
postDownload: function (
url: string,
data: {},
options?: {
filename?: string; // 默认文件名
isThrow?: boolean; // 是否使用reject(error)外抛错误。默认为false
}
): Promise<void> {
const config = {
responseType: "blob" as ResponseType,
};
return new Promise((resolve, reject) => {
axiosInstance
.post(url, data, config)
.then((response) => {
helper.download(response, options?.filename);
resolve();
})
.catch(function (error) {
if (options?.isThrow) {
reject(error);
}
});
});
},
};
export default HttpHelper;
- 数据类型
/api/types.ts
export interface IHttpApiResponse<T> {
type: "IHttpApiResponse";
/** 请求的唯一id */
requestId: string;
/** 响应编码, 200-成功;非200-业务异常码 */
code: number;
/** 提示信息 */
message: string;
/** 应答消息体 */
data: T;
}
export interface IHttpApiError<T> {
type: "IHttpApiError";
/** 是否是业务异常 */
isBizError: boolean;
/** 错误详细数据 */
data: any | IHttpApiResponse<T>;
}
- 全局错误处理
/api/handler.ts
import type { IHttpApiResponse } from "./types";
const handleGlobalHttpStatusError = (status: number): void => {
let message = "未知错误";
if (status) {
switch (status) {
case 400:
message = "错误的请求";
break;
case 401:
message = "未授权,请重新登录";
break;
case 403:
message = "拒绝访问";
break;
case 404:
message = "请求错误,未找到该资源";
break;
case 405:
message = "请求方法未允许";
break;
case 408:
message = "请求超时";
break;
case 500:
message = "服务器端出错";
break;
case 501:
message = "网络未实现";
break;
case 502:
message = "网络错误";
break;
case 503:
message = "服务不可用";
break;
case 504:
message = "网络超时";
break;
case 505:
message = "http版本不支持该请求";
break;
default:
message = `其他错误 --${status}`;
}
} else {
message = `无法连接到服务器!`;
}
console.log(message);
};
const handleGlobalBizError = (resp: IHttpApiResponse<any>): void => {
switch (resp.code) {
case 200:
break;
case 30000:
console.log(`Business Error: ${resp.message}`);
break;
default:
}
};
export { handleGlobalHttpStatusError, handleGlobalBizError };
export default {};
2 模块API
以test模块为示例
- API文件
/api/modules/test/index.ts
import HttpHelper from "@/api/HttpHelper";
import type { IHttpApiError, IHttpApiResponse } from "@/api/types";
import type { CreateRequestV1, CreateResponseV1 } from "./types";
const Api = {
v1: {
get200: `/v1/test/get200`,
get200BizError: "/v1/test/get200BizError",
get2xx: "/v1/test/get2xx",
get5xx: "/v1/test/get5xx",
create: "/v1/test/create",
upload: "/v1/test/upload",
getDownload: "/v1/download/getDownload",
getImage: "/v1/download/getImage",
getDownloadBizError: "/v1/download/getDownloadBizError",
postDownload: "/v1/download/postDownload",
},
};
const test = {
get200: function (): Promise<IHttpApiResponse<string>> {
return HttpHelper.get(Api.v1.get200);
},
get200BizError: function (isThrow: boolean = false): Promise<IHttpApiResponse<string>> {
return HttpHelper.get(Api.v1.get200BizError, { isThrow });
},
get2xx: function (isThrow: boolean = false): Promise<IHttpApiResponse<string>> {
return HttpHelper.get(Api.v1.get2xx, { isThrow });
},
get5xx: function (isThrow: boolean = false): Promise<IHttpApiResponse<string>> {
return HttpHelper.get(Api.v1.get5xx, { isThrow });
},
create: function (params: CreateRequestV1): Promise<IHttpApiResponse<CreateResponseV1>> {
return HttpHelper.postJson(Api.v1.create, params);
},
upload: function (): Promise<IHttpApiResponse<Array<string>>> {
return HttpHelper.uploadFiles(Api.v1.upload, {
testId: 8001,
files: (document.querySelector("#files") as HTMLInputElement).files,
});
},
getDownload: function (): Promise<void> {
return HttpHelper.getDownload(Api.v1.getDownload, { filename: "1.jpg" });
},
getJpgImage: function (): Promise<void> {
return HttpHelper.getDownload(Api.v1.getImage + "?type=jpg");
},
getPngImage: function (): Promise<void> {
return HttpHelper.getDownload(Api.v1.getImage + "?type=png");
},
getDownloadBizError: function (): Promise<void> {
return HttpHelper.getDownload(Api.v1.getDownloadBizError, { isThrow: true });
},
postDownload: function (): Promise<void> {
return HttpHelper.postDownload(
Api.v1.postDownload,
{
fileName: "中文.jpg",
},
{
filename: "自定义名称.jpg",
}
);
},
};
export default test;
- 数据类型
/api/modules/test/types.ts
export interface CreateRequestV1 {
testId: number;
name: string;
}
export interface CreateResponseV1 {
message: string;
}
3 统一API export
- /api/HttpApi.ts
import test from "./modules/test";
const HttpApi = {
test,
};
export default HttpApi;
4 示例
- App.vue
<script setup lang="ts">
import HttpApi from "./api/HttpApi";
import type { CreateResponseV1 } from "./api/modules/test/types";
import type { IHttpApiResponse } from "./api/types";
function get200() {
HttpApi.test.get200().then(function (response: IHttpApiResponse<string>) {
console.log(response);
});
}
function get200BizError() {
HttpApi.test.get200BizError().then(function (response: IHttpApiResponse<string>) {
console.log(response);
});
}
function get200BizErrorThrow() {
HttpApi.test
.get200BizError(true)
.then(function (response: IHttpApiResponse<string>) {
console.log(response);
})
.catch((error: any) => {
if (error.isBizError) {
console.log("BizError", error.data);
} else {
console.log("SystemError", error.data);
}
});
}
function get2xx() {
HttpApi.test.get2xx().then(function (response: IHttpApiResponse<string>) {
console.log(response);
});
}
function get2xxThrow() {
HttpApi.test
.get2xx(true)
.then(function (response: IHttpApiResponse<string>) {
console.log(response);
})
.catch((error: any) => {
if (error.isBizError) {
console.log("BizError", error.data);
} else {
console.log("SystemError", error.data);
}
});
}
function get5xx() {
HttpApi.test.get5xx().then(function (response: IHttpApiResponse<string>) {
console.log(response);
});
}
function get5xxThrow() {
HttpApi.test
.get5xx(true)
.then(function (response: IHttpApiResponse<string>) {
console.log(response);
})
.catch((error: any) => {
if (error.isBizError) {
console.log("BizError", error.data);
} else {
console.log("SystemError", error.data);
}
});
}
function post() {
HttpApi.test
.create({
testId: 3001,
name: "Tom",
})
.then(function (response: IHttpApiResponse<CreateResponseV1>) {
console.log(response);
});
}
function uploadFiles() {
HttpApi.test.upload().then(function (response: IHttpApiResponse<Array<string>>) {
console.log(response);
});
}
function getDownloadBizError() {
HttpApi.test.getDownloadBizError().catch((error: any) => {
if (error.isBizError) {
console.log("BizError", error.data);
} else {
console.log("SystemError", error.data);
}
});
}
</script>
<template>
<button @click="get200">GET (200)</button>
<button @click="get200BizError">GET (200, BizError)</button>
<button @click="get200BizErrorThrow">GET (200, BizError, Throw)</button><br />
<button @click="get2xx">GET (2xx)</button>
<button @click="get2xxThrow">GET (2xx, Throw)</button><br />
<button @click="get5xx">GET (5xx)</button>
<button @click="get5xxThrow">GET (5xx, Throw)</button>
<button @click="post">POST</button>
<button @click="uploadFiles">UPLOAD FILES</button><br />
<button @click="HttpApi.test.getDownload">GET DOWNLOAD</button>
<button @click="HttpApi.test.getJpgImage">GET JPG IMAGE</button>
<button @click="HttpApi.test.getPngImage">GET PNG IMAGE</button>
<button @click="getDownloadBizError">GET DOWNLOAD (BizError, Throw)</button>
<button @click="HttpApi.test.postDownload">POST DOWNLOAD</button>
<fieldset>
<legend>Form</legend>
<form method="post" enctype="multipart/form-data" action="http://localhost:8080/v1/test/upload">
testId: <input name="testId" value="8001" /><br />
files: <input id="files" type="file" name="files" multiple /><br />
<input type="submit" value="Submit" />
</form>
</fieldset>
</template>
<style scoped>
button,
input {
margin: 5px;
}
</style>