1. validate介绍
1.1 前言
前端录入数据,在后台进行校验工作是必不可少的。例如:为空校验,邮箱格式校验,手机号校验等。
后端校验的目的是避免用户绕过浏览器,使用http工具直接向后端请求一些违法数据,服务端的数据校验也是必要的,可以防止脏数据落到数据库中,如果数据库中出现一个非法的邮箱格式,也会让运维人员头疼不已。
下面介绍spring 框架中的自动校验机制。
1.2 框架提供的校验注解
注解 | 说明 |
---|---|
@NotNull | 值不能为空 |
@Null | 值必须为空 |
@Pattern(regexp=) | 字符串必须匹配正则表达式 |
@Size(min=,max=) | 集合的元素数量必须在min和max之间 |
@CreditCardNumber(ignoreNonDigitCharacters) | 字符串必须是信用卡号(按美国标准校验) |
字符串必须是Email邮箱 | |
@Length(min=,max=) | 检查字符串的长度 |
@NotBlank | 字符串必须有字符 |
@NotEmpty | 字符串不为null,集合有元素 |
@Range(min=,max=) | 数字必须大于等于min,小于等于max |
@SafeHtml | 字符串是安全的html |
@URL | 字符串是合法的URL |
@AssertFalse | 值必须是false |
@AssertTrue | 值必须是true |
@DecimalMax(value=,inclusive=) | 值必须小于等于(inclusive=true)/小于(inclusive=false) value属性指定的值,可以注解在字符串类型的属性上 |
@DecimalMin(value=,inclusive=) | 值必须大于等于(inclusive=true)/大于(inclusive=false) value属性指定的值,可以注解在字符串类型的属性上 |
@Digits(integer=,fraction=) | 数字格式检查,integer指定整数部分的最大长度,fraction指定小数部分的最大长度 |
@Future | 值必须是未来的日期 |
@Past | 值必须是过去的日期 |
@Max(value=) | 值必须小于等于value指定的值,不能注解在字符串类型的属性上 |
@Min(value=) | 值必须大于等于value指定的值,不能注解在字符串类型的属性上 |
1.3 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
<!--因为lombok ,让代码更简洁-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>

1.4 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User implements Serializable {
private static final long serialVersionUID = -8441401775719174836L;
private Integer id;
private String userName;
@NotBlank(message = "密码不能为空")
private String password;
@Past(message = "生日必须是过去的时间")
private Date birthDay;
}
1.5 控制层
import javax.validation.Valid;
import org.springframework.validation.BindingResult;
/**
* 说明:
* 添加 BindingResult参数后,就算验证不通过,也能继续执行方法体
* BindingResult的参数的作用:
* 方便记录日志(如果不想继续向下执行,可以直接再抛异常)
* @param user
* @param errors
* @return
*/
@PostMapping()
@JsonView(User.UserSimpleView.class)
public Object create(@Valid @RequestBody User user, BindingResult result){
// 判断BindingResult是否保存错误的验证信息,如果有,则直接return
if (result.hasErrors()) {
Map<String, String> errors = Maps.newHashMap();
result.getFieldErrors().stream().forEach(f -> errors.put(f.getField(), f.getDefaultMessage()));
return errors;
}
//对 user的处理,然后返回
return user;
}
2 Hibernate-validate工具类,手动调用校验返回结果
2.1 添加 hibernate-validate依赖
<!--
如果引入了spring-boot-starter-web就可以不用引用下面的依赖了
-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
</dependency>
2.2 接收处理结果,以及输出格式化的一个实体类
import org.apache.commons.lang3.StringUtils;
import java.text.MessageFormat;
import java.util.Map;
public class ValidationResult {
/**
* 是否有异常
*/
private boolean hasErrors;
/**
* 异常消息记录
*/
private Map<String, String> errorMsg;
/**
* 获取异常消息组装
*
* @return
*/
public String getMessage() {
if (errorMsg == null || errorMsg.isEmpty()) {
return StringUtils.EMPTY;
}
StringBuilder message = new StringBuilder();
errorMsg.forEach((key, value) -> {
message.append(MessageFormat.format("{0}:{1} \r\n", key, value));
});
return message.toString();
}
public boolean hasErrors() {
return hasErrors;
}
public boolean isHasErrors() {
return hasErrors;
}
public void setHasErrors(boolean hasErrors) {
this.hasErrors = hasErrors;
}
public Map<String, String> getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(Map<String, String> errorMsg) {
this.errorMsg = errorMsg;
}
@Override
public String toString() {
return "ValidationResult{" +
"hasErrors=" + hasErrors +
", errorMsg=" + errorMsg +
'}';
}
}
2.3 创建工具类,提供公共方法校验,返回结果
import org.apache.commons.collections.CollectionUtils;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class ValidateUtil {
private ValidateUtil() {
}
/**
* 验证器
*/
private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
/**
* 校验实体,返回实体所有属性的校验结果
*
* @param obj
* @param <T>
* @return
*/
public static <T> ValidationResult validateEntity(T obj) {
//解析校验结果
Set<ConstraintViolation<T>> validateSet = validator.validate(obj, Default.class);
return buildValidationResult(validateSet);
}
/**
* 校验指定实体的指定属性是否存在异常
*
* @param obj
* @param propertyName
* @param <T>
* @return
*/
public static <T> ValidationResult validateProperty(T obj, String propertyName) {
Set<ConstraintViolation<T>> validateSet = validator.validateProperty(obj, propertyName, Default.class);
return buildValidationResult(validateSet);
}
/**
* 将异常结果封装返回
*
* @param validateSet
* @param <T>
* @return
*/
private static <T> ValidationResult buildValidationResult(Set<ConstraintViolation<T>> validateSet) {
ValidationResult validationResult = new ValidationResult();
if (CollectionUtils.isNotEmpty(validateSet)) {
validationResult.setHasErrors(true);
Map<String, String> errorMsgMap = new HashMap<>();
for (ConstraintViolation<T> constraintViolation : validateSet) {
errorMsgMap.put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage());
}
validationResult.setErrorMsg(errorMsgMap);
}
return validationResult;
}
}
2.4 使用示例
@PostMapping("/add2")
public R<Boolean> add(@RequestBody MyTest myTest) throws Exception {
ValidationResult validationResult = ValidateUtil.validateEntity(myTest);
if (validationResult.hasErrors()) {
return R.error(validationResult.getMessage());
}
return R.ok(true);
}
3 自定义校验注解
自定义注解用途:校验字段的在数据库里的唯一性等
3.1 定义注解类
**
* 自定义校验注解
* 类似与 @NotEmpty
*
* @Constraint(validatedBy = MyConstraintValidator.class) 指明这是一个校验的注解
* validatedBy = MyConstraintValidator.class 指定由MyConstraintValidator类去校验
*
* 必须定义三个成员 message,groups,payload
*
*/
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint {
String message() default "{javax.validation.constraints.NotEmpty.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
3.2 定义校验类
/**
* 不用添加 @Component注解
* spring会扫描实现了ConstraintValidator 接口的类后,然后自动管理
*
* ConstraintValidator<MyConstraint, String>:
* 泛型参数说明:
* 第一个参数 指定校验MyConstraint注解的字段
* 第二参数 注解修改字段的类型
* @author chennan
* @date 2019.7.22 22:19
*/
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, String> {
/**
* 可以注入任何spring管理的服务
*/
@Autowired
private HelloService helloService;
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
System.out.println("------------MyConstraintValidator---------");
System.out.println(value);
System.out.println(helloService.greeting("tom"));
System.out.println("------------MyConstraintValidator---------");
//true-校验通过 false-校验不通过
return false;
}
@Override
public void initialize(MyConstraint constraintAnnotation) {
System.out.println("my validator init");
}
}
3.3 使用自定义校验注解
public class User implements Serializable{
private static final long serialVersionUID = -8441401775719174836L;
@MyConstraint(message = "测试自定义注解")
private String userName;
}
网友评论