需求场景

平时在controller的入参校验中大多都是单个参数校验
一般都是直接用javax的注解 + javax.validation.Valid注解一把梭。

不过也有场景是需要动态校验。
假设入参的年龄和地区相关。
上海地区的年龄范围需要在10-20
浙江地区的年龄范围需要在10-30

这个时候要么就是直接硬编码了,但是前人肯定遇到过这种问题。

org.hibernate.validator.group.GroupSequenceProvider注解就能帮我们稍微实现的漂亮一点。

如果当前项目中没有对应依赖的话先引入依赖

<dependency>
	<groupId>org.hibernate.validator</groupId>
	<artifactId>hibernate-validator</artifactId>
	<version>6.2.0.Final</version>
</dependency>

代码Demo

自定义编码实现动态校验


package com.xxx.xxx.valid.group;

/**
 * 根据动态条件来决定是否判断 NotNull
 */
public interface NotNullByConditionGroup {
}




package com.xxx.xxx.xxx;

import com.xxx.xxx.valid.group.NotNullByConditionGroup;
import com.xxx.xxx.valid.ProductAccessoriesSkuQueryProvider;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.group.GroupSequenceProvider;

import javax.validation.constraints.NotNull;
import java.io.Serializable;


@GroupSequenceProvider(ProductAccessoriesSkuQueryProvider.class)
@Data
public class ProductAccessoriesSkuQueryListDTO implements Serializable {

    private static final long serialVersionUID = 8297881653432975830L;

    @ApiModelProperty(value = "产品id")
    @NotNull(message = "产品id不能为空", groups = NotNullByConditionGroup.class)
    private Long productId;

    @ApiModelProperty(value = "颜色id,详情见数据字典 color")
    @NotNull(message = "颜色id不能为空", groups = NotNullByConditionGroup.class)
    private Long colorId;

    @ApiModelProperty(value = "尺寸id,详情见数据字典 sales_size")
    @NotNull(message = "尺寸id不能为空", groups = NotNullByConditionGroup.class)
    private Long sizeId;

    @ApiModelProperty(value = "产品skuId")
    private Long productSkuId;


}


package com.xxx.xxx.valid;

import com.xxx.xxx.xxx.ProductAccessoriesSkuQueryListDTO;
import com.xxx.xxx.xxx.group.NotNullByConditionGroup;
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;


/**
 * 校验当 productSkuId 不存在的时候 sizeId + productId + colorId 不能为空
 */
public class ProductAccessoriesSkuQueryProvider implements DefaultGroupSequenceProvider<ProductAccessoriesSkuQueryListDTO> {

    @Override
    public List<Class<?>> getValidationGroups(ProductAccessoriesSkuQueryListDTO bean) {
        List<Class<?>> defaultGroupSequence = new ArrayList<>();
        defaultGroupSequence.add(ProductAccessoriesSkuQueryListDTO.class); // 这一步不能省,否则Default分组都不会执行了,会抛错的
        if(Objects.nonNull(bean)){
            Long productSkuId = bean.getProductSkuId();

            if(Objects.isNull(productSkuId)){
                defaultGroupSequence.add(NotNullByConditionGroup.class);
            }

        }

        return defaultGroupSequence;
    }
}




@PostMapping("queryList")

public CommonResult<List<? extends xxx>> queryList(@RequestBody @Validated ProductAccessoriesSkuQueryListDTO queryList){
    return CommonResult.success();
}


@Validated指定分组校验

public class ValidatedGroup {
 
    public interface CREATE{}
 
    public interface DELET{}
 
    public interface UPDATE{}
 
    public interface QUERY{}
}



import lombok.Data;
 
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
 
@Data
public class ValidatedVO {
 
    //更新、删除时不能为空
    @NotNull(message = "id不能为空", groups = {ValidatedGroup.UPDATE.class, ValidatedGroup.DELET.class})
    private Long id;
 
    //新增、更新时不能为空
    @NotBlank(message = "name不能为空", groups = {ValidatedGroup.CREATE.class ,ValidatedGroup.UPDATE.class})
    private String name;
 
    //查询时不能为空
    @NotBlank(message = "queryParam不能为空", groups = {ValidatedGroup.QUERY.class})
    private String queryParam;
 
}




public ValidatedVO bindValidate(@RequestBody @Validated(value= {ValidatedGroup.DELET.class}) ValidatedVO validatedVO/*, BindingResult result*/) {
    return validatedVO;
}

@Validated和@Valid的区别

在校验的时候会发现有2个很类似的注解,@Valid和@Validated
首先这两个的包都是不同的
javax.validation.Valid
org.springframework.validation.annotation.Validated

其次
@Valid:标准JSR-303规范的标记型注解,用来标记验证属性和方法返回值,进行级联和递归校验
@Valid可以用在属性级别约束,用来表示级联校验。
@Valid可用于方法、字段、构造器和参数上

@ValidatedSpring的注解,是标准JSR-303的一个变种(补充),提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。上面提到的分组校验就是利用了其分组功能
@Validated注解可以用于类级别,用于支持Spring进行方法级别的参数校验。
@Validated只能用在类、方法和参数上;

自定义校验注解

定义一个校验注解

package com.xx.xx.xx;

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

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//这里指向的是我们自定义的校验规则,可以治党多个不同校验器,适配不同的情况
@Constraint(validatedBy = {DataCheckVaildator.class})
public @interface DataCheck {

    //当然这里的message也可以去属性文件中指定
    String message() default "请提交规范数据";

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

    Class<? extends Payload>[] payload() default {};
    //用来获取注解属性值
    int[] vals() default {};

}

在定义个注解校验器

需要声明校验的注解和被校验的数据类型

package com.xxx.xxx.xxx;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class DataCheckVaildator implements ConstraintValidator<DataCheck, Long> {

    private Set<Integer> setNum = new HashSet<>();

    @Override
    public void initialize(DataCheck constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            setNum.add(val);
        }
    }

    @Override
    public boolean isValid(Long value, ConstraintValidatorContext context) {
        if(Objects.isNull(value)){
            return true;
        }

        return setNum.contains(value);
    }
}

使用

@ApiModelProperty(value = "产品skuId")
@DataCheck(vals = {1,2,3}, message = "产品skuId不符")
private Long productSkuId;


//这里使用spring的org.springframework.validation.annotation.Validated
//和jsr的javax.validation.Valid
//都可以
@PostMapping("queryList")
public CommonResult<List<? extends ProductAccessoriesSkuListVO>> queryList(@RequestBody @Validated ProductAccessoriesSkuQueryListDTO queryList){
    return CommonResult.success();
}

控制注解校验顺序

有些时候需要在前一个校验通过后再校验,这个时候就需要控制一下校验顺序了

可以使用javax.validation.GroupSequence来定义顺序,然后声明这个顺序接口class就行了

上代码demo

定义校验顺序

package com.xxx.xxx.xxx.xxx;

import javax.validation.GroupSequence;
import javax.validation.groups.Default;

/**
 * 定义校验顺序
 */
@GroupSequence({Default.class, TaxPriceCalculateValidOrderGroup.TaxPriceCalculateGroup.class})
public interface TaxPriceCalculateValidOrderGroup {

    interface TaxPriceCalculateGroup {};

}

使用分组

public CommonResult<?> edit(@RequestBody @Validated(value = TaxPriceCalculateValidOrderGroup.class) ProductDTO productDTO){
xxxx

在目标类上使用注解

@TaxPriceCalculate(message = "含税价-不含税价与比例不符", groups = TaxPriceCalculateValidOrderGroup.TaxPriceCalculateGroup.class)
public class SkuSupplierBaseInfoDTO {
xxx
Logo

一站式 AI 云服务平台

更多推荐