文章目录

  • @[toc]
  • 前言
  • 一、OpenAPI 规范
  • 二、OpenAPI 规范引入
  • 三、生成 REST API
  • 小结

前言

到目前为止,我们已经了解了如何生成一个新的 spring boot 应用程序,然后如何将其容器化。但是,我们的应用程序没有任何功能。今天我们将学习如何使用 Spring boot 创建 REST API。我们将采用模式优先的方法生成 REST API 接口,本文将采用 OpenAPI 规范以及如何使用该规范生成 REST API 接口。

一、OpenAPI 规范

API 是应用程序与应用程序使用者之间的契约。这些消费者可以是机器,也可以是人类。OpenAPI 是一种以人类和机器可读格式编写 API 合约的规范,它标准化了我们描述 API 的方式,整个说明可以在这里找到 https://spec.openapis.org/oas/v3.1.0 。

二、OpenAPI 规范引入

我们创建一个新服务,称之为 inventory-service。我们现在知道如何生成新的 Spring Boot 应用程序。我们在 src/resources/spec/inventory-api.yml 中添加了一个 yml openAPI 规范文件。配置文件如下所示:

openapi: "3.0.3"
info:
  title: inventory-api
  version: 1.0.0
paths:
  /products:
    get:
      description: Get All Products
      operationId: getAllProducts
      responses:
        '200':
          description: All products are returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListOfProducts'
        '404':
          description: No Product returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      description: Add A Product to inventory
      operationId: addProduct
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Product'
      responses:
        '201':
          description: Product added successfully
          headers:
            location:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /products/{id}:
    get:
      description: Get A Product By ID
      operationId: getProductById
      parameters:
        - in: path
          name: id
          schema:
            type: string
            format: uuid
          required: true
      responses:
        '200':
          description: Get a product by id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '404':
          description: No Product returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    put:
      description: Update A Product
      operationId: updateProduct
      parameters:
        - in: path
          name: id
          schema:
            type: string
            format: uuid
          required: true
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Product'
      responses:
        '200':
          description: Created product is  returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Product Does not Exist
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    ListOfProducts:
      type: array
      items:
        $ref: '#/components/schemas/Product'

    Product:
      type: object
      properties:
        id:
          type: string
          format: UUID
    Error:
      type: object
      properties:
        message:
          type: string

这是一个非常小的API ,我们可以在路径部分看到我们对 API 的描述,每个 API 端点都有其可选的请求正文和响应正文,我们还可以定义是否需要一些自定义标头、路径参数、查询参数等。

在组件部分,我们定义了模型,这些模型在我们的 API 中被引用。我不会更深入地研究 OpenAPI 规范,但因为它非常庞大,但我们始终可以针对我们的特定用例查阅该规范。

三、生成 REST API

现在我们有了 OpenAPI 规范,有一些插件和工具可用于从我们的规范中生成代码。我们可以使用 openapi-generator https://openapi-generator.tech/docs/installation 来生成我们的 REST API,也可以使用 cli 来生成我们的 REST API。还有一个 maven 插件 https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-maven-plugin 我们将使用它来生成我们的源代码。

maven 插件使用 openapi-generator 生成源代码,要使用 maven-plugin,我们会将其添加到构建部分,如下所示 -

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>5.4.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/resources/spec/inventory-api.yml</inputSpec>
                <generatorName>spring</generatorName>
                <generateSupportingFiles>false</generateSupportingFiles>
                <configOptions>
                    <basePackage>com.sab.inventory</basePackage>
                    <sourceFolder>src/java/main</sourceFolder>
                    <interfaceOnly>true</interfaceOnly>
                    <modelPackage>com.sab.inventory.dto</modelPackage>
                    <apiPackage>com.sab.inventory.api</apiPackage>
                    <skipDefaultInterface>true</skipDefaultInterface>
                    <openApiNullable>false</openApiNullable>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

插件和实际的 openapi-generator 都有很多配置选项,我们可以从 https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-maven-plugin 和 https://openapi-generator.tech/docs/generators/spring 检查它。

在上面的示例中,我使用了最低配置,我将在下面解释它们。

* `inputSpec` - This is the path to the OpenAPI spec file.
* `generatorName` - ooenapi-generator can produce source code multiple language and framework. Because we want to generate for Spring I chose spring as the generator name.
* `generateSupportingFiles` - When I generated first I saw some unecessary supporting files were generated as I do not need those I turned it to false.
* `configOptions` - Properties that maps directly to the generator options.
   ** `basePackage` - package name for your generated sources
   ** `sourceFolder` - folder where your generated sources will be placed
   **  `interfaceOnly` - We can either generate only REST API interface or we can generate some default code. I make it true as I want to generate only REST API interface.
   ** `modelPackage` - package name for your generated model/dto classes.
   ** `apiPackage` - package name for your generated API classes.
   **   `skipDefaultInterface` - We can skip the genration of default methods in the interface.
   ** `openApiNullable` - With the value true it will generate an import of `org.openapitools.jackson.nullable.JsonNullable` however I didn't need it so I make it false.

那么上面代码的输出是什么呢?下面是我们的 REST API 接口的样子

/**
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) (5.4.0).
 * https://openapi-generator.tech
 * Do not edit the class manually.
 */
package com.sab.inventory.api;

import com.sab.inventory.dto.Error;
import com.sab.inventory.dto.Product;
import java.util.UUID;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Generated;

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-04-23T22:00:49.464591+02:00[Europe/Berlin]")
@Validated
@Tag(name = "products", description = "the products API")
public interface ProductsApi {

    /**
     * POST /products
     * Add A Product to inventory
     *
     * @param product  (optional)
     * @return All products are returned (status code 201)
     *         or No Product returned (status code 400)
     */
    @Operation(
        operationId = "addProduct",
        responses = {
            @ApiResponse(responseCode = "201", description = "All products are returned", content = @Content(mediaType = "application/json", schema = @Schema(implementation =  Product.class))),
            @ApiResponse(responseCode = "400", description = "No Product returned", content = @Content(mediaType = "application/json", schema = @Schema(implementation =  Error.class)))
        }
    )
    @RequestMapping(
        method = RequestMethod.POST,
        value = "/products",
        produces = { "application/json" },
        consumes = { "application/json" }
    )
    ResponseEntity<Product> addProduct(
        @Parameter(name = "Product", description = "", schema = @Schema(description = "")) @Valid @RequestBody(required = false) Product product
    );


    /**
     * GET /products
     * Get All Products
     *
     * @return All products are returned (status code 200)
     *         or No Product returned (status code 404)
     */
    @Operation(
        operationId = "getAllProducts",
        responses = {
            @ApiResponse(responseCode = "200", description = "All products are returned", content = @Content(mediaType = "application/json", schema = @Schema(implementation =  Product.class))),
            @ApiResponse(responseCode = "404", description = "No Product returned", content = @Content(mediaType = "application/json", schema = @Schema(implementation =  Error.class)))
        }
    )
    @RequestMapping(
        method = RequestMethod.GET,
        value = "/products",
        produces = { "application/json" }
    )
    ResponseEntity<List<Product>> getAllProducts(

    );


    /**
     * GET /products/{id}
     * Get A Product By ID
     *
     * @param id  (required)
     * @return All products are returned (status code 200)
     *         or No Product returned (status code 404)
     */
    @Operation(
        operationId = "getProductById",
        responses = {
            @ApiResponse(responseCode = "200", description = "All products are returned", content = @Content(mediaType = "application/json", schema = @Schema(implementation =  Product.class))),
            @ApiResponse(responseCode = "404", description = "No Product returned", content = @Content(mediaType = "application/json", schema = @Schema(implementation =  Error.class)))
        }
    )
    @RequestMapping(
        method = RequestMethod.GET,
        value = "/products/{id}",
        produces = { "application/json" }
    )
    ResponseEntity<Product> getProductById(
        @Parameter(name = "id", description = "", required = true, schema = @Schema(description = "")) @PathVariable("id") UUID id
    );


    /**
     * PUT /products/{id}
     * Update A Product
     *
     * @param id  (required)
     * @param product  (optional)
     * @return Created product is  returned (status code 200)
     *         or Error (status code 400)
     *         or Product Does not Exist (status code 404)
     */
    @Operation(
        operationId = "updateProduct",
        responses = {
            @ApiResponse(responseCode = "200", description = "Created product is  returned", content = @Content(mediaType = "application/json", schema = @Schema(implementation =  Product.class))),
            @ApiResponse(responseCode = "400", description = "Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation =  Error.class))),
            @ApiResponse(responseCode = "404", description = "Product Does not Exist", content = @Content(mediaType = "application/json", schema = @Schema(implementation =  Error.class)))
        }
    )
    @RequestMapping(
        method = RequestMethod.PUT,
        value = "/products/{id}",
        produces = { "application/json" },
        consumes = { "application/json" }
    )
    ResponseEntity<Product> updateProduct(
        @Parameter(name = "id", description = "", required = true, schema = @Schema(description = "")) @PathVariable("id") UUID id,
        @Parameter(name = "Product", description = "", schema = @Schema(description = "")) @Valid @RequestBody(required = false) Product product
    );

}

现在我们有了我们的API接口,我们现在可以创建我们的控制器并实现这些方法。

小结

本节我们学习了OpenAPI接口规范以及如何通过OpenAPI接口规范来生成我们自己的接口,通过本节的学习,我们可以轻松实现我们的RestAPI接口定义,接下来我们就可以通过接口实现我们的也能功能了。