优秀的编程知识分享平台

网站首页 > 技术文章 正文

使用Swagger和OpenAPI使RESTAPI文档变轻松

nanyue 2024-08-22 17:28:49 技术文章 5 ℃

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 /usersPOST /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

最近发表
标签列表