一、添加依赖

<!--参数校验-->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

二、校验注解分类

1.空和非空检查

@NotBlank:只能用于字符串不为null和"",并且字符串调用trim()方法后的length要大于0。

@NotNull:不能为null。

@Null:必须为null。

@NotEmpty:集合对象元素不能为0,集合不能为空。

2.数值检查

@DecimalMax(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值。

@DecimalMin(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值。

@Digits(integer,fraction):被注释的元素必须是一个数字,其值必须在可接受的范围内。

@Positive:被注释的元素必须是正数。

@PositiveOrZero:被注释的元素必须是正数或0。

@Negative:被注释的元素必须是负数。

@NegativeOrZero:被注释的元素必须是负数或0。

@Max(value):被注释的元素的值只能小于或等于该值。

@Min(value):被注释的元素的值只能大于或等于该值。

@Range(min,max):被注释的元素必须在min和max之间。

@Length(min,max): 被注释的字符串的长度必须在min和max之间。

3.Boolean 值检查

@AssertTrue:被注释的元素必须为true。

@AssertFalse:被注释的元素必须为false。

4.长度检查

@Size(min,max):判断字符串、集合、数组、Map的长度是否在min和max之间。

5.日期检查

@Past:被注释的元素的日期必须是过去的日期。

@PathOrPresent:被注释的元素的日期必须是过去或现在的日期。

@Future:被注释的元素的日期必须是将来的日期。

@FutureOrPresent:被注释的元素的日期必须是将来或现在的日期。

6.其他

@URL:判断被注释的字符串必须是一个有效的URL。

@SafeHtml:判断提交的HTML是否安全。

三、@Valid和@Validated的区别

@Valid:可以添加在普通方法、构造方法、方法参数、方法返回、成员变量上,表示它们需要进行约束校验。支持嵌套校验,不支持分组校验。

@Validated:可以添加在类、方法参数、普通方法上,不能用在成员变量上。不支持嵌套校验,支持分组校验。

总的来说,绝大多数场景下,我们使用 @Validated 注解即可。

四、普通测试

1.创建一个用于测试的实体类

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

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 用户登录请求类
 */
@Data
public class UserLoginRequest {

    /**
     * 账号
     */
    @NotBlank(message = "账号不能为空")
    @Length(min = 2, max = 20, message = "账号的长度为2-20位")
    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号的格式为大小写字母和数字")
    private String username;

    /**
     * 密码
     */
    @NotBlank(message = "密码不能为空")
    @Length(min = 4, max = 16, message = "密码的长度为4-16位")
    private String password;

}

2.创建测试控制器

import com.example.quartzdemo.request.UserLoginRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 测试
 */
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {


    /**
     * 登录
     *
     * @param userLoginRequest 使用@Validated校验输入项
     */
    @PostMapping("/login")
    public String login(@Validated UserLoginRequest userLoginRequest) {
        log.info("userLoginRequest:{}", userLoginRequest);
        return "登录验证通过";
    }
}

3.启动程序,进行测试

我们在postman上进行接口的测试

SpringBoot参数校验入门-LMLPHP

 没有传入password参数,错误异常提示了密码不能为空。

SpringBoot参数校验入门-LMLPHP

 我们传入的username不是大小写字母或数字,错误异常日志提示了账号格式为大小写字母或数字。

SpringBoot参数校验入门-LMLPHP

 我们正确填写了账号和密码,验证通过。

五、分组校验测试

1.我们先创建两个分组接口,一个用于新增一个用于修改。

因为新增和修改的需要的参数不一定相同,比如新增的时候不需要ID,但是修改的时候必须要有ID参数,所以需要使用分组来实现我们的需求。

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 新增用户分组
 */
public interface AddUserGroup {
}
/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 修改用户分组
 */
public interface UpdateUserGroup {
}

2.修改用户实体,在实体类中加上新增和修改的分组

import com.example.quartzdemo.interfaces.AddUserGroup;
import com.example.quartzdemo.interfaces.UpdateUserGroup;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 用户登录请求类
 */
@Data
public class UserLoginRequest {


    /**
     * 用户ID
     */
    @NotNull(message = "用户ID不能为空", groups = UpdateUserGroup.class)
    private Long id;

    /**
     * 账号
     */
    @NotBlank(message = "账号不能为空", groups = {AddUserGroup.class, UpdateUserGroup.class})
    @Length(min = 2, max = 20, message = "账号的长度为2-20位")
    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号的格式为大小写字母和数字")
    private String username;

    /**
     * 密码
     */
    @NotBlank(message = "密码不能为空", groups = {AddUserGroup.class, UpdateUserGroup.class})
    @Length(min = 4, max = 16, message = "密码的长度为4-16位")
    private String password;

}

我们设置ID只能是修改的时候才会判断是否存在,账号和密码在新增和修改的时候都要判断存在。

3.在控制器中测试

import com.example.quartzdemo.interfaces.AddUserGroup;
import com.example.quartzdemo.interfaces.UpdateUserGroup;
import com.example.quartzdemo.request.UserLoginRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 测试
 */
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {


    /**
     * 登录
     *
     * @param userLoginRequest 使用@Validated校验输入项
     */
    @PostMapping("/login")
    public String login(@Validated UserLoginRequest userLoginRequest) {
        log.info("userLoginRequest:{}", userLoginRequest);
        return "登录验证通过";
    }

    /**
     * 新增用户
     *
     * @param userLoginRequest 使用@Validated校验输入项 校验的注解中加上了新增分组的参数
     */
    @PostMapping("/add")
    public String add(@Validated(AddUserGroup.class) UserLoginRequest userLoginRequest) {
        log.info("userLoginRequest:{}", userLoginRequest);
        return "新增用户验证通过";
    }

    /**
     * 修改用户
     *
     * @param userLoginRequest 使用@Validated校验输入项 校验的注解中加上了修改分组的参数
     */
    @PostMapping("/update")
    public String update(@Validated(UpdateUserGroup.class) UserLoginRequest userLoginRequest) {
        log.info("userLoginRequest:{}", userLoginRequest);
        return "修改用户验证通过";
    }


}

我们继续在postman上进行接口的测试

SpringBoot参数校验入门-LMLPHP

 可以看到我们在新增的时候没有传入ID参数校验通过了。

接下来我们现在调试修改的接口,没有传递ID参数。

SpringBoot参数校验入门-LMLPHP

 我们在调试修改接口的时候,没有传递ID参数,验证不能通过了。

最后我们加上ID参数,验证通过。

SpringBoot参数校验入门-LMLPHP

 六、错误提示的友好处理。

1.创建校验不通过的枚举类

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 业务层异常枚举
 */
public enum ServiceExceptionEnum {
    SUCCESS(0, "成功"),
    ERROR(1, "失败"),
    SYS_ERROR(1000, "服务端发生异常"),
    MISSING_REQUEST_PARAM_ERROR(1001, "参数缺失"),
    INVALID_REQUEST_PARAM_ERROR(1002, "请求参数不合法");

    private final String message;

    private final int code;

    ServiceExceptionEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public int getCode() {
        return code;
    }
}

2.统一返回结果实体类

package com.example.quartzdemo.common;

import com.example.quartzdemo.enums.ServiceExceptionEnum;

import java.io.Serializable;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 统一返回结果实体类
 */
public class CommonResult<T> implements Serializable {


    /**
     * 错误码
     */
    private Integer code;
    /**
     * 错误提示
     */
    private String message;
    /**
     * 返回数据
     */
    private T data;

    /**
     * 成功
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> success(T data) {
        CommonResult<T> commonResult = new CommonResult<>();
        commonResult.setCode(ServiceExceptionEnum.SUCCESS.getCode());
        commonResult.setMessage(ServiceExceptionEnum.SUCCESS.getMessage());
        commonResult.setData(data);
        return commonResult;
    }

    /**
     * 失败
     *
     * @param message
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> error(String message) {
        CommonResult<T> commonResult = new CommonResult<>();
        commonResult.setCode(ServiceExceptionEnum.ERROR.getCode());
        commonResult.setMessage(message);
        return commonResult;
    }

    /**
     * 失败
     *
     * @param message
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> error(int code, String message) {
        CommonResult<T> commonResult = new CommonResult<>();
        commonResult.setCode(code);
        commonResult.setMessage(message);
        return commonResult;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "CommonResult{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

3.创建全局异常处理类

package com.example.quartzdemo.exception;

import com.example.quartzdemo.common.CommonResult;
import com.example.quartzdemo.enums.ServiceExceptionEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 全局异常处理
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理 MissingServletRequestParameterException 异常
     * <p>
     * SpringMVC 参数不正确
     */
    @ResponseBody
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResult missingServletRequestParameterExceptionHandler(HttpServletRequest req, MissingServletRequestParameterException ex) {
        log.error("[missingServletRequestParameterExceptionHandler]", ex);
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getMessage());
    }

    @ResponseBody
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
        log.error("[constraintViolationExceptionHandler]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ConstraintViolation<?> constraintViolation : ex.getConstraintViolations()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(constraintViolation.getMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }

    @ResponseBody
    @ExceptionHandler(value = BindException.class)
    public CommonResult bindExceptionHandler(HttpServletRequest req, BindException ex) {
        log.info("========进入了 bindException======");
        log.error("[bindExceptionHandler]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex.getAllErrors()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(objectError.getDefaultMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }

    @ResponseBody
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CommonResult MethodArgumentNotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException ex) {
        log.info("-----------------进入了 MethodArgumentNotValidException-----------------");
        log.error("[MethodArgumentNotValidException]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex.getBindingResult().getAllErrors()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(objectError.getDefaultMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }


    /**
     * 处理其它 Exception 异常
     *
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
        // 记录异常日志
        log.error("[exceptionHandler]", e);
        // 返回 ERROR CommonResult
        return CommonResult.error(ServiceExceptionEnum.SYS_ERROR.getCode(),
                ServiceExceptionEnum.SYS_ERROR.getMessage());
    }
}

我们重新做postman上进行调试

SpringBoot参数校验入门-LMLPHP

 我们可以看到错误异常的的提示很友好了。

06-14 18:40