Spring 可分页界面的 Swagger 文档

Swagger documentation for Spring Pageable interface

我使用 Spring Boot 开发了一个微服务。 REST API 的文档是用 Swagger 制作的。一些 REST 资源利用 Spring 概念免费提供分页。下面是一个例子:

@RequestMapping(value = "/buckets", method = GET)
public PagedResources list(Pageable pageable, PagedResourcesAssembler assembler) {
    return bucketService.listBuckets(pageable, assembler);
}

如果我打开 Swagger 页面,可以使用以下表格获取资源:

我遇到的问题是检测到内容类型为 application/json 的可分页参数,我不知道如何传递值来更改页面例如尺寸。所有值似乎都被忽略了。

是否可以将查询参数作为 JSON 对象传递?或者是否可以配置 Swagger 为 Pageable 接口包含的 getter 生成独立的查询参数字段?

请注意,我正在使用 Springfox 和 Gradle:

compile 'io.springfox:springfox-spring-web:2.3.1'
compile 'io.springfox:springfox-swagger2:2.3.1'
compile 'io.springfox:springfox-swagger-ui:2.3.1'

这是 Spring-Fox 的一个已知问题。请参阅问题 #755. Based on zdila's comment 2,此时替代方法是添加 @ApiImplicitParams,这并不理想,但确实有效。

@ApiImplicitParams({
    @ApiImplicitParam(name = "page", dataType = "integer", paramType = "query",
            value = "Results page you want to retrieve (0..N)"),
    @ApiImplicitParam(name = "size", dataType = "integer", paramType = "query",
            value = "Number of records per page."),
    @ApiImplicitParam(name = "sort", allowMultiple = true, dataType = "string", paramType = "query",
            value = "Sorting criteria in the format: property(,asc|desc). " +
                    "Default sort order is ascending. " +
                    "Multiple sort criteria are supported.")
})

[

1 https://github.com/springfox/springfox/issues/755

2 https://github.com/springfox/springfox/issues/755#issuecomment-135059871

Vineet Bhatia @ApiImplicitParams 的回答看起来不错。但是我遇到了这样的情况,当 @ApiIgnor@ApiParam(hidden = true) 不起作用时,您仍然可以观察到 asembler 和 pageable 参数。我通过添加下一行

解决了这个问题
docket.ignoredParameterTypes(Pageable.class, PagedResourcesAssembler.class);

到我的 SwaggerConfig 中的 Docket bean。

当您不在 运行 本地主机上时,Vineet Bhatia 的回答将出现验证问题。它将争论整数参数,它们不对应于 json 模式。

所以我将整数更改为字符串:

    @ApiImplicitParams({
        @ApiImplicitParam(name = "page", dataType = "string", paramType = "query",
                value = "Results page you want to retrieve (0..N)"),
        @ApiImplicitParam(name = "size", dataType = "string", paramType = "query",
                value = "Number of records per page."),
        @ApiImplicitParam(name = "sort", allowMultiple = true, dataType = "string", paramType = "query",
                value = "Sorting criteria in the format: property(,asc|desc). " +
                        "Default sort order is ascending. " +
                        "Multiple sort criteria are supported.")
})

基于 Vineet Bhatia 的回答,您可以将解决方案包装在自定义注释中以实现可重用性:

@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@ApiImplicitParams({
    @ApiImplicitParam(name = "page", dataType = "int", paramType = "query", value = "Results page you want to retrieve (0..N)"),
    @ApiImplicitParam(name = "size", dataType = "int", paramType = "query", value = "Number of records per page."),
    @ApiImplicitParam(name = "sort", allowMultiple = true, dataType = "string", paramType = "query", value = "Sorting criteria in the format: property(,asc|desc). "
            + "Default sort order is ascending. " + "Multiple sort criteria are supported.") })
@interface ApiPageable {
}

然后可以这样使用:

@ApiPageable
public Page<Data> getData(Pageable pageRequest) {

虽然使用隐式参数的解决方案有效,但它引入了很多额外的、脆弱的代码。最后我们采用了以下解决方案:

@GetMapping(value = "/")
public HttpEntity<PagedResources<Item>> getItems(
    @RequestParam(value = "page", required = false) Integer page,
    @RequestParam(value = "size", required = false) Integer size,
    PagedResourcesAssembler assembler) {
    Page<Item> itemPage = itemService.listItems(PageRequest.of(page, size, Sort.unsorted()));
    return new ResponseEntity<>(assembler.toResource(itemPage), HttpStatus.OK);
}

我们将 PageRequest(实现 Pageable)传递给我们的服务,returns Page。 (全部来自 org.springframework.data.domain)。

org.springframework.data.web.PagedResourcesAssembler 通过控制器方法自动注入并允许将项目映射到 org.springframework.hateoas.PagedResources

我们不需要动态排序,所以我们忽略了它;它对添加排序提出了一些挑战,因为 springfox 不能与 org.springframework.data.domain.Sort.

配合使用

对于想在 2019 年解决此问题的人。通过 springfox documentation 进行的此配置工作正常,但不能设置参数说明。

代码在这里。

https://github.com/springfox/springfox/blob/ef1721afc4c910675d9032bee59aea8e75e06d27/springfox-data-rest/src/main/java/springfox/documentation/spring/data/rest/configuration/SpringDataRestConfiguration.java

Evgeny 指出的验证问题的答案。

正在使用

@ApiImplicitParams({
    @ApiImplicitParam(name = "page", dataType = "int", paramType = "query", value = "Results page you want to retrieve (0..N)"),
    @ApiImplicitParam(name = "size", dataType = "int", paramType = "query", value = "Number of records per page."),
    @ApiImplicitParam(name = "sort", allowMultiple = true, dataType = "string", paramType = "query", value = "Sorting criteria in the format: property(,asc|desc). "
            + "Default sort order is ascending. " + "Multiple sort criteria are supported.") })

抛出异常:

Illegal DefaultValue  for parameter type integer
java.lang.NumberFormatException: For input string: ""
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:68)
    at java.base/java.lang.Long.parseLong(Long.java:709)
    at java.base/java.lang.Long.valueOf(Long.java:1151)
    at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412)
    at jdk.internal.reflect.GeneratedMethodAccessor366.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:688)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)

(至少,它适用于 springfox-swagger2 和 springfox-swagger2-ui 版本 2.9.2)

您可以按照 Evgeny 的回答或为整数参数添加默认值和示例值来避免异常:

@ApiImplicitParams({
    @ApiImplicitParam(name = "page", dataType = "int", paramType = "query", value = "Results page you want to retrieve (0..N)", defaultValue = "0", example = "2"),
    @ApiImplicitParam(name = "size", dataType = "int", paramType = "query", value = "Number of records per page.", defaultValue = "20", example = "10"),
    @ApiImplicitParam(name = "sort", allowMultiple = true, dataType = "string", paramType = "query", value = "Sorting criteria in the format: property(,asc|desc). "
            + "Default sort order is ascending. " + "Multiple sort criteria are supported.") })

此解决方案无需在每个控制器中注释每个 API 方法即可工作。首先,我们用正确的 属性 名称和描述创建 Pageable class 的替换(Kotlin 代码,您可以使用 Java 的接口):

data class SwaggerPageable(
        @ApiModelProperty("Number of records per page", example = "20")
        val size: Int?,

        @ApiModelProperty("Results page you want to retrieve (0..N)", example = "0")
        val page: Int?,

        @ApiModelProperty("Sorting criteria in the format: property(,asc|desc)." +
                "Default sort order is ascending. Multiple sort criteria are supported.")
        var sort: String?
)

然后在 Swagger 配置中,只需将 Pageable 的直接替代添加到此 class(同样是 Kotlin 代码,但 Java 应该非常相似):

@Bean
fun api(): Docket {
    return Docket(DocumentationType.SWAGGER_2)
            .select()
            .paths(PathSelectors.any())
            .build()
            .directModelSubstitute(Pageable::class.java, SwaggerPageable::class.java)
}

结果如下所示:

缺点是无法在 ApiModelProperty 中定义默认值,但这对我的项目来说已经足够好了。

这是集成到 OpenAPI v3 的 springdoc-openapi-data-rest 中的注释版本:

@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Parameter(in = ParameterIn.QUERY
        , description = "Zero-based page index (0..N)"
        , name = "page"
        , content = @Content(schema = @Schema(type = "integer", defaultValue = "0")))
@Parameter(in = ParameterIn.QUERY
        , description = "The size of the page to be returned"
        , name = "size"
        , content = @Content(schema = @Schema(type = "integer", defaultValue = "20")))
@Parameter(in = ParameterIn.QUERY
        , description = "Sorting criteria in the format: property(,asc|desc). "
        + "Default sort order is ascending. " + "Multiple sort criteria are supported."
        , name = "sort"
        , content = @Content(array = @ArraySchema(schema = @Schema(type = "string"))))
public @interface PageableAsQueryParam {

}

https://springdoc.github.io/springdoc-openapi-demos/faq.html#how-can-i-map-pageable-spring-date-commons-object-to-correct-url-parameter-in-swagger-ui

Java 例子:

豆子:

@Bean
public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
        .select()
        .paths(PathSelectors.any())
        .build()
        .directModelSubstitute(Pageable.class, SwaggerPageable.class);
}

SwaggerPageable:

@Getter
private static class SwaggerPageable {

    @ApiParam(value = "Number of records per page", example = "0")
    @Nullable
    private Integer size;

    @ApiParam(value = "Results page you want to retrieve (0..N)", example = "0")
    @Nullable
    private Integer page;

    @ApiParam(value = "Sorting criteria in the format: property(,asc|desc). Default sort order is ascending. Multiple sort criteria are supported.")
    @Nullable
    private String sort;

}

大摇大摆:

开放 API 3.0 无缝集成。

例如,

@GetMapping("/filter")
public Page<Employee> filterEmployees(Pageable pageable) {
     return repository.getEmployees(pageable);
}

添加springdoc-openapi-data-rest依赖

implementation 'org.springdoc:springdoc-openapi-data-rest:1.5.2'

注意:如果您有多个参数,您可以添加'@ParameterObject'

public Page<Employee> filterEmployees(@ParameterObject Pageable pageable)

这是 springdoc-openapi-ui

的解决方案
SpringDocUtils.getConfig()
.replaceWithClass(org.springframework.data.domain.Pageable.class, SwaggerPageable.class);
    
@Getter
private static class SwaggerPageable {
    
        @ApiParam(value = "Number of records per page", example = "0")
        @Nullable
        private Integer size;
    
        @ApiParam(value = "Results page you want to retrieve (0..N)", example = "0")
        @Nullable
        private Integer page;
    
        @ApiParam(value = "Sorting criteria in the format: property(,asc|desc). Default sort order is ascending. Multiple sort criteria are supported.")
        @Nullable
        private String sort;
    
}