概述

当我们使用Swagger生成验证时,通常会依赖基本规范。然而,有时我们需要添加Spring自定义验证注解。本文将指导如何在关注OpenAPI服务器生成器而不是约束验证器的情况下,利用这些验证来创建模型和REST API。

安装设置

我们将使用之前Baeldung教程中的内容,从OpenAPI 3.0.0规范生成一个服务器:从OpenAPI 3.0.0定义生成服务器。接下来,我们会添加一些自定义验证注解,并引入所有必要的依赖。

PetStore API OpenAPI定义

假设我们有一个PetStore API的OpenAPI定义,并需要为REST API和描述的模型(Pet)添加自定义验证。

3.1. API模型的自定义验证

为了创建宠物,我们需要让Swagger使用我们的自定义验证注解检查宠物名称是否大写。因此,为了本教程,我们将这个验证命名为Capitalized

请查看下面示例中的x-constraints规范,它将告诉Swagger我们需要生成不同于已知类型的另一种注解:

openapi: 3.0.1
info:
  version: "1.0"
  title: PetStore
paths:
  /pets:
    post:
      #.. post described here
components:
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
          x-constraints: "Capitalized(required = true)"
        tag:
          type: string

3.2. REST API端点的自定义验证

同样,我们会在描述查找所有以特定名称的宠物的端点时采用相同的方法。为了演示目的,假设我们的系统对大小写敏感,所以我们将再次为name输入参数添加x-constraints验证:

/pets:
    # post defined here
    get: 
      tags: 
        - pet 
      summary: Finds Pets by name 
      description: 'Find pets by name' 
      operationId: findPetsByTags 
      parameters: 
        - name: name
          in: query 
          schema:
            type: string 
          description: Tags to filter by 
          required: true 
          x-constraints: "Capitalized(required = true)" 
      responses: 
        '200':
          description: default response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
        '400': 
          description: Invalid tag value

4. 创建Capitalized注解

为了强制执行自定义验证,我们需要创建一个确保功能的注解。

首先,我们创建一个接口——@Capitalized

@Documented
@Constraint(validatedBy = {Capitalized.class})
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Capitalized{
    String message() default "Name should be capitalized.";
    boolean required() default true;
    // default annotation methods
}

请注意,我们为了演示目的添加了required方法,稍后会解释。

接下来,我们需要添加CapitalizedValidator,这是上面@Constraint注解中引用的:

public class CapitalizedValidator implements ConstraintValidator<Capitalized, String> {

    @Override
    public boolean isValid(String nameField, ConstraintValidatorContext context) {
        // validation code here
    }
}

5. 生成验证注解

5.1. 指定Mustache模板目录

为了在模型中生成带有@Capitalized验证的注解,我们需要特定的Mustache模板,告诉Swagger在模型中生成它。

因此,在OpenAPI生成插件中,在<configuration>...</configuration>标签内,我们需要添加一个模板目录:

<plugin>  
  //... 
  <executions>
    <execution>
      <configuration
        //...
        <templateDirectory>
          ${project.basedir}/src/main/resources/openapi/templates
        </templateDirectory>
        //...
      </configuration>
    </execution>
  </executions>        
  //...
</plugin> 

5.2. 添加Mustache Bean验证配置

在这个章节中,我们将配置Mustache模板来生成验证规范。为了提供更多的细节,我们将修改[beanValidationCore.mustache](https://raw.githubusercontent.com/swagger-api/swagger-codegen/master/modules/swagger-codegen/src/main/resources/Java/beanValidationCore.mustache)[model.mustache](https://github.com/swagger-api/swagger-codegen/blob/master/modules/swagger-codegen/src/main/resources/Java/model.mustache)[api.muctache](https://github.com/swagger-api/swagger-codegen/blob/master/modules/swagger-codegen/src/main/resources/Java/api.mustache)文件,以成功生成代码。

首先,需要在swagger-codegen模块的[beanValidationCore.mustache](https://raw.githubusercontent.com/swagger-api/swagger-codegen/master/modules/swagger-codegen/src/main/resources/Java/beanValidationCore.mustache)中添加供应商扩展规范:

{{{ vendorExtensions.x-constraints }}}

其次,如果我们的注解具有内部属性,如@Capitalized(required = "true"),则需要在[beanValidationCore.mustache](https://raw.githubusercontent.com/swagger-api/swagger-codegen/master/modules/swagger-codegen/src/main/resources/Java/beanValidationCore.mustache)文件的第二行指定特定模式:

{{#required}}@Capitalized(required="{{{pattern}}}") {{/required}}

接着,我们需要更改[model.mustache](https://github.com/swagger-api/swagger-codegen/blob/master/modules/swagger-codegen/src/main/resources/Java/model.mustache)规范,使其包含必要的导入。例如,我们将导入@Capitalized注解和Capitalized.*。导入应在model.mustachepackage标签之后:

{{#imports}}import {{import}}; {{/imports}} import 
com.baeldung.openapi.petstore.validator.CapitalizedValidator; 
import com.baeldung.openapi.petstore.validator.Capitalized;

最后,为了使注解生成到API中,我们需要在[api.mustache](https://github.com/swagger-api/swagger-codegen/blob/master/modules/swagger-codegen/src/main/resources/Java/api.mustache)文件中添加@Capitalized注解的导入。

{{#imports}}import {{import}}; {{/imports}} import 
com.baeldung.openapi.petstore.validator.Capitalized;

此外,[api.mustache](https://github.com/swagger-api/swagger-codegen/blob/master/modules/swagger-codegen/src/main/resources/Java/api.mustache)依赖于[cookieParams.mustache](https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/JavaSpring/cookieParams.mustache)文件。因此,我们需要将其添加到openapi/templates目录中。

6. 生成源代码

最后,我们可以使用生成的代码。至少运行mvn generate-sources,这将生成模型:

public class Pet {
    @JsonProperty("id")
    private Long id = null;

    @JsonProperty("name")
    private String name = null;
    // other parameters
    @Schema(required = true, description = "")
    @Capitalized public String getName() { return name; } // default getters and setter }

同时也会生成API:

default ResponseEntity<List<Pet>> findPetsByTags(
    @Capitalized(required = true)
    @ApiParam(value = "Tags to filter by") 
    @Valid @RequestParam(value = "name", required = false) String name) {
    
    // default generated code here 
    return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}

7. 使用curl测试

启动应用后,我们将运行一些curl命令进行测试。

另外,请注意,违反约束会抛出ConstraintViolationException。异常需要通过@ControllerAdvice正确处理,返回400 Bad Request状态。

7.1. 测试Pet模型验证

这个Pet模型的名称是小写的,因此应用程序应该返回400 Bad Request:

curl -X 'POST' \
  'http://localhost:8080/pet' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": 1,
  "name": "rockie"
}'

7.2. 测试查找Pet API

同样,因为名称是小写的,应用程序也应该返回400 Bad Request:

curl -I http://localhost:8080/pets/name="rockie"

8. 总结

在这篇教程中,我们学习了如何在实现REST API服务器的同时,启用Spring自定义约束验证器的生成。

一如既往,代码可以在GitHub上找到。