请求
介绍
Teek 基于 Axios 封装了一套开箱即用的 http 请求。包括请求 Loading、错误处理、取消重复请求等功能,在 src/common/http
目录下。
特性
- 多实例支持:可以创建多个具有不同配置的请求实例
- 重复请求取消:自动取消重复的请求
- 请求/响应拦截:提供完整的拦截器机制
- 全局 Loading:自动显示和隐藏全屏
Loading
- Content-Type 快速设置:提供多种
Content-Type
快速设置方式 - 参数序列化:支持多种参数序列化格式
- 响应数据处理:支持多种响应数据处理方式
- 可配置的全局处理器:支持自定义消息提示、路由跳转、用户登出等处理
- Token 自动刷新:支持配置自动刷新
Token
机制 - 请求缓存:支持
GET
请求缓存机制 - 请求重试:支持失败请求自动重试
- 进度监控:支持上传/下载进度监控
- 高度可定制:可以轻松适配各种项目需求
本地跨域处理
大部分本地开发环境会涉及与后端的跨域问题,可以按照下面指示配置来解决跨域问题:
配置本地请求域名
在 .env.development
文件中添加了 VITE_API_URL
变量,用于本地访问链接域名。
# 本地环境接口地址
VITE_API_URL = '/api'
配置开发服务器代理
vite.config.mts
文件中配置跨域代理
import type { ConfigEnv, UserConfig } from "vite";
export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
return {
server: {
// 跨域代理配置
proxy: {
"/api": {
target: "https://vue3-design.teek.top",
changeOrigin: true,
secure: true, // 是否忽略 https 安全证书问题,true 不忽略,false 忽略
rewrite: path => path.replace(/^\/api/, ""), // 转发到 target 时,将 /api 替换为空
},
},
}
}
}
此时通过 Axios
请求接口且链接没有写死域名时,则会自动将 VITE_API_URL
作为域名拼接在接口地址的前面,并触发 proxy
的 /api
拦截规则,最终转发到 target
指定的目标地址。
- 请求地址为
/users
时,则通过VITE_API_URL
拼接成/api/users
,走跨域代理,最终请求target
地址为https://vue3-design.teek.top/users
- 请求地址为
http:///vue3-design.teek.top/api/users
,此时直接访问该地址,因为携带/api
,所以会走跨域代理 - 请求地址为
http:///vue3-design.teek.top/users
时,直接访问该地址,因不携带/api
不会走跨域处理 - 请求地址为
http:///vue3-design.teek.top/demo/api/users
时,会走跨域代理,最终转发地址为http:///vue3-design.teek.top/demo/users
(proxy.rewrite
将/api
替换为空)
如:
import axios from "axios";
// 走跨域代理
axios.get("/user").then(res => {
console.log(res);
});
// 走跨域代理
axios.get("http:///vue3-design.teek.top/api/users").then(res => {
console.log(res);
});
// 不走跨域代理
axios.get("http:///vue3-design.teek.top/users").then(res => {
console.log(res);
});
Axios 配置
Teek 默认创建的 Axios 实例的 baseURL
默认读取环境变量文件 .env.*
的 VITE_API_URL
:
export const http = createRequest({
options: {
baseURL: import.meta.env.VITE_API_URL,
},
});
基本使用
Teek 封装了六种方法,基本满足请求的场景。
import { http } from "@/common/http";
// 发送 GET 请求
const userData = await http.get("/user/1");
// 发送 POST 请求
const newUser = await defaultRequest.post("/user", { name: "John", age: 30 });
// 发送 PUT 请求
const updatedUser = await http.put("/user/1", { name: "John Doe", age: 31 });
// 发送 DELETE 请求
await http.delete("/user/1");
// 发送 DOWNLOAD 请求
await http.download("/user/1/avatar", "avatar.png");
// 完整写法
http.request({
url: "/test",
method: "post",
data: {
name: "Teek",
age: 18,
},
});
添加数据的 TS 返回类型:
import http from "@/common/http";
interface User {
name: string;
age: number;
}
const userData = await http.get<User>("/user/1");
const userData = http.request<User>({
url: "/test",
method: "post",
data: {
name: "Teek",
age: 18,
},
});
创建自定义实例
Teek 默认提供了 http
实例,当该实例无法满足场景时,可以创建自定义实例:
基础创建
import { createRequest } from "@/common/http";
// 创建一个自定义配置的实例
const customRequest = createRequest({
options: {
baseURL: "https://api.example.com",
timeout: 5000,
},
});
// 使用自定义实例
const data = await customRequest.get("/users");
配置拦截器
import { createRequest } from "@/common/http";
const request = createRequest({
options: {
baseURL: "https://api.example.com",
},
interceptors: {
// 请求前处理
onRequest: config => {
// 可以在这里添加 token 等认证信息
// config.headers.Authorization = `Bearer ${token}`;
return config;
},
// 请求错误处理
onRequestError: error => {
console.error("请求错误:", error);
return Promise.reject(error);
},
// 响应处理
onResponse: response => {
// 可以在这里统一处理响应数据
return response.data;
},
// 响应错误处理
onResponseError: error => {
console.error("响应错误:", error);
return Promise.reject(error);
},
},
});
配置全局处理器
import { createRequest } from "../request";
const request = createRequest({
options: {
baseURL: "https://api.example.com",
},
interceptors: {},
handlers: {
// 显示加载动画
showLoading: () => {
// 显示加载动画的实现
console.log("自定义显示加载动画");
},
// 隐藏加载动画
hideLoading: () => {
// 隐藏加载动画的实现
console.log("自定义隐藏加载动画");
},
// 消息提示
showMessage: (msg, type = "info") => {
// 消息提示的实现
console.log(`显示${type}消息:`, message);
},
// 用户登出处理
logout: () => {
console.log("用户登出");
},
// 添加错误日志
resolveError: (error: any) => {
// 错误处理
console.log("错误处理:", error);
},
// 刷新 token
refreshToken: async () => {
// 刷新 token 的实现
console.log("尝试刷新 token");
// 这里应该实现实际的刷新逻辑
// 返回 true 表示刷新成功,false 表示刷新失败
return false;
},
},
});
功能使用
Teek 默认集成了常用的请求功能,这些功能都是通过配置项开启。
ContentType 参数
Teek 对 ContentType 进行了一些封装,提供了关键词 contentType
来快速修改 ContentType,该关键词目前接收 7 个参数:
json
:请求头为application/json;charset=UTF-8
form
:请求头为application/x-www-form-urlencoded;charset=UTF-8
file
:请求头为application/form-data;charset=UTF-8
multipart
:请求头为multipart/form-data;charset=UTF-8
text
:请求头为text/plain;charset=UTF-8
xml
:请求头为application/xml;charset=UTF-8
stream
:请求头为application/octet-stream
如果不填写 contentType
,则默认是 json
。
import { http } from "@/common/http";
http.get("/test", { name: "Teek", age: 18 }, { contentType: "form" });
http.post("/test", { name: "Teek", age: 18 }, { contentType: "form" });
http.request({
url: "/test",
method: "post",
data: { name: "Teek", age: 18 },
contentType: "form",
});
GET 数组封装
Teek 经历过这样一个场景,发生 GET 请求时后台不接受数组作为参数,而是需要在 URL 后面进行拼接来形成数组:
不接受如下:
import { http } from "@/common/http";
http.request({
url: "/test",
method: "get",
params: {
arr: [1, 2, 3],
},
});
只接收如下:
import http from "@/common/http";
http.request({
url: "https://vue3-design.teek.cn/test?arr=1&arr=2&arr=3",
method: "get",
});
针对这个场景,Teek 进行了封装,满足开发人员传递的 依然是数组。
在发送请求时,在 axios
配置中传入关键词 paramsType
,该类型有如下值:
brackets
:ids[]=1&ids[]=2&ids[]=3
comma
:ids=1,2,3
indices
:ids[0]=1&ids[1]=2&ids[]=3
repeat
:ids=1&ids=2&ids=3
上述说问题是 repeat
类型,即 ids=1&ids=2&ids=3
。
import { http } from "@/common/http";
http.get("/test", { arr: [1, 2, 3] }, { paramsType: "repeat" });
http.request({
url: "https://vue3-design.teek.cn/test",
method: "get",
params: {
arr: [1, 2, 3],
},
paramsType: "repeat",
});
Loading 封装
如果发送请求时,需要显示全局 loading
黑屏加载功能,则在 axios
配置中传入关键词 loading: true
来控制显示 loading
import { http } from "@/common/http";
http.get("/test", { arr: [1, 2, 3] }, { loading: true });
export const api = () => {
http.request({
url: "/generic/api",
// ...
loading: true,
});
};
这样当请求 api
的时候,将全屏显示 Loading,直到请求结束。
防止重复请求
为了避免请求重复,Teek 内置了 cancel
机制,你可以在请求配置里添加 cancel
参数,Teek 会自动取消重复的请求。
import { http } from "@/common/http";
http.get("/test", { arr: [1, 2, 3] }, { cancel: true });
export const api = () => {
http.request({
url: "/generic/api",
// ...
cancel: true,
});
};
响应数据格式
Teek 提供关键词 responseReturn
来决定响应数据返回的格式:
raw
: 原始的AxiosResponse
,包括headers
、status
等,不做是否成功请求的检查body
: 返回响应数据的 Body 部分(只会根据status
检查请求是否成功,忽略对code
的判断,这种情况下应由调用方检查请求是否成功)data
: 解构响应的Body
数据,只返回其中的data
节点数据(会检查status
和code
是否为成功状态)
默认为 data
。
import { http } from "@/common/http";
http.get("/test", { arr: [1, 2, 3] }, { responseReturn: "body" });
export const api = () => {
http.request({
url: "/generic/api",
// ...
responseReturn: "body",
});
};
使用缓存机制
缓存机制仅针对 GET
请求。
// 可以使用自己创建的 Axios 实例
import { http } from "@/common/http";
// 启用缓存的 GET 请求,内部自动生成缓存 key
const cachedData = await http.get("/user/profile", null, {
cache: {
enabled: true,
ttl: 60000, // 缓存 1 分钟
},
});
// 使用自定义缓存键
const cachedData2 = await http.get(
"/user/list",
{ page: 1 },
{
cache: {
enabled: true,
key: "user_list_page_1",
},
}
);
// 清除缓存
http.clearCache(); // 清除所有缓存
http.clearCacheByKey("user_list_page_1"); // 清除特定缓存
使用重试机制
import { http } from "@/common/http";
// 配置重试机制
const data = await http.get("/user/profile", null, {
retry: {
count: 3, // 重试 3 次
delay: 1000, // 每次重试间隔 1 秒
exponentialBackoff: true, // 指数退避
retryStatusCodes: [408, 429, 500, 502, 503, 504], // 需要重试的状态码
},
});
监控上传/下载进度
onUploadProgress
用于监控文件上传进度的回调函数,接收一个 AxiosProgressEvent
参数,包含以下属性:
loaded
: 已上传的字节数total
: 总字节数(如果服务器提供了Content-Length
头)progress
: 上传进度 (0-1)
onDownloadProgress
用于监控文件下载进度的回调函数,接收一个 AxiosProgressEvent
参数,包含以下属性:
loaded
: 已下载的字节数total
: 总字节数(如果服务器提供了Content-Length
头)progress
: 下载进度 (0-1)
注意:在某些情况下,服务器可能不会提供 total
字节数,这时需要根据具体情况处理。
import { http } from "@/common/http";
// 监控文件上传进度
const uploadData = await http.post("/upload", formData, {
contentType: "multipart",
onUploadProgress: progressEvent => {
// 处理上传进度
const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total!);
console.log(`上传进度: ${progress}%`);
},
});
// 监控文件下载进度
const downloadData = await http.get("/download/file", null, {
responseType: "blob",
onDownloadProgress: progressEvent => {
// 处理下载进度
const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total!);
console.log(`下载进度: ${progress}%`);
},
});
全局开启/关闭
在创建自定义实例的时候,可以传入这些关键词来默认开启或关闭对应的功能:
import { createRequest } from "@/common/http";
// 创建一个自定义配置的实例
const customRequest = createRequest({
options: {
baseURL: "https://api.example.com",
timeout: 5000,
loading: true,
cancel: true,
cache: {
enabled: true,
ttl: 60000, // 缓存 1 分钟
},
retry: {
count: 3, // 重试 3 次
delay: 1000, // 每次重试间隔 1 秒
exponentialBackoff: true, // 指数退避
retryStatusCodes: [408, 429, 500, 502, 503, 504], // 需要重试的状态码
},
},
});
// 使用自定义实例
const data = await customRequest.get("/users");
// 请求时传的参数优先级更高
const data = await customRequest.get("/users", {}, { loading: false });
提示
请求时传的参数优先级更高。
请求异常封装
Teek 针对 Axios 发生的请求异常进行捕获并在页面上提示,如果开启了错误日志,则将异常存放到错误日志。
配置选项
RequestConfigOptions
创建请求实例时的配置选项:
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
baseURL | string | / | 基础 URL |
timeout | number | 10000 | 超时时间(毫秒) |
withCredentials | boolean | true | 是否携带凭证 |
... | 支持所有的 InternalRequestConfig 类型 | ... | ... |
RequestInterceptors
请求拦截器配置:
属性 | 类型 | 描述 |
---|---|---|
onRequest | function | 请求前处理 |
onRequestError | function | 请求错误处理 |
onResponse | function | 响应处理 |
onResponseError | function | 响应错误处理 |
GlobalHandlers
全局处理器配置:
属性 | 类型 | 描述 |
---|---|---|
showLoading | function | 显示加载动画 |
hideLoading | function | 隐藏加载动画 |
showMessage | function | 消息提示 |
logout | function | 用户登出处理 |
resolveError | function | 错误处理 |
refreshToken | function | 刷新 Token |
RequestInstanceConfig
请求实例配置(创建实例时使用):
属性 | 类型 | 描述 |
---|---|---|
options | RequestConfigOptions | 请求配置选项 |
interceptors | RequestInterceptors | 请求拦截器配置 |
handlers | GlobalHandlers | 全局处理器配置 |
InternalRequestConfig
发送请求时的配置选项:
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
loading | boolean | true | 是否显示全局 loading |
cancel | boolean | true | 是否取消重复请求 |
contentType | string | json | Content-Type 类型 |
paramsType | string | undefined | GET 参数序列化类型 |
responseReturn | string | data | 响应数据处理方式 |
cache | object | undefined | 缓存配置 |
retry | object | undefined | 重试配置 |
ContentType 选项
json
:application/json;charset=UTF-8
form
:application/x-www-form-urlencoded;charset=UTF-8
file
:application/form-data;charset=UTF-8
multipart
:multipart/form-data;charset=UTF-8
text
:text/plain;charset=UTF-8
xml
:application/xml;charset=UTF-8
stream
:application/octet-stream
ParamsType 选项
brackets
:ids[]=1&ids[]=2&ids[]=3
comma
:ids=1,2,3
indices
:ids[0]=1&ids[1]=2&ids[2]=3
repeat
:ids=1&ids=2&ids=3
Response 选项
raw
:返回原始的AxiosResponse
body
:返回响应数据的Body
部分data
:返回响应数据中的data
字段
Cache 配置 CacheConfig
属性 | 类型 | 必需 | 描述 |
---|---|---|---|
enabled | boolean | 是 | 是否启用缓存 |
ttl | number | 否 | 缓存时间(毫秒) |
key | string | 否 | 自定义缓存键 |
Retry 配置 RetryConfig
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
count | number | 无默认值 | 重试次数 |
delay | number | 1000 | 重试延迟(毫秒) |
exponentialBackoff | boolean | false | 是否使用指数退避 |
retryStatusCodes | number[] | [408, 429, 500, 502, 503, 504] | 需要重试的 HTTP 状态码 |