项目背景
背景介绍
在前端开发过程中,后端提供了众多接口,但后端接口的类型和路径时常变动,这给前端开发带来了极大的不确定性和挑战。由于无法及时得知后端的具体变更情况,往往导致开发过程中出现难以察觉的错误,严重影响开发效率和项目质量。
为了解决这一问题,我们期望引入一个强大的框架。该框架能够根据后端提供的 OpenAPI 3 文档,自动生成对应的接口和类型定义。这样一来,当后端的类型发生变化时,TypeScript 会立即报错,使得开发者能够快速定位错误所在之处。
然而,我们并不希望生成的请求都局限于 umi 格式或 axios 格式。因此,我们计划安装特定的依赖,通过配置模板文件和配置文件,编写自己的模版,以适应我们所使用的特定前端框架。如此,我们只需进行简单的配置,即可实现高效、准确的接口调用和数据处理,极大地提高前端开发的效率和稳定性。
方案调研
目前在这方面做的比较好的是Umi提供的OpenAPI插件,项目地址https://github.com/chenshuai2144/openapi2typescript,如果你使用 umi ,你可以使用@umijs/plugin-openapi 插件。
一些博客:
对于umi的这个插件存在的一些痛点:
- umi提供的这个插件不能自定义修改模版内容。
- 如果后端Java提供的tag为中文,生成的接口名称是拼音,而我们更希望是xxxController的格式。
- umi只提供了umi-request插件的模版,然而我们实际开发中可能用axios的更多,所以希望能够支持更多的模版
基于这些问题,我决定对umi-openapi的源码进行改造,适用自己的系统开发,同时也可以让用户自定义模版,适用于各自的系统。
OpenAPI规范研究
基本结构
接下来会一层一层的研究OpenAPI的结构:
{
"openapi": "3.0.1", // OpenAPI 规范的版本
"info": { // API 的基本信息
"title": "代码精灵1.0 接口文档", // API 文档的标题
"description": "代码精灵接口文档", // API 文档的描述
"contact": { // 联系人信息
"name": "蔡徐坤", // 联系人姓名
"url": "http://yunfei.plus", // 联系人网址
"email": "666@163.com" // 联系人邮箱
},
"version": "1.0" // API 的版本号
},
"servers": [ // 服务器信息
{
"url": "''", // 服务器的 URL
"description": "Generated server url" // 服务器的描述
}
],
"security": [ // 安全信息
{
"x-access-token": [] // 访问令牌的安全配置
}
],
"paths": { // API 路径
xxx // API 路径的具体定义
},
"components": { // 组件信息
xxx // 组件的具体定义
},
"x-openapi": { // 扩展的 OpenAPI 信息
"x-markdownFiles": [] // Markdown 文件的列表
}
这里面都是一些基本的信息,对我们本次开发来说,最有用的是paths和components两个字段
paths
简单post请求
"paths": {
"/userRole/queryPage": {
"post": {
"tags": [
"role-controller"
],
"summary": "分页查询用户和角色关联表数据 @author houyunfei",
"operationId": "queryUserRoleByPage",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserRoleQueryForm"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "OK",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/BaseResponsePageResultUserRoleVO"
}
}
}
}
}
}
},
解释:
-
/userRole/queryPage
:接口的具体路径。 -
post
:表示这是一个 POST 请求。-
tags
:用于对操作分类,说明是某个类下的接口。 -
summary
:简短描述接口功能,通常提供对接口的快速理解。 -
operationId
:操作的唯一标识符,用于在代码中标记或调用这个接口。 -
requestBody
:定义 POST 请求的请求体,包括内容类型和数据结构引用。content
:指定请求体的内容类型,通常为application/json
。schema
:使用$ref
来引用UserRoleQueryForm
数据结构,这表示请求体需要符合该模式。
required
:指定请求体是否为必填项,值为true
表示此请求体是必须的。
-
responses
:定义接口的响应,包括状态码、描述和返回数据的结构引用。200
:表示成功时返回的 HTTP 状态码。description
:描述成功响应的含义,如 "OK"。content
:定义响应体的内容类型。*/*
:支持任何类型的响应格式。schema
:使用$ref
引用BaseResponsePageResultUserRoleVO
数据结构,表示响应体的数据格式必须符合该结构。
-
带有路径参数
带有路径参数的:
"/user/public/resetPassword/{link}": {
"post": {
"parameters": [
{
"name": "link",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
]
}
},
parameters
:定义路径中的参数,这里的参数是 {link}
,表示动态路径参数。
name
:参数的名称,这里是link
,它出现在路径中。in
:表示参数的位置,这个比较重要,这里为path
,即路径参数。required
:指定参数是否为必填项,true
表示该参数是必须的。schema
:定义参数的数据类型,这里是string
,表示link
参数是字符串类型。
get请求参数
"/user/query": {
"get": {
"parameters": [
{
"name": "userId",
"in": "query",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
]
}
},
这里和上面的区别是in
字段:
路径参数 (path
):
- 参数出现在 URL 路径中,如
/user/public/resetPassword/{link}
。参数通过路径的一部分传递,如/user/public/resetPassword/abc123
。 - 定义时,
in
的值为"path"
,表示参数来自 URL 路径的一部分。 - 路径参数通常是必填项。
查询参数 (query
):
- 参数出现在 URL 的查询字符串部分,通常跟在
?
后,如/user/query?userId=123
。 - 定义时,
in
的值为"query"
,表示参数来自 URL 的查询部分。 - 查询参数可以是可选或必填,根据
required
的值来定义。
带有文件的
"/submission/add": {
"post": {
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"required": [
"file"
],
"type": "object",
"properties": {
"submissionAddForm": {
"$ref": "#/components/schemas/SubmissionAddForm"
},
"file": {
"type": "string",
"format": "binary"
}
}
}
}
}
}
},
这里的不同点在于 requestBody
使用了 multipart/form-data
,并且有文件上传的支持:
-
multipart/form-data
:- 表示请求体的内容类型为多部分表单数据,通常用于上传文件时。
-
properties
:- 定义了请求体中的不同字段:
submissionAddForm
:引用了SubmissionAddForm
,这是一个复杂的数据结构。file
:类型为string
,格式为binary
,表示这是一个二进制文件,支持文件上传。
- 定义了请求体中的不同字段:
-
format: binary
:- 用于表示
file
是二进制数据,也就是上传文件的字段。
- 用于表示
components
对components进行细分
字段
{
"components": { // 组件部分
"schemas": {}, // 模式定义
"securitySchemes": { // 安全方案
"x-access-token": { // 访问令牌
"type": "apiKey", // 类型为API密钥
"name": "x-access-token", // 密钥名称
"in": "header" // 在请求头中
}
}
}
}
这里面比较重要的是schemas字段:https://openapi.apifox.cn/#schema-%E5%AF%B9%E8%B1%A1
解释
从中选取一些比较经典的进行讲解,我们后续的代码生成都是从这里进行生成:
比较简单的结构:
"SortItem": {
"required": [ // 必填的字段
"column",
"isAsc"
],
"type": "object", // 定义为对象类型
"properties": {
"isAsc": {
"type": "boolean", // 表示该字段为布尔类型
"description": "true正序|false倒序" // 描述该字段的作用
},
"column": {
"type": "string", // 表示该字段为字符串类型
"description": "排序字段" // 描述该字段的作用
}
},
"description": "排序字段集合" // 描述整个对象的作用
},
稍微多一点:
"UserRoleQueryForm": {
"required": [
"pageNumber",
"pageSize"
],
"type": "object",
"properties": {
"pageNumber": {
"type": "integer",
"description": "页码(不能为空)",
"format": "int64",
"example": 1
},
"pageSize": {
"maximum": 500,
"type": "integer",
"description": "每页数量(不能为空)",
"format": "int64"
},
"sortItemList": {
"maxItems": 10,
"minItems": 0,
"type": "array",
"description": "排序字段集合",
"items": {
"$ref": "#/components/schemas/SortItem"
}
}
}
},
在每个字段中,可能还会存在format
等字段,都很好理解,这里比较重要的是item
字段:
在 OpenAPI 规范中,当 “type”
为 “array”
(数组类型)时,“items”
字段用于指定数组中元素的类型。这里表示这个数组中的元素是引用自“#/components/schemas/SortItem”
所定义的数据结构。也就是说,这个数组将包含一个或多个 “SortItem”
类型的元素。
有的item
就不是引用,而是基本类型,例如:
"IdListForm": {
"type": "object",
"properties": {
"ids": {
"type": "array",
"items": {
"type": "integer",
"format": "int64"
}
}
}
},