数据校验(JSR303、SpringBoot、自定义注解)

发布于:2024-06-25 ⋅ 阅读:(121) ⋅ 点赞:(0)
在一个项目中,不仅前端要对用户输入的数据进行校验,避免发送不必要的请求,而且后端也要对数据进行对应的校验,因为操作不都是通过页面过来的。

前端

不是很了解

  • 正则表达式

  • 配合各种组件使用

后端

这里以Java为例,这里主要说声明式,JSR303校验。

常用的注解

  • @Null 验证对象是否为null

  • @NotNull 验证对象是否不为null

  • @Max 对Number和String的最大值进行限制

  • @Min 对Number和String的最小值进行限制

  • @NotBlank 检查字符串是否不为空,即不是null,也不是""

  • @URL 检查是否符合路径规则。

对数据进行校验

  1. 第一步引入对应的依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.0.5.RELEASE</version>
    </dependency>

  2. 在实体类中的属性加上对应的注解

    /**
     * 品牌名
     */
    @NotBlank(message = "name参数校验错误")
    private String name;

    message是在校验不正确的时候,响应的信息。

  3. 在对应的Controller中使用@Valid注解

      
    /**
       * 修改
       */
      @RequestMapping("/update")
      //@RequiresPermissions("product:brand:update")
      public R update(@Valid @RequestBody BrandEntity brand){
    brandService.updateById(brand);
          
          return R.ok();
      }

    数据校验不通过,就会朝前断响应400 Bad Request,参数不正确。

    当然,你也可以自定义返回的结果:

    1. 在要校验的参数后面,紧跟一个BindingResult ,就可以拿到校验的结果。

    /**
       * 修改
       */
      @RequestMapping("/update")
      //@RequiresPermissions("product:brand:update")
      public R update(@Valid @RequestBody BrandEntity brand,BindingResult result){
    brandService.updateById(brand);
          if(result.hasErrors()){
            //1. 获得所有的错误
            result.getFieldErrors().forEach((item) -> {
                //TODO:进行操作。
            })
            //TODO:返回自定义的信息即可。  
          }
          return R.ok();
      }

  4. 这样写,显然不合适,每个Controller都有错误校验的话,重复代码太多了,而且这些代码都和我们业务代码耦合了。所以最佳实践:全局异常处理。

     /**
       * 修改
       */
      @RequestMapping("/update")
      //@RequiresPermissions("product:brand:update")
      public R update(@Valid @RequestBody BrandEntity brand){
    brandService.updateById(brand);
          
          return R.ok();
      }

    还是上面的一段代码

    全局异常使用SpringBoot的ControllerAdvice注解。

    1. 编写一个类,加上@ControllerAdvice注解。

    2. 编写对应的方法,加上@ExceptionHandler注解,并写上能处理的异常。

    3. 在方法里写上对应的逻辑。

      //集中处理所有异常  
      @Slf4j
      @RestControllerAdvice(basePackages = "com.atguigu.gulimail.product.app")
      public class GulimailExcdeptionControllerAdvice {
      ​
          @ExceptionHandler(value = MethodArgumentNotValidException.class)
          public R handleVaildException(MethodArgumentNotValidException e) {
              log.error("数据校验出现问题{},异常类型:{}", e.getMessage(), e.getClass());
              BindingResult bindingResult = e.getBindingResult();
              //TODO :进行操作即可
          }
      ​
          @ExceptionHandler(value = Throwable.class)
          public R handleException(Throwable throwable) {
              log.error("错误", throwable);
              //TODO:进行操作即可。
          }
      }

  5. 分组校验

    对于一个实体类的属性,在不同的方法下,标准可能是不一样的,比如一个商品的ID,在创建时应该是@Null(前端不用传,数据库自己生成),但在更新修改方法中就得是@NotNull了,所以分组校验就十分重要了。

    那注解就可以这样写:

    /**
     * 品牌id
     */
    @NotNull(message = "修改id不能为空",groups = {UpdateGroup.class})
    @Null(message = "新增id要为空")
    private Long brandId;

    groups接受一个数组,数组里面的是接口,自己定义接口即可,不用做任何操作,就相当于一个标识。

    @Controller层的@valid注解不支持分组,这是就得使用@valiated(Spring提供的注解)注解了。

     /**
       * 修改
       */
      @RequestMapping("/update")
      //@RequiresPermissions("product:brand:update")
      public R update(@Validated({UpdateStatusGroup.class}) @RequestBody BrandEntity brand){
    brandService.updateById(brand);
          return R.ok();
      }

    {}里的就是校验分组的标识。

    注意

    @NotBlank在分组情况下不生效,只有在@Validated情况下生效

  6. 自定义注解:

    先看一下@NotBlank注解:

    @Documented
    @Constraint(
        validatedBy = {}
    )//指定的校验方法。
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME) //触发的时机
    @Repeatable(List.class)
    public @interface NotBlank {
        String message() default "{javax.validation.constraints.NotBlank.message}"; //默认去找的信息
    ​
        Class<?>[] groups() default {};   //分组情况
    ​
        Class<? extends Payload>[] payload() default {};
        
        //上面三个是必须要有的。
        
    ​
        @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        public @interface List {
            NotBlank[] value();
        }
    }

    自己写一个类

    1. 首先需要引入对应的依赖:

      <dependency>
          <groupId>javax.validation</groupId>
          <artifactId>validation-api</artifactId>
          <version>2.0.1.Final</version>
      </dependency>

    2. 编写一个注解

      @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}";
      ​
          Class<?>[] groups() default {};
          Class<? extends Payload>[] payload() default  {};
      ​
          int[] vals() default {};
      }

      String message() default "{com.atguigu.common.valid.ListValue.message}"; 是默认错误信息的路径地址,在resource包下创建一个properties文件即可。

      ValidationMessages.properties

      com.atguigu.common.valid.ListValue.message=信息有误

    3. 创建处理方法

      这两个泛型分别是:注解和需要添加注解的数据的类型。

      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 value, ConstraintValidatorContext context) {
              return set.contains(value);
          }
      }

    4. 使用注解:

      /**
       * 显示状态[0-不显示;1-显示]
       */
      @ListValue(vals = {0,1})
      private Integer showStatus;

      在ListValueConstraintValidator的initialize方法中,会将vals里的所有值放到set集合中,然后isValid判断实体属性是否在这个set里面,这里可以根据自己的逻辑修改。


网站公告

今日签到

点亮在社区的每一天
去签到