一、@Valid和@Validated的介绍
1.引入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. @Valid和@Validated的作用
@Validated、@Valid 等校验注解来替代手动对参数进行校验,简单来说就是将参数校验和业务逻辑代码分开。
3.@Valid和@Validated的区别
- @Valid:没有分组的功能。
- @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
- @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制
- @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
4.常用的参数校验注解
二、@Valid和@Validated的使用
1. 捕获全局异常配置类
因为使用注解检验参数会抛出异常,而这个异常是在方法中捕获不到的所以需要全局捕获异常,再根据异常类型提示相应的提示。
package com.fwy.corebasic.common.handlerexception;
import com.fwy.common.help.DoResult;
import com.fwy.common.help.DoResultType;
import com.fwy.common.utils.CommonResponse;
import com.fwy.common.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
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 org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author 前端接口和后台参数校验
*/
@ControllerAdvice
public class MyExceptionHandler {
public final static String[] INTERFACE_PATH_ARRAY = {"api","wxApi"};
@ResponseBody
@ExceptionHandler(value = {BindException.class,MethodArgumentNotValidException.class})
public Object validated(Exception e,HttpServletRequest request) {
LogUtil.warn("参数校验提示:{}",request.getRequestURL(), e);
List<FieldError> fieldErrors = null;
if (e instanceof BindException){
BindException e1=(BindException) e;
fieldErrors= e1.getBindingResult().getFieldErrors();
}else {
MethodArgumentNotValidException e2= (MethodArgumentNotValidException) e;
fieldErrors =e2.getBindingResult().getFieldErrors();
}
List<String> validationResults = new ArrayList<>();
for (FieldError fieldError : fieldErrors) {
validationResults.add(fieldError.getDefaultMessage());
}
String messages = StringUtils.join(validationResults.toArray(), ";");
return returnMessage(request,messages);
}
@ResponseBody
@ExceptionHandler(ConstraintViolationException.class)
public Object paramException(ConstraintViolationException e,HttpServletRequest request){
LogUtil.warn("参数校验提示:{}",e);
List<String> msgList = new ArrayList<>();
for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
msgList.add(constraintViolation.getMessage());
}
String messages = StringUtils.join(msgList.toArray(), ";");
return returnMessage(request,messages);
}
/**
* 违反约束异常处理
* @param e
* @return
*/
// @ExceptionHandler(ConstraintViolationException.class)
// @ResponseBody
// public Object handConstraintViolationException(ConstraintViolationException e) {
// LogUtil.error(e.getMessage(), e);
// return e;
// }
// @ExceptionHandler(AbstractHandlerMethodExceptionResolver.class)
// @ResponseBody
// public Object methodWayHandle(ExceptionHandlerExceptionResolver e,HttpServletRequest request){
// LogUtil.error("前端传入{}参数不符合接口格式",e.getApplicationContext());
// return returnMessage(request,"错误");
// }
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
@ResponseBody
public Object methodParamHand(MethodArgumentTypeMismatchException e, HttpServletRequest request){
LogUtil.error("前端传入{}参数不符合接口格式,提示信息{}",e.getName(),e.getMessage());
return returnMessage(request,"前端传入参数"+e.getName()+"不符合接口格式");
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Object handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
LogUtil.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
return returnMessage(request,"请求地址'"+requestURI+"',不支持'"+e.getMethod()+"'请求");
}
@ExceptionHandler(Exception.class)
@ResponseBody
public Object exceptionHand(Exception e, HttpServletRequest request, HttpServletResponse response) {
LogUtil.error("发生异常的请求地址{}",request.getRequestURL(),e);
return returnMessage(request,"系统异常");
}
public static Object returnMessage(HttpServletRequest request,String message){
String url = request.getRequestURI();
AtomicBoolean flag = new AtomicBoolean(false);
Arrays.stream(INTERFACE_PATH_ARRAY).forEach(path -> {
if (url.contains(path)) {
flag.set(true);
}
});
if(flag.get()){
DoResult result = new DoResult();
result.setStateMsg(message);
result.setStateType(DoResultType.fail);
return result;
}else {
CommonResponse result = new CommonResponse();
result.setMsg(message);
result.setCode(DoResultType.fail.getValue());
return result;
}
}
}
2.单个接口参数校验
/****
* description: 获取学习试题数据接口
* version: 1.0 ->
* date: 2022/5/17 11:45
* author: xiaYZ
* iteration: 迭代说明
* @param deptId 部门id
* @param weiXinUserId 微信用户id
* @param businessId 业务id
* @return com.fwy.common.help.DoResult
*/
@GetMapping("getStudyQuestionPaper")
@Validated
public DoResult getStudyQuestionPaper(@NotNull(message = "部门id不能为空") Long deptId,
@NotNull(message = "微信用户id不能空") Long weiXinUserId,
@NotNull(message = "业务id不能为空") Long businessId){
DoResult result = new DoResult();
return result;
}
如图展示,当业务id为空是提示参数校验信息
3.接口类参数校验提示
/***
* description: saveStageQuestion
* version: 1.0
* date: 2020/4/24 17:58
* author: objcat
* @param questionBank
* @return com.jrwp.common.help.CommonResponse
*/
@ResponseBody
@RequestMapping("saveQuestion")
@Description("保存题库数据")
public CommonResponse saveQuestion(@Validated QuestionBank questionBank){
CommonResponse response = new CommonResponse();
return response;
}
@TableName(value ="QUESTION_BANK")
@KeySequence(value = "SEQ_QUESTION_BANK")
@Data
public class QuestionBank implements Serializable {
public static final String REDIS_KEY = "QUESTION_BANK";
/**
* 主键
*/
@TableId
private Long id;
/**
* 知识体系id(数据字典中)
*/
@TableField(value = "KNOWLEDGE_ID")
private Long knowledgeId;
/**
* 目类型(0单选题,1多选题)
*/
@TableField(value = "QUESTION_TYPE")
private Integer questionType;
/**
* 是否启用(0否,1是)
*/
@TableField(value = "IS_START")
private Integer isStart;
/**
* 题目标题
*/
@TableField(value = "TITLE")
@Length(max = 500,message = "题目标题长度不能超过500")
private String title;
/**
* 题目答案
*/
@TableField(value = "ANSWER")
@Length(max = 20,message = "题目答案长度不能超过20")
private String answer;
/**
* 说明
*/
@TableField(value = "REMARKS")
@Length(max = 500,message = "说明长度不能超过500")
private String remarks;
/**
* 题目选项用|分割
*/
@TableField(value = "OPTIONS")
@Length(max = 500,message = "题目选项长度不能超过500")
private String options;
/**
* 创建时间
*/
@TableField(value = "CREATE_TIME")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8",locale = "zh")
private Date createTime;
/**
* 知识体系名称
*/
@TableField(value = "KNOWLEDGE_NAME")
private String knowledgeName;
/**
* 题目图片路径
*/
@TableField(value = "PhOTO_URL")
private String photoUrl;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
如图提示:当数据字段长度超过注解中注释的则会提示信息
4.嵌套类注解提示
当我们需要校验嵌套类时,如A包含B,C两个类(A:{B,C}),需要检验B中b字段。
4.1. 实体类数据
@Data
public class SubmitStudyParam {
/**
* 学习记录
*/
@Valid
@NotNull(message = "学习记录不能为空")
private StudyRecordApi studyRecordApi;
/**
* 业务名称
*/
@NotEmpty(message = "业务名称不能为空")
private String businessName;
/**
* 部门id
*/
@NotNull(message = "部门id不能为空")
private Long deptId;
/**
* 部门名称
*/
@NotEmpty(message = "部门名称不能为空")
private String deptName;
/**
* 当前刷题id
*/
private Long nowQuestionId;
}
@Data
public class StudyRecordApi implements Serializable {
/**
* 主键
*/
private Long id;
/**
* 注册用户id
*/
@NotNull(message = "注册用户id不能为空")
private Long registerUserId;
/**
* 注册用户姓名
*/
@NotEmpty(message = "注册用户姓名不能为空")
private String registerUserName;
/**
* 学习类型(0刷题学习,1文档学习,2视频学习)
*/
@NotNull(message = "学习类型不能为空")
private Integer type;
/**
* 事件id,不同类型对应不同表,0时对应刷题学习表,1,2时对应文档表
*/
private Long eventId;
/**
* 学习开始时间
*/
private Date startTime;
/**
* 开始学习时间字符串
*/
@NotEmpty(message = "开始学习时间不能为空")
private String startTimeStr;
/**
* 学习结束时间
*/
private Date endTime;
/**
* 学习结束时间字符串
*/
@NotEmpty(message = "学习结束时间不能为空")
private String endTimeStr;
/**
* 创建时间
*/
private Date createTime;
/**
* 申请记录id
*/
@NotNull(message = "申请记录id不能为空")
private Long applyId;
private static final long serialVersionUID = 1L;
}
4.2.接口校验
/****
* description: 提交文档学习
* version: 1.0 ->
* date: 2022/5/18 11:50
* author: xiaYZ
* iteration: 迭代说明
* @param submitStudyParam
* @return com.fwy.common.help.DoResult
*/
@ResponseBody
@PostMapping("submitStudyFile")
public DoResult submitStudyFile(@Validated @RequestBody SubmitStudyParam submitStudyParam){
DoResult result = new DoResult();
boolean flag = studyService.submitStudyFile(submitStudyParam);
if(flag){
result.setStateMsg("提交文档学习成功");
result.setStateType(DoResultType.success);
}else{
result.setStateMsg("提交文档学习失败");
result.setStateType(DoResultType.fail);
}
return result;
}
4.3. 接口调用
5. 分组校验
5.1 校验的实体类数据
@Data
public class SubmitStudyParam {
/**
* 练习题目
*/
@NotNull(message = "练习题目不能为空",groups = {StudyService.class})
@Valid
private PracticePaper practicePaper;
/**
* 学习记录
*/
@Valid
@NotNull(message = "学习记录不能为空")
private StudyRecordApi studyRecordApi;
/**
* 业务名称
*/
@NotEmpty(message = "业务名称不能为空")
private String businessName;
/**
* 部门id
*/
@NotNull(message = "部门id不能为空")
private Long deptId;
/**
* 部门名称
*/
@NotEmpty(message = "部门名称不能为空")
private String deptName;
/**
* 当前刷题id
*/
private Long nowQuestionId;
}
注意:PracticePaper类注解中有一个group参数
5.2 接口校验
/****
* description: 提交文档学习
* version: 1.0 ->
* date: 2022/5/18 11:50
* author: xiaYZ
* iteration: 迭代说明
* @param submitStudyParam
* @return com.fwy.common.help.DoResult
*/
@ResponseBody
@PostMapping("submitStudyFile")
public DoResult submitStudyFile(@Validated @RequestBody SubmitStudyParam submitStudyParam){
DoResult result = new DoResult();
boolean flag = studyService.submitStudyFile(submitStudyParam);
if(flag){
result.setStateMsg("提交文档学习成功");
result.setStateType(DoResultType.success);
}else{
result.setStateMsg("提交文档学习失败");
result.setStateType(DoResultType.fail);
}
return result;
}
/****
* description: 提交学习刷题试卷接口
* version: 1.0 ->
* date: 2022/5/18 10:26
* author: xiaYZ
* iteration: 迭代说明
* @param submitStudyParam 提交学习刷题试卷参数
* @return com.fwy.common.help.DoResult
*/
@ResponseBody
@PostMapping("submitStudyPaper")
public DoResult submitStudyPaper(@Validated({StudyService.class}) @RequestBody SubmitStudyParam submitStudyParam){
DoResult result = new DoResult();
boolean flag = studyService.submitStudyPaper(submitStudyParam);
if(flag){
result.setStateMsg("提交学习试卷成功");
result.setStateType(DoResultType.success);
}else{
result.setStateMsg("提交学习试卷失败");
result.setStateType(DoResultType.fail);
}
return result;
}
两个接口都使用了SubmitStudyParam 作为接口参数,但是“提交学习刷题试卷接口”,我需要它校验“练习题目类”是否为空,但是“提交文档学习接口”则不需要,所以我使用了@Validated中的group参数
5.3 接口调用
1.提交学习刷题试卷接口演示
2.提交文档学习接口演示
6.分组校验和嵌套校验不能一起使用
换句话说当你使用@Valid做嵌套检验是,再使用group参数做分组校验时,它不能校验@Valid注解类中的字段
刷题学习类的校验提示
@Data
public class PracticePaper implements Serializable {
/**
* 主键
*/
private Long id;
/**
* 注册用户id
*/
@NotNull(message = "注册用户id不能为空")
private Long registerUserId;
/**
* 题目id列表
*/
@NotEmpty(message = "题目id列表不能为空")
private String questionIdList;
/**
* 提交答案列表
*/
@NotEmpty(message = "提交答案列表不能为空")
private String submitAnswerList;
/**
* 实际答案列表
*/
@NotEmpty(message = "实际答案列表不能为空")
private String answerList;
/**
* 创建时间
*/
private Date createTime;
private static final long serialVersionUID = 1L;
}
解释:如图我这里的开始学习时间字段为空,但是提交接口后没有为空的提示,直接报错了