Skip to content

请求

介绍

Teek 基于 Axios 封装了一套开箱即用的 http 请求。包括请求 Loading、错误处理、取消重复请求等功能,在 src/common/http 目录下。

Axios 中文文档

特性

  • 多实例支持:可以创建多个具有不同配置的请求实例
  • 重复请求取消:自动取消重复的请求
  • 请求/响应拦截:提供完整的拦截器机制
  • 全局 Loading:自动显示和隐藏全屏 Loading
  • Content-Type 快速设置:提供多种 Content-Type 快速设置方式
  • 参数序列化:支持多种参数序列化格式
  • 响应数据处理:支持多种响应数据处理方式
  • 可配置的全局处理器:支持自定义消息提示、路由跳转、用户登出等处理
  • Token 自动刷新:支持配置自动刷新 Token 机制
  • 请求缓存:支持 GET 请求缓存机制
  • 请求重试:支持失败请求自动重试
  • 进度监控:支持上传/下载进度监控
  • 高度可定制:可以轻松适配各种项目需求

本地跨域处理

大部分本地开发环境会涉及与后端的跨域问题,可以按照下面指示配置来解决跨域问题:

配置本地请求域名

.env.development 文件中添加了 VITE_API_URL 变量,用于本地访问链接域名。

sh
# 本地环境接口地址
VITE_API_URL = '/api'

配置开发服务器代理

vite.config.mts 文件中配置跨域代理

ts
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/usersproxy.rewrite/api 替换为空)

如:

ts
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

ts
export const http = createRequest({
  options: {
    baseURL: import.meta.env.VITE_API_URL,
  },
});

基本使用

Teek 封装了六种方法,基本满足请求的场景。

ts
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 返回类型:

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 实例,当该实例无法满足场景时,可以创建自定义实例:

基础创建

ts
import { createRequest } from "@/common/http";

// 创建一个自定义配置的实例
const customRequest = createRequest({
  options: {
    baseURL: "https://api.example.com",
    timeout: 5000,
  },
});

// 使用自定义实例
const data = await customRequest.get("/users");

配置拦截器

ts
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);
    },
  },
});

配置全局处理器

ts
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

ts
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 后面进行拼接来形成数组:

不接受如下:

ts
import { http } from "@/common/http";

http.request({
  url: "/test",
  method: "get",
  params: {
    arr: [1, 2, 3],
  },
});

只接收如下:

ts
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,该类型有如下值:

  • bracketsids[]=1&ids[]=2&ids[]=3
  • commaids=1,2,3
  • indicesids[0]=1&ids[1]=2&ids[]=3
  • repeatids=1&ids=2&ids=3

上述说问题是 repeat 类型,即 ids=1&ids=2&ids=3

ts
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

ts
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 会自动取消重复的请求。

ts
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,包括 headersstatus 等,不做是否成功请求的检查
  • body: 返回响应数据的 Body 部分(只会根据 status 检查请求是否成功,忽略对 code 的判断,这种情况下应由调用方检查请求是否成功)
  • data: 解构响应的 Body 数据,只返回其中的 data 节点数据(会检查 statuscode 是否为成功状态)

默认为 data

ts
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 请求。

ts
// 可以使用自己创建的 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"); // 清除特定缓存

使用重试机制

typescript
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 字节数,这时需要根据具体情况处理。

ts
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}%`);
  },
});

全局开启/关闭

在创建自定义实例的时候,可以传入这些关键词来默认开启或关闭对应的功能:

ts
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

创建请求实例时的配置选项:

属性类型默认值描述
baseURLstring/基础 URL
timeoutnumber10000超时时间(毫秒)
withCredentialsbooleantrue是否携带凭证
...支持所有的 InternalRequestConfig 类型......

RequestInterceptors

请求拦截器配置:

属性类型描述
onRequestfunction 请求前处理
onRequestErrorfunction 请求错误处理
onResponsefunction 响应处理
onResponseErrorfunction 响应错误处理

GlobalHandlers

全局处理器配置:

属性类型描述
showLoadingfunction 显示加载动画
hideLoadingfunction 隐藏加载动画
showMessagefunction 消息提示
logoutfunction 用户登出处理
resolveErrorfunction 错误处理
refreshTokenfunction 刷新 Token

RequestInstanceConfig

请求实例配置(创建实例时使用):

属性类型描述
optionsRequestConfigOptions请求配置选项
interceptorsRequestInterceptors请求拦截器配置
handlersGlobalHandlers全局处理器配置

InternalRequestConfig

发送请求时的配置选项:

属性类型默认值描述
loadingbooleantrue是否显示全局 loading
cancelbooleantrue是否取消重复请求
contentTypestringjsonContent-Type 类型
paramsTypestringundefinedGET 参数序列化类型
responseReturnstringdata响应数据处理方式
cacheobject undefined缓存配置
retryobject undefined重试配置

ContentType 选项

  • jsonapplication/json;charset=UTF-8
  • formapplication/x-www-form-urlencoded;charset=UTF-8
  • fileapplication/form-data;charset=UTF-8
  • multipartmultipart/form-data;charset=UTF-8
  • texttext/plain;charset=UTF-8
  • xmlapplication/xml;charset=UTF-8
  • streamapplication/octet-stream

ParamsType 选项

  • bracketsids[]=1&ids[]=2&ids[]=3
  • commaids=1,2,3
  • indicesids[0]=1&ids[1]=2&ids[2]=3
  • repeatids=1&ids=2&ids=3

Response 选项

  • raw:返回原始的 AxiosResponse
  • body:返回响应数据的 Body 部分
  • data:返回响应数据中的 data 字段

Cache 配置 CacheConfig

属性类型必需描述
enabledboolean是否启用缓存
ttlnumber缓存时间(毫秒)
keystring自定义缓存键

Retry 配置 RetryConfig

属性类型默认值描述
countnumber无默认值重试次数
delaynumber1000重试延迟(毫秒)
exponentialBackoffbooleanfalse是否使用指数退避
retryStatusCodesnumber[][408, 429, 500, 502, 503, 504]需要重试的 HTTP 状态码