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 引入依赖
org.springframework.boot
spring-boot-starter-web
2.1.6.RELEASE
org.projectlombok
lombok
1.18.8
1.4 实体类(包含嵌套实体的校验)
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
@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;
@NotEmpty(message = "地址信息不能为空")
@Valid
private List<@NotNull(message = "地址信息不能为空") Address> addressList;
@Data
public static class Address implements Serializable{
private static final long serialVersionUID = -6795109699856978126L;
@NotEmpty(message = "手机号不能为空")
private String mobile;
private String detail;
}
}
1.5 控制层
import com.google.common.collect.Maps;
import com.nanc.demo.modules.test.entity.bo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/test")
public class MyTestController {
/**
* 说明:
* 添加 BindingResult参数后,就算验证不通过,也能继续执行方法体
* BindingResult的参数的作用:
* 方便记录日志(如果不想继续向下执行,可以直接再抛异常)
* @param user
* @param result
* @return
*/
@PostMapping()
public Object create(@Valid @RequestBody User user, BindingResult result){
// 判断BindingResult是否保存错误的验证信息,如果有,则直接return
if (result.hasErrors()) {
Map errors = Maps.newHashMap();
result.getFieldErrors().stream().forEach(f -> errors.put(f.getField(), f.getDefaultMessage()));
return errors;
}
//对 user的处理,然后返回
return user;
}
}
1.6 请求测试
### 测试--数组中传 null
POST http://127.0.0.1:8888/demo/test/
Content-Type: application/json
{
"userName": "aaaaa",
"password": "bbbbb",
"birthDay": "2022-07-22 11:27:56",
"addressList": [
null
]
}
### 测试--嵌套实体不传值
POST http://127.0.0.1:8888/demo/test/
Content-Type: application/json
{
"userName": "aaaaa",
"password": "bbbbb",
"birthDay": "2022-07-22 11:27:56",
"addressList": [
]
}
### 测试--手机号不填
POST http://127.0.0.1:8888/demo/test/
Content-Type: application/json
{
"userName": "aaaaa",
"password": "bbbbb",
"birthDay": "2022-07-22 11:27:56",
"addressList": [
{
"detail": "abc"
}
]
}
###
2 Hibernate-validate工具类,手动调用校验返回结果
2.1 添加 hibernate-validate依赖
org.hibernate
hibernate-validator
6.0.18.Final
2.2 接收处理结果,以及输出格式化的一个实体类
import org.apache.commons.lang3.StringUtils;
import java.text.MessageFormat;
import java.util.Map;
public class ValidationResult {
/**
* 是否有异常
*/
private boolean hasErrors;
/**
* 异常消息记录
*/
private Map 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 getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(Map 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
* @return
*/
public static ValidationResult validateEntity(T obj) {
//解析校验结果
Set> validateSet = validator.validate(obj, Default.class);
return buildValidationResult(validateSet);
}
/**
* 校验指定实体的指定属性是否存在异常
*
* @param obj
* @param propertyName
* @param
* @return
*/
public static ValidationResult validateProperty(T obj, String propertyName) {
Set> validateSet = validator.validateProperty(obj, propertyName, Default.class);
return buildValidationResult(validateSet);
}
/**
* 将异常结果封装返回
*
* @param validateSet
* @param
* @return
*/
private static ValidationResult buildValidationResult(Set> validateSet) {
ValidationResult validationResult = new ValidationResult();
if (CollectionUtils.isNotEmpty(validateSet)) {
validationResult.setHasErrors(true);
Map errorMsgMap = new HashMap<>();
for (ConstraintViolation constraintViolation : validateSet) {
errorMsgMap.put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage());
}
validationResult.setErrorMsg(errorMsgMap);
}
return validationResult;
}
}
2.4 使用示例
@PostMapping("/add2")
public R 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注解的字段
* 第二参数 注解修改字段的类型
* @author chennan
* @date 2019.7.22 22:19
*/
public class MyConstraintValidator implements ConstraintValidator {
/**
* 可以注入任何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;
}