springboot - 参数校验

1.前言

何为参数校验?

在我们写项目时,在写controller时,多多少少会写过类似这样的代码

@RequestMapping("/{studentId}")
public ResponseEntity<String> queryInfo(@PathVariable("studentId") String sudentId){
    
    

	if(!StringUtils.isEmpty(sudentId)){
    
    
		//....
	}
}

我们需要对前端传过来的数据进行校验,再进行业务操作…
或许你会想,校验就校验呗。但是如果我们需要验证的数据有很多呢,一个方法有十个,有100个这样的方法呢…就会导致我们在数据校验上浪费时间,显然我们并不希望这样。

在Controller层有时候需要对接口的输入参数进行校验,若是采用自身的校验逻辑代码来实现的话,会有一些弊端,一是会分散自己的注意力,不能让自己专心撰写业务逻辑代码;二是会让校验逻辑代码和业务逻辑代码产生耦合性,代码体积也比较臃肿。为了规避这种情况,我们可以采用Spring validation的Validated注解来完成接口参数校验的工作,下面举实例说明。

2.如何使用

2.1 添加maven依赖

 <!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <version>1.1.0.Final</version>
    </dependency>

2.2 在校验字段上添加校验注解

class Profile{
    
    
        @NotNull(message = "字段值不能为空")
        private String name;
        @NotNull
        private String sex;
        @Max(value = 20,message = "最大长度为20")
        private String address;
        @NotNull
        @Size(max=10,min=5,message = "字段长度要在5-10之间")
        private String fileName;
        @Pattern(regexp = "^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$",message = "不满足邮箱正则表达式")
        private String email;
        @AssertTrue(message = "字段为true才能通过")
        private boolean isSave;
        @Future(message = "时间在当前时间之后才可以通过")
        private Date date;
 
}

2.3 在Controller层使用@Validated进行验证

@RequestMapping("file/upload")
 public void upload(@RequestPart("files") MultipartFile files, @Validated Profile profile, Errors error) throws IOException {
    
    
       if(error.hasErrors()){
    
    
           return;
       }
       files.transferTo(new File(files.getOriginalFilename()));保存文件
}

也可以直接在controller中进行校验

  1. Controller开启Validated校验
@RestController
@RequestMapping("/test")
@Validated
public class TestController{
    
    ...}
  1. 方法上直接校验参数
@GetMapping(value = "/method/{type}")
    public ResponseModel test(
    	@Pattern(regexp = "[1,2]",message = "类型只能为1或2")@PathVariable(value = "type")String types,
		@NotBlank(message="内容不能为空")String content,
        @Length(max = 64,message = "长度最大为64")String title,
        @NotEmpty(message = "集合不能为空")List<String> ids,
        @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$",message = "手机号格式有误") @NotBlank String mobile,
        @Max(value = 100,message = "最大值为100") @Min(value = 1,message = "最小值为1")Integer intValue) {
    
    ...}

2.4 有哪些注解

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

2.5 分组校验

介绍一下这样的场景:在对用户的帐号密码进行编辑保存以及新增是两种不一样的情况。

编辑修改->保存:只需要验证username与password是否符合条件即可,不需要验证id(因为在数据库中id已经存在)。

新增->保存:新增需要验证username与password是否符合条件,还要验证id。

这时候就用到groups分组分情况对Bean属性变量进行验证,也可以满足多验证。具体的需要一下两个步骤

第一步:创建分组接口类

分组接口类只是普通的接口类并没有多大意义,只是用来标识这个属性哪种情况下被验证,这类似于java.io.Serializable

public interface addUser{
    
     

}

public interface editUser{
    
    

}

第二步:Controller方法参数中增加xxx.class接口

在对新增的用户进行ID验证,增加@Validated({addUser.class})接口类用来表示新增的User.getId()需要验证。

@Controller  
public class UserController {
    
      

  	@RequestMapping("/saveAdd")  
    public String saveAddUser(@Validated({
    
    addUser.class}) User user, BindingResult result) {
    
      

        if(result.hasErrors()) {
    
      
			return "error";  
        }  
        return "success";  
    }
}

第三步:Bean中添加groups分组

在User实体类中添加groups分组@NotEmpty(groups={addUser.class})与UserController中@Validated({addUser.class})对应,说明在执行saveAddUser新增用户的情况下,才对新增的用户id进行验证。

public class User {
    
       

    //在分组addUser时,验证id不能为空,其他情况下不做验证
	@NotEmpty(groups={
    
    addUser.class})
    private String id;

    @NotEmpty(message = "用户名不能为空")
    private String username;

    @Size(min=6 ,max= 20 ,message = "密码最少6位,最高20位")
    private String password;
    ......
}

以上三步就可以简单地完成分组验证,但是对分组验证补充一下三点:

  1. 不分配groups分组时,默认每次都需要验证
  2. 通过groups分组可以对同一个变量进行多个验证,如下代码
//对用户名进行两次不同情况的验证。
@NotEmpty(groups={
    
    First.class})
@Size(min=1,max=10,groups={
    
    Second.class})
public String username; 
  1. 默认的情况下,不同的分组约束验证是无序的,但是在有些情况下验证的相互约束很重要(比如前一个组验证失败,后面的将不再验证等情况),所以groups分组的验证也有前后验证顺序。使用@GroupSequence注解进行排序。
/*
 * 分组顺序接口类
 */
import javax.validation.GroupSequence;

//分组序列先Frist再Second
@GroupSequence({
    
    First.class,Second.class})
public interface Group{
    
    

}

@Controller  
public class UserController {
    
      

    @RequestMapping("/saveAdd")  
    public String saveAddUser(@Validated({
    
    Group.class}) User user, BindingResult result) {
    
      
        if(result.hasErrors()) {
    
      
            return "error";  
        }  
        return "success";  
    }

3.异常捕获

上面我们了解了如何进行数据校验,但是有一个问题,如果校验不通过,出现异常怎么办?我们想获取异常信息,然后告诉前端到底发生了怎么异常。

如何封装到一个JSON对象返回给前端呢?用一个案例来演示

  1. 添加测试Bean,测试类中定义几个字段,并且每个字段都做一定的限制
public class BeanValidation {
    
    
	@Size(min=6,max=10)
	private String field1;
	@Range(min=0,max=1)
	private Long field2;
	@AssertFalse
	private Boolean field3;
	public String getField1() {
    
    
		return field1;
	}
	public void setField1(String field1) {
    
    
		this.field1 = field1;
	}
	public Long getField2() {
    
    
		return field2;
	}
	public void setField2(Long field2) {
    
    
		this.field2 = field2;
	}
	public Boolean getField3() {
    
    
		return field3;
	}
	public void setField3(Boolean field3) {
    
    
		this.field3 = field3;
	}
	
}
  1. 添加测试接口,接口中使用@Validated注解对参数进行合法性检查,如果参数合法,返回原始数据
	@RequestMapping("globalexceptiontest")
	public Object globalExceptionTest(@Validated @RequestBody BeanValidation data)
	{
    
    
		ResultMsg resultMsg = new ResultMsg(ResultStatusCode.OK.getErrcode(), ResultStatusCode.OK.getErrmsg(), data);
		return resultMsg;
	}
  1. 如果未添加全局异常处理,将会使用默认的异常处理,返回结果,如下图

在这里插入图片描述返回的结果和自己的数据结构有很大的差异,对于前端处理返回结果也很麻烦

  1. 自定义参数异常返回的数据类ArgumentInvalidResult
public class ArgumentInvalidResult {
    
    
	private String field;
	private Object rejectedValue;
	private String defaultMessage;
 
	public String getField() {
    
    
		return field;
	}
	public void setField(String field) {
    
    
		this.field = field;
	}
	public Object getRejectedValue() {
    
    
		return rejectedValue;
	}
	public void setRejectedValue(Object rejectedValue) {
    
    
		this.rejectedValue = rejectedValue;
	}
	public String getDefaultMessage() {
    
    
		return defaultMessage;
	}
	public void setDefaultMessage(String defaultMessage) {
    
    
		this.defaultMessage = defaultMessage;
	}
}
  1. 添加全局异常处理类GlobalExceptionHandler
@ControllerAdvice
//如果返回的为json数据或其它对象,添加该注解
@ResponseBody
public class GlobalExceptionHandler {
    
    
	//添加全局异常处理流程,根据需要设置需要处理的异常,本文以MethodArgumentNotValidException为例
	@ExceptionHandler(value=MethodArgumentNotValidException.class)
	public Object MethodArgumentNotValidHandler(HttpServletRequest request,
			MethodArgumentNotValidException exception) throws Exception
	{
    
    
		//按需重新封装需要返回的错误信息
		List<ArgumentInvalidResult> invalidArguments = new ArrayList<>();
		//解析原错误信息,封装后返回,此处返回非法的字段名称,原始值,错误信息
		for (FieldError error : exception.getBindingResult().getFieldErrors()) {
    
    
			ArgumentInvalidResult invalidArgument = new ArgumentInvalidResult();
			invalidArgument.setDefaultMessage(error.getDefaultMessage());
			invalidArgument.setField(error.getField());
			invalidArgument.setRejectedValue(error.getRejectedValue());
			invalidArguments.add(invalidArgument);
		}
		
		ResultMsg resultMsg = new ResultMsg(ResultStatusCode.PARAMETER_ERROR.getErrcode(), ResultStatusCode.PARAMETER_ERROR.getErrmsg(), invalidArguments);
		return resultMsg;
	}
}
  1. 运行测试
  • 当参数合法

在这里插入图片描述

  • 当参数非法,返回与参数合法时有相同风格的错误信息

在这里插入图片描述

注意:

  1. 这里@ControllerAdvice注解标注,@ControllerAdvice是@Controller的增强版,一般与@ExceptionHandler搭配使用。

如果标注@Controller,异常处理只会在当前controller类中的方法起作用,但是使用@ControllerAdvice,则全局有效。

  1. @ExceptionHandler注解里面填写想要捕获的异常类class对象

猜你喜欢

转载自blog.csdn.net/saienenen/article/details/112756539