背景
- 刚入行时,我们的接口文档基本上都是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 请求,使用方式如下:
- 下载
@uni-helper/axios-adapternpm包 - 修改
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
- 打开 Apifox 桌面客户端
- 选择需要查阅 API 文档的服务,点击进入
- 点击服务左侧工具栏目中的
项目设置 - 点击
导出数据 - 选择 OpenAPI Spec 版本:
OpenAPI3.0,文件格式:JSON,包含 Apifox 扩展的 OpenAPI 字段(x-apifox-***):包含,将 API 文档的目录,作为 Tags 字段导出:否 - 点击
打开URL按钮,会生成临时的接口文档链接:http://127.0.0.1:4523/export/openapi/2?version=3.0 - 修改
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[];
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[];