springdoc

406 阅读8分钟

Swagger 规范已于 2015 年捐赠给 Linux 基金会后改名为 OpenAPI,并定义最新的规范为 OpenAPI 3.0。所以现在的 Swagger 3.0 就是 OpenAPI 3.0。跟Swagger2差不多,只是将@EnableSwagger2换成@EnableOpenAPI

knife4j 是一个Swagger的增强工具,能够完善项目的接口文档。

SpringFox是Spring社区维护的一个项目(非官方),帮助使用者将Swagger2集成到Spring中。常常用于Spring中帮助开发者生成文档,并可以轻松的在Spring Boot中使用。

SpringDoc也是Spring社区维护的一个项目(非官方),帮助使用者将Swagger3集成到Spring中。

Swagger官网:https://swagger.io/

SpringFox项目:https://github.com/springfox/springfox

SpringDoc项目:https://github.com/springdoc/springdoc-openapi

SpringDoc文档:https://springdoc.org/

访问地址:openapi3: localhost:8080//swagger-ui/index.html

引入依赖

        <!-- springdoc -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.7.0</version>
        </dependency>

配置

在Spring Boot 应用程序中,通常不需要额外配置,Springdoc会自动扫描控制器和模型。可以在 application.propertiesapplication.yml中进行一些基本配置,例如:

# Docs API配置
springdoc:
  swagger-ui:
    # API文档, 默认路径: /swagger-ui/index.html
    path: /swagger-ui/index.html
    # 开启Swagger UI界面
    enabled: true
    # 根据HTTP方法对API路径进行排序
    operations-sorter: method
  api-docs:
    # OpenAPI的路径描述,默认路径:/v3/api-docs
    # OpenAPI描述定义默认为JSON格式, 通过http://localhost:8080/v3/api-docs.yaml获取yaml格式
    path: /v3/api-docs
    # 开启api-docs
    enabled: true
  # 配置需要生成接口文档的扫描包路径
  packages-to-scan: com.ldk
  # 配置需要生成接口文档的接口路径
  paths-to-match:  /**

可以创建一个配置类来自定义 Springdoc 的行为:设置标题、描述和版本等。还可以对API进行分组:按模块、按功能划分API。

@Configuration
public class OpenApiConfig {

    /**
     * 配置自定义的 OpenAPI 规范
     * 通过 @Bean 注解声明该方法返回一个 Spring Bean,该 Bean 是一个 OpenAPI 对象
     * 该方法允许通过 Spring Context 初始化 OpenAPI 对象,并自定义 API 的标题、版本、描述等信息
     * 
     * @return 自定义的 OpenAPI 对象
     */
    @Bean
    public OpenAPI customOpenAPI() {
        // 创建并配置 OpenAPI 对象
        return new OpenAPI()
            .info(new Info()
                  .title("我的 API")            // 设置 API 标题
                  .version("v0.0.1")            // 设置 API 版本
                  .description("这是一个示例 API") // 设置 API 描述
                  .license(new License().name("Apache 2.0").url("http://example.com")) // 设置 API 的许可证信息,包括许可证名称和 URL
                  .contact(new Contact().name("开发者姓名").url("http://example.com").email("developer@example.com"))) // 设置联系人的姓名 url地址,邮箱
            .externalDocs(new ExternalDocumentation().description("外部文档的描述").url("http://example.com")) // 设置外部文档的描述 设置外部文档的 URL
    }  

创建controller

@Tag(name = "测试API接口", description = "这是一个关于测试API接口的描述")
@RequestMapping("/test")
@RestController
public class HelloController {

    @Operation(summary = "获取问候信息", description = "返回一个简单的问候信息")
    @ApiResponse(responseCode = "200", description = "成功")
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }
}

访问:localhost:8080//swagger-ui/index.html

image-20240903111412778.png

设置分组

配置方式设置分组

# Docs API配置
springdoc:
  swagger-ui:
    path: /swagger-ui/index.html
    enabled: true
  group-configs:
    - group: '测试1'
      paths-to-match: /test/**

配置类中设置分组

@Bean
public GroupedOpenApi testApi() {
    return GroupedOpenApi.builder()
        .group("test") // 设置组名
        .pathsToMatch("/test/**") // 设置需要匹配的路径模式
        .build();
}

image-20240903111037529.png

常用注解

注解描述
@Tag为一组 API 操作添加标签,便于在文档中组织和分组。
@Operation描述一个 API 操作,包括摘要和详细描述。
@Parameter描述方法参数,包括路径变量、请求体和查询参数。
@Schema描述数据模型的属性和结构,通常用于模型类或 API 方法的参数和返回值。
@ApiResponse描述单个 HTTP 响应状态码的详细信息。
@ApiResponses描述多个 HTTP 响应状态码的详细信息。
@RequestBody指定请求体的内容类型和结构。
@Content描述响应内容的类型和格式。
@SecurityRequirement描述 API 操作所需的安全要求,例如认证和授权。
@Hidden指定某个 API 操作或模型在文档中隐藏。
@Deprecated表示某个 API 操作或模型已被弃用。
@ArraySchema描述数组类型的响应内容,通常用于返回列表。
@ExampleObject提供示例对象,用于 API 文档中展示请求或响应的示例。
@MediaType指定请求或响应的媒体类型。
@Link描述 API 之间的链接关系。
@ParameterObject描述复合参数对象,通常用于请求体中的复杂结构。

左边时swagger2,右边时openapi3的注解

@Api -> @Tag
@ApiIgnore -> @Parameter(hidden = true) 或 @Operation(hidden = true) 或 @Hidden
@ApiImplicitParam -> @Parameter
@ApiImplicitParams -> @Parameters
@ApiModel -> @Schema
@ApiModelProperty(hidden = true) -> @Schema(accessMode = READ_ONLY)
@ApiModelProperty -> @Schema
@ApiOperation(value = "foo", notes = "bar") -> @Operation(summary = "foo", description = "bar")
@ApiParam -> @Parameter
@ApiResponse(code = 404, message = "foo") -> @ApiResponse(responseCode = "404", description = "foo")

详细示例

创建一个更详细、简单的图书管理API REST控制器示例,展示如何使用Springdoc 生成 API 文档的同时,处理不同的 HTTP 方法和请求参数。

创建模型类

首先,定义一个图书模型类 Book:

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

@Data
@Schema(description = "图书信息")
public class Book {
    
    @Schema(description = "图书ID", example = "1")
    private Long id;

    @Schema(description = "图书标题", example = "Spring Boot 入门")
    private String title;

    @Schema(description = "图书作者", example = "张三")
    private String author;
}

创建REST控制器

接下来,创建一个 BookController 控制器,提供 CRUD 操作:

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.tags.Tag;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@Tag(name = "图书管理API接口", description = "这是一个关于图书管理API接口的描述")
@RestController
@RequestMapping("/books")
public class BookController {

    private final List<Book> books = new ArrayList<>();

    @Operation(summary = "获取所有图书", description = "返回图书列表", method = "GET")
    @ApiResponse(responseCode = "200", description = "成功", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Book.class)))
    @GetMapping
    public List<Book> getAllBooks() {
        return books;
    }

    @Operation(summary = "根据ID获取图书", description = "通过图书ID获取图书信息", method = "GET")
    @ApiResponse(responseCode = "200", description = "成功")
    @ApiResponse(responseCode = "404", description = "未找到图书")
    @GetMapping("/{id}")
    public Book getBookById(@Parameter(description = "图书ID", required = true) @PathVariable Long id) {
        return books.stream()
                .filter(book -> book.getId().equals(id))
                .findFirst()
                .orElse(null);
    }

    @Operation(summary = "添加新图书", description = "创建新的图书记录", method = "POST")
    @ApiResponse(responseCode = "201", description = "图书创建成功")
    @PostMapping
    public Book createBook(@Parameter(description = "图书信息", required = true) @RequestBody Book book) {
        books.add(book);
        return book;
    }

    @Operation(summary = "更新图书信息", description = "根据ID更新图书信息", method = "PUT")
    @ApiResponse(responseCode = "200", description = "图书更新成功")
    @ApiResponse(responseCode = "404", description = "未找到图书")
    @PutMapping("/{id}")
    public Book updateBook(@Parameter(description = "图书ID", required = true) @PathVariable Long id,
                           @Parameter(description = "图书信息", required = true) @RequestBody Book updatedBook) {
        for (int i = 0; i < books.size(); i++) {
            if (books.get(i).getId().equals(id)) {
                books.set(i, updatedBook);
                return updatedBook;
            }
        }
        return null; // 或者抛出异常
    }

    @Operation(summary = "删除图书", description = "根据ID删除图书", method = "DELETE")
    @ApiResponse(responseCode = "204", description = "图书删除成功")
    @ApiResponse(responseCode = "404", description = "未找到图书")
    @DeleteMapping("/{id}")
    public void deleteBook(@Parameter(description = "图书ID", required = true) @PathVariable Long id) {
        books.removeIf(book -> book.getId().equals(id));
    }
}

安全策略

在OpenAPI规范中,安全策略类型(SecurityScheme.Type)用于定义API的认证机制。SpringDoc通过SecurityScheme类来配置这些认证机制。

SecuritySchemeType类:


public enum SecuritySchemeType {
    DEFAULT(""),
    APIKEY("apiKey"),
    HTTP("http"),
    OPENIDCONNECT("openIdConnect"),
    OAUTH2("oauth2");
}

用于基于HTTP的认证机制,如Basic Auth或Bearer Token。

例如,Bearer Token认证用于JWT令牌认证。

@Bean
public OpenAPI customOpenAPI() {
    // 创建并配置 OpenAPI 对象
    return new OpenAPI()
        .info(new Info()
              .title("我的 API")            // 设置 API 标题
              .version("v0.0.1")            // 设置 API 版本
              .description("这是一个示例 API") // 设置 API 描述
              .license(new License().name("Apache 2.0").url("http://example.com")) // 设置 API 的许可证信息,包括许可证名称和 URL
              .contact(new Contact().name("开发者姓名").url("http://example.com").email("developer@example.com"))) // 设置联系人的姓名 url地址,邮箱
        .externalDocs(new ExternalDocumentation().description("外部文档的描述").url("http://example.com")) // 设置外部文档的描述 设置外部文档的 URL
        .components(new io.swagger.v3.oas.models.Components()
                    .addSecuritySchemes(HttpHeaders.AUTHORIZATION,
                                        new SecurityScheme().type(SecurityScheme.Type.HTTP)
                                        .scheme("bearer")
                                        .bearerFormat("JWT")
                                        .in(SecurityScheme.In.HEADER)
                                        .name(HttpHeaders.AUTHORIZATION)));
}

请求会加入Authorization请求头,值为:Bearer a.b.c

curl -X GET -H  "Accept:*/*" 
-H  "Authorization:Bearer a.b.c" 
-H  "Content-Type:application/x-www-form-urlencoded" "http://localhost:8080/test/hello"

全局接口设置 Authorization

@Bean
public GlobalOpenApiCustomizer globalOpenApiCustomizer() {
    return openApi -> {
        // 全局添加鉴权参数
        if (openApi.getPaths() != null) {
            openApi.getPaths().forEach((url, pathItem) -> {
                //注册登录不用认证
                if(url.equals("/login") || url.equals("/register")){
                    return;
                }
                // 接口添加鉴权参数
                pathItem.readOperations().forEach(operation ->
                                                  operation.addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION))
                                                 );
            });
        }
    };
}

集成网关

@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", matchIfMissing = true)
public class SpringDocConfiguration implements InitializingBean {

	private final SwaggerUiConfigProperties swaggerUiConfigProperties;

	private final DiscoveryClient discoveryClient;

	/**
	 * 在初始化后调用的方法,用于注册SwaggerDocRegister订阅器
	 */
	@Override
	public void afterPropertiesSet() {
		SwaggerDocRegister swaggerDocRegister = new SwaggerDocRegister(swaggerUiConfigProperties, discoveryClient);
		// 手动调用一次,避免监听事件掉线问题
		swaggerDocRegister.onEvent(null);
		NotifyCenter.registerSubscriber(swaggerDocRegister);
	}

}

/**
 * Swagger文档注册器,继承自Subscriber<InstancesChangeEvent>
 */
@RequiredArgsConstructor
class SwaggerDocRegister extends Subscriber<InstancesChangeEvent> {

	private final SwaggerUiConfigProperties swaggerUiConfigProperties;

	private final DiscoveryClient discoveryClient;

	/**
	 * 事件回调方法,处理InstancesChangeEvent事件
	 * @param event 事件对象
	 */
	@Override
	public void onEvent(InstancesChangeEvent event) {
		Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> swaggerUrlSet = discoveryClient.getServices()
			.stream()
			.flatMap(serviceId -> discoveryClient.getInstances(serviceId).stream())
			.filter(instance -> StringUtils.isNotBlank(instance.getMetadata().get("spring-doc")))
			.map(instance -> {
				AbstractSwaggerUiConfigProperties.SwaggerUrl swaggerUrl = new AbstractSwaggerUiConfigProperties.SwaggerUrl();
				swaggerUrl.setName(instance.getServiceId());
				swaggerUrl.setUrl(String.format("/%s/v3/api-docs", instance.getMetadata().get("spring-doc")));
				return swaggerUrl;
			})
			.collect(Collectors.toSet());

		swaggerUiConfigProperties.setUrls(swaggerUrlSet);
	}

	/**
	 * 订阅类型方法,返回订阅的事件类型
	 * @return 订阅的事件类型
	 */
	@Override
	public Class<? extends Event> subscribeType() {
		return InstancesChangeEvent.class;
	}

}

SpringDoc:一个用于自动生成API文档的工具SpringDoc是一个用于Spring Boot的库,可以帮助生成 - 掘金 (juejin.cn)

knife4j

springboot2 openapi3 springdoc

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
    <version>4.4.0</version>
</dependency>

配置:

# Docs API配置
springdoc:
  swagger-ui:
    # API文档, 默认路径: /swagger-ui/index.html
    path: /swagger-ui/index.html
    # 开启Swagger UI界面
    enabled: true
    # 根据HTTP方法对API路径进行排序
    operations-sorter: method
  api-docs:
    # OpenAPI的路径描述,默认路径:/v3/api-docs
    # OpenAPI描述定义默认为JSON格式, 通过http://localhost:8080/v3/api-docs.yaml获取yaml格式
    path: /v3/api-docs
    # 开启api-docs
    enabled: true
  # 配置需要生成接口文档的扫描包路径
  packages-to-scan: com.ldk
  # 配置需要生成接口文档的接口路径
  paths-to-match:  /**
knife4j:
  enable: true
  setting:
    language: zh_cn

配置类:

@Configuration
public class OpenApiConfig {

    /**
     * 配置自定义的 OpenAPI 规范
     * 通过 @Bean 注解声明该方法返回一个 Spring Bean,该 Bean 是一个 OpenAPI 对象
     * 该方法允许通过 Spring Context 初始化 OpenAPI 对象,并自定义 API 的标题、版本、描述等信息
     * 
     * @return 自定义的 OpenAPI 对象
     */
    @Bean
    public OpenAPI customOpenAPI() {
        // 创建并配置 OpenAPI 对象
        return new OpenAPI()
            .info(new Info()
                  .title("我的 API")            // 设置 API 标题
                  .version("v0.0.1")            // 设置 API 版本
                  .description("这是一个示例 API") // 设置 API 描述
                  .license(new License().name("Apache 2.0").url("http://example.com")) // 设置 API 的许可证信息,包括许可证名称和 URL
                  .contact(new Contact().name("开发者姓名").url("http://example.com").email("developer@example.com"))) // 设置联系人的姓名 url地址,邮箱
            .externalDocs(new ExternalDocumentation().description("外部文档的描述").url("http://example.com")) // 设置外部文档的描述 设置外部文档的 URL
    }  
}

访问:

image-20240903112336007.png

注意:使用knife4j时不要使用分组,否则会报错

访问路径http://localhost:8080/doc.html