swagger3新玩法

swagger工具的出现很好的解决了开发Java后台接口的文档化需求。它为前后端分离的基于API的对接提供了很好的保障,后台开发了哪些接口,出入参结构是怎样的,入参的约束,描述性的信息等等,都一目了然,我们还可以依赖它来做在线调试。所以说,对于前后端分离的项目,在开发和测试阶段,swagger在线文档可以很好的统一大家对Rest API的认识,尽管它的展示风格不是那么符合国人的审美。

关于swagger2

还记得当年前后端分离在企业开发中变得流行起来时,前后端开发人员在一起做一个新项目,在并行开发的情况下,最大的沟通问题就是在双方之间反复确认一些变化的出入参。因为接口是由后台开发提供的,根据需求和产品原型来梳理和定义API接口的重任便落在了后台开发人员肩上。准确来说,swagger在线文档并不等于API接口文档,因为swagger2的规范还没有形成,它更多是站在不成文的实现角度给开发人员提供了swagger注解API,这无疑增加了接口开发人员的负担,除了要应对变化无常的业务需求,还要手动写swagger注解来生成相关的在线文档,而这并不能第一时间成为各方认可的API文档标准。

openApi规范

从swagger3开始,swagger官方做出了权威的一举,制定了接口对接的统一规范------OpenAPI Specification(OAS)。这样swagger3的文档注解也相应的在命名和功能上做了调整,其实swagger3的注解现在以变成了一个中间产物,开发人员无须再以它为主导再去做繁杂的代码注释工作。新版的idea工具中包含了对openApi可视化的插件支持,如:

甚至,swagger官方贴心的考虑到手写swagger3注解的繁重任务,而推出了相关的生成器------Swagger Codegen。基于openApi的yaml文件就可以生成包含swagger注解的代码。因为它是一个规范,基于它的实现可以是各种后台编程语言,因此该生成器同时也是跨语言的,对于Java代码而言,我们自然期望生成包含spring mvc框架的api接口类文件。

swagger生成器

关于swagger生成器的使用以及功能的扩展,可以参考小卷的技术专栏《spring boot小卷生鲜电商项目实战》中介绍swagger生成器的笔记。这目前是一个单体的电商应用,考虑到模块多了,openApi的yaml文件会显得比较臃肿,好在openApi规范中可以采用$ref语法以json文档内容的节点定位形式来访问yaml文档中(或者外部文档)某一部分,这样我们自然就能够实现将一个很臃肿的openApi的yaml文件,把它拆分为按模块划分的多个小yaml文件。

这里的API接口和数据模型schema,我们按照模块以及是前台还是后台管理功能进行了细粒度的拆分,每个文件中我们可以维护pathsschemas下的内容,然后我们提供了一个template.ftl文件,借助freemarker模板帮我们生成整合后的内容,实际生成的临时文件的内容如下:

yaml 复制代码
openapi: 3.0.1
info:
  ...
paths:
  ...
  /mall-api/admin/categories:
    $ref: "./module/category-admin.yaml#/paths/~1mall-api~1admin~1categories"
  /mall-api/admin/categories/move:
    $ref: "./module/category-admin.yaml#/paths/~1mall-api~1admin~1categories~1move"
  /mall-api/admin/categories/{id}:
    $ref: "./module/category-admin.yaml#/paths/~1mall-api~1admin~1categories~1{id}"
  /mall-api/admin/product/updateSellStatus:
    $ref: "./module/product-admin.yaml#/paths/~1mall-api~1admin~1product~1updateSellStatus"
  /mall-api/admin/products:
    $ref: "./module/product-admin.yaml#/paths/~1mall-api~1admin~1products"
  /mall-api/portal/products:
    $ref: "./module/product.yaml#/paths/~1mall-api~1portal~1products"
  /mall-api/portal/product-detail/{id}:
    $ref: "./module/product.yaml#/paths/~1mall-api~1portal~1product-detail~1{id}"
  /mall-api/portal/product-latest/{categoryId}:
    $ref: "./module/product.yaml#/paths/~1mall-api~1portal~1product-latest~1{categoryId}"
components:
  schemas:
    CategoryItem:
      $ref: "./module/category.yaml#/components/schemas/CategoryItem"
    UserItem:
      $ref: "./module/user-admin.yaml#/components/schemas/UserItem"
    Token:
      $ref: "./module/user.yaml#/components/schemas/Token"
    UserInfo:
      $ref: "./module/user.yaml#/components/schemas/UserInfo"
    UserRegister:
      $ref: "./module/user.yaml#/components/schemas/UserRegister"
    PageQuery:
      $ref: "./module/common.yaml#/components/schemas/PageQuery"
    CategoryCondition:
      $ref: "./module/category-admin.yaml#/components/schemas/CategoryCondition"
    ...

以上的内容我们都不用关心,只需要关注各部分的yaml文件即可。

借助于swagger-codegen相关插件以及我们优化后的整合方案,我们就可以基于这些拆分出来的openApi子文件来一键生成后台代码:

生成的内容包含了我们已经实现的扩展功能:

  • 内置校验注解、自定义注解
  • 对同一DTO模型的新增、更新api的分组校验支持
  • 返回结果为通用的分页结果类型
  • 文件上传对Multipart类型的支持
  • 针对表单对象形式提交的swagger表单域校验支持
  • 其他

以上扩展功能我们对Swagger-Codegen的源码进行相关的改进以支持这些扩展,具体的改进请参考小卷的技术专栏《spring boot小卷生鲜电商项目实战》中相关笔记,这里不再赘述。

如前所述,开发人员开发后台Web层的工作从传统的Controller开发,包含了请求映射、参数(DTO)定义、各种注解(映射、校验、swagger),变成了api的声明与实现分离的模式,即只需要开发人员(前端甚至非开发人员)维护下openApi的yaml文件定义,然后交由代码生成器自动生成相关后台代码,后台开发人员只要写一个实现生成api接口的Controller组件即可。

示例

下面我们做一下演示。以后台的商品分类API开发为例,我们只需要先定义下相关的yaml文件内容:

yaml 复制代码
openapi: 3.0.1
info:
  title: 小卷生鲜OpenApi
  description: 小卷生鲜在线API文档
  version: 1.0.0
  termsOfService: https://edu.xiaojuan.com
  contact:
    name: xiaojuan
    url: https://edu.xiaojuan.com
    email: xiaojuan@162.com
servers:
  - url: 'http://localhost:8080'
    description: dev
paths:
  /mall-api/admin/categories:
    get:
      tags:
        - categoryAdmin
      summary: 查询分类列表
      description: 分页查询分类列表
      operationId: listCategories
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/CategoryCondition'
      x-pageResult: true
      responses:
        200:
          description: 查询成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryInfo'
    post:
      tags:
        - categoryAdmin
      summary: 新增分类
      description: 新增一个分类
      operationId: addCategory
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CategoryAdd'
      responses:
        200:
          description: 新增成功
          content:
            application/json:
              schema:
                type: integer
                format: int64
    put:
      tags:
        - categoryAdmin
      summary: 更新分类
      description: 更新分类信息
      operationId: updateCategory
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CategoryUpdate'
      responses:
        200:
          description: 更新成功
        500:
          description: 更新失败
          x-errCode: '10001:参数校验失败(分类名称已存在、分类层级最多3级)'
  /mall-api/admin/categories/move:
    post:
      tags:
        - categoryAdmin
      summary: 分类拖拽
      description: 对分类进行拖拽调整分类或排序
      operationId: moveCategories
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CategoryMove'
      responses:
        200:
          description: 移动成功
  /mall-api/admin/categories/{id}:
    delete:
      tags:
        - categoryAdmin
      summary: 删除分类
      description: 按照id删除分类
      operationId: deleteCategory
      parameters:
        - name: id
          in: path
          description: 分类id
          schema:
            type: integer
            format: int64
      responses:
        200:
          description: 删除成功
components:
  schemas:
    CategoryCondition:
      description: 分类分页查询条件DTO类
      type: object
      allOf:
        - $ref: './common.yaml#/components/schemas/PageQuery'
      properties:
        name:
          description: 分类名称(模糊匹配)
          type: string
        pid:
          description: 分类父级id
          type: integer
          format: int64
    CategoryInfo:
      description: 分类信息DTO类
      type: object
      properties:
        id:
          description: 分类id
          type: integer
          format: int64
        pid:
          description: 父级分类id
          type: integer
          format: int64
        name:
          description: 分类名称
          type: string
        level:
          description: 分类层级
          type: integer
        orderNum:
          description: 排序
          type: integer
        createTime:
          description: 创建时间
          type: string
          format: date
        updateTime:
          description: 更新时间
          type: string
          format: date
    CategoryAdd:
      description: 分类新增DTO类
      type: object
      properties:
        name:
          description: 分类名称
          type: string
          x-validation: "@NotNull(message = MSG_CATEGORY_NAME_REQUIRED) @Size(min = 2, max = 5, message = MSG_CATEGORY_NAME_LENGTH_RANGE)"
        pid:
          description: 父级分类
          type: integer
          format: int64
          default: 0
    CategoryUpdate:
      description: 分类更新DTO类
      type: object
      properties:
        id:
          description: 分类id
          type: integer
          format: int64
          x-validation: "@NotNull(message = MSG_CATEGORY_ID_REQUIRED)"
        name:
          description: 分类名称
          type: string
    CategoryMove:
      description: 分类移动DTO类
      type: object
      properties:
        rootId:
          description: 要移动的分类id
          type: integer
          format: int64
          x-validation: "@NotNull(message = MSG_DRAG_CATEGORY_ID_REQUIRED)"
        targetId:
          description: 目标分类id
          type: integer
          format: int64
          x-validation: "@NotNull(message = MSG_DROP_TARGET_ID_REQUIRED)"
        type:
          description: 放置类型 0-之前 1-之中 2-之后
          type: integer
          x-validation: "@NotNull(message = MSG_DROP_TYPE_REQUIRED)"

然后执行生成器生成相关代码,最后写一个Controller来实现生成的接口即可:

java 复制代码
package com.xiaojuan.boot.web.controller.admin;

import ...

@RequiredArgsConstructor
@RestController
public class CategoryAdminController implements CategoryAdminApi {

    private final CategoryService categoryService;

    @SneakyThrows
    @Override
    public Long addCategory(CategoryAddDTO categoryAddDTO) {
        return categoryService.addCategory(categoryAddDTO);
    }

    @SneakyThrows
    @Override
    public void updateCategory(CategoryUpdateDTO updateDTO) {
        categoryService.updateCategory(updateDTO);
    }

    @Override
    public void deleteCategory(Long id) {
        categoryService.deleteCategory(id);
    }

    @SneakyThrows
    @Override
    public void moveCategories(CategoryMoveDTO body) {
        categoryService.moveCategories(body);
    }

    @Override
    public PageResultDTO<CategoryInfoDTO> listCategories(CategoryConditionDTO body) {
        return categoryService.listCategoriesByPage(body);
    }
}

内容看起来非常简洁,这就是接口与实现分离的好处,繁杂的注解声明由生成器帮我们在接口中做好了。

玩法升级

现在我们的工作重心就在openApi文档的声明和维护上,因为有了它,我们的后台代码也有了,swagger在线文档自然也有了,当然swagger在线文档显得不那么重要了,因为我们完全可以基于openApi的yaml内容,将其转换为我们想要的数据结构,持久化到数据库中,然后写自己的web端来维护这些数据,以方便非开发人员来维护在线API文档,可以将原始openApi的yaml文件经我们的数据结构导出为静态的html文档、markdown文件等形式,亦或者反向到处页面维护后的yaml格式,以便由swagger代码生成器来重新生成和同步与最新文档一致的后台代码。

这种不依赖于swagger注解的运行时元数据的形式,可以很方便的针对微服务分布式系统下各个微服务的API进行集中维护管理,可提供es搜索能力、针对指定环境的在线调试能力等等。

这就是我们所憧憬的玩法,相信也是不难实现的!

以上是给目前使用基于openAPI的swagger组件的小伙伴们提供的一点思路,也许朝着这个思路一点点去实现,就能实现一个企业内部真正所需要的"open"式API呢。大家加油!

相关推荐
逆风就重开4 小时前
【软件基础知识】什么是 API,详细解读
api
VinciYan20 小时前
Rust使用Actix-web和SeaORM库开发WebAPI通过Swagger UI查看接口文档
rust·api·web·orm
Snowbowღ1 天前
OpenAI / GPT-4o:Python 返回结构化 / JSON 输出
python·json·openai·api·gpt-4o·pydantic·结构化输出
凉风听雪1 天前
百度营销转化追踪(网页JS布码)
百度·api·营销·网站统计·转化追踪·数据追踪
幂简集成1 天前
如何免费调用GPT API进行自然语言处理
gpt·api
OH五星上将4 天前
如何编译OpenHarmony SDK API
嵌入式硬件·移动开发·api·sdk·harmonyos·openharmony·鸿蒙开发
秋窗75 天前
调用百度翻译API遇到的跨域问题解决方案
nginx·api·跨域
营赢盈英7 天前
How to see if openAI (node js) createModeration response “flagged“ is true
javascript·ai·node.js·openai·api
佛州小李哥8 天前
零基础5分钟上手亚马逊云科技-利用API网关管理API
科技·架构·云计算·api·开发·aws·亚马逊云科技
Hoper.J10 天前
0. 阿里大模型API获取步骤
大模型·aigc·api