系列文章目录
一、@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.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.servlet.ModelAndView;
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;
@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 url = request.getRequestURI();
String messages = StringUtils.join(validationResults.toArray(), ";");
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(messages);
result.setStateType(DoResultType.fail);
return result;
}else{
CommonResponse result = new CommonResponse();
result.setMsg(messages);
result.setCode(DoResultType.fail.getValue());
return result;
}
}
@ResponseBody
@ExceptionHandler(ConstraintViolationException.class)
public Object paramException(ConstraintViolationException e,HttpServletRequest request){
LogUtil.warn("参数校验异常:{}",e);
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();
List<String> msgList = new ArrayList<>();
for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
msgList.add(constraintViolation.getMessage());
}
String messages = StringUtils.join(msgList.toArray(), ";");
result.setStateMsg(messages);
result.setStateType(DoResultType.fail);
return result;
}else{
CommonResponse result = new CommonResponse();
List<String> msgList = new ArrayList<>();
for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
msgList.add(constraintViolation.getMessage());
}
String messages = StringUtils.join(msgList.toArray(), ";");
result.setMsg(messages);
result.setCode(DoResultType.fail.getValue());
return result;
}
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public Object handConstraintViolationException(ConstraintViolationException e) {
LogUtil.error(e.getMessage(), e);
return e;
}
@ExceptionHandler(Exception.class)
public Object exceptionHand(Exception e, HttpServletRequest request, HttpServletResponse response) {
LogUtil.error("发生异常的请求地址{}",request.getRequestURL(),e);
if (request.getHeader("X-Requested-With") != null &&
"XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
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("系统异常");
result.setStateType(DoResultType.fail);
return result;
}else{
CommonResponse result = new CommonResponse();
result.setMsg("系统异常");
result.setCode(DoResultType.fail.getValue());
return result;
}
} else {
ModelAndView mav = new ModelAndView();
mav.addObject("msg", e.getMessage());
mav.addObject("code", response.getStatus());
mav.addObject("uri", request.getRequestURI());
mav.setViewName("error");
return mav;
}
}
}
2.单个接口参数校验
@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.接口类参数校验提示
@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;
@TableField(value = "KNOWLEDGE_ID")
private Long knowledgeId;
@TableField(value = "QUESTION_TYPE")
private Integer questionType;
@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;
@NotNull(message = "部门id不能为空")
private Long deptId;
@NotEmpty(message = "部门名称不能为空")
private String deptName;
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.接口校验
@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;
@NotNull(message = "部门id不能为空")
private Long deptId;
@NotEmpty(message = "部门名称不能为空")
private String deptName;
private Long nowQuestionId;
}
注意:PracticePaper类注解中有一个group参数
5.2 接口校验
@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;
}
@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;
@NotNull(message = "注册用户id不能为空")
private Long registerUserId;
@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;
}
解释:如图我这里的开始学习时间字段为空,但是提交接口后没有为空的提示,直接报错了
|