springBoot参数校验

232 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

一 前言

在服务端的开发中,我们不可避免的要对前端输入参数进行校验,除查询外基本上会对每个接口都要对参数进行校验,比如一些非空校验、格式校验、字段长度校验等。如果参数比较少的话可以在业务层去做校验,但是参数一旦多了起来就会出现大量的if-else语句。代码的可读性就会变的很低,同时业务层也会出现大量冗余的代码。所以我们可以使用validator组件来代替我们进行不必要的coding操作。本文基于validator的介绍资料,也结合自己在项目中的实际使用经验进行了总结,希望能帮到大家。

本文分四个步骤来介绍参数校验。一、引入依赖(很重要),对全局异常做出处理;二、常见注解校验;三、自定义参数注解;四、分组校验,

1、引入依赖

```
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

(2)全局异常处理

```
import constants.ResultBody;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import static ics.imedx.visit.constant.FlagData.*;
/**
 * 全局异常处理类
 *
 * @author yxg
 * @since 2022-06-01 mainResourcePatternDefiner
 */
@RestControllerAdvice
public class ExceptionHandler {

    @org.springframework.web.bind.annotation.ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultBody MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        return ResultBody.error(FAIL_CODE,e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
    }

}
```

2、常见注解校验

注解功能
@NotBlank字符串不能为空,同时去除trim()后依然不为""
@NotNull不能为null,可以是空
@Pattern校验正则表达式
@Length校验字段长度
@Min校验最小值,常用于整型
@Max校验最大值,常用于整型
@Length校验字段长度
@Range校验值范围
@Future日期必须在当前日期的未来
@Past日期必须在当前日期的过去
@Email必须是email格式

注:这些只是常用的校验注解

3、自定义参数校验

1、创建自定义接口

虽然Spring Validation 提供的注解基本上够用,但是面对复杂的定义,我们还是需要自己定义相关注解来实现校验。 自定义注解接口用到的注解解释:

  1. @Documented:注解表明这个注解应该被 javadoc工具记录.
  2. @Target:元注解。该注解可以声明在哪些目标元素之前,也可理解为注释类型的程序元素的种类。
  3. @Retention:告诉编译程序如何处理,也可理解为注解类的生命周期。(RetentionPolicy.RUNTIME  : 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;)
  4. @Constraint:限定自定义注解的方法
/**
 * 校验证件类型
 * @author yxg
 * @since 2022-06-08
 *
 */
@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = idNo.class)
public @interface idNoValid {

    String message() default "证件类型和证件号不匹配";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

2、自定义校验逻辑,并实现自定义注解接口

initialize():初始化,如果需要初始化赋值可用到 isValid():调用此接口实现校验逻辑 注:代码仅供大家参考。

image.png

import com.fasterxml.jackson.databind.ObjectMapper;
import ics.imedx.visit.annotations.idNoValid;
import ics.imedx.visit.entity.VisitOp;
import ics.imedx.visit.util.Valid;
import org.apache.commons.lang3.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 校验证件类型和证件号是否匹配
 */
public class idNo implements ConstraintValidator<idNoValid, Object> {
    @Override
    public void initialize(idNoValid constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value==null){
            return true;
        }
         VisitOp visitOp = new ObjectMapper().convertValue(value, VisitOp.class);
         //判断是否有证件类型和证件号
        if (StringUtils.isNotBlank(visitOp.getCertificateTypeId()) && StringUtils.isNotBlank(visitOp.getCertificateNo())) {
        //封装了一个方法,判断证件类型和证件号是否符合标准
            return   Valid.certificateValid(visitOp.getCertificateTypeId(),visitOp.getCertificateNo());
        }
        return true;
    }
}

4、分组校验

场景:我们经常对不同的接口会做不同的校验,比如新增学生信息,我们可能会对姓名、性别都做校验,但是修改时我们可能只修改性别,如果所有的校验都放在一个实体类中,就会出现新增接口会影响修改接口。Validator校验框架已经考虑到了这种场景并且提供了解决方案,就是分组校验。

1、定义分组接口

这里我们定义一个分组接口ValidGroup让其继承 javax.validation.groups.Default,再在分组接口中定义出多个不同的操作类型,Create,Update,Query,Delete。

```
package ics.imedx.visit.annotations;

import lombok.Builder;

public interface ValidGroup extends Builder.Default {
    interface Crud extends ValidGroup{
        interface Create extends Crud{

        }

        interface Update extends Crud{

        }
        interface updateVisitStatus extends Crud{

        }
        interface updateRegisterStatus extends Crud{

        }

        interface Query extends Crud{

        }

        interface Delete extends Crud{

        }
    }
}
```

2、给实体类中属性进行分组

在常见的注解中添加groups,对该校验注解可以分组多个接口,可根据实际需要添加。对于未分组的按照默认分组来,也就是不会影响现有的。

//@Pattern(regexp = "^([0,1]{1})$",message = "复诊标志只能允许为0或1")
@Min(groups = {ValidGroup.Crud.Create.class,ValidGroup.Crud.Update.class},value = 0,message ="复诊标志只能允许为0或1" )
@Max(groups = {ValidGroup.Crud.Create.class,ValidGroup.Crud.Update.class},value = 1,message ="复诊标志只能允许为0或1" )
//    @ApiModelProperty(value = "0:初诊;1:复诊")
@Schema(description = "复诊标志")
@TableField("RETURN_VISIT")
private Integer returnVisit;

3、给需要参数校验的方法指定分组

@RequestMapping(value = "/add", method = RequestMethod.POST)
public ResultBody insertVisitOp(@Validated(value= ValidGroup.Crud.Create.class) @RequestBody VisitOp visitOp, HttpServletRequest request) {
    return visitOpService.insertVisitOp(visitOp, request);
}

4、体检效果

{
  "status": 400,
  "message": "ID不能为空; 应用ID不能为空",
  "data": null,
  "timestamp": 1628492514313
}

总结

我们日常服务端的开发少不了对参数的校验,为了避免在业务层中出现大量校验,就需要在springboot开发中使用它提供的校验。