当前位置 博文首页 > 竹根七:如何优雅的做参数校验-JSR330

    竹根七:如何优雅的做参数校验-JSR330

    作者:竹根七 时间:2021-06-04 18:31

    前言:
    
    本文不是讲@Validate、@Valid是如何使用的、区别是什么,想看这些内容的请换篇文章。
    
    背景:
    
    当前端传过来的参数是进行对称性加密、base64加密等处理后过的参数时,在controller接口使用@Validae、@Valid 注解是没用的。
    接口收到加密参数后,第一步应该是解密,然后再转换为实际的参数。那么判断参数字段是否符合条件又要写代码去判断了,不够优雅。
    那么我是怎么做的呢?看下文...
    

    假设我们现在有一个登录接口,大概是下面这样↓↓↓↓↓↓↓↓↓↓↓

    原本的登录逻辑

    @PostMapping(value = "/login")
    public R login(@RequestParam(value = "param") String param){
    
        // 1:将参数解密出来拿到解密后的字符串(对称性解密)
        String realParam = rsa.decryptStr(encryptParamsVO.getParams(), KeyType.PrivateKey, StandardCharsets.UTF_8);
    
        // 2:将解密后的字符串转换为dto
        LoginDto dto = JSON.parseObject(realParam , UserDto.class);
    
        // 3:靠代码进行字段规则判断
        if(StringUtils.isBlank(dto.getUsername)){
            throw new ServiceException("username不能为空");
        }
        if(StringUtils.isBlank(dto.getPassword)){
            throw new ServiceException("password不能为空");
        }
    
        // 4:校验通过后登陆
        userService.login(dto);
    
        return R.success();
    }
    

    可以看到第三步有这明显的缺陷,如果 LoginDto 参数一多,那么就要写很多 if 语句代码来进行判断,这样势必是不优雅的写法。

    参数类

    @Data
    public class LoginDto {
    
        @NotBlank(message = "用户名不能为空")
        @Size(min = 6, max = 16, message = "用户名格式不正确")
        private String username;
    
        @NotBlank(message = "密码不能为空")
        @Size(min = 8, max = 16, message = "密码格式不正确")
        private String password;
    }
    
    

    这个类其实才是接口中实际用到的参数类。而它本身的字段其实是用了@NotNull、@Size注解修饰过的,只不过没有起到作用。
    那么参数在解密后转换为 LoginDto 后如何让这些注解起到原本的作用呢?

    自己写个工具类

    @Slf4j
    public class JSRValidatorUtil {
    
        private final static Validator VALIDATOR = Validation.byProvider(HibernateValidator.class)
                .configure()
                .buildValidatorFactory().getValidator();
    
        public static <T> void validate(T param) {
            Set<ConstraintViolation<T>> validate = VALIDATOR.validate(param);
            validate.forEach(v -> {
                log.error("JSR校验异常,property:{},message:{}", v.getPropertyPath(), v.getMessage());
                throw new ServiceException(v.getMessage());
            });
        }
    }
    

    看看测试效果:

        @Test
        public void validateTest() {
            LoginDto loginDto = new LoginDto();
            loginDto.setUsername("123456");
            loginDto.setPassword("123456");
    
            JSRValidatorUtil.validate(loginDto);
        }
    
    drawing

    利用Spring自带的Validator、SpringValidatorAdapter

        @Resource
        private SpringValidatorAdapter adapter;
        @Resource
        private Validator validator;
    
        @Test
        public void validate() {
            LoginDto loginDto = new LoginDto();
            loginDto.setUsername("123456");
            loginDto.setPassword("123456");
    
            Set<ConstraintViolation<LoginDto>> validateResult = adapter.validate(loginDto);
            //Set<ConstraintViolation<LoginDto>> validateResult = validator.validate(loginDto);
    
            validateResult.forEach(v -> {
                logger.error("JSR校验异常,property:{},message:{}", v.getPropertyPath(), v.getMessage());
                throw new ServiceException(v.getMessage());
            });
        }
    
    
    drawing

    自定义符合JSR330规范的注解

    很多场景下需要自定义注解实现的,比如字段属性为 性别、密码、身份证 等等。

    自定义一个密码格式校验注解:

    @Constraint(validatedBy = PasswordValidation.class)
    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Password {
    
        String message() default "密码必须包含大小写英文字符、数字、特殊字符";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
    }
    

    规则实现类:

    public class PasswordValidation implements ConstraintValidator<Password, String> {
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            boolean haveDigit = false;
            boolean uppercase = false;
            boolean lowercase = false;
            boolean special = false;
    
            char v;
            for (int i = 0; i < value.length(); i++) {
                v = value.charAt(i);
                if (Character.isDigit(v))
                    haveDigit = true;
                else if (Character.isUpperCase(v))
                    uppercase = true;
                else if (Character.isLowerCase(v))
                    lowercase = true;
            }
            if (Pattern.compile("[ _`~!@#$%^&*()+=|{}':;,\\[\\].<>/?!¥…()—【】‘;:”“’。,、?]|\n|\r|\t").matcher(value).find())
                special = true;
    
            return haveDigit && uppercase && lowercase && special;
        }
    }
    

    使用@Password 注解修饰

        @Password
        private String password;
    

    看效果:
    drawing

    bk