本文示例代码基于Spring Boot 2.2.6
、JDK1.8
。Spring Boot
已经内置了所需参数校验的框架。代码中使用了lombok
注解。
本文涉及的代码地址:https://gitee.com/qiwan/params-validated-demo.git
1.基本类型参数入参校验及校验异常统一处理接口返回数据:
package com.qiwan.validated.controller;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.qiwan.validated.entity.User;
import com.qiwan.validated.result.Result;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Validated
@RestController
@RequestMapping("/user")
public class UserController {
/**
* @Description: 根据姓名查询用户接口
* @param name 前端入参查询参数
* @return Result
*/
@GetMapping
public Result queryUser(
@NotBlank(message = "姓名不能为空!")
@Length(min = 2, max = 10, message = "姓名的长度只能在{min}到{max}之间!")
String name) {
User user = new User();
user.setName(name);
user.setAge(18);
user.setPhone("13111111111");
return new Result().success("查询用户数据成功!", user);
}
打开Postman
使用Get
请求http://localhost:8080/user
接口,在参数name
为空的情况下,结果如下图:
在Controller
类上面使用了@Validated
注解,虽然参数校验成功了,但是这种返回格式不利于接口调用者处理返回值,下面使用@RestControllerAdvice
注解对返回数据进行统一处理。
新建ValidatedExceptionHandler
类(类名随便定义),在类上添加@RestControllerAdvice
注解,用于拦截Controller
中的参数校验异常
并处理。代码如下:
package com.qiwan.validated.exception;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.qiwan.validated.result.Result;
import lombok.extern.slf4j.Slf4j;
/**
* @Description: 入参校验全局异常统一处理
* @author: Qiwan
* @date: 2020年5月4日 下午4:37:24
*/
@Slf4j
@RestControllerAdvice
public class ValidatedExceptionHandler {
/**
* @Description: 基本类型参数入参校验异常返回数据处理
* @param e
* @return Result
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result basicTypeParamsCheckException(ConstraintViolationException e) {
log.info("============基本类型参数入参校验异常============");
List<String> defaultMsg = e.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
return new Result().failure(defaultMsg.get(0));
}
}
注意:@ExceptionHandler(ConstraintViolationException.class)
注解,基本类型参数校验异常
处理时使用的是ConstraintViolationException.class
。
打开Postman
使用Get
请求http://localhost:8080/user
接口,在参数name
为空的情况下,结果如下图:
在name
符合验证规则的情况下,结果如下图:
接口返回这种数据的话那接口的调用者就非常好处理了。
2.实体类型参数入参校验及校验异常统一处理接口返回数据:
在上面的UserController
中添加如下方法,
/**
* @Description: 添加用户接口
* @param user 前端入参待添加数据
* @return Result
*/
@PostMapping
public Result addUser(@Validated User user) {
log.info("==========进入添加用户接口==========");
return new Result().success("添加用户成功!");
}
注意:除了Controller
类上使用了@Validated
注解,实体参数前面也要使用@Validated
注解。同样的,我们需要对校验异常进行统一处理,否则不利于接口的调用者使用接口的返回数据。
在上面的ValidatedExceptionHandler
类中添加如下代码:
/**
* @Description: 实体类型参数入参校验异常返回数据处理
* @param e
* @return Result
*/
@ExceptionHandler(BindException.class)
public Result entityTypeParamsCheckException(BindException e) {
log.info("============实体类型参数入参校验异常============");
return new Result().failure(e.getBindingResult().getFieldError().getDefaultMessage());
}
注意:@ExceptionHandler(BindException.class)
注解,实体类型参数校验异常
处理时使用的是BindException.class
。
打开Postman
使用Post
请求http://localhost:8080/user
接口,在参数user
为空的情况下,结果如下图:
在入参为空的情况下,成功进行了校验并返回标准的JSON
数据。在设置了完整参数的情况下,返回结果如下图:
至此,接口统一入参校验功能基本完成。为什么说基本呢?因为上面对实体类型参数
进行校验时,如果入参为空的话,我们使用Postman
多测几次就会发现,接口返回JSON
里的字段校验提示信息的顺序是不固定的。下面我们就来固定一下实体中字段验证的先后顺序。
3.固定字段校验的先后顺序:
自定义一些表示顺序的接口,如:
One.java
public interface One {}
Two.java
public interface Two {}
等等,够用就行了。
然后自定义UserCheckSequence.java
接口,名字随便起,代码如下:
package com.qiwan.validated.order;
import javax.validation.GroupSequence;
@GroupSequence({ One.class, Two.class, Three.class, Four.class, Five.class, Six.class })
public interface UserCheckSequence {
}
在@GroupSequence
注解中添加校验的顺序,然后在User.java
中给需要校验的字段指定校验顺序,代码如下:
package com.qiwan.validated.entity;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.Length;
import com.qiwan.validated.order.Eight;
import com.qiwan.validated.order.Five;
import com.qiwan.validated.order.Four;
import com.qiwan.validated.order.One;
import com.qiwan.validated.order.Seven;
import com.qiwan.validated.order.Six;
import com.qiwan.validated.order.Three;
import com.qiwan.validated.order.Two;
import lombok.Data;
@Data
public class User {
@NotBlank(message = "姓名不能为空!", groups = One.class)
@Length(min = 2, max = 20, message = "姓名的长度只能在{min}到{max}之间!", groups = Two.class)
private String name;
@NotNull(message = "年龄不能为空!", groups = Three.class)
@DecimalMin(message = "年龄不能小于{value}岁!", value = "16", groups = Four.class)
@DecimalMax(message = "年龄不能大于{value}岁!", value = "100", groups = Five.class)
private Integer age;
@NotBlank(message = "手机号码不能为空!", groups = Six.class)
@Length(min = 11, max = 11, message = "手机号码长度只能为{max}位!", groups = Seven.class)
@Pattern(regexp ="^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号码格式有误!", groups = Eight.class)
private String phone;
}
然后再把Controller
的方法中,参数User
前面的@Validated
注解修改为:@Validated(UserCheckSequence.class)
,代码如下:
@PostMapping
public Result addUser(@Validated(UserCheckSequence.class) User user) {
log.info("==========进入添加用户接口==========");
return new Result().success("添加用户成功!");
}
最后再使用Postman
对实体入参的接口进行测试后发现,字段的校验顺序已经按我们指定的顺序固定了。
补充:
Result.java
package com.qiwan.validated.result;
import java.io.Serializable;
import lombok.Data;
/**
* @Description: 用于向页面传递信息的类
* @author: Qiwan
* @date: 2020年5月3日 上午2:29:18
*/
@Data
public class Result implements Serializable {
private static final long serialVersionUID = 3166337785106881468L;
private int status;
private String msg;
private Object data;
public static String MSG_OK = "成功";
public static String MSG_FAIL = "失败";
public static int STATUS_OK = 1;
public static int STATUS_FAIL = 0;
public Result() {}
public Result(int status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public Result success(String msg) {
this.status= STATUS_OK;
this.msg= msg;
return this;
}
public Result success(String msg, Object data) {
this.status= STATUS_OK;
this.msg= msg;
this.data = data;
return this;
}
public Result failure(String msg) {
this.status= STATUS_FAIL;
this.msg= msg;
return this;
}
public Result failure(String msg,Object data) {
this.status= STATUS_FAIL;
this.msg= msg;
this.data = data;
return this;
}
public Result failure(int status,String msg) {
this.status= status;
this.msg= msg;
return this;
}
}