Springboot如何优雅的参数校验

1.为什么需要参数校验

在接口开发中,为了防止非法参数对业务造成影响,经常需要对接口的参数做校验,靠代码对接口参数一个个校验的话就太繁琐了,
Validator框架就是为了解决开发人员在开发的时候少写代码,提升开发效率;Validator专门用来进行接口参数校验,例如常见的非空、集合个数、数值范围校验等等…

2.SpringBoot中集成参数校验

2.1 引入依赖

注:从springboot-2.3开始,校验包被独立成了一个starter组件,所以需要引入validation和web,而springboot-2.3之前的版本只需要引入 web 依赖就可以了

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

2.2 定义校验器配置

使用时可以不配置这个类,默认的校验模式就是全部参数都校验一遍,然后返回整体结果,此处配置设置校验模式为快速失败,单个校验不通过就结束校验返回结果

@Configuration(proxyBeanMethods = false)
public class ValidatorConfiguration {
    @Bean
    @ConditionalOnMissingBean(Validator.class)
    public LocalValidatorFactoryBean validator(AutowireCapableBeanFactory springFactory) {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
        factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
        // 快速失败
        factoryBean.getValidationPropertyMap().put(BaseHibernateValidatorConfiguration.FAIL_FAST, Boolean.TRUE.toString());
        return factoryBean;
    }
}

2.3 定义需要参数校验的实体类

@Data
public class InfoRequest {

    @ApiModelProperty("主键id")
    @NotNull(message = "主键,不能为空")
    private Integer id;

    @ApiModelProperty("性别")
    @NotBlank(message = "性别,不能为空")
    @Pattern(regexp = "^(男|女){1}$",message = "性别只允许输入 男、女")
    private String sex;

    @ApiModelProperty("姓名")
    @NotBlank(message = "姓名,不能为空")
    @Length(min = 2,max = 5)
    private String name;

    @ApiModelProperty("车辆列表")
    @NotEmpty(message = "车辆列表,不能为空")
    private List<String> carList;

    @ApiModelProperty("记录列表")
    @NotEmpty(message = "记录列表,不能为空")
    @Size(min = 2, max = 3, message = "记录列表,个数必须在2和3之间")
    private Set<String> ids;

    @ApiModelProperty("邮件地址")
    @Email(message = "邮件地址,不正确")
    private String email;

    @ApiModelProperty("邮件地址")
    @IdCard(message = "身份证号不正确")
    private String idCardNumber;

    @ApiModelProperty("年龄")
    @Max(value = 100, message = "最大年龄100岁")
    @Min(value = 1, message = "最小年龄1岁")
    private Integer age;

    @ApiModelProperty("车辆数")
    @Range(min = 0, max = 2, message = "车辆数超出范围")
    private String carNumber;

    @ApiModelProperty("内嵌参数")
    @NotNull(message = "内嵌参数不能为空")
    @Valid
    private InnerParam innerParam;

    @Data
    public static class InnerParam{
        @NotNull(message = "内嵌参数id不能为空")
        private Integer id;

        @NotEmpty(message = "内嵌参数ids集合,不能为空")
        private Set<String> ids;
    }
}

2.4 定义需要参数校验的实体类

2.4.1 校验对象参数

@RestController
@RequestMapping("validation")
@Api(tags = "参数验证学习")
public class VerifyController {

    @Autowired
    private Validator validator;

    @ApiOperation(value = "对象参数校验接口")
    @PostMapping("checkObject")
    // 此处使用@Valid 或者 @Valided 都可以
    public JsonResult checkObject(@RequestBody @Valid InfoRequest infoRequest) {
        return JsonResult.success();
    }
}

2.4.2 校验单个参数

 @RestController
@RequestMapping("validation")
@Api(tags = "参数验证学习")
@Validated // 步骤一 类标记校验注解
public class VerifyController {
    @ApiOperation(value = "单个参数校验接口")
    @PostMapping("checkSingleParams") // 步骤二 参数前标记约束校验注解
    public JsonResult checkSingleParams(@NotBlank(message = "主键id不能为空") @RequestParam("id1") String id) {
        return JsonResult.success();
    }

  }

2.4.3 手动执行校验逻辑

@RestController
@RequestMapping("validation")
@Api(tags = "参数验证学习")
public class VerifyController {

    @Autowired
    private Validator validator;

    @ApiOperation(value = "手动执行校验接口")
    @PostMapping("handCheck")
    public JsonResult<String> handCheck(@RequestBody InfoRequest infoRequest) {
        Set<ConstraintViolation<InfoRequest>> set = validator.validate(infoRequest);
        return JsonResult.success(set.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(":")));
    }
}

2.5 自定义参数校验

虽然Spring Validation 提供的注解基本上够用,但是面对复杂的定义,我们还是需要自己定义相关注解来实现自动校验。

2.5.1 创建自定义校验注解

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(IdCard.List.class)
@Documented
@Constraint(validatedBy = {IdCardValidator.class}) // 此处关联自定义校验的实现类
public @interface IdCard {
    String message() default "身份证格式错误";

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

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

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface List {
        IdCard[] value();
    }
}

2.5.2 实现校验逻辑

public class IdCardValidator implements ConstraintValidator<IdCard, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        if (value == null) {
            return false;
        }
        String regular = "";

        if (value.length() == 15) {
            regular = "^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$";
        } else if (value.length() == 18) {
            regular = "^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
        } else {
            return false;
        }

        return Pattern.compile(regular).matcher(value).find();
    }
}

2.5.3 在字段上使用自定义校验的注解

    @ApiModelProperty("邮件地址")
    @IdCard(message = "身份证号不正确")
    private String idCardNumber;

2.5.3 使用效果

在这里插入图片描述

2.6 优雅捕获校验异常

参数校验失败,会抛校验失败异常,如果不做异常捕获,返回结果就不那么优雅了,如下展示

在这里插入图片描述

 @ExceptionHandler(value = {MethodArgumentNotValidException.class, ValidationException.class, BindException.class})
    public JsonResult methodArgumentNotValidException(Throwable e) {
        StringBuilder sb = new StringBuilder();
        BindingResult bindingResult = null;
        if (e instanceof MethodArgumentNotValidException) {
            bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
            bindingResult.getFieldErrors().forEach(fieldError -> sb.append(fieldError.getDefaultMessage()));
        } else if (e instanceof ValidationException) {
            if (e instanceof ConstraintViolationException) {
                Set<ConstraintViolation<?>> violations = ((ConstraintViolationException) e).getConstraintViolations();
                String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
                sb.append(message);
            } else {
                sb.append(e.getMessage());
            }
        } else if (e instanceof BindException) {
            bindingResult = ((BindException) e).getBindingResult();
            bindingResult.getFieldErrors().forEach((fieldError) -> sb.append(fieldError.getDefaultMessage()));
        }

        return JsonResult.fail(1001, sb.toString());
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值