跳到主要内容

项目背景

背景介绍

在前端开发过程中,后端提供了众多接口,但后端接口的类型和路径时常变动,这给前端开发带来了极大的不确定性和挑战。由于无法及时得知后端的具体变更情况,往往导致开发过程中出现难以察觉的错误,严重影响开发效率和项目质量。

为了解决这一问题,我们期望引入一个强大的框架。该框架能够根据后端提供的 OpenAPI 3 文档,自动生成对应的接口和类型定义。这样一来,当后端的类型发生变化时,TypeScript 会立即报错,使得开发者能够快速定位错误所在之处。

然而,我们并不希望生成的请求都局限于 umi 格式或 axios 格式。因此,我们计划安装特定的依赖,通过配置模板文件和配置文件,编写自己的模版,以适应我们所使用的特定前端框架。如此,我们只需进行简单的配置,即可实现高效、准确的接口调用和数据处理,极大地提高前端开发的效率和稳定性。

方案调研

目前在这方面做的比较好的是Umi提供的OpenAPI插件,项目地址https://github.com/chenshuai2144/openapi2typescript,如果你使用 umi ,你可以使用@umijs/plugin-openapi 插件。

一些博客:

对于umi的这个插件存在的一些痛点:

  1. umi提供的这个插件不能自定义修改模版内容。
  2. 如果后端Java提供的tag为中文,生成的接口名称是拼音,而我们更希望是xxxController的格式。
  3. umi只提供了umi-request插件的模版,然而我们实际开发中可能用axios的更多,所以希望能够支持更多的模版

基于这些问题,我决定对umi-openapi的源码进行改造,适用自己的系统开发,同时也可以让用户自定义模版,适用于各自的系统。

OpenAPI规范研究

https://openapi.apifox.cn/

基本结构

接下来会一层一层的研究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,并且有文件上传的支持:

  1. multipart/form-data

    • 表示请求体的内容类型为多部分表单数据,通常用于上传文件时。
  2. properties

    • 定义了请求体中的不同字段:
      • submissionAddForm:引用了 SubmissionAddForm,这是一个复杂的数据结构。
      • file:类型为 string,格式为 binary,表示这是一个二进制文件,支持文件上传。
  3. 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"
}
}
}
},