和swagger_swagger

OpenAPI Specification

OpenAPI 规范(OAS)为 RESTful API 定义了一套标准的、跨语言的接口,以便人类和计算机均无需通过源码、文档或网络流量检测探知和理解服务器提供的能力。OAS 可用于制作文档生成器、代码生成器、测试工具等等。OpenAPI 以 json 对象形式呈现,因此它基本遵循 json schema 规范,可以用 json 或 yaml 格式编写。

在 OpenAPI 规范的基础上,Swagger 提供了一套开源工具用于为服务端和客户端设计、制作 api 文档以及代码,包含 Swagger Editor、Swagger UI、Swagger Codegen。基于 Swagger,开发流程可能是这样的:设计 api,使用 Swagger Codegen 制作服务端代码,稍后再实现业务逻辑;使用 Swagger Codegen 生成客户端脚本库;使用 Swagger UI 制作交互式文档界面等等。

OpenAPI 文档的基础数据包含:

  • openapi: OAS 版本号,影响解析策略。
  • info: RESTful API 的元信息,包含标题、描述、联系方式、许可证、版本号、服务条款链接。
  • servers: 服务器信息。当 servers 未指定时,将使用 url 为 '/' 的服务器对象。
  • paths: RESTful API 的路径。单个路径包含相同配置时会造成冲突,这时会由工具决定选用哪一个。路径下挂操作对象。
  • components: 在 OAS 中作为可重用的部件,被引用时才生效。
  • security: RESTful API 的安全机制,可以在操作对象层级进行改写。
  • tags: 标签列表,即针对操作对象打标签。
  • externalDocs: 扩展文档。
penapi: 3.0.0
info:
  title: Sample API
  description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
  version: 0.1.9
servers:
  - url: https://api.example.com/v1
    description: Production server (uses live data)
  - url: https://sandbox-api.example.com:8443/v1
    description: Sandbox server (uses test data)
paths:
  /users:
    get:
      summary: Returns a list of users.
      description: Optional extended description in CommonMark or HTML.
      security:
        - OAuth2: [read]     # <------ 使用 oauth2 认证
      responses:
        '200':    # status code
          description: A JSON array of user names
          content:
            application/json:
              schema: 
                type: array
                items: 
                  type: string
components:
  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic
security:
  - BasicAuth: []

OAS 使用 end point、operation object 描述 api。以上 schema 中,/users 是一个 end point,get 是一个 operation object。end point 下可以包含如下的 operation object:put、post、delete、options、head、patch、trace。operation object 可以配置 tags、summary、description、externalDocs、operationId、parameters、requestBody、responses、callbacks、deprecated、security、servers 属性。典型如下:

paths:
  /users/{id}: # 正则格式
    get:
      tags:
        - Users
      summary: Gets a user by ID.
      description: A detailed description of the operation.
        Use markdown for rich text representation,
        such as **bold**, *italic*, and [links](https://swagger.io).
      operationId: getUserById
      parameters:
        - name: id
          in: path
          description: User ID
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User' # 使用 $ref 引用 components 中定义的实体
      externalDocs:
        description: Learn more about user operations provided by this API.
        url: http://api.example.com/docs/user-operations/
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
      required:
        - id
        - name

后端接入 swagger2

java 中介入 swagger2 可按以下方式进行:

  1. pom.xml 添加 springfox-swagger2、springfox-swagger-ui 依赖。
  2. 使用 @Configuration、@EnableSwagger2 注解编写 Swagger 配置类。
  3. 在 Controller 中使用 Swagger 注解接口。
  4. 访问 swagger-ui.html 查看接口文档。
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.9.2</version>
</dependency>
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>2.9.2</version>
</dependency>
// configuration
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket customDocket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        Contact contact = new Contact("团队名", "www.my.com", "my@my.com");
        return new ApiInfoBuilder()
                .title("文档标题")
                .description("文档描述")
                .contact(contact)   // 联系方式
                .version("1.1.0")  // 版本
                .build();
    }
}

// controller
@RestController
@Api(tags="接口所在的类")
public class HelloController {
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    @ApiOperation(value = "接口名", notes = "接口描述", httpMethod = "POST")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "length",value = "参数1", required = true, paramType = "path"),
            @ApiImplicitParam(name = "size",value = "参数2", required = true, paramType = "query"),
            @ApiImplicitParam(name = "page",value = "参数3", required = true, paramType = "header"),
            @ApiImplicitParam(name = "total",value = "参数4", required = true, paramType = "form"),
            @ApiImplicitParam(name = "start",value = "参数5",dataType = "string", paramType = "body")
    })
    public String index(){
        return "Hello World!";
    }
}

生成前端接口

这里以 openapi-generator 为例,说明一下怎么根据 swagger-ui 生成前端服务层调用代码和实体类接口。通过后端应用接入 swagger2 之后,访问 /v2/api-docs 即可查看复合 OAS 规范的全量接口信息,openapi-generator 就是在这份接口数据的基础上,通过解析并使用 nunjucks 模板生成 js 脚本的。下图即为 /v2/api-docs 数据内容。

27f731281ce2f89286f7a5aa3dab276e.png

openapi-generator 的核心代码见于 ServiceGenerator 类,该类的作用即是解析 /v2/api-doc 数据内容并使用模板生成服务层调用及接口文件。除了解析 OAS 数据外,它基于以下流程实现:

  1. 基于 config.requestLib 配置为真生成服务层调用基类,文件名为 base.js 或 base.ts。
  2. 基于 config.type 配置为 'ts' 生成实体类接口,文件名为 typings.d.ts。
  3. 基于 config.serviceType 生成服务层调用脚本,类或函数形式。
class ServiceGenerator {
  // 生成服务层调用代码和实体类接口文件
  genFile() {
    // 基于 openapi-generator 内置的 template/base.njk 生成服务层调用基类
    this.genRequestLib();

    // 基于 openapi-generator 内置的 template/interface.njk 或外部模板生成实体类接口
    if (this.config.type === 'ts') {
      debug('[GenSDK] gen interface.');
      this.genFileFromTemplate('typings.d.ts', 'interface', {
        namespace: this.config.namespace,
        list: this.getInterfaceTP(),// 获取 /v2/api-doc 数据内容中的 definitions
      });
    }

    // 基于 openapi-generator 内置的 template/interface.njk 或外部模板生成实体类接口
    this.getServiceTP()// 获取 /v2/api-doc 数据内容中的 paths(经 ServiceGenerator.constructor 处理)
      .filter(tp => {
        tp.list = tp.list.filter(
          item =>
            !this.config.filter ||
            this.config.filter.some(f => {
              return f instanceof RegExp
                ? f.test(item.path)
                : typeof f === 'function'
                ? f(item)
                : true;
            })
        );
        return tp.list.length;
      })
      .map(tp => {
        debug('[GenSDK] generate service:', tp.className);
        this.genFileFromTemplate(
          this.getFinalFileName(`${tp.className}.${this.config.type}`),
          'service',
          {
            namespace: this.config.namespace,
            ...tp,
          }
        );
      });
  }

  protected genFileFromTemplate(fileName: string, type: 'interface' | 'service', params: any) {
    try {
      const template = this.getTemplate(type);
      this.writeFile(fileName, nunjucks.renderString(template, params));
    } catch (error) {
      console.warn('[GenSDK] file gen fail:', fileName, 'type:', type);
      throw error;
    }
  }

  protected getTemplate(type: 'interface' | 'service') {
    const configFilePath =
      type === 'interface' ? this.config.interfaceTemplatePath : this.config.templatePath;
    try {
      if (configFilePath) {
        this.mkdir(path.dirname(configFilePath));
        if (fs.existsSync(configFilePath)) {
          return fs.readFileSync(configFilePath, 'utf8');
        }
      }

      const fileContent = fs.readFileSync(
        path.join(
          __dirname,
          'template',
          type === 'service' ? `${type}.${this.config.serviceType}.njk` : `${type}.njk`
        ),
        'utf8'
      );
      if (configFilePath) {
        fs.writeFileSync(configFilePath, fileContent, 'utf8');
      }
      return fileContent;
    } catch (error) {
      console.warn(`[GenSDK] get {${type}} template fail:`, configFilePath);
      throw error;
    }
  }
}

在 ServiceGenerator 类的基础上,openapi-generator 支持基于接口、文件获取 OAS 数据并生成前端脚本。

openapi-generator url http://xxx/v2/api-docs -c true # 基于接口
openapi-generator config ./xxx.js # 基于文件

后记

写作本文的目的本意在于探究怎样通过后端脚本生成前端 service 层代码。我曾接触过的 midway 前端框架可以生成 hsf 服务中使用的代理层文件(实现与后端服务的参数对接和类型转换),前端也能根据 swagger 文档生成 service 层代码。也许,midway 中生成的代理文件也是基于 hsf 平台上标准化的接口文档。在这次探寻过程上,我发现了一个新的思考点:既可以根据 OAS 生成 service 脚本,也可以根据 OAS 提供 mock 服务。OAS 规范也使人思忖:在团队能力较弱或应用场景较小的情况下,探讨逻辑流程的紧要程度高于制定输入输出文档;在应用场景达到规模化或团队能力可信赖的情况下,规范化的输入输出文档高于逻辑实现。“路漫漫而修远兮,吾将上下而求索”。

参考

Swagger Codegen 高效开发客户端对接服务端代码

开源小工具 - swagger API访问代码生成器(js/typescript)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值