参数校验
项目地址
https://github.com/zhaobao1830/misszb
校验前端传过来的参数
安装插件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.1.0</version>
</dependency>
SpringBoot 2.3版本后,必须安装上面的插件,不然@Validated、@Max、@Min这些插件不能用
常用校验注解
@Validated:开启校验
1、在需要校验参数的Controller中加上该注解,Controller方法里属性参数的校验才会生效;
@RestController
@RequestMapping(value = "/banner")
@Validated
public class BannerController {
@RequestMapping(value = "/test/{id}", method = RequestMethod.POST)
public PersonDTO test(@PathVariable @Max(value = 10, message = "id不能大于10") Integer id,
@RequestParam String name
) {
System.out.println(id);
System.out.println(name);
PersonDTO dto = new PersonDTO();
if (name.equals("zb")) {
throw new ForbiddenException(10002);
}
return dto;
}
}
2、如果参数是对象形式,必须在前面加上@Validated,否则无法校验对象里的属性,(这时Controller上不用加@Validated)
@RestController
@RequestMapping(value = "/banner")
public class BannerController {
@RequestMapping(value = "/test", method = RequestMethod.POST)
public PersonDTO test(@RequestBody @Validated PersonDTO personDTO) {
PersonDTO dto = new PersonDTO();
if (personDTO.getPersonName().equals("zb")) {
throw new ForbiddenException(10002);
}
return dto;
}
}
@Valid:级联校验
当对象参数的类里,有其他对象的属性,就需要在级联的对象上添加@Valid,这样才能校验级联对象里的属性
@RestController
@RequestMapping(value = "/test")
@Validated
public class TestController {
@RequestMapping(value = "/test/{id}", method = RequestMethod.POST)
public PersonDTO test(@PathVariable @Max(value = 10, message = "id不能大于10") Integer id,
@RequestParam String name,
@RequestBody @Validated PersonDTO personDTO
) {
System.out.println(id);
System.out.println(name);
PersonDTO dto = new PersonDTO();
if (name.equals("zb")) {
throw new ForbiddenException(10002);
}
return dto;
}
}
PersonDTO.java
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@PasswordEqual(message = "用户的密码不一致")
public class PersonDTO {
@Length(min = 2, max = 10, message = "personName长度必须在2-10之间")
private String personName;
private Integer age;
private String password1;
private String password2;
@Valid
private SchoolDTO schoolDTO;
}
SchoolDTO.java
@Getter
@Setter
public class SchoolDTO {
@Length(min = 2)
private String schoolName;
}
@Length:定义字符串的长度
@Max和@Min:定义最大值和最小值
备注
通常,验证的参数多为基本类型,比如要求是数字类型,要求是正整数。但是如果提交的参数是一个对象,且对象的某个属性又是另外一个对象,要验证这个内嵌对象的属性,应该如何进行验证?
以上面的test请求为例,请求参数里有PersonDTO对象,PersonDTO对象里有个属性是SchoolDTO,也是对象
1、请求方法里,需要在PersonDTO前面加@RequestBody和@Validated注解
2、PersonDTO类里,在SchoolDTO属性上面加上@Valid注解(开启SchoolDTO类的校验)
3、SchoolDTO类里,在需要的属性上加校验注解
自定义校验注解
在做项目的时候,如果我们要校验复杂的逻辑,自带的校验注解无法满足,就需要自定义校验注解
以校验俩次输入的密码是否一样为例:
项目地址:https://github.com/zhaobao1830/misszb
一、新建注解PasswordEqual
PasswordEqual.java
package com.zb.misszb.dto.validators;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@Constraint(validatedBy = PasswordValidator.class )
public @interface PasswordEqual {
int min() default 4;
int max() default 6;
String message() default "passwords are not equal";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Documented是元注解,生成文档信息的时候保留注解,对类作辅助说明
@Retention作用是定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中.
从注释上看:
source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略
class:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
@Target:指明了修饰的这个注解的使用范围,即被描述的注解可以用在哪里
ElementType的取值包含以下几种:
TYPE:类,接口或者枚举
FIELD:域,包含枚举常量
METHOD:方法
PARAMETER:参数
CONSTRUCTOR:构造方法
LOCAL_VARIABLE:局部变量
ANNOTATION_TYPE:注解类型
PACKAGE:包
@Constraint:指定具体实现的是哪个类
PasswordValidator.java注解逻辑实现类
package com.zb.misszb.dto.validators;
import com.zb.misszb.dto.PersonDTO;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PasswordValidator implements ConstraintValidator<PasswordEqual, PersonDTO> {
private int min;
private int max;
@Override
public void initialize(PasswordEqual constraintAnnotation) {
this.min = constraintAnnotation.min();
this.max = constraintAnnotation.max();
}
@Override
public boolean isValid(PersonDTO personDTO, ConstraintValidatorContext constraintValidatorContext) {
String password1 = personDTO.getPassword1();
String password2 = personDTO.getPassword2();
boolean match = password1.equals(password2);
return match;
}
}
自定义的类要实现ConstraintValidator接口,需要传入俩个参数,第一个是自定义注解的名称,这里是PasswordEqual;第二个是自定义注解修饰的目标类型,即修饰哪个类就写哪个类,当前 定义的注解是用在PersonDTO类上,所以传PersonDTO;如果是修饰属性,就传入属性的类型,比如String
如果想获取注解里传入的参数,需要:一、定义参数变量(只能用基本类型(int这种),不能用包装类型(Integer));二、重写initialize方法, 通过方法里的constraintAnnotation获取参数
isValid方法:里面写注解的判断,要返回boolean
PersonDTO类中使用
package com.zb.misszb.dto;
import com.zb.misszb.dto.validators.PasswordEqual;
import lombok.*;
import org.hibernate.validator.constraints.Length;
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@PasswordEqual(message = "用户的密码不一致")
public class PersonDTO {
@Length(min = 2, max = 10, message = "personName长度必须在2-10之间")
private String personName;
private Integer age;
private String password1;
private String password2;
}
抽取message信息
参数校验里的message信息,我们可以抽取到公共文件ValidationMessages.properties里(这个文件的名称是固定的,必须写成这样,放在resources文件夹下)
ValidationMessages.properties
id.positive = id必须是正整数
token.password = password不符合规范:当前值是${validatedValue};最大值应该是{max},最小值应该是{min}
如果参数校验注解里有max、min属性,可以按照上面的写法展示
方法里使用
用{}
引入
@RequestMapping(value = "/id/{id}/simplify", method = RequestMethod.GET)
public SpuSimplifyVO getSimplifySpu(@PathVariable @Positive(message = "{id.positive}") Long id) {
Spu spu = spuService.getSpuDetailById(id);
SpuSimplifyVO spuSimplifyVO = new SpuSimplifyVO();
BeanUtils.copyProperties(spu, spuSimplifyVO);
return spuSimplifyVO;
}
测试效果:
http://localhost:8082/v1/spu/id/-1/simplify
{
"code": 10001,
"message": "getSimplifySpu.id: id必须是正整数",
"request": "GET /v1/spu/id/-1/simplify"
}
备注
如果觉得message里的提示信息不好,可以在全局异常文件GlobalExceptionAdvice)里对
handleConstraintException方法中的messgae进行处理
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(code= HttpStatus.BAD_REQUEST)
@ResponseBody
public UnifyResponse handleConstraintException(HttpServletRequest req, ConstraintViolationException e){
// 下面是对返回的message进行处理,如果觉得默认的message信息也能看懂,就没必要处理
// 这只是个简单例子,可以根据需求自己修改
String requestUrl = req.getRequestURI();
StringBuilder errorMsg = new StringBuilder();
for (ConstraintViolation error : e.getConstraintViolations()) {
String msg = e.getMessage();
String m = error.getPropertyPath().toString();
String name = m.split("[.]")[1];
errorMsg.append(name).append(" ").append(msg).append(";");
}
String method = req.getMethod();
return new UnifyResponse(10001, errorMsg.toString(), method + " " + requestUrl);
}
注意事项
BindException是@Validation单独使用校验失败时产生的异常;
MethodArgumentNotValidException是@RequestBody和@Validated配合时产生的异常;
BindingResult对于上述两种异常都可以捕获
private Map<String, String> getErrors(BindingResult result) {
Map<String, String> map = new HashMap<>();
List<FieldError> errorList = result.getFieldErrors();
for (FieldError error : errorList) {
// 发生验证错误所对应的某一个属性
String errorField = error.getField();
// 验证错误的信息
String errorMsg = error.getDefaultMessage();
map.put(errorField, errorMsg);
}
return map;
}
Bean Validation 中内置的 constraint
@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=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator 附加的 constraint
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内