本文全面介绍了Swagger学习的相关内容,包括Swagger的基本概念、安装与配置、定义API文档、测试与调试以及高级功能。通过详细讲解和示例,帮助开发者快速掌握Swagger的使用方法和技巧。Swagger提供了丰富的工具集和功能,能够显著提升API文档的创建和维护效率。
Swagger简介Swagger是一种流行的开源框架,用于创建、解析以及分发RESTful API文档。它提供了一套完整的工具集,使开发者能够快速地定义、可视化和测试API,从而提高开发效率并确保API的一致性和质量。
什么是SwaggerSwagger是一个开源项目,其核心组件包括Swagger Core、Swagger UI和Swagger Codegen。Swagger Core用于定义和解析API文档,Swagger UI用于可视化和交互式地展示API文档,而Swagger Codegen则用于自动生成客户端代码、服务器端代码以及API测试代码。
Swagger使用OpenAPI规范来描述API,这是一种基于JSON或YAML的规范,用于定义、表示和共享API。通过使用Swagger,开发者可以轻松地创建和维护API文档,并且Swagger UI提供了交互式的API测试界面,使开发者和测试人员可以更加方便地测试和调用API。
Swagger的作用和优势作用
- 定义API:Swagger允许开发者使用OpenAPI规范定义API的各个组件,如资源、操作、参数等。
- 文档生成:基于定义的API,Swagger自动生成交互式文档,提供文档浏览、请求测试等功能。
- 客户端生成:Swagger可以自动生成客户端代码,简化客户端开发过程。
- 可视化测试:Swagger UI提供了可视化界面,方便开发者和测试人员测试API。
- 版本控制:Swagger支持多版本API的定义和管理。
优势
- 提高效率:通过自动生成文档和客户端代码,Swagger减少了重复工作,提高了开发效率。
- 增强协作:Swagger文档清晰、直观,便于团队成员之间共享和理解API。
- 提高质量:Swagger提供的测试功能有助于发现和修复API中的问题,提高API质量。
- 易于维护:Swagger文档易于维护,更新API时只需更新定义即可。
- 兼容性:Swagger支持多种编程语言和框架,具有较好的兼容性。
Swagger和OpenAPI之间有着密切的关系。OpenAPI规范是由Swagger项目最早提出并发展起来的,它定义了一种统一的格式来表示API。而Swagger则是一个实现这个规范的工具集,它不仅提供了定义API的方法,还提供了展示API文档、自动生成客户端代码等功能。
OpenAPI规范定义了API的基本组件,如资源、操作、参数等,并规定了这些组件的语法和格式。而Swagger则是在OpenAPI规范的基础上,提供了一整套工具来帮助开发者实现API文档的创建、解析和分发。因此,Swagger和OpenAPI之间是一种工具和规范的关系。
安装与配置Swagger在项目中集成Swagger是实现API文档自动生成的第一步。这里以一个Java项目为例,介绍如何集成Swagger,并进行基本配置。
选择合适的Swagger版本目前,Swagger有两个主要版本:Swagger 1.x和Swagger 2.0。Swagger 1.x是最早的版本,而Swagger 2.0则是基于OpenAPI规范的最新版本。由于Swagger 1.x已经不再维护,且与OpenAPI规范不兼容,建议使用Swagger 2.0版本。
安装Swagger 2.0需要在项目中添加Swagger Core、Swagger UI和Swagger Codegen的相关依赖。对于Java项目,可以在Maven或Gradle中添加以下依赖:
对于Maven项目,可以在pom.xml文件中添加如下依赖:
<dependencies>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-core</artifactId>
<version>2.1.8</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jaxrs</artifactId>
<version>2.1.8</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-ui</artifactId>
<version>2.1.8</version>
<scope>runtime</scope>
</dependency>
</dependencies>
对于Gradle项目,可以在build.gradle文件中添加如下依赖:
dependencies {
implementation 'io.swagger:swagger-core:2.1.8'
implementation 'io.swagger:swagger-jaxrs:2.1.8'
runtimeOnly 'io.swagger:swagger-ui:2.1.8'
}
在项目中集成Swagger
集成Swagger的基本步骤包括配置Swagger的核心组件和UI组件,下面以Java项目为例进行介绍。
- 配置Swagger Core:首先,需要创建一个Swagger Configuration类,用于配置Swagger的核心组件。该类通常会继承
Docket
类,并重写setup
方法来设置API的基本信息,如API版本、基础路径等。
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("API Documentation")
.description("This is a sample description.")
.version("1.0.0")
.build();
}
}
- 配置Swagger UI:接下来,需要定义一个Spring MVC配置类,用于配置Swagger UI的资源路径和静态文件访问。
import springfox.documentation.swagger2.web.Swagger2DocumentationConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
通过上述配置,Swagger的核心组件和UI组件已经集成到项目中。接下来,可以通过访问/swagger-ui.html
路径来查看Swagger UI界面。
在集成Swagger后,可以根据项目需求进一步配置Swagger的核心组件和UI组件。这里介绍一些常用的配置项。
- 设置API基本信息:在Swagger Configuration类中,可以通过重写
apiInfo()
方法来设置API的基本信息,如API标题、描述、版本等。
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("API Documentation")
.description("This is a sample description.")
.version("1.0.0")
.build();
}
- 设置API路径和操作:在
Docket
对象中,可以通过select()
方法来设置要包含的API路径和操作。例如,可以使用RequestHandlerSelectors.basePackage()
方法来指定要扫描的包,使用PathSelectors.any()
方法来匹配所有路径。
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
.paths(PathSelectors.any())
.build();
}
- 设置全局参数:还可以通过
Docket
对象的globalOperationParameters()
方法来设置全局参数,这些参数将应用于所有操作。
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.globalOperationParameters(
Arrays.asList(new ParameterBuilder()
.name("Authorization")
.description("Access token")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(false)
.build())
)
.build();
}
通过这些配置,可以更好地控制Swagger生成的API文档,使其更符合项目需求。
使用Swagger定义API文档使用Swagger定义API文档是Swagger核心功能之一。通过定义API文档,可以生成交互式的文档,方便开发者和测试人员查看和测试API。
创建基本的API文档在项目中集成Swagger后,可以通过定义API文档来生成文档。这里以一个简单的RESTful API为例,介绍如何定义API文档。
定义API资源
首先,需要定义API的资源。资源可以是任何可访问的事物,如用户、订单、文章等。在Swagger中,可以通过@Api
注解来定义API的基本信息,如API的版本、描述等。
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@Api(value = "User API", description = "Operations about users")
public class UserController {
// ...
}
定义API操作
接下来,需要定义API的操作。操作可以是任何可执行的动作,如获取用户信息、创建用户、更新用户等。在Swagger中,可以通过@ApiOperation
注解来定义API操作的基本信息,如操作的方法、描述、参数、响应等。
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@Api(value = "User API", description = "Operations about users")
public class UserController {
@ApiOperation(value = "Get user", notes = "Get user by ID")
public User getUser(int id) {
// ...
}
@ApiOperation(value = "Create user", notes = "Create a new user")
public void createUser(User user) {
// ...
}
}
定义API路径
路径是资源的访问地址,可以通过@Api
注解中的value
属性来定义API的基本路径。对于每个操作,可以通过@ApiOperation
注解中的value
属性来定义操作的路径。
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
@Api(value = "User API", description = "Operations about users", produces = "application/json")
public class UserController {
@ApiOperation(value = "Get user", notes = "Get user by ID", produces = "application/json")
@ApiParam(name = "id", value = "User ID", required = true)
public User getUser(@ApiParam(name = "id", value = "User ID", required = true) int id) {
// ...
}
@ApiOperation(value = "Create user", notes = "Create a new user", produces = "application/json")
@ApiParam(name = "user", value = "User object", required = true)
public void createUser(@ApiParam(name = "user", value = "User object", required = true) User user) {
// ...
}
}
通过上述步骤,可以定义一个简单的API文档。接下来,可以通过访问/swagger-ui.html
路径来查看Swagger UI界面,并测试API操作。
在定义API文档时,除了定义资源和操作外,还可以添加路径和操作。路径和操作可以通过@Api
、@ApiOperation
等注解来定义。
添加路径
添加路径通常是在定义API资源时完成的。通过@Api
注解中的value
属性来定义API的基本路径。
import io.swagger.annotations.Api;
@Api(value = "User API", description = "Operations about users", produces = "application/json")
public class UserController {
// ...
}
添加操作
添加操作通常是在定义API操作时完成的。通过@ApiOperation
注解来定义API操作的基本信息。
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@Api(value = "User API", description = "Operations about users", produces = "application/json")
public class UserController {
@ApiOperation(value = "Get user", notes = "Get user by ID", produces = "application/json")
public User getUser(int id) {
// ...
}
@ApiOperation(value = "Create user", notes = "Create a new user", produces = "application/json")
public void createUser(User user) {
// ...
}
}
通过添加路径和操作,可以进一步丰富API文档,使其更详细和完整。
描述参数和响应在定义API操作时,除了定义路径和操作外,还可以描述参数和响应。参数和响应可以通过@ApiParam
、@ApiResponses
等注解来定义。
描述参数
参数是操作的输入和输出。通过@ApiParam
注解来描述参数的基本信息,如参数名称、描述、类型、是否必填等。
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
@Api(value = "User API", description = "Operations about users", produces = "application/json")
public class UserController {
@ApiOperation(value = "Get user", notes = "Get user by ID", produces = "application/json")
@ApiParam(name = "id", value = "User ID", required = true)
public User getUser(@ApiParam(name = "id", value = "User ID", required = true) int id) {
// ...
}
@ApiOperation(value = "Create user", notes = "Create a new user", produces = "application/json")
@ApiParam(name = "user", value = "User object", required = true)
public void createUser(@ApiParam(name = "user", value = "User object", required = true) User user) {
// ...
}
}
描述响应
响应是操作的结果。通过@ApiResponses
注解来描述响应的基本信息,如响应码、响应体等。
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.ApiResponse;
@Api(value = "User API", description = "Operations about users", produces = "application/json")
public class UserController {
@ApiOperation(value = "Get user", notes = "Get user by ID", produces = "application/json")
@ApiParam(name = "id", value = "User ID", required = true)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Success", response = User.class),
@ApiResponse(code = 404, message = "User not found")
})
public User getUser(@ApiParam(name = "id", value = "User ID", required = true) int id) {
// ...
}
@ApiOperation(value = "Create user", notes = "Create a new user", produces = "application/json")
@ApiParam(name = "user", value = "User object", required = true)
@ApiResponses(value = {
@ApiResponse(code = 201, message = "User created", response = User.class),
@ApiResponse(code = 400, message = "Invalid user object")
})
public void createUser(@ApiParam(name = "user", value = "User object", required = true) User user) {
// ...
}
}
通过描述参数和响应,可以进一步丰富API文档,使其更详细和完整。
测试和调试Swagger文档测试和调试Swagger文档是确保API文档质量的重要步骤。通过测试和调试,可以发现和修复文档中的问题,并确保文档的正确性和准确性。
使用Swagger UI进行测试Swagger UI提供了交互式的API测试界面,使开发者和测试人员可以方便地测试API。通过访问/swagger-ui.html
路径,可以打开Swagger UI界面。
访问Swagger UI界面
在项目中集成Swagger后,可以通过访问/swagger-ui.html
路径来打开Swagger UI界面。
<!DOCTYPE html>
<html>
<head>
<title>API Documentation</title>
<link rel="stylesheet" type="text/css" href="/webjars/swagger-ui/3.20.8/swagger-ui.css" />
</head>
<body>
<div id="swagger-ui"></div>
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="/webjars/swagger-ui/3.20.8/swagger-ui-bundle.js"></script>
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="/webjars/swagger-ui/3.20.8/swagger-ui-standalone-preset.js"></script>
<script>
const ui = SwaggerUIBundle({
url: "/v2/api-docs",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundlepreset.standalone,
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl,
],
});
</script>
</body>
</html>
通过访问/swagger-ui.html
路径,可以打开Swagger UI界面,并查看API文档。
使用Swagger UI测试API
在Swagger UI界面中,可以通过点击操作来测试API。对于每个操作,会显示操作的路径、方法、参数、响应等信息。
GET /users/{id}
通过点击操作,可以发送请求并查看响应。此外,Swagger UI还提供了请求参数的输入框,使开发者和测试人员可以更方便地测试API。
识别和修正错误在测试Swagger文档时,可能会发现一些错误。这些错误可能是API文档的定义错误,也可能是API实现中的错误。通过识别和修正错误,可以提高API文档的质量。
识别错误
在测试Swagger文档时,可以通过查看响应和日志来识别错误。例如,如果发送请求后返回错误码或错误信息,则可能存在定义错误或实现错误。
修正错误
一旦识别出错误,可以通过修改Swagger文档的定义来修正错误。例如,如果发现参数定义错误,可以通过修改@ApiParam
注解来修正参数定义。
@ApiOperation(value = "Get user", notes = "Get user by ID", produces = "application/json")
@ApiParam(name = "id", value = "User ID", required = true, type = "integer")
public User getUser(@ApiParam(name = "id", value = "User ID", required = true, type = "integer") int id) {
// ...
}
通过修正错误,可以确保API文档的质量和准确性。
定期更新和维护文档在开发过程中,API文档可能会发生变化。因此,需要定期更新和维护文档,以保持文档的最新性和准确性。
更新文档
在API发生变化时,需要更新Swagger文档的定义。例如,如果新增了一个操作,可以通过添加新的@ApiOperation
注解来更新文档。
@ApiOperation(value = "Update user", notes = "Update user by ID", produces = "application/json")
@ApiParam(name = "id", value = "User ID", required = true, type = "integer")
@ApiParam(name = "user", value = "User object", required = true, type = "User")
public void updateUser(@ApiParam(name = "id", value = "User ID", required = true, type = "integer") int id, @ApiParam(name = "user", value = "User object", required = true, type = "User") User user) {
// ...
}
维护文档
除了更新文档外,还需要定期维护文档,确保文档的一致性和准确性。例如,可以定期检查文档的格式和内容,确保文档的格式符合规范,内容符合实际。
通过定期更新和维护文档,可以保持文档的最新性和准确性。
Swagger高级功能介绍Swagger提供了许多高级功能,使API文档更加丰富和实用。这里介绍一些常见的高级功能,包括安全性和认证机制、使用分组和标签组织文档、与第三方工具的集成等。
安全性和认证机制在实际应用中,API通常需要进行安全性和认证机制的设置。Swagger提供了多种方式来支持这些功能。
基本认证
基本认证是最简单的一种认证方式。可以通过在@ApiOperation
注解中添加@ApiImplicitParams
注解来实现基本认证。
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@Api(value = "User API", description = "Operations about users", produces = "application/json")
public class UserController {
@ApiOperation(value = "Get user", notes = "Get user by ID", produces = "application/json")
@ApiImplicitParams({
@ApiImplicitParam(name = "Authorization", value = "Access token", required = true, dataType = "string", paramType = "header")
})
public User getUser(int id) {
// ...
}
}
OAuth认证
OAuth认证是一种常见的认证方式。可以通过在Docket
对象中设置OAuth认证信息来实现OAuth认证。
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.securitySchemes(Arrays.asList(new OAuth().name("oauth2")
.flows(new OAuthFlows().authorizationCode(new OAuthFlowsAuthorizationCode()
.authorizationEndpoint("https://example.com/oauth2/authorize")
.tokenEndpoint("https://example.com/oauth2/token")
.scopes(new AuthorizationScope[]{new AuthorizationScope("read", "read resources"), new AuthorizationScope("write", "modify resources")}))))
.build();
}
通过这些方式,可以在Swagger文档中设置安全性和认证机制,使文档更加实用。
使用分组和标签组织文档在大型项目中,API文档可能会变得非常庞大。为了更好地组织文档,Swagger提供了分组和标签的功能。
单个API分组
通过@Api
注解中的tags
属性,可以将API分组。
import io.swagger.annotations.Api;
@Api(value = "User API", tags = "User Operations", produces = "application/json")
public class UserController {
// ...
}
多个API分组
通过@ApiOperation
注解中的tags
属性,可以在多个API之间共享标签。
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@Api(value = "User API", tags = "User Operations", produces = "application/json")
public class UserController {
@ApiOperation(value = "Get user", notes = "Get user by ID", produces = "application/json", tags = "User Operations")
public User getUser(int id) {
// ...
}
@ApiOperation(value = "Create user", notes = "Create a new user", produces = "application/json", tags = "User Operations")
public void createUser(User user) {
// ...
}
}
通过这些方式,可以更好地组织API文档,使其更清晰和易于理解。
与第三方工具的集成Swagger可以与许多第三方工具集成,以实现更丰富的功能。这里介绍一些常见的集成方式。
与Jenkins集成
Jenkins是一个流行的持续集成工具。可以通过集成Swagger来实现API文档的自动构建和发布。
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building the API documentation...'
sh 'mvn clean package'
}
}
stage('Deploy') {
steps {
echo 'Deploying the API documentation...'
sh 'mvn deploy'
}
}
}
}
与Selenium集成
Selenium是一个流行的自动化测试工具。可以通过集成Swagger来实现API文档的自动化测试。
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
public class SwaggerTest {
public static void main(String[] args) {
System.setProperty("webdriver.chrome.driver", "path/to/chromedriver");
WebDriver driver = new ChromeDriver();
driver.get("http://localhost:8080/swagger-ui.html");
WebElement userLink = driver.findElement(By.partialLinkText("User Operations"));
userLink.click();
WebElement getUserButton = driver.findElement(By.partialLinkText("Get user"));
getUserButton.click();
WebElement idInput = driver.findElement(By.name("id"));
idInput.sendKeys("123");
WebElement tryItOutButton = driver.findElement(By.cssSelector(".opblock > .try-out"));
tryItOutButton.click();
WebElement responseDiv = driver.findElement(By.cssSelector(".opblock .try-it-out .response-body"));
String response = responseDiv.getText();
System.out.println(response);
driver.quit();
}
}
通过这些方式,可以与第三方工具集成,实现更丰富的功能。
常见问题与解答在使用Swagger时,可能会遇到一些常见问题。这里介绍一些常见的错误和解决方法,并提供常用的Swagger资源和社区支持。
常见错误和解决方法错误一:无法访问Swagger UI界面
问题描述:在访问/swagger-ui.html
路径时,无法打开Swagger UI界面。
解决方法:
- 检查是否已经集成Swagger,确保已经在项目中添加了Swagger的相关依赖。
- 检查是否已经配置了Swagger UI的资源路径和静态文件访问。
- 检查是否已经在Spring Boot应用中启用了Swagger,确保已经在
SwaggerConfig
类中启用了Swagger。
import springfox.documentation.spring.data.repository.RepositoryRestResource;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
错误二:无法发送请求
问题描述:在Swagger UI界面中,无法发送请求。
解决方法:
- 检查API定义是否正确,确保API路径、方法、参数等信息正确。
- 检查API实现是否正确,确保API接口已经正常实现。
错误三:无法解析Swagger文档
问题描述:在Swagger UI界面中,无法解析Swagger文档。
解决方法:
- 检查Swagger文档定义是否正确,确保Swagger文档格式正确。
- 检查Swagger文档路径是否正确,确保Swagger文档路径正确。
Swagger官方文档
Swagger官方文档提供了详细的Swagger使用指南和示例。
Swagger社区
Swagger社区是一个活跃的技术社区,提供了丰富的资源和支持。
Swagger插件和工具
Swagger提供了许多插件和工具,可以用于扩展Swagger的功能。
进一步学习的建议参考文献
学习资源
通过这些资源,可以更好地学习和使用Swagger。
共同学习,写下你的评论
评论加载中...
作者其他优质文章