持续创作,加速成长!这是我参与「掘金日新计划 · 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格式 |
注:这些只是常用的校验注解
3、自定义参数校验
1、创建自定义接口
虽然Spring Validation 提供的注解基本上够用,但是面对复杂的定义,我们还是需要自己定义相关注解来实现校验。 自定义注解接口用到的注解解释:
- @Documented:注解表明这个注解应该被 javadoc工具记录.
- @Target:元注解。该注解可以声明在哪些目标元素之前,也可理解为注释类型的程序元素的种类。
- @Retention:告诉编译程序如何处理,也可理解为注解类的生命周期。(RetentionPolicy.RUNTIME : 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;)
- @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():调用此接口实现校验逻辑 注:代码仅供大家参考。
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开发中使用它提供的校验。