Bootstrap

springboot 参数自动校验使用笔记

参数自动校验可以做什么

使用参数自动校验可以简化验证接口参数的操作。通常我们会使用if-else手动判断接口参数是否合法,参数自动校验允许我们只用为表单字段添加注解就可以实现校验功能。

如何使用

以添加商品接口为例

1.引入自动校验依赖

在项目中引入自动校验的依赖,然后完成相应的配置即可

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2.定义接口参数的实体并添加相关校验的注解

如果实体参数的字段有复杂对象时,需要在字段上再添加@Valid注解,否则嵌套的对象字段的校验规则就无法生效了
这里不做校验注解的解释,后面会列出所有常用的注解并加以解释

import com.might.components.validators.ValidateGroup;
import com.might.po.commerce.product.ProductAttrEntity;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;

/**
 * 添加|编辑商品的表单参数
 * @author manem
 */
@Data
public class ProductFormDTO {
    @NotNull(message = "缺少商品id")
    private Long id;
    @NotBlank(message = "商品名称不能为空")
    @Length(min = 5, max = 100, message = "商品名称的长度范围在5~100个字符之间")
    private String name;
    @NotEmpty(message = "缺少分类信息")
    private List<Integer> categoryId;
    /**
     * 选择的SPU属性和值
     */
    @NotEmpty(message = "缺少spu属性")
    @Valid
    private List<ProductAttrEntity> spu;
}

3.让接口实现自动校验参数

需要在接口的参数前标记@Validated注解,否则不会生效的

@PostMapping("/product")
public Result<Boolean> createProduct(@Validated @RequestBody ProductFormDTO productFormDTO) {
    return Result.ok(productService.create(productFormDTO));
}

4.统一处理校验失败的结果

自动校验失败会抛出MethodArgumentNotValidException异常,只需要在全局异常处理器中额外处理这类异常即可

使用@RestControllerAdvice注解可以创建全局异常处理器,在控制器没有处理的异常都会在这里被处理

import com.might.enums.CodeEnum;
import com.might.response.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.sql.SQLSyntaxErrorException;
import java.util.stream.Collectors;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result<?> handleValidateException(MethodArgumentNotValidException e) {
        log.error("MethodArgumentNotValidException ", e);

        return Result.err(
                CodeEnum.VALIDATE_ERR.getCode(),
                e.getBindingResult()
                        // 取出所有校验失败的字段的message
                        .getAllErrors()
                        // 以strem的方式拼接从message信息
                        .stream()
                        .map(err -> err.getDefaultMessage())
                        .collect(Collectors.joining(","))
        );
    }
}

进阶

分组校验

不同应用场景下一个实体中的字段会有不同的校验规则,如新增的时候不要 id 一定存在,更新的时候却不能少了 id。如果不使用分组校验,需要再额外创建一个实体,导致实体类过于臃肿。
使用分组校验

1.定义分组标识

定义分组标识需要继承javax.validation.groups.Default这个接口

package com.might.components.validators;

import javax.validation.groups.Default;

/**
 * 用于标记参数校验的分组
 * @author manem
 */
public interface ValidateGroup extends Default {
    interface Crud extends ValidateGroup {
        interface Create extends Crud {}
        interface Update extends Crud {}
        interface Query extends Crud {}
        interface Delete extends Crud {}
    }
}

2.按分组设置字段的校验规则

在校验注解中的 groups 指定在哪个分组下生效即可,groups 是一个数组可以填写多个。
支持多个 groups 的写法是groups = {ValidateGroup.Crud.Update.class, ValidateGroup.Crud.Create.class}

import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;

/**
 * 添加|编辑商品的表单参数
 * @author manem
 */
@Data
public class ProductFormDTO {
    @NotNull(groups = ValidateGroup.Crud.Update.class, message = "缺少商品id")
    private Long id;
    @NotBlank(message = "商品名称不能为空")
    @Length(min = 5, max = 100, message = "商品名称的长度范围在5~100个字符之间")
    private String name;
}

3.为接口参数指定使用的分组规则

字段设置了分组标识后也需要在接口参数中标识使用哪个分组

@PostMapping("/product")
public Result<Boolean> createProduct(@Validated(value = ValidateGroup.Crud.Create.class) @RequestBody ProductFormDTO productFormDTO) {
    return Result.ok(productService.create(productFormDTO));
}

常用的校验注解

@NotNull

校验元素值不能为 null。如果元素为 null,则验证失败。通常用于字段级别的验证。

@NotNull(message = "Name cannot be null")private String name;

@NotBlank

校验字符串元素值不能为 null 或空字符串。必须包含至少一个非空格字符(即执行 trim()之后不为’')。如果元素为 null 或者‘‘,则验证失败。通常用于String类型的字段校验。

@NotBlank(message = "Username cannot be blank")private String username;

@NotEmpty

校验集合元素或数组元素或者字符串是否非空。通常作用于集合字段或数组字段,此时需要集合或者数字的元素个数大于 0。也可以作用于字符串,此时校验字符串不能为 null 或空串(可以是一个空格)。注意与@NotBlank的使用区别。

@NotEmpty(message = "List cannot be empty")private List<String> items;

@Length

校验字符串元素的长度。作用于字符串。注:Hibernate-Validator中注解,等同于spring-boot-starter-validation中的@Size。

@Length(min = 5, max = 20, message = "Length must be between 5 and 20 characters")private String username;

@Size

校验集合元素个数或字符串的长度在指定范围内。在集合或字符串字段上添加 @Size 注解。

@Size(min = 1, max = 10, message = "Number of items must be between 1 and 10")private List<String> items;

@Size(min = 5, max = 20, message = "Length must be between 5 and 20 characters")private String username;

@Min

校验数字元素的最小值。

@Min(value = 18, message = "Age must be at least 18")private int age;

@Max

校验数字元素的最大值。

@Max(value = 100, message = "Age must not exceed 100")private int age;

@DecimalMax

作用于BigDecimal类型字段, 校验字段的最大值,支持比较的值为字符串表示的十进制数。通常搭配它的inclusive()使用,区别边界问题。value 属性表示最大值,inclusive 属性表示是否包含最大值。

@DecimalMax(value = "100.00", inclusive = true, message = "Value must be less than or equal to 100.00")private BigDecimal amount;

@DecimalMin

作用于BigDecimal类型字段, 校验字段的最小值,支持比较的值为字符串表示的十进制数。通常搭配它的inclusive()使用,区别边界问题。value 属性表示最小值,inclusive 属性表示是否包含最小值。

@DecimalMin(value = "0.00", inclusive = false, message = "Value must be greater than 0.00")private BigDecimal amount;

@Email

校验字符串元素是否为有效的电子邮件地址。可以通过regexp自定义邮箱匹配正则。

@Email(message = "Invalid email address")private String email;

@Pattern

根据正则表达式校验字符串元素的格式。

@Pattern(regexp = "[a-zA-Z0-9]+", message = "Only alphanumeric characters are allowed")private String username;

@Digits

校验数字元素的整数部分和小数部分的位数。作用于BigDecimal,BigInteger,字符串,以及byte, short,int, long以及它们的包装类型。

@Digits(integer = 5, fraction = 2, message = "Number must have up to 5 integer digits and 2 fraction digits")private BigDecimal amount;

@Past

校验日期或时间元素是否在当前时间之前。即是否是过去时间。作用于 Date 相关类型的字段。

@Past(message = "Date must be in the past")private LocalDate startDate;

@Future

校验日期或时间元素是否在当前时间之后。即是否是未来时间。作用于 Date 相关类型的字段。

@Future(message = "Date must be in the future")private LocalDate endDate;
;