npm package: @itshixun/qst-request-lib
Description: QST接口请求工具库
Author: Mutueye
Latest version: 1.1.12

# @itshixun/qst-request-lib

QST接口请求工具库

## 1. 常用类型和枚举定义：

- QstResult: 约定的数据响应类型
  ```typescript
  /** 约定的常规接口返回数据结构体 */
  export interface QstResult<T> {
    /** http状态码 3位数 比如200、401、500等 */
    status: number;
    /** 扩展状态码 7位数 比如2000000 4010000 4000297 5000000等 */
    code: number;
    /** 成功标识 */
    success?: boolean;
    /** 消息 */
    message?: string;
    /** 返回的数据 */
    data?: T;
    /** 额外的返回数据，比如用户中心的extras.failureCount，返回登录错误的次数 */
    extras?: Record<string, unknown>;
  }
  ```
- QstPagination: 约定的分页数据类型
  ```typescript
  /** 约定的分页数据结构 */
  export interface QstPagination<T> {
    /** 总条数 */
    total: number;
    offset: number;
    limit: number;
    /** 当前页码 */
    pageNumber: number;
    /** 每夜条数 */
    pageSize: number;
    /** 数据数组 */
    rows: T[];
  }
  ```
- ContentTypeEnum: ContentType类型枚举
  ```typescript
  /** request contentType */
  export enum ContentTypeEnum {
    /** json */
    JSON = 'application/json;charset=UTF-8',
    /** form-data qs */
    FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
    /** form-data upload */
    FORM_DATA = 'multipart/form-data;charset=UTF-8',
  }
  ```

## 2. aioxs请求封装方法：requestWrapper

封装常规axios请求方法：requestWrapper：提炼通用的响应处理和错误提示，剥离AxiosResponse\<T\>封装并返回T类型的数据，业务端通常不需要再进行Promise.catch，仅需要处理必要的响应结果，除非在处理响应结果时有可能抛出业务逻辑相关错误时，再通过Promise.catch进行处理。

### 2.1 选项设置

在使用requestWrapper方法前，首先要进行选项设置。请求封装方法的选项类型RequestWrapperOption定义如下：

```typescript
/** 请求封装方法的选项类型定义 */
export interface RequestWrapperOption<T = any> {
  /**
   * 自定义错误处理方法，仅在需要特殊处理的接口请求时使用此方法，作为requestWrapper中默认错误处理方法的补充
   * 要求保持返回的错误类型仍为AxiosError，保证错误信息在Promise链中的延续和可追溯
   */
  resolveError?: (err: AxiosError<T>) => AxiosError<T>;
  /** 显示错误消息提示的方法 */
  handleMessage?: (msg: string, err?: AxiosError<T>) => void;
  /** 错误已处理标志位，默认AxiosError.code === 'handled'时，表示该错误已处理，后续错误处理中判断code==='handled'时，不再弹窗错误提示，避免重复弹出 */
  handledCode?: string;
  /** 处理401(需要登录)的方法 */
  handle401?: (err: AxiosError<T>) => void;
  /** 处理403(无权限需要登录)的方法 */
  handle403?: (err: AxiosError<T>) => void;
  /** aixos实例，使用qstRequest的get/post等便捷请求别名时，需要传入该实例，否者默认使用aixos静态实例 */
  axiosInstance?: AxiosInstance;
}
```

我们可以通过setDefaultRequestWrapperOption设置requestWrapper方法的全局默认选项，比如：

```typescript
setDefaultRequestWrapperOption({
  /** 使用element-plus的Message组件弹出错误提示消息 */
  handleMessage: (msg: string) => ElMessage.error(msg),
  /** 默认error.code 为 handled 时，表示错误已处理，不需要再弹出提示 */
  handledCode: 'handled',
  /** 401时跳转到登录页 */
  handle401: () => router.replace({ name: 'login' }),
  /** 403时无权限，跳转到登录页 */
  handle403: () => {
    ElMessage.error('当前登录角色无权限，请重新登录');
    logout();
    router.replace({ name: 'login' });
  },
  /** 常规的请求错误在requestWrapper中已做处理，一般不需要此处自定义resolveError方法，这里可以不做设置 */
  resolveError: (err: AxiosError<unknown>) => err,
  /** 设置qstRequest使用的默认axios实例 */
  axiosInstance: mainInstance,
});
```

### 2.2 调用方法

一个常规的axios请求流程，大致如下：

```typescript
/** axios请求示例 */
axios
  .get<QstResult<ResData>>('/api/xxx')
  .then((res: AxiosResponse<QstResult<ResData>>) => {
    // 处理返回数据，取得res.data.data
    const data = getResult(res);
    return data;
  })
  .catch((err: AxiosError<QstResult<ResData>>) => {
    // 处理错误信息，弹出错误消息
    handleError(err);
    return Promise.reject(err);
  });
```

我们通过requestWrapper方法，提炼通用的响应处理和错误提示，剥离AxiosResponse\<T\>封装并返回T类型的数据，业务端通常不需要再进行Promise.catch，仅需要处理必要的响应结果，除非在处理响应结果时有可能抛出业务逻辑相关错误时，才通过Promise.catch进行处理。上面的方法，使用requestWrapper方法的示例如下：

```typescript
/** 使用requestWrapper封装请求，此处使用默认设置，不再传入RequestWrapperOption选项 */
requestWrapper<QstResult<ResData>>(() => axios.get('/api/xxx')).then((res: QstResult<ResData>) => {
  return res.data;
});
```

上面的例子，经过requestWrapper处理后，Promise.then可以直接取得剥离AxiosResponse封装的响应数据。同时所有的常见请求错误，都已经在requestWrapper内部做了处理，如果确定Promise.then中不会抛出业务相关的错误，就不需要再使用Promise.catch进行错误处理了。

针对具体的接口，我们也可以传入专门针对该接口的RequestWrapperOption选项，从而使不同的接口可以获得各自不同的处理方式。比如在请求登录接口时，如果返回401说明登录接口本身的后端逻辑出错了，我们给出一个服务端错误的提示；另外我们希望登录接口使用原生alert的方式提醒用户。我们可以像下面这样，调用requestWrapper方法时传入RequestWrapperOption选项：

```typescript
requestWrapper<QstResult<ResData>>(
  () => axios.post('/api/login', { loginData }),
  // 传入RequestWrapperOption，会通过Object.assign合并默认的RequestWrapperOption
  {
    handle401: () => alert('服务端错误：登录接口返回401!'),
    handleMessage: (msg: string) => alert(msg),
  }
).then((res) => {
  // 保存登录token
  setToken(res.data.accessToken);
  // 登录成功的后续处理...
});
```

通过setDefaultRequestWrapperOption设置全局选项，并且具体接口也可以在调用requestWrapper时传入不同的配置选项，可以实现：大部分接口请求使用统一的错误处理/错误提醒/401登录跳转等方法的同时，部分特殊接口使用自定义的错误处理/错误提醒/401状态处理方法。

## 3 仿aixos的请求别名

基于requestWrapper方法，我们构建了qstRequest对象，进一步封装了请求别名(get/post等)方法。我们可以像使用axios的便捷请求别名方法一样，以更简洁和符合使用习惯的方式进行接口请求。比如上面[2.2 调用方法](#22-调用方法)的示例：

```typescript
/** 使用requestWrapper封装请求，此处使用默认设置，不传入RequestWrapperOption选项 */
requestWrapper<QstResult<ResData>>(() => axios.get('/api/xxx'));
```

我们也可以使用便捷请求别名进行请求，如下所示：

```typescript
/** 使用qstRequest.get请求别名，此处使用默认设置，不传入RequestWrapperOption选项 */
qstRequest.get<QstResult<ResData>>('/api/xxx');
```

上述示例使用全局设置的默认RequestWrapperOption选项。如果某个特定接口使用另外的aixos实例进行请求，我们可以如下例这样，传入RequestWrapperOption选项，针对具体请求进行特殊配置：

```typescript
qstRequest.post<QstResult>('/api/xxx/save', data, config, {
  // 针对本接口，使用名为"mySaveInstance"的axios实例进行请求
  axiosInstance: mySaveInstance,
  // 配置本接口不弹出错误提示消息，仅在控制台打印错误信息
  handleMessage: (msg: string) => {
    console.error(msg);
  },
});
```

`注意`：axios中AxiosInstance的定义，在v1.5.x和1.6.x中不同，因此如果您使用typescript开发，在配置RequestWrapperOption的axiosInstance时，需要安装最新的axios版本(目前 1.6.1)，才可保证ts类型正确。

### qstRequest的请求方式别名列表

```typescript
qstRequest: {
  get<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>, option?: RequestWrapperOption<T>): Promise<T>;
  delete<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>, option?: RequestWrapperOption<T>): Promise<T>;
  post<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig, option?: RequestWrapperOption<T>): Promise<T>;
  put<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig, option?: RequestWrapperOption<T>): Promise<T>;
  patch<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig, option?: RequestWrapperOption<T>): Promise<T>;
  postForm<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig, option?: RequestWrapperOption<T>): Promise<T>;
  putForm<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig, option?: RequestWrapperOption<T>): Promise<T>;
  patchForm<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig, option?: RequestWrapperOption<T>): Promise<T>;
}
```

## 4 取得Axios原始响应数据AxiosResponse

上述工具(requestWrapper和qstRequest)帮助我们剥离AxiosResponse<T>结构，直接返回T类型的数据，方便进行后续的数据处理；但是有些特殊情况下需要获取完整的AxiosResponse数据，比如要获取 response headers 中某个字段的值。这里我们通过requestWrapperRaw的方法，或者qstRequestRaw对象的get/post等请求别名，来获取Axios的原始响应数据：

```typescript
/** 使用requestWrapperRaw封装请求，返回原始的Axios响应数据，即AxiosResponse<ResponseData>类型的数据 */
requestWrapperRaw<ResponseData>(() => axios.get('/api/xxx'));

/** 同样，使用qstRequestRaw下定义的请求方法别名，返回原始Axios响应数据 */
qstRequestRaw.get<ResponseData>('/api/xxx');
```