更好的阅读体验:优雅的入参校验,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 = "列表不能为空")@Validprivate List<ValidQueryChild> items;@Data@ToStringstatic 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";}
}