Skip to content

参数校验

项目地址

https://github.com/zhaobao1830/misszb

校验前端传过来的参数

安装插件

xml
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
			<version>3.1.0</version>
		</dependency>

SpringBoot 2.3版本后,必须安装上面的插件,不然@Validated、@Max、@Min这些插件不能用

常用校验注解

@Validated:开启校验

1、在需要校验参数的Controller中加上该注解,Controller方法里属性参数的校验才会生效;

java
@RestController
@RequestMapping(value = "/banner")
@Validated
public class BannerController {
    @RequestMapping(value = "/test/{id}", method = RequestMethod.POST)
    public PersonDTO test(@PathVariable @Max(value = 10, message = "id不能大于10") Integer id,
                          @RequestParam String name
                          ) {
        System.out.println(id);
        System.out.println(name);
        PersonDTO dto = new PersonDTO();
        if (name.equals("zb")) {
            throw new ForbiddenException(10002);
        }
        return dto;
    }
}

2、如果参数是对象形式,必须在前面加上@Validated,否则无法校验对象里的属性,(这时Controller上不用加@Validated)

java
@RestController
@RequestMapping(value = "/banner")
public class BannerController {
    @RequestMapping(value = "/test", method = RequestMethod.POST)
    public PersonDTO test(@RequestBody @Validated PersonDTO personDTO) {
        PersonDTO dto = new PersonDTO();
        if (personDTO.getPersonName().equals("zb")) {
            throw new ForbiddenException(10002);
        }
        return dto;
    }
}

@Valid:级联校验

当对象参数的类里,有其他对象的属性,就需要在级联的对象上添加@Valid,这样才能校验级联对象里的属性

java
@RestController
@RequestMapping(value = "/test")
@Validated
public class TestController {
    @RequestMapping(value = "/test/{id}", method = RequestMethod.POST)
    public PersonDTO test(@PathVariable @Max(value = 10, message = "id不能大于10") Integer id,
                          @RequestParam String name,
                          @RequestBody @Validated PersonDTO personDTO
                          ) {
        System.out.println(id);
        System.out.println(name);
        PersonDTO dto = new PersonDTO();
        if (name.equals("zb")) {
            throw new ForbiddenException(10002);
        }
        return dto;
    }
}

PersonDTO.java

java
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@PasswordEqual(message = "用户的密码不一致")
public class PersonDTO {
    @Length(min = 2, max = 10, message = "personName长度必须在2-10之间")
    private String personName;
    private Integer age;

    private String password1;
    private String password2;

    @Valid
    private SchoolDTO schoolDTO;
}

SchoolDTO.java

java
@Getter
@Setter
public class SchoolDTO {
    @Length(min = 2)
    private String schoolName;
}

@Length:定义字符串的长度

@Max和@Min:定义最大值和最小值

备注

通常,验证的参数多为基本类型,比如要求是数字类型,要求是正整数。但是如果提交的参数是一个对象,且对象的某个属性又是另外一个对象,要验证这个内嵌对象的属性,应该如何进行验证?

以上面的test请求为例,请求参数里有PersonDTO对象,PersonDTO对象里有个属性是SchoolDTO,也是对象

1、请求方法里,需要在PersonDTO前面加@RequestBody和@Validated注解

2、PersonDTO类里,在SchoolDTO属性上面加上@Valid注解(开启SchoolDTO类的校验)

3、SchoolDTO类里,在需要的属性上加校验注解

自定义校验注解

在做项目的时候,如果我们要校验复杂的逻辑,自带的校验注解无法满足,就需要自定义校验注解

以校验俩次输入的密码是否一样为例:

项目地址:https://github.com/zhaobao1830/misszb

一、新建注解PasswordEqual

PasswordEqual.java

java
package com.zb.misszb.dto.validators;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@Constraint(validatedBy = PasswordValidator.class )
public @interface PasswordEqual {
    int min() default 4;

    int max() default 6;

    String message() default "passwords are not equal";

    Class<?>[] groups() default {};

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

@Documented是元注解,生成文档信息的时候保留注解,对类作辅助说明

@Retention作用是定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中.

从注释上看:

source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略

class:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期

runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在

这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。

@Target:指明了修饰的这个注解的使用范围,即被描述的注解可以用在哪里

ElementType的取值包含以下几种:

  • TYPE:类,接口或者枚举

  • FIELD:域,包含枚举常量

  • METHOD:方法

  • PARAMETER:参数

  • CONSTRUCTOR:构造方法

  • LOCAL_VARIABLE:局部变量

  • ANNOTATION_TYPE:注解类型

  • PACKAGE:包

@Constraint:指定具体实现的是哪个类

PasswordValidator.java注解逻辑实现类

java
package com.zb.misszb.dto.validators;

import com.zb.misszb.dto.PersonDTO;

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

public class PasswordValidator implements ConstraintValidator<PasswordEqual, PersonDTO> {
    private int min;
    private int max;
    
    @Override
    public void initialize(PasswordEqual constraintAnnotation) {
        this.min = constraintAnnotation.min();
        this.max = constraintAnnotation.max();
    }

    @Override
    public boolean isValid(PersonDTO personDTO, ConstraintValidatorContext constraintValidatorContext) {
        String password1 = personDTO.getPassword1();
        String password2 = personDTO.getPassword2();
        boolean match = password1.equals(password2);
        return match;
    }
}

自定义的类要实现ConstraintValidator接口,需要传入俩个参数,第一个是自定义注解的名称,这里是PasswordEqual;第二个是自定义注解修饰的目标类型,即修饰哪个类就写哪个类,当前 定义的注解是用在PersonDTO类上,所以传PersonDTO;如果是修饰属性,就传入属性的类型,比如String

如果想获取注解里传入的参数,需要:一、定义参数变量(只能用基本类型(int这种),不能用包装类型(Integer));二、重写initialize方法, 通过方法里的constraintAnnotation获取参数

isValid方法:里面写注解的判断,要返回boolean

PersonDTO类中使用

java
package com.zb.misszb.dto;

import com.zb.misszb.dto.validators.PasswordEqual;
import lombok.*;
import org.hibernate.validator.constraints.Length;

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@PasswordEqual(message = "用户的密码不一致")
public class PersonDTO {
    @Length(min = 2, max = 10, message = "personName长度必须在2-10之间")
    private String personName;
    private Integer age;

    private String password1;
    private String password2;
}

抽取message信息

参数校验里的message信息,我们可以抽取到公共文件ValidationMessages.properties里(这个文件的名称是固定的,必须写成这样,放在resources文件夹下)

ValidationMessages.properties

id.positive = id必须是正整数
token.password = password不符合规范:当前值是${validatedValue};最大值应该是{max},最小值应该是{min}

如果参数校验注解里有max、min属性,可以按照上面的写法展示

方法里使用

{}引入

java
@RequestMapping(value = "/id/{id}/simplify", method = RequestMethod.GET)
    public SpuSimplifyVO getSimplifySpu(@PathVariable @Positive(message = "{id.positive}") Long id) {
        Spu spu = spuService.getSpuDetailById(id);
        SpuSimplifyVO spuSimplifyVO = new SpuSimplifyVO();
        BeanUtils.copyProperties(spu, spuSimplifyVO);
        return spuSimplifyVO;
    }

测试效果:

http://localhost:8082/v1/spu/id/-1/simplify

{
    "code": 10001,
    "message": "getSimplifySpu.id: id必须是正整数",
    "request": "GET /v1/spu/id/-1/simplify"
}

备注

如果觉得message里的提示信息不好,可以在全局异常文件GlobalExceptionAdvice)里对

handleConstraintException方法中的messgae进行处理

java
 @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(code= HttpStatus.BAD_REQUEST)
    @ResponseBody
    public UnifyResponse handleConstraintException(HttpServletRequest req, ConstraintViolationException e){
        // 下面是对返回的message进行处理,如果觉得默认的message信息也能看懂,就没必要处理
        // 这只是个简单例子,可以根据需求自己修改
        String requestUrl = req.getRequestURI();
        StringBuilder errorMsg = new StringBuilder();
        for (ConstraintViolation error : e.getConstraintViolations()) {
            String msg = e.getMessage();
            String m = error.getPropertyPath().toString();
            String name = m.split("[.]")[1];
            errorMsg.append(name).append(" ").append(msg).append(";");
        }
        String method = req.getMethod();

        return new UnifyResponse(10001, errorMsg.toString(), method + " " + requestUrl);
    }

注意事项

  • BindException是@Validation单独使用校验失败时产生的异常;

  • MethodArgumentNotValidException是@RequestBody和@Validated配合时产生的异常;

  • BindingResult对于上述两种异常都可以捕获

java
private Map<String, String> getErrors(BindingResult result) {
        Map<String, String> map = new HashMap<>();
        List<FieldError> errorList = result.getFieldErrors();
        for (FieldError error : errorList) {
            // 发生验证错误所对应的某一个属性
            String errorField = error.getField();
            // 验证错误的信息
            String errorMsg = error.getDefaultMessage();

            map.put(errorField, errorMsg);
        }
        return map;
    }

Bean Validation 中内置的 constraint

@Null 被注释的元素必须为 null

@NotNull 被注释的元素必须不为 null

@AssertTrue 被注释的元素必须为 true

@AssertFalse 被注释的元素必须为 false

@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@Size(max=, min=) 被注释的元素的大小必须在指定的范围内

@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内

@Past 被注释的元素必须是一个过去的日期

@Future 被注释的元素必须是一个将来的日期

@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

@NotBlank(message =) 验证字符串非null,且长度必须大于0

@Email 被注释的元素必须是电子邮箱地址

@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内

@NotEmpty 被注释的字符串的必须非空

@Range(min=,max=,message=) 被注释的元素必须在合适的范围内

如有转载或 CV 的请标注本站原文地址