Spring Boot 参数验证

在日常的开发过程之中,前端都是调用 API 接口,访问后端接口,后端接收到请求,进行业务处理的起点,一定是 ”参数验证“,这也是保证系统健壮性的必要条件。

一、 快速上手

比如我们现在的业务场景是:用户建立项目,项目必须要关联负责人,请求参数示例如下:

@Data
public class ProjectReq {

    private Long id;

    private String name;

    private UserReq groupReq;
}
@Data
public class UserReq {

    private Long id;

    private String role;
}

现在所有的字段都是必填的,后端验证的时候,可能会写大部分的 if 判断,进行验证,这样写当然没有问题,但是如果说增加了字段,需要同步在自定义方法中补充对应的验证规则。并且排查问题较为麻烦,需要核对每个字段,是否都在自定义的验证方法之中存在。

在 JDK 内部实际上已经存在这样的验证体系,可以替换大部分场景之下的自定义验证规则,这也是:JSR-303 规范。用来实现这个规范的实际上是:hibernate-validator。

不过在 Spring Boot 的验证包中,已经引入了这个依赖,这里我直接导入 Spring Boot 的验证包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@PostMapping("/create")
public Result<Void> createUser(@Valid @RequestBody ProjectReq projectReq, BindingResult bindingResult) {
    if (bindingResult.hasFieldErrors()) {
        String defaultMessage = bindingResult.getFieldError().getDefaultMessage();
        return Result.error(500, defaultMessage);
    }
    log.info("---> {}", projectReq);
    return new Result<>();
}
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

@Data
public class ProjectReq {

    @NotNull(message = "用户 ID 不能为空")
    private Long id;

    @NotNull(message = "用户名称不能为空")
    private String name;

    @Valid
    @NotNull(message = "user Req 不能为空")
    private UserReq userReq;
}

但是这样做,对于每个接口都进行验证,较为复杂,我们可以通过接入全局异常拦截器进行处理,例如:

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> MethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        FieldError fieldError = bindingResult.getFieldError();
        String errorDefaultMessage = fieldError.getDefaultMessage();
        return Result.error(500 ,errorDefaultMessage);
    }
    
}

通过这样实现,我们就可以替换原有一大部分的非空,格式不正确验证。

二、常用注解

2.1 字符串类

注解 判断
@NotBlank 不能为空,也不能是空字符串
@NotEmpty 不能为空,可以是空字符串
@Size 判断字符串的长度
@Pattern 正则表达式验证

2.2 数字类

注解 判断
@Min 最小值,如果是 null ,不进行验证
@Max 最大值
@Positive 只能是正数
@PositiveOrZero 不能是非负数

2.1 集合类

注解 判断
@NotEmpty 集合不能为空,这里包含集合是 null 和 空集合两种情况
@Size 判断集合的长度, @Size(min = 2, max = 3) 集合的最小数量是 2,最大数量是 3

三、自定义验证注解

在日常的开发之中,对于前端传递的参数,如果值是一个枚举值,为防止恶意参数,需要对参数验证,满足业务需求。

这里首先定义一个接口,用来做验证

public interface ArrayValuable<T> {
    T[] array();
}
@Getter
@AllArgsConstructor
public enum ProjectStatusEnum implements ArrayValuable<String>{

    running("running", "运行中"),

    stop("stop", "停止")
    ;

    private final String code;

    private final String desc;

    public static final String[] ARRAYS = Arrays.stream(values()).map(ProjectStatusEnum::getCode).toArray(String[]::new);

    @Override
    public String[] array() {
        return ARRAYS;
    }
}

接下来,我们创建自定义注解,加在字段上面,就可以快速的实现枚举类型参数的快速验证

@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target({
        ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE
})
@Constraint(validatedBy = EnumParamValidator.class)
public @interface InEnum {

    /**
     * 枚举,标识当前值的可选值
     */
    Class<? extends ArrayValuable<?>> value();
    /**
     * 分组
     */
    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    /**
     * 错误提示
     */
    String message() default "当前传递值不在可选范围内";
}

创建一个:ConstraintValidator 类型的验证器,用来进行实际的验证逻辑

public class EnumParamValidator implements ConstraintValidator<InEnum, Object> {

    private List<Object> values = new ArrayList<>();

    @Override
    public void initialize(InEnum constraintAnnotation) {
        ArrayValuable<?>[] enumConstants = constraintAnnotation.value().getEnumConstants();
        if (enumConstants.length == 0) {
            this.values = Collections.emptyList();
        } else {
            this.values = Arrays.asList(enumConstants[0].array());
        }
    }

    @Override
    public boolean isValid(Object prepareValidParam, ConstraintValidatorContext constraintValidatorContext) {
        if (CollectionUtils.isEmpty(values)) {
            return true;
        }
        return values.contains(prepareValidParam);
    }
}
posted @ 2026-03-04 23:02  不会Coding  阅读(0)  评论(0)    收藏  举报