SpringCloud里参数校验/参数验证

共 5148字,需浏览 11分钟

 ·

2020-10-07 17:00

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

66套java从入门到精通实战课程分享

1、前言

在控制器类的方法里自己写校验逻辑代码当然也可以,只是代码比较丑陋,有点“low”。业界有更好的处理方法,分别阐述如下。

2、PathVariable校验

@GetMapping("/path/{group:[a-zA-Z0-9_]+}/{userid}")
@ResponseBody
public String path(@PathVariable("group") String group, @PathVariable("userid") Integer userid) {
    return group + ":" + userid;
}

用法是:路径变量:正则表达式。当请求URI不满足正则表达式时,客户端将收到404错误码。不方便的地方是,不能通过捕获异常的方式,向前端返回统一的、自定义格式的响应参数。

3、方法参数校验

@GetMapping("/validate1")
@ResponseBody
public String validate1(
        @Size(min = 1,max = 10,message = "姓名长度必须为1到10")@RequestParam("name") String name,
        @Min(value = 10,message = "年龄最小为10")@Max(value = 100,message = "年龄最大为100") @RequestParam("age") Integer age) {
    return "validate1";
}


如果前端传递的参数不满足规则,则抛出异常。注解Size、Min、Max来自validation-api.jar,更多注解参见相关标准小节。

4、表单对象/VO对象校验

当参数是VO时,可以在VO类的属性上添加校验注解。

public class User {
    @Size(min = 1,max = 10,message = "姓名长度必须为1到10")
    private String name;
 
    @NotEmpty
    private String firstName;
 
    @Min(value = 10,message = "年龄最小为10")@Max(value = 100,message = "年龄最大为100")
    private Integer age;
 
    @Future
    @JSONField(format="yyyy-MM-dd HH:mm:ss")
    private Date birth;
    。。。
}


其中,Future注解要求必须是相对当前时间来讲“未来的”某个时间。

@PostMapping("/validate2")
@ResponseBody
public User validate2(@Valid @RequestBody User user){
    return user;
}

5、自定义校验规则

5.1 自定义注解校验

需要自定义一个注解类和一个校验类。

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
 
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.FIELD})
@Constraint(validatedBy = FlagValidatorClass.class)
public @interface FlagValidator {
    // flag的有效值,多个使用,隔开
    String values();
 
    // flag无效时的提示内容
    String message() default "flag必须是预定义的那几个值,不能随便写";
 
    Class[] groups() default {};
 
    Class[] payload() default {};
}


import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
 
public class FlagValidatorClass implements ConstraintValidator {
    /**
     * FlagValidator注解规定的那些有效值
     */
    private String values;
 
    @Override
    public void initialize(FlagValidator flagValidator) {
        this.values = flagValidator.values();
    }
 
    /**
     * 用户输入的值,必须是FlagValidator注解规定的那些值其中之一。
     * 否则,校验不通过。
     * @param value 用户输入的值,如从前端传入的某个值
     */
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
        // 切割获取值
        String[] value_array = values.split(",");
        Boolean isFlag = false;
 
        for (int i = 0; i < value_array.length; i++){
            // 存在一致就跳出循环
            if (value_array[i] .equals(value)){
                isFlag = truebreak;
            }
        }
 
        return isFlag;
    }
}


使用我们自定义的注解:

public class User {
    // 前端传入的flag值必须是1或2或3,否则校验失败
    @FlagValidator(values = "1,2,3")
    private String flag ;
    。。。
}

5.2 分组校验

import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
 
public class Resume {
    public interface Default {
    }
 
    public interface Update {
    }
 
    @NotNull(message = "id不能为空", groups = Update.class)
    private Long id;
 
    @NotNull(message = "名字不能为空", groups = Default.class)
    @Length(min = 4, max = 10, message = "name 长度必须在 {min} - {max} 之间", groups = Default.class)
    private String name;
 
    @NotNull(message = "年龄不能为空", groups = Default.class)
    @Min(value = 18, message = "年龄不能小于18岁", groups = Default.class)
    private Integer age;
    。。。
}


    /**
     * 使用Defaul分组进行验证
     * @param resume
     * @return
     */
    @PostMapping("/validate5")
    public String addUser(@Validated(value = Resume.Default.class) @RequestBody Resume resume) {
        return "validate5";
    }
 
    /**
     * 使用Default、Update分组进行验证
     * @param resume
     * @return
     */
    @PutMapping("/validate6")
    public String updateUser(@Validated(value = {Resume.Update.class, Resume.Default.class}) @RequestBody Resume resume) {
        return "validate6";
    }


建立了两个分组,名称分别为Default、Update。POST方法提交时使用Defaut分组的校验规则,PUT方法提交时同时使用两个分组规则。

6、异常拦截器

通过设置全局异常处理器,统一向前端返回校验失败信息。

import com.scj.springbootdemo.WebResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
 
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
 
/**
 * 全局异常处理器
 */
@ControllerAdvice
public class GlobalExceptionHandler {
 
    private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
 
    /**
     * 用来处理bean validation异常
     * @param ex
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public  WebResult resolveConstraintViolationException(ConstraintViolationException ex){
        WebResult errorWebResult = new WebResult(WebResult.FAILED);
        Set> constraintViolations = ex.getConstraintViolations();
        if(!CollectionUtils.isEmpty(constraintViolations)){
            StringBuilder msgBuilder = new StringBuilder();
            for(ConstraintViolation constraintViolation :constraintViolations){
                msgBuilder.append(constraintViolation.getMessage()).append(",");
            }
            String errorMessage = msgBuilder.toString();
            if(errorMessage.length()>1){
                errorMessage = errorMessage.substring(0,errorMessage.length()-1);
            }
            errorWebResult.setInfo(errorMessage);
            return errorWebResult;
        }
        errorWebResult.setInfo(ex.getMessage());
        return errorWebResult;
    }
 
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public WebResult resolveMethodArgumentNotValidException(MethodArgumentNotValidException ex){
        WebResult errorWebResult = new WebResult(WebResult.FAILED);
        List  objectErrors = ex.getBindingResult().getAllErrors();
        if(!CollectionUtils.isEmpty(objectErrors)) {
            StringBuilder msgBuilder = new StringBuilder();
            for (ObjectError objectError : objectErrors) {
                msgBuilder.append(objectError.getDefaultMessage()).append(",");
            }
            String errorMessage = msgBuilder.toString();
            if (errorMessage.length() > 1) {
                errorMessage = errorMessage.substring(0, errorMessage.length() - 1);
            }
            errorWebResult.setInfo(errorMessage);
            return errorWebResult;
        }
        errorWebResult.setInfo(ex.getMessage());
        return errorWebResult;
    }
}
 

7、相关标准

JSR 303 是Bean验证的规范 ,Hibernate Validator 是该规范的参考实现,它除了实现规范要求的注解外,还额外实现了一些注解。
validation-api-1.1.0.jar 包括如下约束注解:

约束注解说明
@AssertFalse被注释的元素必须为 false
@AssertTrue被注释的元素必须为 true
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
@Null被注释的元素必须为 null
@NotNull被注释的元素必须不为 null
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)被注释的元素的大小必须在指定的范围内
@Past被注释的元素必须是一个过去的日期
@Future被注释的元素必须是一个将来的日期
@Pattern(value)被注释的元素必须符合指定的正则表达式

hibernate-validator-5.3.6.jar 包括如下约束注解:

约束注解说明
@Email被注释的元素必须是电子邮箱地址
@Length被注释的字符串的大小必须在指定的范围内
@NotBlank被注释的字符串的必须非空
@NotEmpty被注释的字符串、集合、Map、数组必须非空
@Range被注释的元素必须在合适的范围内
@SafeHtml被注释的元素必须是安全Html
@URL被注释的元素必须是有效URL

8、参数校验原理

这篇文章 写得比较深入,我没有太理解。

9、本文源码

公司不让上传源码到GitHub,可以参加这篇文章。

10、同时校验2个或更多个字段/参数

常见的场景之一是,查询某信息时要输入开始时间和结束时间。显然,结束时间要≥开始时间。可以在查询VO类上使用自定义注解,下面的例子来自这里。划重点:@ValidAddress使用在类上。

@ValidAddress
public class Address {
 
    @NotNull
    @Size(max = 50)
    private String street1;
 
    @Size(max = 50)
    private String street2;
 
    @NotNull
    @Size(max = 10)
    private String zipCode;
 
    @NotNull
    @Size(max = 20)
    private String city;
 
    @Valid
    @NotNull
    private Country country;
 
    // Getters and setters
}


public class Country {
 
    @NotNull
    @Size(min = 2, max = 2)
    private String iso2;
 
    // Getters and setters
}

@Documented
@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = { MultiCountryAddressValidator.class })
public @interface ValidAddress {
 
    String message() default "{com.example.validation.ValidAddress.message}";
 
    Class[] groups() default {};
 
    Class[] payload() default {};
}


public class MultiCountryAddressValidator 
       implements ConstraintValidator {
 
    public void initialize(ValidAddress constraintAnnotation) {
 
    }
 
    @Override
    public boolean isValid(Address address, 
                           ConstraintValidatorContext constraintValidatorContext) {
 
        Country country = address.getCountry();
        if (country == null || country.getIso2() == null || address.getZipCode() == null) {
            return true;
        }
 
        switch (country.getIso2()) {
            case "FR":
                return // Check if address.getZipCode() is valid for France
            case "GR":
                return // Check if address.getZipCode() is valid for Greece
            default:
                return true;
        }
    }
}




原文链接:

https://blog.csdn.net/jinjiankang/article/details/89711493





     



感谢点赞支持下哈 

浏览 18
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报