1.注解式校验
1.1 常见校验注解
定义的校验类型
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@URL(protocol=,host=, port=,regexp=, flags=) ip地址校验Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为""时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.
1.2 实现简单的注解式校验栗子
1.2.1 请求实体
@Data
public class CommonReqVO<T> {
/**
* 互感器主键
*/
@NotNull(message = "业务类型不能为空")
private String type;
/**
* 请求数据
*/
@Valid
@NotNull(message = "数据不能为空")
private T data;
@NotNull(message = "开始时间不能为空!")
@Pattern(regexp = "^((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[4]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29))\\s+([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$",message = "日期格式不正确")
// @DateTime(format = "yyyy-MM-dd", message = "时间格式错误,格式应为'yyyy-MM-dd'")
private String startDate;
}
@Data
public class UserVO {
@NotBlank(message = "name不能为空",groups = S1.class)
private String name;
@Range(min = 1,max = 120,message = "年龄必须在1-120之间",groups = S1.class)
private Integer age;
@NotBlank(message = "address不能为空",groups = S2.class)
private String address;
@NotBlank(message = "phone不能为空",groups = S2.class)
private String phone;
public interface S1{}
public interface S2{}
}
1.2.2 controller
@RestController
@RequestMapping("test")
public class TestController {
@PostMapping("t3")
public String test3(@RequestBody CommonReqVO<UserVO> userVO){
ValidateUtil.validateParams(userVO,"请求参数不能为空");
if ("01".equals(userVO.getType())){
ValidateUtil.validateParams(userVO,"请求参数不能为空",UserVO.S1.class);
}else if("02".equals(userVO.getType())){
ValidateUtil.validateParams(userVO,"请求参数不能为空",UserVO.S2.class);
}else {
throw new CustomException("请求参数type类型不匹配!");
}
return "t3";
}
@PostMapping("t4")
public String test4(@RequestBody @Validated CommonReqVO<UserVO> userVO){
return "4";
}
}
1.2.3 统一异常处理类及相关类
@RestControllerAdvice
@Slf4j
public class ExceptionHandle {
@Value("${validator.showField:false}")
private Boolean showFile;
@ExceptionHandler(CustomException.class)
public ResultInfo handleCustomException(CustomException e){
log.error("【用户自定义异常】{}", e);
return ResultInfoUtil.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResultInfo handleReqException(HttpMediaTypeNotSupportedException e){
log.error("【请求类型不支持异常】{}", e);
if (e.getMessage().contains("not supported")){
return ResultInfoUtil.error(0, "请求Content-Type不匹配");
}
return ResultInfoUtil.error(0, e.getMessage());
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResultInfo handleReqBodyException(HttpMessageNotReadableException e){
log.error("【请求体异常】{}", e);
if (e.getMessage().contains("Required request body is missing")){
return ResultInfoUtil.error(0, "请求体不能为空");
}
return ResultInfoUtil.error(0, e.getMessage());
}
@ExceptionHandler(Exception.class)
public ResultInfo handleOtherException(Exception e){
log.error("【系统异常】{}", e);
return ResultInfoUtil.error(0, e.getMessage());
}
/**
* 处理validation框架中的{@link MethodArgumentNotValidException}异常
* <p>该异常一般出在用{@code @RequestBody}注解标记的参数校验,在对象中的参数有问题是抛出</p>
*
* @param e {@link MethodArgumentNotValidException}
* @return {@link ResultInfo}
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultInfo doNoValidExceptionHandler(MethodArgumentNotValidException e) {
log.info("捕捉到参数校验异常: [{}]", e);
return validationExceptionHandler(e.getBindingResult(), e.getMessage());
}
/**
* 处理validation框架中的{@link ConstraintDeclarationException}异常
* <p>该异常一般出在用{@code @RequestParam}注解标记的参数校验</p>
*
* @param e {@link ConstraintDeclarationException}
* @return {@link ResultInfo}
*/
@ExceptionHandler(ConstraintDeclarationException.class)
public ResultInfo doNoValidExceptionHandler(ConstraintDeclarationException e) {
String errorMsg = e.getMessage();
log.info("捕捉到参数校验异常: [{}]", errorMsg, e);
return ResultInfoUtil.error(0, e.getMessage());
}
/**
* 处理validation框架中的{@link BindException}异常
* <p>该异常一般出在用{@code @RequestBody}注解标记的参数校验,在对象嵌套类型参数有问题时抛出</p>
*
* @param e {@link BindException}
* @return {@link ResultInfo}
*/
@ExceptionHandler(BindException.class)
public ResultInfo doNoValidExceptionHandler(BindException e) {
log.info("捕捉到参数校验异常: [{}]", e);
return validationExceptionHandler(e.getBindingResult(), e.getMessage());
}
/**
* 处理validation框架的异常
*
* @param bindingResult {@link BindingResult} 异常绑定的对象信息
* @param message 异常信息
* @return {@link ResultInfo}
*/
private ResultInfo validationExceptionHandler(BindingResult bindingResult, String message) {
//默认捕获第一个不符合校验规则的错误信息
//错误字段对象
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
List<String> errorMessages = new ArrayList<>(fieldErrors.size());
if (showFile){
fieldErrors.stream().forEach(e->{
errorMessages.add(e.getField()+"-"+e.getDefaultMessage());
});
}else {
fieldErrors.stream().forEach(e->{
errorMessages.add(e.getDefaultMessage());
});
}
log.info("捕捉到参数校验异常详情: {}", errorMessages);
return ResultInfoUtil.error(0, errorMessages.toString());
}
}
@Data
public class ResultInfo<T> {
/**
*返回码
*/
private Integer code;
/**
*返回提示信息
*/
private String msg;
/**
*返回具体对象
*/
private T data;
}
public class ResultInfoUtil {
public static ResultInfo success(Object object) {
ResultInfo result = new ResultInfo();
ResultInfoEnum successEnum = ResultInfoEnum.SUCCESS;
result.setCode(successEnum.getCode());
result.setMsg(successEnum.getMsg());
result.setData(object);
return result;
}
public static ResultInfo success() {
return success(null);
}
public static ResultInfo error(Integer code, String msg) {
ResultInfo result = new ResultInfo();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
@Data
public class CustomException extends RuntimeException {
private Integer code;
public CustomException(ResultInfoEnum resultInfoEnum) {
super(resultInfoEnum.getMsg());
this.code = resultInfoEnum.getCode();
}
public CustomException(String msg) {
super(msg);
this.code = ResultInfoEnum.FAILED.getCode();
}
}
注意上面配置了 failFast 为false。所以会校验全部参数
1.2.4 请求及响应
请求
{}
响应
{
"code": 0,
"msg": "[data-数据不能为空, type-业务类型不能为空, startDate-开始时间不能为空!]",
"data": null
}
2.编程式校验
2.1 校验相关类
@Configuration
public class ValidateUtil<T> {
private static Validator validator;
/**
* 是否启用快速失败
*/
private static Boolean failFast;
/**
* 是否显示错误字段
*/
private static Boolean showField;
public static void setFailFast(Boolean failFast) {
ValidateUtil.failFast = failFast;
}
public static void setShowField(Boolean showField) {
ValidateUtil.showField = showField;
}
/**
* @param obj 校验的对象
* @param <T>
* @return
*/
public static <T> Set<ConstraintViolation<T>> validate(T obj, Class... groupClasses) {
Set<ConstraintViolation<T>> constraintViolations;
if (groupClasses != null && groupClasses.length > 0) {
constraintViolations = validator.validate(obj, groupClasses);
} else {
constraintViolations = validator.validate(obj);
}
return constraintViolations;
}
/**
* 验证请求参数是否为空
*
* @param o 参数对象
* @param msg 报错信息
*/
public static void checkIsNotNull(Object o, String msg) {
if (o == null) {
throw new CustomException(msg);
}
}
public static <T> void validateParams(T t, String msg, Class... groupClasses) {
checkIsNotNull(t, msg);
Set<ConstraintViolation<T>> constraintViolationSet = validate(t, groupClasses);
List<String> resultString = new ArrayList<>(constraintViolationSet.size());
if (!constraintViolationSet.isEmpty()) {
if (showField){
constraintViolationSet.stream().forEach(c -> {
resultString.add(c.getPropertyPath().toString() + "-" + c.getMessage());
});
}else {
constraintViolationSet.stream().forEach(c -> {
resultString.add(c.getMessage());
});
}
throw new CustomException(resultString.toString());
}
}
/**
* 注入validator
*
* @param validatorBean
*/
public static void setValidator(Validator validatorBean) {
validator = validatorBean;
}
}
@Configuration
public class ValidatorConfiguration {
@Value("${validator.failFast:true}")
private Boolean failFast;
@Value("${validator.showField:false}")
private Boolean showField;
@Autowired
private ServletContext servletContext;
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败
.failFast(failFast)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
/**
* 给ValidateUtil初始化
*/
@PostConstruct
public void initValidateUtil() {
ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
Validator validator = (Validator) applicationContext.getBean("validator");
ValidateUtil.setValidator(validator);
ValidateUtil.setFailFast(failFast);
ValidateUtil.setShowField(showField);
}
}
2.2 配置
#校验相关配置
validator:
#快速失败
failFast: true
#错误提示是否显示错误字段
showField: false
3.自定义校验注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateTime.DateTimeValidator.class)
public @interface DateTime {
String message() default "格式错误";
String format() default "yyyyMM";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
class DateTimeValidator implements ConstraintValidator<DateTime, String> {
private DateTime dateTime;
@Override
public void initialize(DateTime dateTime) {
this.dateTime = dateTime;
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 如果 value 为空则不进行格式验证,为空验证可以使用 @NotBlank @NotNull @NotEmpty 等注解来进行控制,职责分离
if (value == null) {
return true;
}
String format = dateTime.format();
if (value.length() != format.length()) {
return false;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
try {
simpleDateFormat.parse(value);
} catch (Exception e) {
return false;
}
return true;
}
}
}
3.1 使用方法
@DateTime(format = "yyyy-MM-dd", message = "时间格式错误,格式应为'yyyy-MM-dd'")
private String startDate;