有接口文档的项目,建议不要再手写ts, axios, uni.request, taro.request了

720 阅读4分钟

背景

  • 刚入行时,我们的接口文档基本上都是txt、wps、postman手写,但是其实后端也不愿意写,更不愿意测,接口文档也不会和代码同步
  • 后来出来了各种 api 规范,比较出名的就是 swagger/openapi 规范了,后端按这些规范实现代码注解就可以生成接口文档,还有接口配置,但是注解有时候忘记更新,因为写注解也是一种额外开销,但是想想可以方便后端自己测试、测试人员测试、前端人员测试,后端咬咬牙也就坚持下来了
  • 可能写注解确实也是一种额外开销,也容易和代码不同步,所以有没有一种直接解析代码,生成接口文档、接口配置的工具呢,apifox + apifox-ide插件或许是这一块做的稍微好点的了吗?
  • 前端是否可以利用接口配置生成各种请求 clent 代码呢,这无疑是可以的,但是很多人都没有这个概念,就连一些出名的开源应用都是手写client,如果是自动生成 + 结合ts提示,可以省去很多维护client的精力,这对前端同学无疑是更友好的,而且后端偷偷改接口也很容易轻易发现
  • 使用 openapi-ts-request 生成ts, axios, uni.request, taro.request 等等请求client, 下面演示 openapi-ts-request 如何使用,感谢大家提 star, pr, issue 一起建设

1. 已有 Swagger/Openapi 接口文档,生成请求client(默认是: axios, 除开演示场景,都需要自行封装 request 函数)

在前端项目根目录新建 openapi-ts-request.config.ts 文件,然后加入以下代码:

import type { GenerateServiceProps } from 'openapi-ts-request';

export default [
  {
    schemaPath: 'https://petstore.swagger.io/v2/swagger.json',
  },
] as GenerateServiceProps[];

package.json 文件的 script 中添加命令: "openapi": "openapi-ts"

生成结果:

npm run openapi

2. 自定义请求 request 函数

import type { GenerateServiceProps } from 'openapi-ts-request';

export default [
  {
    schemaPath: 'https://petstore.swagger.io/v2/swagger.json',
    requestLibPath: '@/core/request/index.ts',
  },
] as GenerateServiceProps[];

package.json 文件的 script 中添加命令: "openapi": "openapi-ts"

生成结果:

npm run openapi

2.1. 基于 axios 封装 request 函数,参考代码如下:

import { notification } from 'antd';
import axios, { AxiosRequestConfig } from 'axios';

import {
  ILoginInfoStorageState,
  defaultLoginInfoStorage,
  loginInfoStorageKey,
} from '@/store';

const BASE_URL = 'https://localhost:port';

const instance = axios.create({
  baseURL: BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
  timeout: 120000, // 超时时间120秒
});

instance.interceptors.response.use(
  (response) => {
    // data解构
    if (response.data) {
      return response.data;
    }
    return response;
  },
  (error) => {
    // 统一错误处理
    if (error.response.status >= 300) {
      notification.error({
        message: error.response.data?.msg,
        duration: 2,
      });
    }
    return Promise.reject(error);
  }
);

instance.interceptors.request.use((config) => {
  const loginInfoStorageStr =
    globalThis.localStorage.getItem(loginInfoStorageKey);
  const loginInfoStorage = loginInfoStorageStr
    ? (JSON.parse(loginInfoStorageStr) as ILoginInfoStorageState)
    : defaultLoginInfoStorage;

  if (loginInfoStorage.state.loginInfo) {
    config.headers.Authorization = loginInfoStorage.state.loginInfo.accessToken;
  }

  return config;
});

const request = async <T = unknown>(
  url: string,
  options: AxiosRequestConfig = {}
) => {
  return await instance.request<T, T>({
    url,
    ...options,
  });
};

export default request;

2.2. 基于 uniapp 封装 request 函数,参考代码如下:

如果我们想在 uniapp 中使用,那么肯定不能用 axios 客户端, 可以基于 uni.request 封装 request 函数(推荐),参考以下代码:

export default async function request(url, options: AxiosRequestConfig = {}) {
  return new Promise((resolve, reject) => {
    const {
      method = 'GET',
      headers = {},
      data = {},
      timeout,
      withCredentials,
      ...otherOptions
    } = options;

    uni.request({
      url,
      method,
      header: headers,
      data,
      timeout,
      withCredentials, // 用于跨域请求时是否携带凭证
      ...otherOptions,
      success: (res) => {
        // 构造符合 uniapp 的响应对象
        const response = {
          data: res.data,
          status: res.statusCode,
          statusText: res.errMsg,
          headers: res.header,
          config: options,
          request: res
        };
        // 根据 HTTP 状态码判断请求是否成功
        if (res.statusCode >= 200 && res.statusCode < 300) {
          resolve(response);
        } else {
          reject(response);
        }
      },
      fail: (error) => {
        // 构造符合 uniapp 错误格式的对象
        const err = {
          message: error.errMsg || 'Request failed',
          config: options,
          request: error
        };
        reject(err);
      }
    });
  });
}

另外一种方式就是使用 @uni-helper/axios-adapter 来将axios请求适配为 uni.request 请求,使用方式如下:

  1. 下载 @uni-helper/axios-adapter npm包
  2. 修改 openapi-ts-request.config.ts 文件为如下代码
import type { GenerateServiceProps } from 'openapi-ts-request';

export default [
  {
    schemaPath: 'https://petstore.swagger.io/v2/swagger.json',
    requestImportStatement: `import request from 'axios';\n
import { createUniAppAxiosAdapter } from '@uni-helper/axios-adapter';\n
request.defaults.adapter = createUniAppAxiosAdapter();`,
  },
] as GenerateServiceProps[];

2.3. 基于 taro 封装 request 函数

参考基于 uniapp 封装 request 函数的思路

3. 已有 Apifox 接口文档,生成请求client

  1. 打开 Apifox 桌面客户端
  2. 选择需要查阅 API 文档的服务,点击进入
  3. 点击服务左侧工具栏目中的 项目设置
  4. 点击 导出数据
  5. 选择 OpenAPI Spec 版本:OpenAPI3.0 ,文件格式:JSON,包含 Apifox 扩展的 OpenAPI 字段(x-apifox-***):包含,将 API 文档的目录,作为 Tags 字段导出:
  6. 点击 打开URL 按钮,会生成临时的接口文档链接:http://127.0.0.1:4523/export/openapi/2?version=3.0
  7. 修改 openapi-ts-request.config.ts 文件为下面的代码
import type { GenerateServiceProps } from 'openapi-ts-request';

export default [
  {
    schemaPath: 'http://127.0.0.1:4523/export/openapi/2?version=3.0',
    requestLibPath: '@/core/request/index.ts',
  },
] as GenerateServiceProps[];

image.png

4. 只生成需要的接口

如果你只需要部分接口,可以使用 allowedTags 参数进行配置,它允许你只生成指定 tags 分组的接口

import type { GenerateServiceProps } from 'openapi-ts-request';

export default [
  {
    schemaPath: 'https://petstore.swagger.io/v2/swagger.json',
    requestLibPath: '@/core/request/index.ts',
    allowedTags: ['pet'],
  },
] as GenerateServiceProps[];

5. 访问 openapi.json 需要 token

如果接口配置文件 openapi.json 需要 token 才能访问,可以配置 authorization 参数,这样获取 openapi.json 会携带上 authorization

import type { GenerateServiceProps } from 'openapi-ts-request';

export default [
  {
    schemaPath: 'https://petstore.swagger.io/v2/swagger.json',
    requestLibPath: '@/core/request/index.ts',
    authorization: 'secret key',
  },
] as GenerateServiceProps[];

6. 接口 path 没有声明网关前缀,然而实际访问接口需要拼接上网关前缀

某些场景中,可能接口都进行了划分了网关,比如:有些接口属于网关 /user,有些接口是属于网关 /manage,有些接口属于网关 /shop,它们的 path 在接口配置上看都没有显式声明属于这些网关,而是需要我们自行拼接 path 前缀,需要使用 apiPrefix 参数进行配置:

export default [
  {
    schemaPath: 'http://127.0.0.1:4523/export/openapi/2?version=3.0',
    requestLibPath: '@/core/request/index.ts',
    apiPrefix: '"/user"',
  },
  {
    schemaPath: 'http://127.0.0.1:4523/export/openapi/3?version=3.0',
    requestLibPath: '@/core/request/index.ts',
    apiPrefix: '"/manage"',
  },
  {
    schemaPath: 'http://127.0.0.1:4523/export/openapi/4?version=3.0',
    requestLibPath: '@/core/request/index.ts',
    apiPrefix: '"/mall"',
  },
] as GenerateServiceProps[];