非常实用的参数验证框架

Hi,大家好,在我们Java猿进行项目开发的时候,写接口是我们最常做的事情,接口的参数校验,这个是一个很不起眼,但是做的不好,代码会非常冗余难看的事情,今天给大家分享一个框架,其实大家很常见的框架:hibernate-validator,接下来说说我在想项目里如何使用的。

首先引入依赖

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

接下来我要定义一个注解,主要是修饰需要做参数验证的方法:


/**
 * 参数验证
 *
 * @author Administrator
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ParameterValidation {

}

定义一个切面用来处理这个注解修饰的方法,代码如下:

/**
 * 拦截并打印Controller日志
 *
 * @author chenhj_sh
 */
@Slf4j
@Aspect
@Order(value = 12)
public class ParameterValidationAspect {
    private Validator validator;

    /**
     * 切入点
     */
    @Pointcut(value = "@annotation(com.billion.common.annotation.ParameterValidation)")
    public void pointCut() {
        // point cut
    }

    public ParameterValidationAspect(Validator validator) {
        this.validator = validator;
    }

    /**
     * 环绕通知
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            for (Object arg : args) {
                if (arg != null) {
                    Set<ConstraintViolation<Object>> constraintViolations = validator.validate(arg);
                    if (!CollectionUtils.isEmpty(constraintViolations)) {
                        for (ConstraintViolation constraintViolation : constraintViolations) {
                            throw new PayException(ErrorCode.INVALID_ARGUMENT.getCode(), constraintViolation.getMessageTemplate());
                        }
                    }
                }
            }
        }

        return joinPoint.proceed();
    }
}

这里如果校验不通过,我们可以做自己的判断,我这里是抛出自定义的异常,然后再统一异常里处理,返回自定义格式,同学们可以按照自己的处理方案处理。

接着在使用的时候

@Configuration
public class ValidatorConfig {

    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory();
        return validatorFactory.getValidator();
    }

    @Bean
    public ParameterValidationAspect parameterValidationAspect(Validator validator) {
        return new ParameterValidationAspect(validator);
    }
}

在我们需要做参数校验的方法上加注解

    /**
     * 分页查询角色
     *
     * @param queryParam
     * @return
     */
    @Override
    @ParameterValidation
    public PageResult<RoleAdminDto> getRoleByPage(QueryParam<RoleQueryCondition> queryParam) {
        PageResult<Role> pageResult = roleProvider.findByPage(queryParam.getPageArg(), queryParam.getSortArgs(), queryParam.getCondition());
        return new PageResult<>(pageResult.getPageInfo(), roleConverter.convertRoleList2DtoList(pageResult.getResult()));
    }

接下来说下各个校验规则:

例如:

/**
 * 查询数据的相关参数
 *
 * @author chenhuaijie
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@ApiModel(value = "查询参数")
public class QueryParam<C extends QueryCondition> {

    @NotNull(message = "Parameter [condition] can not be null.")
    @ApiModelProperty(value = "查询条件", position = 1)
    private C condition;

    @Valid
    @NotNull(message = "Parameter [pageArg] can not be null.")
    @ApiModelProperty(value = "分页参数", position = 2)
    private PageArg pageArg;

    @Valid
    @ApiModelProperty(value = "排序参数", position = 3)
    private List<SortArg> sortArgs;
}

@NotNull都是内置的常用的校验注解

注意,如果包含的是对象,且对象里的也需要校验,就要用@Valid修饰,嵌套对象里的注解写法与外面一致

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@ApiModel(value = "分页参数")
public class PageArg {

    @NotNull(message = "Parameter [pageSize] can not be null.")
    @Min(value = 1, message = "Parameter [pageSize] can not less than 1")
    @Max(value = 10000, message = "Parameter [pageSize] can not grater than 10000")
    @ApiModelProperty(value = "每页记录数(大于等于1,小于等于10000)", position = 1, example = "10", required = true)
    private Integer pageSize;

    @NotNull(message = "Parameter [pageNum] can not be null.")
    @Min(value = 1, message = "Parameter [pageNum] can not less than 1")
    @ApiModelProperty(value = "页码(从1开始编号)", position = 2, example = "1", required = true)
    private Integer pageNum;
}

这样我们就可以做参数校验了,我测试了一下:

 

这个框架本身自带了很多校验注解,在多数时候都是满足我们需求的:

但是框架提给我们提供了拓展的能力,我们可以自定义注解来满足我们的要求。

例如我要实现一个验证,就是给定一个值,这个值得有效值必须是指定枚举的有效值,这个能够理解吧,如果我传入了一个性别的参数,但是性别枚举只有两种值,我要校验这个值是否有效。

定义枚举:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidEnumsValidator.class)
public @interface ValidEnum {

    String message() default "";

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

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

    Class enumClass();

    String fieldName() default "value";
}

校验逻辑实现:

@Slf4j
public class ValidEnumsValidator implements ConstraintValidator<ValidEnum, Object> {

    private Class enumClass;
    private String fieldName;

    @Override
    public void initialize(ValidEnum constraintAnnotation) {
        enumClass = constraintAnnotation.enumClass();
        fieldName = constraintAnnotation.fieldName();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        try {
            Object[] objs = enumClass.getEnumConstants();
            if (objs == null || objs.length == 0) {
                return false;
            }

            String methodName = methodName(fieldName);
            Method method = enumClass.getMethod(methodName);
            return Arrays.stream(objs).anyMatch(x -> {
                try {
                    Object object = method.invoke(x);
                    return object.equals(value);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                    return false;
                }
            });
        } catch (NoSuchMethodException e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    private String methodName(String filedName) {
        return "getUsername" + String.valueOf(filedName.charAt(0)).toUpperCase() + filedName.substring(1);
    }
}

 使用的时候这么用:

    @NotBlank(message = "Parameter [order] can not be null.")
    @ValidEnum(enumClass = Order.class, message = "Parameter [order] is invalid,required 'asc' or 'desc'")
    @ApiModelProperty(value = "排序方式:降序-\"desc\";升序-\"asc\"(不区分大小写)", position = 2)
    private String order;

 

总结:

参数验证这个很不起眼的功能,不做的话,会导致很多垃圾数据进入我们的系统,轻则报一些很低级的错误,严重的脏数据进入了数据库可能会产生难以估量的风险。

如何做到很优雅的校验参数,上面是我的工作总结,希望可以帮到大家。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值