文章目录
- @[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接口定义,接下来我们就可以通过接口实现我们的也能功能了。