Swagger和OpenAPI规范使我们能够轻松,无缝地设计和开发REST API。这些规范允许描述整个REST API的结构,以便机器可以读取和模拟它们。本文提供了有关这些概念的全面指南,并提供了所有主要Swagger组件的端到端示例。
(原文作者:Somnath Musib)
?在过去的几年中,REST风格的Web服务引起了相当大的关注,并成为Web服务生态系统中的事实上的标准。它不仅隔开了它的复杂对象SOAP,而且已经成为API设计和开发中的默认选择。
API本质上是合同。API发布者及其使用者都必须遵守该合同,以进行有效的交流。与大多数其他合同一样,为了以预期的方式工作,API应该记录其各个方面。它提供的端点,端点支持的操作,操作可理解的签名以及针对请求返回的响应等方面?此外,为了适应不断变化的需求,API随着时间的推移而发展。在这种情况下,对API进行良好的文档编制不是一种选择,而是其产品所不可或缺的一部分,以确保获得更好的客户体验。
但是,我们如何记录我们的API?我们如何定义应作为文档齐全的API一部分的一组组件?此外,我们一直在更新我们的服务产品,因此需要维护这些API的多个版本。那我们如何版本API文档呢?一个可能具有数百个端点的API。我们甚至如何编写所有这些端点及其操作的文档?手动吗?决不。我们甚至如何确保消费者可以理解我们的API文档?有没有办法标准化API文档及其生成过程?
本文试图为上述问题提供答案。我们将讨论OpenAPI和围绕OpenAPI的工具-Swagger,该工具可帮助我们提供我们的API文档以及基于这些API的进一步开发。
什么是OpenAPI规范?
OpenAPI规范(以前称为Swagger规范https://en.wikipedia.org/wiki/OpenAPI_Specification)是REST API的API描述格式。兼容OpenAPI规范的文件使我们能够描述完整的REST API。它通常以YAML(https://en.wikipedia.org/wiki/YAML)或JSON(https://en.wikipedia.org/wiki/JSON)文件格式编写。
该文件让我们:-
- 描述所有可用的API端点(例如/users, /users/{id})
- 端点中的操作,例如GET /users,POST /user
- 每个操作的输入和输出参数
- 认证机制
- 联系信息,API许可,使用条款和其他信息
Swagger是什么?
Swagger是围绕OpenAPI规范构建的一组开源工具,可以帮助我们设计,构建,记录和使用REST API。API描述其自身结构的能力是Swagger中所有出色功能的根源。请注意,Swagger不仅可以帮助我们设计和记录REST API,还可以让我们构建(服务器根节点)和消费端(其余客户端)之间的REST API。
Swagger的主要工具包括:
- Swagger Editor (http://editor.swagger.io/)-基于浏览器的编辑器,可以在其中编写OpenAPI规范
- Swagger UI(https://swagger.io/swagger-ui/) —将O?penAPI规范呈现为交互式API文档
- Swagger Codegen(https://github.com/swagger-api/swagger-codegen) —根据OpenAPI规范生成服务器根和客户端库
我们将在本文中详细讨论上述三个工具。
以下是Swagger编辑器中的示例OpenAPI文档:-
Swagger使用
现在既然我们已经了解了OpenAPI和Swagger是什么,那么让我们看看它们的实际作用。作为本文的一部分,我们将开发一个REST应用程序。然后,我们将使用Swagger UI呈现我们的API文档。然后,我们通过Swagger编辑器访问API文档(以JSON格式提供)。最后,我们将使用Swagger Codegen CLI生成服务器和客户端存根,以演示如何使用OpenAPI文档来模拟REST Web服务。
我们在建什么?
我们将构建一个Spring Boot应用程序,向我们提供管理献血者的功能。它允许我们创建,更新,删除和查看捐助者信息。
有关如何在开发环境中设置应用程序的逐步指南,请参考此链接(https://medium.com/codefountain/design-a-rest-api-with-spring-boot-and-mysql-a5572d94ccc7)。可以从此Github(https://github.com/musibs/swagger-medium-demo)存储库下载完整的源代码。
以下是步骤摘要:
- 使用JPA,H2和Web依赖关系创建一个新的Spring引导应用程序
- 创建模型(model),服务(service)和控制器(controllers)
- 运行该应用程序,然后尝试访问各种端点及其操作
以下是应用程序pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.codefountain.swagger</groupId>
<artifactId>swagger-medium-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>swagger-medium-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
pom.xml文件(https://gist.github.com/musibs/01d1fead61717823bdbdef1fe18a00e1/raw/76ad9a91233cda1a2b4ea51f26281e7414dd3b52/pom.xml)
我们从io.springfox添加了以下两个附加依赖项,以启用Swagger 2和Swagger UI:-
<依赖>
< groupId > io.springfox </ groupId >
< artifactId > springfox-swagger2 </ artifactId >
<版本> 2.9.2 </ version >
</依赖>
<依赖关系>
< groupId > io.springfox </ groupId >
< artifactId > springfox-swagger-ui </ artifactId >
<版本> 2.9.2 </版本>
</依赖>
Swagger 的配置
现在该项目已启动并正在运行,我们可以访问我们的REST端点,让我们添加swagger配置:-
package io.codefountain.swagger.configs;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("io.codefountain.swagger"))
.paths(PathSelectors.any())
.build().apiInfo(apiEndPointInfo());
}
public ApiInfo apiEndPointInfo(){
return new ApiInfoBuilder().title("Spring Boot Rest API")
.description("Donor Management API")
.contact(new Contact("Somnath Musib", "medium.com/codefountain", "codefountain@gmail.com"))
.license("Apache 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.version("0.0.1-SNAPSHOT")
.build();
}
}
Swagger 的配置(https://gist.github.com/musibs/38638a5f220628c54de6e3d6e27074ed/raw/652382db977773cc6b23df9ee6638945c50c170d/SwaggerConfiguration.java)
这是带有Swagger文档信息的Spring配置。我们添加了有关REST API的元数据信息,例如API名称,作者,网站,许可证等。我们还指示Swagger只为io.codefountain.swagger包中的组件生成文档。
访问Swagger UI
由于已启用Swagger,因此让我们查看Swagger完成的API端点的文档。这是通过以下链接中的Swagger UI呈现的:
hhttp://localhost:8080//swagger-ui.html#/donor-controller
Swagger汇集了以下信息:-
- 文档元数据(metadata)(API名称,许可证(license),网站(website),联系人(contact)等)
- 具有默认信息的所有REST端点都可以从代码中推断出这些信息。请注意,端点描述是方法名称
这些是默认信息。现在,让我们用醒目的注释来显式地记录API的文档,以提供详细的描述以及有关端点和操作的信息。
记录Rest 控制器
如上所述,我们现在将明确记录REST控制器。Swagger提供了几个注释,以添加它在生成文档时提取的文档元数据。
对于每个REST端点及其关联的操作,我们都提供了ApiOperation及其各种响应以及ApiResponses批注。
package io.codefountain.swagger.controllers;
import io.codefountain.swagger.exceptions.DonorAlreadyExistsException;
import io.codefountain.swagger.exceptions.DonorNotFoundException;
import io.codefountain.swagger.exceptions.GenericApiException;
import io.codefountain.swagger.model.Donor;
import io.codefountain.swagger.repository.DonorRepository;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.ConstraintViolationException;
import java.util.Optional;
@RestController
@RequestMapping("/api/donors")
@Api(produces = "application/json", value = "Operations pertaining to manager blood donors in the application")
public class DonorController {
@Autowired
private DonorRepository donorRepository;
@PostMapping
@ApiOperation(value = "Create a new donor", response = ResponseEntity.class)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully created a new donor"),
@ApiResponse(code = 401, message = "You are not authorized to view the resource"),
@ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found"),
@ApiResponse(code = 500, message = "Application failed to process the request")
}
)
public ResponseEntity<Donor> createDonor(@RequestBody Donor donor){
try{
Donor savedDonor = donorRepository.save(donor);
return new ResponseEntity<Donor>(savedDonor, HttpStatus.OK);
}
catch(Exception e){
if( e instanceof ConstraintViolationException){
throw new DonorAlreadyExistsException(e.getMessage());
}
throw new GenericApiException(e.getMessage());
}
}
@GetMapping
@ApiOperation(value = "View all donors", response = Iterable.class)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully retrieved all donors"),
@ApiResponse(code = 401, message = "You are not authorized to view the resource"),
@ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found"),
@ApiResponse(code = 500, message = "Application failed to process the request")
}
)
public Iterable<Donor> getDonors(){
return donorRepository.findAll();
}
@GetMapping("/{id}")
@ApiOperation(value = "Retrieve specific donor with the supplied donor id", response = ResponseEntity.class)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully retrieved the donor with the supplied id"),
@ApiResponse(code = 401, message = "You are not authorized to view the resource"),
@ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found"),
@ApiResponse(code = 500, message = "Application failed to process the request")
}
)
public ResponseEntity<Donor> getDonor(@PathVariable("id") Long id){
Optional donor = donorRepository.findById(id);
if(!donor.isPresent()){
return new ResponseEntity<Donor>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<Donor>((Donor) donor.get(), HttpStatus.OK);
}
@PutMapping
@ApiOperation(value = "Update a donor information", response = ResponseEntity.class)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully updated donor information"),
@ApiResponse(code = 401, message = "You are not authorized to view the resource"),
@ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found"),
@ApiResponse(code = 500, message = "Application failed to process the request")
}
)
public ResponseEntity<Donor> UpdateDonor(@RequestBody Donor donor){
try{
Optional findDonor = donorRepository.findById(donor.getDonorId());
if(!findDonor.isPresent()){
throw new DonorNotFoundException("Donor with id "+donor.getDonorId()+" is not present");
}
Donor savedDonor = donorRepository.save(donor);
return new ResponseEntity<Donor>(savedDonor, HttpStatus.OK);
}
catch(Exception e){
e.printStackTrace();
return new ResponseEntity<Donor>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@DeleteMapping("/{id}")
@ApiOperation(value = "Deletes specific donor with the supplied donor id")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully deletes the specific donor"),
@ApiResponse(code = 401, message = "You are not authorized to view the resource"),
@ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found"),
@ApiResponse(code = 500, message = "Application failed to process the request")
}
)
public void delete(@PathVariable("id") Long id){
donorRepository.deleteById(id);
}
}
带有明确文档的REST控制器(https://gist.github.com/musibs/fd22dce69cd6fd3d0952ac91882ef374/raw/ed95a56dba7a3af8dc550361a1abc19c4a5dae5d/DonorController.java)
重新启动应用程序并访问相同的URL:
这次,Swagger提取了通过注释提供的信息。不仅如此,它现在还添加了带有HTTP响应代码的显式响应信息:
访问Swagger编辑器
到目前为止,我们已经在本地访问了API文档。Swagger还会遵循OpenAPI规范以JSON文件格式生成文档。我们可以与使用者共享此JSON文件,他们可以读取端点信息,生成客户端和服务器存根。
可以通过以下URL访问我们的REST API文档:-
http://localhost:8080/v2/api-docs
{
"swagger": "2.0",
"info": {
"description": "Donor Management API",
"version": "0.0.1-SNAPSHOT",
"title": "Spring Boot Rest API",
"contact": {
"name": "Somnath Musib",
"url": "medium.com/codefountain",
"email": "codefountain@gmail.com"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"host": "localhost:8080",
"basePath": "/",
"tags": [
{
"name": "donor-controller",
"description": "Donor Controller"
}
],
"paths": {
"/api/donors": {
"get": {
"tags": [
"donor-controller"
],
"summary": "View all donors",
"operationId": "getDonorsUsingGET",
"produces": [
"*/*"
],
"responses": {
"200": {
"description": "Successfully retrieved all donors",
"schema": {
"$ref": "#/definitions/Iterable"
}
},
"401": {
"description": "You are not authorized to view the resource"
},
"403": {
"description": "Accessing the resource you were trying to reach is forbidden"
},
"404": {
"description": "The resource you were trying to reach is not found"
},
"500": {
"description": "Application failed to process the request"
}
},
"deprecated": false
},
"post": {
"tags": [
"donor-controller"
],
"summary": "Create a new donor",
"operationId": "createDonorUsingPOST",
"consumes": [
"application/json"
],
"produces": [
"*/*"
],
"parameters": [
{
"in": "body",
"name": "donor",
"description": "donor",
"required": true,
"schema": {
"$ref": "#/definitions/Donor"
}
}
],
"responses": {
"200": {
"description": "Successfully created a new donor",
"schema": {
"$ref": "#/definitions/ResponseEntity"
}
},
"201": {
"description": "Created"
},
"401": {
"description": "You are not authorized to view the resource"
},
"403": {
"description": "Accessing the resource you were trying to reach is forbidden"
},
"404": {
"description": "The resource you were trying to reach is not found"
},
"500": {
"description": "Application failed to process the request"
}
},
"deprecated": false
},
"put": {
"tags": [
"donor-controller"
],
"summary": "Update a donor information",
"operationId": "UpdateDonorUsingPUT",
"consumes": [
"application/json"
],
"produces": [
"*/*"
],
"parameters": [
{
"in": "body",
"name": "donor",
"description": "donor",
"required": true,
"schema": {
"$ref": "#/definitions/Donor"
}
}
],
"responses": {
"200": {
"description": "Successfully updated donor information",
"schema": {
"$ref": "#/definitions/ResponseEntity"
}
},
"201": {
"description": "Created"
},
"401": {
"description": "You are not authorized to view the resource"
},
"403": {
"description": "Accessing the resource you were trying to reach is forbidden"
},
"404": {
"description": "The resource you were trying to reach is not found"
},
"500": {
"description": "Application failed to process the request"
}
},
"deprecated": false
}
},
"/api/donors/{id}": {
"get": {
"tags": [
"donor-controller"
],
"summary": "Retrieve specific donor with the supplied donor id",
"operationId": "getDonorUsingGET",
"produces": [
"*/*"
],
"parameters": [
{
"name": "id",
"in": "path",
"description": "id",
"required": true,
"type": "integer",
"format": "int64"
}
],
"responses": {
"200": {
"description": "Successfully retrieved the donor with the supplied id",
"schema": {
"$ref": "#/definitions/ResponseEntity"
}
},
"401": {
"description": "You are not authorized to view the resource"
},
"403": {
"description": "Accessing the resource you were trying to reach is forbidden"
},
"404": {
"description": "The resource you were trying to reach is not found"
},
"500": {
"description": "Application failed to process the request"
}
},
"deprecated": false
},
"delete": {
"tags": [
"donor-controller"
],
"summary": "Deletes specific donor with the supplied donor id",
"operationId": "deleteUsingDELETE",
"produces": [
"*/*"
],
"parameters": [
{
"name": "id",
"in": "path",
"description": "id",
"required": true,
"type": "integer",
"format": "int64"
}
],
"responses": {
"200": {
"description": "Successfully deletes the specific donor"
},
"204": {
"description": "No Content"
},
"401": {
"description": "You are not authorized to view the resource"
},
"403": {
"description": "Accessing the resource you were trying to reach is forbidden"
},
"404": {
"description": "The resource you were trying to reach is not found"
},
"500": {
"description": "Application failed to process the request"
}
},
"deprecated": false
}
}
},
"definitions": {
"Donor": {
"type": "object",
"properties": {
"bloodGroup": {
"type": "string"
},
"contactNo": {
"type": "string"
},
"dateOfBirth": {
"type": "string",
"format": "date"
},
"donorId": {
"type": "integer",
"format": "int64"
},
"emergencyContactNo": {
"type": "string"
},
"firstName": {
"type": "string"
},
"firstTimeDonor": {
"type": "boolean"
},
"lastName": {
"type": "string"
}
},
"title": "Donor"
},
"Iterable": {
"type": "object",
"title": "Iterable"
},
"Iterable?Donor?": {
"type": "object",
"title": "Iterable?Donor?"
},
"ResponseEntity": {
"type": "object",
"properties": {
"body": {
"type": "object"
},
"statusCode": {
"type": "string",
"enum": [
"100 CONTINUE",
"101 SWITCHING_PROTOCOLS",
"102 PROCESSING",
"103 CHECKPOINT",
"200 OK",
"201 CREATED",
"202 ACCEPTED",
"203 NON_AUTHORITATIVE_INFORMATION",
"204 NO_CONTENT",
"205 RESET_CONTENT",
"206 PARTIAL_CONTENT",
"207 MULTI_STATUS",
"208 ALREADY_REPORTED",
"226 IM_USED",
"300 MULTIPLE_CHOICES",
"301 MOVED_PERMANENTLY",
"302 FOUND",
"302 MOVED_TEMPORARILY",
"303 SEE_OTHER",
"304 NOT_MODIFIED",
"305 USE_PROXY",
"307 TEMPORARY_REDIRECT",
"308 PERMANENT_REDIRECT",
"400 BAD_REQUEST",
"401 UNAUTHORIZED",
"402 PAYMENT_REQUIRED",
"403 FORBIDDEN",
"404 NOT_FOUND",
"405 METHOD_NOT_ALLOWED",
"406 NOT_ACCEPTABLE",
"407 PROXY_AUTHENTICATION_REQUIRED",
"408 REQUEST_TIMEOUT",
"409 CONFLICT",
"410 GONE",
"411 LENGTH_REQUIRED",
"412 PRECONDITION_FAILED",
"413 PAYLOAD_TOO_LARGE",
"413 REQUEST_ENTITY_TOO_LARGE",
"414 URI_TOO_LONG",
"414 REQUEST_URI_TOO_LONG",
"415 UNSUPPORTED_MEDIA_TYPE",
"416 REQUESTED_RANGE_NOT_SATISFIABLE",
"417 EXPECTATION_FAILED",
"418 I_AM_A_TEAPOT",
"419 INSUFFICIENT_SPACE_ON_RESOURCE",
"420 METHOD_FAILURE",
"421 DESTINATION_LOCKED",
"422 UNPROCESSABLE_ENTITY",
"423 LOCKED",
"424 FAILED_DEPENDENCY",
"425 TOO_EARLY",
"426 UPGRADE_REQUIRED",
"428 PRECONDITION_REQUIRED",
"429 TOO_MANY_REQUESTS",
"431 REQUEST_HEADER_FIELDS_TOO_LARGE",
"451 UNAVAILABLE_FOR_LEGAL_REASONS",
"500 INTERNAL_SERVER_ERROR",
"501 NOT_IMPLEMENTED",
"502 BAD_GATEWAY",
"503 SERVICE_UNAVAILABLE",
"504 GATEWAY_TIMEOUT",
"505 HTTP_VERSION_NOT_SUPPORTED",
"506 VARIANT_ALSO_NEGOTIATES",
"507 INSUFFICIENT_STORAGE",
"508 LOOP_DETECTED",
"509 BANDWIDTH_LIMIT_EXCEEDED",
"510 NOT_EXTENDED",
"511 NETWORK_AUTHENTICATION_REQUIRED"
]
},
"statusCodeValue": {
"type": "integer",
"format": "int32"
}
},
"title": "ResponseEntity"
}
}
}
api-docs.json(https://gist.github.com/musibs/caa48074703bd62489ba0dda428f4ba9/raw/3007a0b280a17b999ae520d778e0160ed5f5d879/api-docs.json)
该JSON文档符合OpenAPI规范,可以通过Swagger编辑器进行访问,如下所示:
有权访问此文档的任何人都可以查看API端点和所有其他相关的元数据,例如模型结构,数据类型等。
Swagger Codegen存根
我们Swagger已经提供了两个漂亮的工具来管理文档生成和访问。第三个工具Swagger代码生成器使我们能够从提供的OpenAPI文档中生成服务器存根和客户端SDK。Swagger Codegen目前支持以下服务器(超过20种语言)和客户端SDK生成(超过40种语言):
Swagger Codegen可以通过命令行界面(codegen-cli)或Maven插件进行访问。在本文中,我们将使用Swagger代码生成CLI。要访问codegen CLI jar文件,您可以
- 从这里下载jar(https://search.maven.org/classic/remotecontent?filepath=io/swagger/swagger-codegen-cli/2.2.3/swagger-codegen-cli-2.2.3.jar)
- 或者,如果您想要最新的更改,请浏览到Swagger Codegen Github存储库(https://github.com/swagger-api/swagger-codegen)并将其克隆到本地计算机。生成项目并浏览到swagger-codegen/modules/swagger-codegen-cli/target(https://github.com/swagger-api/swagger-codegen)文件夹
生成Spring Boot服务器存根
我们将从OpenAPI文档中生成一个服务器存根。该存根可用于模拟和测试端点。当提供者可能已经共享了API文档但使用者没有访问提供者基础结构的权限时,这是一种常见情况。在这种情况下,模拟端点和操作以模拟API访问绝对至关重要。
在本节中,我们将生成服务器存根并实现GET映射/api/donors/{id}
要生成服务器根,请浏览至swagger codegen CLI jar文件的位置,然后运行以下命令:
java -jar swagger-codegen-cli.jar generate
-i C:\Somnath\Writing\Medium\docs\api-docs.json
--api-package io.codefountain.swagger
--model-package io.codefountain.swagger.client.model
--invoker-package io.codefountain.swagger.client.invoker
--group-id io.codefountain
--artifact-id spring-swagger-codegen-api-client
--artifact-version 0.0.1-SNAPSHOT
-l spring
-o C:\Somnath\Dev\Programming\Spring\donor-app-server
--additional-properties dateLibrary=java8-localdatetime
根生成命令
在上面的命令中,我们正在执行以下操作:
- 使用-i参数指定API规范的位置。根据您的环境位置更改此位置
- 指定包结构。如果未提供此选项,则Swagger将使用默认的io.swagger包
- 带-l选项的服务器根的类型
- 最后,带有-o选项的spring boot项目的位置
命令执行后,将生成带有所有端点根的spring boot项目。默认情况下,所有这些根方法都将返回HTTP错误代码501(未实现)。您可以从Github存储库(https://github.com/musibs/donor-app-server/tree/master)下载我为本文生成的示例存根。
以下是更新的类:
/**
* NOTE: This class is auto generated by the swagger code generator program (2.4.10-SNAPSHOT).
* https://github.com/swagger-api/swagger-codegen
* Do not edit the class manually.
*/
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.codefountain.swagger.client.model.Donor;
import io.codefountain.swagger.client.model.Iterable;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2019-11-16T11:12:22.062+11:00")
@Api(value = "api", description = "the api API")
public interface ApiApi {
Logger log = LoggerFactory.getLogger(ApiApi.class);
static final String UTF_8 = "UTF-8";
default Optional<ObjectMapper> getObjectMapper() {
return Optional.empty();
}
default Optional<HttpServletRequest> getRequest() {
return Optional.empty();
}
default Optional<String> getAcceptHeader() {
return getRequest().map(r -> r.getHeader("Accept"));
}
static String asString(Resource resource) {
try (Reader reader = new InputStreamReader(resource.getInputStream(), UTF_8)) {
return FileCopyUtils.copyToString(reader);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
default Resource getResource() {
ResourceLoader resourceLoader = new DefaultResourceLoader();
return resourceLoader.getResource("classpath:donor.json");
}
@ApiOperation(value = "Create a new donor", nickname = "createDonorUsingPOST", notes = "", response = Donor.class, tags={ "donor-controller", })
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully created a new donor", response = Donor.class),
@ApiResponse(code = 201, message = "Created"),
@ApiResponse(code = 401, message = "You are not authorized to view the resource"),
@ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found"),
@ApiResponse(code = 500, message = "Application failed to process the request") })
@RequestMapping(value = "/api/donors",
produces = { "*/*" },
consumes = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<Donor> createDonorUsingPOST(@ApiParam(value = "donor", required = true) @Valid @RequestBody Donor donor) {
if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) {
if (getAcceptHeader().get().contains("")) {
try {
return new ResponseEntity<>(getObjectMapper().get().readValue("", Donor.class), HttpStatus.NOT_IMPLEMENTED);
} catch (IOException e) {
log.error("Couldn't serialize response for content type ", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
} else {
log.warn("ObjectMapper or HttpServletRequest not configured in default ApiApi interface so no example is generated");
}
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
@ApiOperation(value = "Deletes specific donor with the supplied donor id", nickname = "deleteUsingDELETE", notes = "", tags={ "donor-controller", })
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully deletes the specific donor"),
@ApiResponse(code = 204, message = "No Content"),
@ApiResponse(code = 401, message = "You are not authorized to view the resource"),
@ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found"),
@ApiResponse(code = 500, message = "Application failed to process the request") })
@RequestMapping(value = "/api/donors/{id}",
produces = { "*/*" },
method = RequestMethod.DELETE)
default ResponseEntity<Void> deleteUsingDELETE(@ApiParam(value = "id", required = true) @PathVariable("id") Long id) {
if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) {
} else {
log.warn("ObjectMapper or HttpServletRequest not configured in default ApiApi interface so no example is generated");
}
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
@ApiOperation(value = "Retrieve specific donor with the supplied donor id", nickname = "getDonorUsingGET", notes = "", response = Donor.class, tags={ "donor-controller", })
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully retrieved the donor with the supplied id", response = Donor.class),
@ApiResponse(code = 401, message = "You are not authorized to view the resource"),
@ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found"),
@ApiResponse(code = 500, message = "Application failed to process the request") })
@RequestMapping(value = "/api/donors/{id}",
produces = { "application/json" },
method = RequestMethod.GET)
default ResponseEntity<Donor> getDonorUsingGET(@ApiParam(value = "id", required = true) @PathVariable("id") Long id) {
if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) {
if (getAcceptHeader().get().contains("application/json")) {
try {
String donor = asString(getResource());
System.out.println(donor);
return new ResponseEntity<>(getObjectMapper().get().readValue(donor, Donor.class), HttpStatus.OK);
//return new ResponseEntity<>(getObjectMapper().get().readValue("{ \"bloodGroup\" : \"bloodGroup\", \"firstName\" : \"firstName\", \"lastName\" : \"lastName\", \"firstTimeDonor\" : true, \"dateOfBirth\" : \"2000-01-23\", \"donorId\" : 0, \"emergencyContactNo\" : \"emergencyContactNo\", \"contactNo\" : \"contactNo\"}", Donor.class), HttpStatus.NOT_IMPLEMENTED);
} catch (IOException e) {
log.error("Couldn't serialize response for content type application/json", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
} else {
log.warn("ObjectMapper or HttpServletRequest not configured in default ApiApi interface so no example is generated");
}
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
@ApiOperation(value = "View all donors", nickname = "getDonorsUsingGET", notes = "", response = Iterable.class, tags={ "donor-controller", })
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully retrieved all donors", response = Iterable.class),
@ApiResponse(code = 401, message = "You are not authorized to view the resource"),
@ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found"),
@ApiResponse(code = 500, message = "Application failed to process the request") })
@RequestMapping(value = "/api/donors",
produces = { "*/*" },
method = RequestMethod.GET)
default ResponseEntity<Iterable> getDonorsUsingGET() {
if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) {
if (getAcceptHeader().get().contains("")) {
try {
return new ResponseEntity<>(getObjectMapper().get().readValue("", Iterable.class), HttpStatus.NOT_IMPLEMENTED);
} catch (IOException e) {
log.error("Couldn't serialize response for content type ", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
} else {
log.warn("ObjectMapper or HttpServletRequest not configured in default ApiApi interface so no example is generated");
}
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
@ApiOperation(value = "Update a donor information", nickname = "updateDonorUsingPUT", notes = "", response = Donor.class, tags={ "donor-controller", })
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully updated donor information", response = Donor.class),
@ApiResponse(code = 201, message = "Created"),
@ApiResponse(code = 401, message = "You are not authorized to view the resource"),
@ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found"),
@ApiResponse(code = 500, message = "Application failed to process the request") })
@RequestMapping(value = "/api/donors",
produces = { "*/*" },
consumes = { "application/json" },
method = RequestMethod.PUT)
default ResponseEntity<Donor> updateDonorUsingPUT(@ApiParam(value = "donor", required = true) @Valid @RequestBody Donor donor) {
if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) {
if (getAcceptHeader().get().contains("")) {
try {
return new ResponseEntity<>(getObjectMapper().get().readValue("", Donor.class), HttpStatus.NOT_IMPLEMENTED);
} catch (IOException e) {
log.error("Couldn't serialize response for content type ", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
} else {
log.warn("ObjectMapper or HttpServletRequest not configured in default ApiApi interface so no example is generated");
}
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}
https://gist.github.com/musibs/b268da2c1fc7bc3de22a9bd734c0933b/raw/4133944e31078d908229ca96a9abb014cf91d4cf/ApiApi.java
该项目配置为在端口8081中运行。一旦尝试访问api/donors/1 端点,我们将收到以下信息:
生成Node JS服务器存根
在本节中,我们将从API文档中生成一个节点JS服务器存根。
java -jar swagger-codegen-cli.jar
generate -i C:\Somnath\Writing\Medium\docs\api-docs.json
-l nodejs-server
-o C:\Somnath\Dev\Programming\node
生成Node JS根(https://gist.github.com/musibs/ad058fa84a004fa342c6de9450be0378/raw/361a68621377e24bad40c595e793eeb54aa349cc/nodejs.txt)
该命令生成Node JS应用程序。打开index.js并更改服务器端口8082。浏览到node/service/DonorControllerService文件并编辑getDonorUsingGET函数,如下所示:-
'use strict';
/**
* Create a new donor
*
* donor Donor donor
* returns Donor
**/
exports.createDonorUsingPOST = function(donor) {
return new Promise(function(resolve, reject) {
var examples = {};
if (Object.keys(examples).length > 0) {
resolve(examples[Object.keys(examples)[0]]);
} else {
resolve();
}
});
}
/**
* Deletes specific donor with the supplied donor id
*
* id Long id
* no response value expected for this operation
**/
exports.deleteUsingDELETE = function(id) {
return new Promise(function(resolve, reject) {
resolve();
});
}
/**
* Retrieve specific donor with the supplied donor id
*
* id Long id
* returns Donor
**/
exports.getDonorUsingGET = function(id) {
return new Promise(function(resolve, reject) {
var examples = {};
examples['application/json'] = {
"bloodGroup" : "A+",
"firstName" : "John",
"lastName" : "Smith",
"firstTimeDonor" : true,
"dateOfBirth" : "2000-01-23",
"donorId" : 120,
"emergencyContactNo" : "87654309",
"contactNo" : "9876530"
};
if (Object.keys(examples).length > 0) {
resolve(examples[Object.keys(examples)[0]]);
} else {
resolve();
}
});
}
/**
* View all donors
*
* returns Iterable
**/
exports.getDonorsUsingGET = function() {
return new Promise(function(resolve, reject) {
var examples = {};
if (Object.keys(examples).length > 0) {
resolve(examples[Object.keys(examples)[0]]);
} else {
resolve();
}
});
}
/**
* Update a donor information
*
* donor Donor donor
* returns Donor
**/
exports.updateDonorUsingPUT = function(donor) {
return new Promise(function(resolve, reject) {
var examples = {};
if (Object.keys(examples).length > 0) {
resolve(examples[Object.keys(examples)[0]]);
} else {
resolve();
}
});
}
NodeJS服务(https://gist.github.com/musibs/5500ce6c4085331e1269b10aa5f9d9b7/raw/1506516306e69d88b4e6b0493fec3a05754e8d49/DonorControllerService.js)
执行npm start以启动服务器:
访问API文档:-
访问/api/donors/1端点:-
结论
恭喜!如果您设法到达这里,那么您肯定会学到很多。Swagger是一种功能非常强大的工具,它在REST API生态系统中开辟了一个新的领域。它提供了绝对的灵活性来自动化API文档过程。
参考
- OpenAPI规范(https://en.wikipedia.org/wiki/OpenAPI_Specification)
- Swagger文档(https://swagger.io/docs/specification/about/)
- https://mjstealey.github.io/swagger-demo/swagger/
原文地址:https://medium.com/swlh/restful-api-documentation-made-easy-with-swagger-and-openapi-6df7f26dcad