更好的阅读体验:优雅的入参校验,Valid常用校验
对于前端传递的参数,正常情况下后端是要进行一些必要的校验,最简单的做法是用 if
效果是可以,但不优雅。使用 @Validator 代替 if,就会优雅很多
ps:Validator 也可用于Dubbo参数校验
一、效果展示
如Post请求需要一个name参数,当name参数不传递的时候
二、引入 Validator
2-1、pom 文件引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<scope>compile</scope>
</dependency>
本质上引入的是
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<scope>compile</scope>
</dependency>
2-2、全局异常处理器
校验不通过会抛出异常,所以需要一个异常处理器来做提示语处理
对全局异常拦截器感兴趣的看这里 @ControllerAdvice异常拦截原理解析
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
@ControllerAdvice
@RestController
public class AllControllerAdvice {
public final static Logger logger = LoggerFactory.getLogger(AllControllerAdvice.class);
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(IllegalArgumentException.class)
public String illegalArgumentHandler(IllegalArgumentException e) {
logger.error("IllegalArgumentException-error->", e);
return e.getMessage();
}
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(MethodArgumentNotValidException.class)
public String handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
logger.error("MethodArgumentNotValidException->", e);
return this.getErrorMessages(e.getBindingResult());
}
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(BindException.class)
public String handleBindException(BindException e) {
logger.error("BindException error->", e);
return String.format("xxxxxx" + ":%s", this.getBindingErrorField(e.getBindingResult()));
}
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(InvalidFormatException.class)
public String handleInvalidFormatException(InvalidFormatException e) {
logger.error("InvalidFormatException error->", e);
return e.getMessage();
}
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(ConstraintViolationException.class)
public String handleConstraintViolationException(ConstraintViolationException e) {
logger.error("ConstraintViolationException-error->", e);
return e.getConstraintViolations().stream().map(this::getMessage).collect(Collectors.joining(";"));
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public String exceptionHandler(Exception e) {
logger.error("Exception-error->", e);
return "UNKNOWN_ERROR_MESSAGE";
}
private String getBindingErrorField(BindingResult bindingResult) {
return bindingResult
.getAllErrors()
.stream()
.map(this::getFieldName)
.collect(Collectors.joining(";"));
}
private String getErrorMessages(BindingResult bindingResult) {
return bindingResult
.getAllErrors()
.stream()
.map(this::getMessage)
.collect(Collectors.joining(";"));
}
private String getMessage(ObjectError error) {
if (error instanceof FieldError) {
FieldError fieldError = (FieldError) error;
return fieldError.getField() + ":" + fieldError.getDefaultMessage();
}
return error.getObjectName() + ":" + error.getDefaultMessage();
}
private String getFieldName(ObjectError error) {
if (error instanceof FieldError) {
FieldError fieldError = (FieldError) error;
return fieldError.getField();
}
return error.getObjectName();
}
private String getMessage(ConstraintViolation<?> violation) {
return violation.getPropertyPath() + ":" + violation.getMessage();
}
}
如果在Dubbo中使用,那就需要定义一个Filter,这个Filter就用来充当全局异常处理器
2-3、Controller
Controller
import com.xdx97.cli.pojo.entity.ValidQuery;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/cli")
public class CliController {
@PostMapping(value = "/valid")
public String valid(@RequestBody @Validated ValidQuery validQuery) {
System.out.println(validQuery);
return "success";
}
}
ValidQuery
import lombok.Data;
import lombok.ToString;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;
@Data
@ToString
public class ValidQuery {
@NotBlank(message = "不能为空")
@Length(min = 1, max = 5, message = "长度必须在[1,5]之间")
private String name;
}
注:@Data
和@ToString
是 lombok 提供了,用来省略get、set、toString方法,和校验无关
三、常用校验
已经知道如何使用参数校验,下面再来看看常用的校验有哪些,只需要把对应的校验复制到 ValidQuery
就可以验证了
3-1、基础校验
字符串
@NotBlank(message = "不能为空")
@Length(min = 1, max = 5, message = "长度必须在[1,5]之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String name;
数字
Integer、Long 都适用
@NotNull(message = "不能为空")
@Max(value = 100, message = "最大为100")
@Min(value = 1, message = "最小为1")
private Integer age;
小数
BigDecimal、Double、Float 都适用
@NotNull(message = "不能为空")
@DecimalMax(value = "100.00", message = "最大为100.00")
@DecimalMin(value = "1.00", message = "最小为1.00")
@Digits(integer = 3, fraction = 2, message = "金额整数位最多3位,小数位最多2位")
private BigDecimal amount;
集合
List、Set、Map 都适用
@NotEmpty(message = "列表不能为空")
@Size(min = 1, max = 5, message = "列表长度必须在1到5之间")
private List<Integer> statuses;
时间/日期
@NotNull(message = "日期不能为空")
@Past(message = "日期必须是过去的时间")
@Future(message = "日期必须是未来的时间")
private LocalDate date;
3-2、嵌套校验
对象里面嵌套集合对象
@Data
@ToString
public class ValidQuery {
@NotEmpty(message = "列表不能为空")
@Valid
private List<ValidQueryChild> items;
@Data
@ToString
static class ValidQueryChild {
@NotBlank(message = "不能为空")
@Length(min = 1, max = 5, message = "长度必须在[1,5]之间")
private String childName;
}
}
参数是集合对象且对象里面嵌套集合对象
ValidQuery 还是和上面一样,但Controller要改变
- 把 @Validated 移到Controller上
- 入参添加 @Valid和 @NotEmpty
@RestController
@RequestMapping("/cli")
@Validated
public class CliController {
@PostMapping(value = "/valid")
public String valid(@RequestBody @NotEmpty(message = "列表不能为空") @Valid List<ValidQuery> validQueries) {
System.out.println(validQueries);
return "success";
}
}