本文是根据kdyzm项目的代码自测整合的具体的可看原项目地址
validation
一:项目依赖
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.1.Final</version>
</dependency>
二:配置请求异常处理
返回DTO
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
public class ResultDto implements Serializable {
private Integer code;
private String msg;
private Object data;
public static ResultDto success(String msg){
return new ResultDto().setCode(2000).setMsg(msg);
}
public static ResultDto error(String msg){
return new ResultDto().setCode(5000).setMsg(msg);
}
}
在这里插入代码片
注解
注解 | 说明 |
---|---|
@Null | 被注解的元素必须为空 |
@NotNull | 被注解的元素必须不为空 |
@AssertTrue | 被注解的元素必须为true |
@AssertFlase | 被注解的元素必须为false |
@Min(value) | 被注解的元素必须是数字,且必须大于指定的最小值 |
@Max(value) | 被注解的元素必须是数字,且必须小于指定的最大值 |
@DecimalMin(value) | 被注解的元素必须是数字,且必须大于指定的最小值 |
@DecaimalMax(value) | 被注解的元素必须是数字,且必须小于指定的最大值 |
@Size(max=,min=) | 被注解元素的大小必须在指定的范围内 |
@Digit(integer,fraction) | 被注解元素必须是数字,且其值必须在可接受的范围内 |
@Past | 被注解元素必须是一个过去的日期 yyyy/MM/dd 其他格式识别不出来 |
@Futrue | 被注解元素必须是一个将来的日期 yyyy/MM/dd 其他格式识别不出来 |
@Pattern(regex=,flag=) | 被注解元素必须符合指定的正则表达式 |
@NotBlank | 验证非空,且长度必须大于0 |
被注解的元素必须是电子邮件地址 | |
@Length(max=,min=) | 被注解的字符串大小必须在指定的范围内 |
@NotEmpty | 被注解的字符串必须非空 |
@Range(max=,min=) | 被注解的元素必须在指定范围内 |
定义请求异常拦截
经过测试(或许是配置问题)部分异常调用的全局异常处理,这样就失去了意义,但是大部分的异常属于BindException
import com.validation.demo.ResultDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 请求异常拦截
*/
@RestControllerAdvice
@Slf4j
public class ValidationAdvice {
//创建全局异常
@ExceptionHandler(Exception.class)
@ResponseBody
public ResultDto handler(Exception e) {
//获取异常信息,获取异常堆栈的完整异常信息
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
return ResultDto.error("服务异常,请稍后再试");
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public Object handler(ConstraintViolationException e) {
StringBuffer errorMsg = new StringBuffer();
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
violations.forEach(x -> errorMsg.append(x.getMessage()).append(";"));
return ResultDto.error(errorMsg.toString()) ;
}
//处理校验异常,对于对象类型的数据的校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public Object handler(MethodArgumentNotValidException e) {
StringBuffer sb = new StringBuffer();
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
allErrors.forEach(msg -> sb.append(msg.getDefaultMessage()).append(";"));
return ResultDto.error(sb.toString()) ;
}
//会出现拦截到参数异常但是还是调用全局异常的问题 使用BindException来获取错误信息
@ExceptionHandler(BindException.class)
@ResponseBody
public Object handler(BindException e) {
StringBuffer sb = new StringBuffer();
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
allErrors.forEach(i->{
sb.append(i.getDefaultMessage()+";");
});
return ResultDto.error(sb.toString()) ;
}
}
三:接参请求
单个参数
Bean对象
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.*;
import java.util.Date;
import java.util.List;
@Data
@Accessors
public class TestBean {
@NotNull(message = "用户id不能为空")
@NotEmpty(message ="用户id不能为空" )
private String name;
@NotEmpty(message = "手机号不能为空")
private String mobile;
@NotEmpty(message = "电子邮箱不能为空")
@Email(message = "电子邮箱格式不正确")
private String email;
//
@NotNull(message = "年龄不能为空")
@Min(value = 12, message = "允许注册年龄最小为12岁")
@Max(value = 24, message = "允许年龄最大为24岁")
private Integer age;
@AssertTrue
private Boolean isTrue;
@DecimalMin(value = "45",message = "数值必须大于45")
private Integer number;
@Past(message = "必须是过去的日期")
private Date pastDate;
@NotEmpty(message = "联系人不允许为空")
@Size(min = 1, max = 3, message = "联系人长度只允许1到3之间")
private List<String> contacts;
}
{
"code": 2000,
"msg": "TestBean(name\u003ddemoData, mobile\u003ddemoData, email\u003ddemoData@163.com, age\u003d12, isTrue\u003dtrue, number\u003d45, pastDate\u003dMon May 24 00:00:00 CST 2021, contacts\u003d[1, 2, 3])"
}
{
"code": 5000,
"msg": "只能为true;数值必须大于45;
允许注册年龄最小为12岁;联系人长度只允许1到3之间;
电子邮箱格式不正确;必须是过去的日期;"
}
四:自定义校验器
配置注解
public class ValidationUtilFun {
//手机号校验正则
public static final String MOBILE_REGX = "^[1][3-9][0-9]{9}$";
public static final String MOBILE_MSG = "手机号格式错误";
//用户名校验正则
public static final String USERNAME_REGX = "^[a-zA-z]\\w{4,19}$";
public static final String USERNAME_MSG = "账号必须是字母开头,字母、数字、下划线组成,4-20位";
}
import com.validation.demo.validationConfig.utils.ValidationUtilFun;
import com.validation.demo.validationConfig.validator.MobileValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.Pattern;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 整个代码可以copy validation自带的@Email注解
*/
@Documented
//代表处理逻辑是 MobileValidator这个类
@Constraint(validatedBy = {MobileValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface Mobile {
//指定默认的返回文字
String message() default ValidationUtilFun.MOBILE_MSG;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
//指定正则验证
String regexp() default ValidationUtilFun.MOBILE_REGX;
Pattern.Flag[] flags() default {};
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface List {
Mobile[] value();
}
}
配置验证逻辑
import com.validation.demo.validationConfig.annotation.Mobile;
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MobileValidator implements ConstraintValidator<Mobile, String> {
private String regexp;
@Override
public void initialize(Mobile constraintAnnotation) {
//获取校验的手机号的格式
this.regexp = constraintAnnotation.regexp();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (!StringUtils.hasText(value)) {
return true;
}
return value.matches(regexp);
}
}
Bean中使用
@Data
@Accessors
public class TestBean {
// @NotNull(message = "用户id不能为空")
// @NotEmpty(message ="用户id不能为空" )
// private String name;
@NotEmpty(message = "手机号不能为空")
@Mobile(message = "手机号格式不正确")
private String mobile;
{ "code": 5000, "msg": "手机号格式不正确;" }
{
"code": 2000,
"msg": "TestBean(mobile\u003d18141111545)"
}
五:分组校验
这里举个例子 APP的注册方式有1.用户名 2.手机号 3.Email
在使用用户名注册时 不希望手机号 Email验证生效
创建接口
public interface ValidEmail {
}
public interface ValidMobile {
}
public interface ValidUserName {
}
bean中指定使用的分组
字段指定了使用的分组类型,可指定多个
@NotNull(message = "用户id不能为空",groups ={ ValidUserName.class})
@UserName(groups ={ ValidUserName.class})
private String name;
@NotEmpty(message = "手机号不能为空",groups ={ ValidMobile.class})
@Mobile(message = "手机号格式不正确",groups ={ ValidMobile.class})
private String mobile;
@NotEmpty(message = "电子邮箱不能为空",groups ={ ValidEmail.class})
@Email(message = "电子邮箱格式不正确",groups ={ ValidEmail.class})
private String email;
@NotNull(message = "年龄不能为空",groups ={ ValidUserName.class,ValidMobile.class,ValidEmail.class})
@Min(value = 12, message = "允许注册年龄最小为12岁",groups ={ ValidUserName.class,ValidMobile.class,ValidEmail.class})
@Max(value = 24, message = "允许年龄最大为24岁",groups ={ ValidUserName.class,ValidMobile.class,ValidEmail.class})
private Integer age;
控制层接口指定使用的分组
在注解指定使用的分组即可
/**
* 模拟用户名注册
*/
@RequestMapping("/regUserName")
public ResultDto regUserName(@Validated(ValidUserName.class) TestBean testBean){
return ResultDto.success(testBean.toString());
}
/**
* 模拟手机注册
*/
@RequestMapping("/regMobile")
public ResultDto regMobile(@Validated(ValidMobile.class) TestBean testBean){
return ResultDto.success(testBean.toString());
}
/**
* 模拟邮箱注册
*/
@RequestMapping("/regEmail")
public ResultDto regEmail(@Validated(ValidEmail.class) TestBean testBean){
return ResultDto.success(testBean.toString());
}
用户名接口
在参数中未传入手机号和邮箱验证时根据分组进行校验
{
"code": 2000,
"msg": "TestBean(name\u003ddemoData, mobile\u003dnull, email\u003dnull, age\u003d12)"
}
{
"code": 5000,
"msg": "允许注册年龄最小为12岁;"
}
手机号接口
{
"code": 2000,
"msg": "TestBean(name\u003dnull, mobile\u003d15548488484, email\u003dnull, age\u003d12)"
}
六:验证注解
// 空和非空检查: @Null、@NotNull、@NotBlank、@NotEmpty
@Null(message = "验证是否为 null")
private Integer isNull;
@NotNull(message = "验证是否不为 null, 但无法查检长度为0的空字符串")
private Integer id;
@NotBlank(message = "检查字符串是不是为 null,以及去除空格后长度是否大于0")
private String name;
@NotEmpty(message = "检查是否为 NULL 或者是 EMPTY")
private List<String> stringList;
// Boolean值检查: @AssertTrue、@AssertFalse
@AssertTrue(message = " 验证 Boolean参数是否为 true")
private Boolean isTrue;
@AssertFalse(message = "验证 Boolean 参数是否为 false ")
private Boolean isFalse;
// 长度检查: @Size、@Length
@Size(min = 1, max = 2, message = "验证(Array,Collection,Map,String)长度是否在给定范围内")
private List<Integer> integerList;
@Length(min = 8, max = 30, message = "验证字符串长度是否在给定范围内")
private String address;
// 日期检查: @Future、@FutureOrPresent、@Past、@PastOrPresent
@Future(message = "验证日期是否在当前时间之后")
private Date futureDate;
@FutureOrPresent(message = "验证日期是否为当前时间或之后")
private Date futureOrPresentDate;
@Past(message = "验证日期是否在当前时间之前")
private Date pastDate;
@PastOrPresent(message = "验证日期是否为当前时间或之前")
private Date pastOrPresentDate;
// 其它检查: @Email、@CreditCardNumber、@URL、@Pattern、
@ScriptAssert、@UniqueElements
@Email(message = "校验是否为正确的邮箱格式")
private String email;
@CreditCardNumber(message = "校验是否为正确的信用卡号")
private String creditCardNumber;
@URL(protocol = "http", host = "127.0.0.1", port = 8080, message= "校验是否为正确的URL地址")
private String url;
@Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "正则校验是否为正确的手机号")
private String phone;
// 对关联对象元素进行递归校验检查
@Valid
@UniqueElements(message = "校验集合中的元素是否唯一")
private List<CalendarEvent> calendarEvent;
@Data
@ScriptAssert(lang = "javascript", script ="_this.startDate.before(_this.endDate)",message = "通过脚本表达式校验参数")
private class CalendarEvent {
private Date startDate;
private Date endDate;
}
// 数值检查: @Min、@Max、@Range、@DecimalMin、@DecimalMax、@Digits
@Min(value = 0, message = "验证数值是否大于等于指定值")
@Max(value = 100, message = "验证数值是否小于等于指定值")
@Range(min = 0, max = 100, message = "验证数值是否在指定值区间范围内")
private Integer score;
@DecimalMin(value = "10.01", inclusive = false, message = "验证数值是否大于等于指定值")
@DecimalMax(value = "199.99", message = "验证数值是否小于等于指定值")
@Digits(integer = 3, fraction = 2, message = "限制整数位最多为3,小数位最多为2")
private BigDecimal money;