在前端接收数据和前端向后端传递数据的时候,都需要进行数据校验,避免传入错误的信息,比如在需要传入一个非空的值时,传入了一个空字符串,需要传入邮箱号码的时候,传入的非邮箱格式的数据。同时在写接口时经常要写效验请求参数逻辑,这时候我们会常用做法是写大量的 if 与 if else 类似这样的代码,大量if-else代码看起来比较混乱,降低了代码的可读性。
一.JSR303数据校验 1.引入依赖 1 2 3 4 5 <dependency > <groupId > org.hibernate</groupId > <artifactId > hibernate-validator</artifactId > <version > 6.0.7.Final</version > </dependency >
2.给实体类添加校验注解,并定义自己的message提示 常用的检验注解
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package com.atguigu.gulimall.product.entity;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable;import lombok.Data;import org.hibernate.validator.constraints.URL;import javax.validation.constraints.Min;import javax.validation.constraints.NotBlank;import javax.validation.constraints.NotEmpty;import javax.validation.constraints.Pattern;@Data @TableName("pms_brand") public class BrandEntity implements Serializable { private static final long serialVersionUID = 1L ; @TableId private Long brandId; @NotBlank(message = "品牌名必须提交") private String name; @NotEmpty @URL(message = "logo必须是一个合法的URL地址") private String logo; private String descript; private Integer showStatus; @NotEmpty @Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母") private String firstLetter; @NotNull @Min(value = 0, message = "排序必须大于等于0") private Integer sort; }
3.开启校验功能@Valid 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @RequestMapping("/save") public R save (@Valid @RequestBody BrandEntity brand, BindingResult result) { if (result.hasErrors()){ Map<String,String> map = new HashMap <>(); for (FieldError fieldError : result.getFieldErrors()) { String defaultMessage = fieldError.getDefaultMessage(); String field = fieldError.getField(); map.put(field,defaultMessage); } return R.error(400 ,"提交的数据不合法" ).put("data" ,map); }else { brandService.save(brand); } return R.ok(); }
测试查看返回的数据的格式,这里我们输入的都是不合法的数据格式,返回的结果如下
1 2 3 4 5 6 7 8 9 10 { "msg" : "提交的数据不合法" , "code" : 400 , "data" : { "name" : "品牌名必须提交" , "logo" : "logo必须是一个合法的URL地址" , "sort" : "排序必须大于等于0" , "firstLetter" : "检索首字母必须是一个字母" } }
参数没有错误之后返回的数据
1 2 3 4 { "msg" : "success" , "code" : 0 }
4.上面的代码过于冗余,我们可以直接使用统一异常处理处理数据校验的异常 在统一异常处理类上加上数据校验异常的异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.atguigu.gulimall.product.exception;import com.atguigu.common.utils.R;import lombok.extern.slf4j.Slf4j;import org.springframework.validation.BindingResult;import org.springframework.validation.FieldError;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.HashMap;import java.util.Map;@Slf4j @RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller") public class GulimallExceptionControllerAdvice { @ExceptionHandler(value = MethodArgumentNotValidException.class) public R handleValidException (MethodArgumentNotValidException e) { log.error("数据校验出现问题:{},异常类型:{}" ,e.getMessage(),e.getClass()); Map<String,String> map = new HashMap <>(); BindingResult result = e.getBindingResult(); for (FieldError fieldError : result.getFieldErrors()) { String defaultMessage = fieldError.getDefaultMessage(); String field = fieldError.getField(); map.put(field,defaultMessage); } return R.error(400 ,"数据校验出现问题" ).put("data" ,map); } }
这个时候上面的代码就可以简化为下面的格式,数据校验出现问题之后就直接在统一异常处理中处理了
1 2 3 4 5 6 7 8 @RequestMapping("/save") public R save (@Valid @RequestBody BrandEntity brand ) { brandService.save(brand); return R.ok(); }
5.分组校验功能 例如:当我们在添加一个品牌的时候,我们不需要传入这个品牌的id信息,需要这个品牌的品牌名信息,但是在修改这个品牌的时候,我们需要这个品牌的id信息和品牌名的信息,这时我们就需要使用分组校验了
5.1 定义空接口,作为分组校验的组 添加操作的组
1 2 3 4 5 package com.atguigu.common.valid;public interface AddGroup {}
修改操作的组
1 2 3 4 5 package com.atguigu.common.valid;public interface UpdateGroup {}
5.2 在实体类上添加上分组的信息 注意:使用了分组校验之后,其余的字段也要加上分组信息,否则没有加上分组信息的会失效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.atguigu.gulimall.product.entity;import com.atguigu.common.valid.AddGroup;import com.atguigu.common.valid.UpdateGroup;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable;import lombok.Data;import org.hibernate.validator.constraints.URL;import javax.validation.constraints.*;@Data @TableName("pms_brand") public class BrandEntity implements Serializable { private static final long serialVersionUID = 1L ; @NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class}) @Null(message = "新增不能指定id", groups = {AddGroup.class}) @TableId private Long brandId; @NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class}) private String name; }
5.3 在控制层添加上相应的注解信息,指定当前是操作属于的分组 注意:这里如果产生数据校验的出现问题的异常,会由统一异常处理进行处理
保存的控制器方法上添加上添加分组信息
1 2 3 4 5 6 7 8 9 @RequestMapping("/save") public R save (@Validated({AddGroup.class}) @RequestBody BrandEntity brand ) { brandService.save(brand); return R.ok(); }
修改的分组上添加上修改的分组信息
1 2 3 4 5 6 7 8 9 @RequestMapping("/update") public R update (@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand) { brandService.updateById(brand); return R.ok(); }
测试:我们在新增的时候加上品牌的id,这时就会产生错误
修改的时候不带品牌的id信息
6.自定义校验 这里我们以编写一个输入的值只能是指定值的注解为例
1 2 3 4 5 6 @ListValue(vals = {0, 1}, groups = {AddGroup.class, UpdateGroup.class}) private Integer showStatus;
6.1 编写一个自定义的校验注解 编写自定义注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.atguigu.common.valid;import javax.validation.Constraint;import javax.validation.Payload;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.ElementType.TYPE_USE;import static java.lang.annotation.RetentionPolicy.RUNTIME;@Documented @Constraint(validatedBy = {}) @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) public @interface ListValue { String message () default "{com.atguigu.common.valid.ListValue.message}" ; Class<?>[] groups() default {}; Class<? extends Payload >[] payload() default {}; int [] vals() default {}; }
编写注解中默认提示消息的配置文件
ValidationMessages.properties
1 com.atguigu.common.valid.ListValue.message =必须提交指定的值
6.2 编写一个自定义的校验器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.atguigu.common.valid;import javax.validation.ConstraintValidator;import javax.validation.ConstraintValidatorContext;import java.util.HashSet;import java.util.Set;public class ListValueConstraintValidator implements ConstraintValidator <ListValue,Integer> { private Set<Integer> set = new HashSet <>(); @Override public void initialize (ListValue constraintAnnotation) { int [] vals = constraintAnnotation.vals(); for (int val : vals) { set.add(val); } } @Override public boolean isValid (Integer integer, ConstraintValidatorContext constraintValidatorContext) { return set.contains(integer); } }
6.3 关联自定义的校验器和校验注解 在自定义的注解上面关联上上面自定义的校验规则
测试自定义的注解