文章目录
在前后端分离项目中,前端通过制定rules校验数据,能最早的让用户发现输入错误,如果使用postman发送数据,就会跳过前端校验,因此需要后端同样需要做验证。
步骤
1. JSR303的校验注解
-
值校验
@Null(message = "必须为null")
:被注解的元素必须为null@NotNull(message = "必须不为null")
:被注解的元素必须不为null@NotBlank(message = "必须不为空")
:验证注解的元素值不为空(不为null、去除首位空格后长度为0) ,并且类型为String。@NotEmpty(message = "必须不为null且不为空")
:验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) ,并且类型为String。@AssertTrue(message = "必须为true")
:被注解的元素必须为true,并且类型为boolean。@AssertFalse(message = "必须为false")
:被注解的元素必须为false,并且类型为boolean。
-
范围校验
@Min(value = 18, message = "必须大于等于18")
:被注解的元素其值必须大于等于最小值,并且类型为int,long,float,double。@Max(value = 18, message = "必须小于等于18")
:被注解的元素其值必须小于等于最小值,并且类型为int,long,float,double。@DecimalMin(value = "150", message = "必须大于等于150")
:验证注解的元素值大于等于@DecimalMin指定的value值,并且类型为BigDecimal。@DecimalMax(value = "300", message = "必须大于等于300")
:验证注解的元素值小于等于@DecimalMax指定的value值 ,并且类型为BigDecimal。@Range(max = 80, min = 18, message = "必须大于等于18或小于等于80")
:验证注解的元素值在最小值和最大值之间,并且类型为BigDecimal,BigInteger,CharSequence,byte,short,int,long。@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Past(message = "必须为过去的时间")
:被注解的元素必须为过去的一个时间,并且类型为java.util.Date。@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Future(message = "必须为未来的时间")
:被注解的元素必须为未来的一个时间,并且类型为java.util.Date。
-
长度校验
-
@Size(max = 11, min = 7, message = "长度必须大于等于7或小于等于11")
:被注解的元素的长度必须在指定范围内,并且类型为String,Array,List,Map。 -
@Length(max = 11, min = 7, message = "长度必须大于等于7或小于等于11")
:验证注解的元素值长度在min和max区间内 ,并且类型为String。
-
-
格式校验
@Digits(integer=3,fraction = 2,message = "整数位上限为3位,小数位上限为2位")
:验证注解的元素值的整数位数和小数位数上限 ,并且类型为float,double,BigDecimal。@Pattern(regexp = "\\d{11}",message = "必须为数字,并且长度为11")
:被注解的元素必须符合指定的正则表达式,并且类型为String。@Email(message = "必须是邮箱")
:验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式,类型为String。
2. 使用JSR303对数据进行校验步骤
①:给bean添加校验注解
导入 javax.validation.constraints,并定义自己的message提示
@NotBlank(message = "账号不能为空")
private String account;
@NotBlank(message = "密码不能为空")
private String password;
@NotBlank(message = "姓名不能为空")
private String name;
//注意:Integer 字段必须用 @NotNull
@NotNull(message = "任务id不能为空!")
private Integer taskId;
@NotBlank(message = "身份证不能为空")
@Pattern(regexp="^(\\d{18,18}|\\d{15,15}|(\\d{17,17}[x|X]))$", message="身份证格式错误")
private String identityCard;
//注意NotBlank不能作用于integer @NotNull可以
@NotNull(message = "角色不能为空")
private Integer role;
/**
* 估损金额(终勘)
*/
@Pattern(regexp = "\\d", message = "估损金额(终勘)必须为数字")
private String assessmentAmountThird;
/**
* 第三方报告文书质量得分
*/
@Range(max = 10, min = 0, message = "第三方报告文书质量得分范围为0-10分")
@NotNull(message = "第三方报告文书质量得分不能为空!")
private Integer reportQualityScore;
②:在Controller层开启@Valid验证注解
效果:传入的数据校验失败会返回错误信息
//添加用户 @Valid
@PostMapping("/addUser")
public RtnResult addUser(@Valid @RequestBody ClaimUser claimUser){
return claimUserService.addUser(claimUser);
}
③:统一异常处理
给controller后的需要校验的bean后加一个BindingResult类就可以获得校验的结果,
但是在大项目中一般使用统一异常处理校验结果,返回json格式,如下所示
// @Valid 数据校验异常
@ExceptionHandler(value= MethodArgumentNotValidException.class)
@ResponseBody
public R handleVaildException(MethodArgumentNotValidException e){
logger.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError)->{
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(500,"参数格式检验失败").put("data",errorMap);
}
3. 分组校验(多场景的复杂校验)
分组校验应用场景:添加和更新需要不同的校验规则时
- 步骤1:先创建添加和更新的接口
作用:仅仅是多场景的一个标识,接口内容为空即可 - 步骤2:在上述 @NotBlank(message = “密码不能为空”)后加上groups
作用:给校验注解标注什么情况下需要校验(添加/更新)
//AddGroup.class是添加的标识组,可以有多个
@NotBlank(message = "密码不能为空", groups = {AddGroup.class})
private String password;
@NotBlank(message = "姓名不能为空", groups = {AddGroup.class,UpdateGroup.class})
private String name;
- 步骤3:替换controller层原来的@Valid注解,使用mvc提供的@Validated注解,否则不生效
//添加用户 @Validated指定哪个操作使用校验
@PostMapping("/addUser")
public RtnResult addUser(@Validated(AddGroup.class) @RequestBody ClaimUser claimUser){
return claimUserService.addUser(claimUser);
}
注意:如果使用分组校验,校验注解没有使用groups,则校验不生效,一定要使用groups
4. 自定义校验注解、自定义校验器
场景:当显示和隐藏的属性showStatus只能时0或1,需要自定义校验注解和自定义校验器
- 1.在需要校验的属性上添加自定义注解
// @NotBlank(message = "开关状态不能为空") 不再使用非空,使用@ListValue自定义校验注解
@ListValue(vals={0,1},message = "开关状态只能是0或1")
private Integer showStatus;
- 2.导入依赖
<!--自定义校验注解-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
- 3.编写自定义校验注解
自定义校验注解可根据其他注解,例如@NotBlank进行编写
//自定义校验注解ListValue
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class}) //关联自定义校验器,可以适配多个校验器,一个注解完成多种校验
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) //可标注位置
@Retention(RetentionPolicy.RUNTIME) //运行环境
public @interface ListValue {
String message() default "{com.atguigu.common.valid.ListValue.message}"; //错误信息从这个配置文件中取 可以使用配置文件,也可直接使用message
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] vals() default {}; //标注在属性上的vals={0,1}
}
- 4.编写自定义校验器
//自定义校验器 必须实现ConstraintValidator
//ConstraintValidator中的 泛型1:绑定的泛型注解 泛型2:校验的数据类型
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
private Set<Integer> set = new HashSet<>();
//初始化方法,获取在Entity类的属性上标注的符合条件的Integer的值:{0,1}
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
//判断是否校验成功方法
//integer:传进来的值
//判断依据:看integer是否在初始化方法的数组中
@Override
public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
if (set.contains(integer)) {
return true;
}
return false;
}
}
- 5.校验测试
完毕!