Live2D

Hibernate Validator使用 (二)

看下面一段注册的接口

/**
* 注册
*
* @param name 用户昵称
* @param phoneNum 用户号码
* @param code 用户验证码
* @param age 年龄
* @param sex 性别
* @param email 邮箱
* @param pwd 密码
* @return
* @throws Exception
*/
@RequestMapping(value = "/register", method = RequestMethod.POST)
public BaseRespObj register(
@RequestParam(name = "name") String name,
@RequestParam(name = "phoneNum") String phoneNum,
@RequestParam(name = "phoneCode") Integer code,
@RequestParam(name = "age") Integer age,
@RequestParam(name = "sex") String sex,
@RequestParam(name = "email") String email,
@RequestParam(name = "pwd") String pwd) throws Exception {

}

为了代码的健壮性,那么这里传上来的每一个参数都得做判空的校验,又不能不做,那么可能就会出现下面这样的校验:

if(null==name || name.length()<=0 || null==phoneNum || phoneNum.length()<=0 || .....){
throw new BusiException("参数数有误")
}
1
2
3
这种操作能写到人高潮迭起,全身乏力,可能这一系列的参数校验,就占用了几十行代码,导致代码的可读性极差;那有没有更好的方式呢,当然是有的;hibernate-validator

优点及注解说明
validator的优点

解耦,数据的校验与业务逻辑进行分离,降低耦合度
规范的校验方式,减少参数校验所带来的繁琐体力活
简洁代码,提高代码的可读性
Validation constraint

注解 作用
@AssertFalse 被注释的元素必须为 false
@AssertTrue 被注释的元素必须为 true
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Email 被注释的元素必须是电子邮箱地址
@Future 被注释的元素必须是一个将来的日期
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Negative 该值必须小于0
@NegativeOrZero 该值必须小于等于0
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@NotEmpty 被注释的字符串的必须非空
@Past 被注释的元素必须是一个过去的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
@Positive 该值必须大于0
@PositiveOrZero 该值必须大于等于0
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
@Size(max=, min=) 数组大小必须在[min,max]这个区间
@URL(protocol=,host,port) 检查是否是一个有效的URL,如果提供了protocol,host等,则该URL还需满足提供的条件
@Valid 该注解主要用于字段为一个包含其他对象的集合或map或数组的字段,或该字段直接为一个其他对象的引用,这样在检查当前对象的同时也会检查该字段所引用的对象
资源导入
pom.xml添加以下配置

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>

代码实现
校验响应对象

package com.lupf.springboottest.utils.validator;

import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;

/**
* 验证的响应对象
*/
public class ValidationResult {
/**
* 是否存在错误
*/
private Boolean hasError = false;

/**
* 错误的信息的map
*/
private Map<String, String> errMsgMap = new HashMap<>();

public Boolean getHasError() {
return hasError;
}

public void setHasError(Boolean hasError) {
this.hasError = hasError;
}

public Map<String, String> getErrMsgMap() {
return errMsgMap;
}

public void setErrMsgMap(Map<String, String> errMsgMap) {
this.errMsgMap = errMsgMap;
}

/**
* 用于获取校验之后错误描述的信息
*
* @return
*/
public String getErrMsg() {
if (null != this.errMsgMap && this.errMsgMap.size() > 0)
return StringUtils.join(this.errMsgMap.values().toArray(), ",");
return null;
}
}

校验帮助类

package com.lupf.springboottest.utils.validator;

import com.lupf.springboottest.error.BusiErrCodeEm;
import com.lupf.springboottest.error.BusiException;
import org.hibernate.validator.HibernateValidator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;

//把这个工具类交由Spring管理
@Component
public class ValidatorUtil implements InitializingBean {

private Validator validator;

/**
* 通过Validator校验对象
*
* @param object
* @param groups
* @return
*/
public ValidationResult validata(Object object, Class<?>... groups) {
ValidationResult validationResult = new ValidationResult();
Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(object, groups);
if (constraintViolationSet.size() > 0) {
constraintViolationSet.forEach(constraintViolation -> {
validationResult.setHasError(true);
String msg = constraintViolation.getMessage();
String propertyName = constraintViolation.getPropertyPath().toString();
validationResult.getErrMsgMap().put(propertyName, msg);
});
}
return validationResult;
}

/**
* 校验请求参数对象,如果出现未校验通过的,直接抛出异常
*
* @param object 待处理的对象
* @param groups 校验分组
* @throws BusiException
*/
public void reqValidata(Object object, Class<?>... groups) throws BusiException {
ValidationResult validationResult = this.validata(object, groups);
if (null != validationResult && validationResult.getHasError()) {
//请求参数存在不合法的数据,这里直接抛出异常
throw new BusiException(BusiErrCodeEm.REQ_PARAM_10001, validationResult.getErrMsg());
}
}

@Override
public void afterPropertiesSet() {
//failFast(true) true:快速校验,遇到不合法的就直接返回 false:全量校验,找出所有不合法的数据
validator = Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory().getValidator();
}
}

测试对象

package com.lupf.springboottest.service.model;

import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
* 用户的业务对象
*/
public class UserModel {
private Integer id;

@NotBlank(message = "用户昵称不能为空")
private String name;

@NotNull(message = "性别选择不能为空")
private Byte sex;

@Max(value = 200, message = "年龄不能大于200岁")
@Min(value = 0, message = "年龄不能小于0岁")
private Integer age;

@NotBlank(message = "手机号码不能为空")
@Pattern(regexp = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$", message = "号码格式不正确!")
private String telphone;

@Email(message = "邮箱格式错误")
private String email;

private String registerMode;
private String thirdPartyId;

@URL(message = "头像必须是链接地址")
private String avatar;

//这里是定义的用户加密后密码
//由于DAO层
//由于DO层用户信息和用户密码是分开的 但是从业务层面来看,这个密码确实是用户一部分,因此在这里的业务对象将用户信息和密码信息合并
@Length(min = 6, max = 18, message = "密码长度为6-18个字符")//这个规则是校验明文的
private String encrptPassword;

//为了减少不必要的代码量 这里自行添加get set方法
}

测试使用

BaseController对象中注入工具类
@Autowired
public ValidatorUtil validatorUtil;

测试示例
validatorUtil.reqValidata(userModel);
自定义校验器(字母大小写)

需求
校验某个参数自允许存在全大写或者全小写的字母
定义大小写的枚举
package com.lupf.springboottest.utils.validator.myvalidator;

/**
* 字母大小写枚举
*/
public enum CaseMode {
//大写
UPPER,
//小写
LOWER;
}

定义校验的注解
package com.lupf.springboottest.utils.validator.myvalidator;


import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
//指定校验器
@Constraint(validatedBy = CaseCheckValidator.class)
public @interface CaseCheck {
String message() default "";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

CaseMode value();
}


定义校验器
package com.lupf.springboottest.utils.validator.myvalidator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;

/**
* 字母大小写校验器
*/
public class CaseCheckValidator implements ConstraintValidator<CaseCheck, String> {
//大小写的枚举
private CaseMode caseMode;

@Override
public void initialize(CaseCheck caseCheck) {
this.caseMode = caseCheck.value();
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
//如果文本是空,则不进行校验,因为有其他的注解是可以校验空或者空字符串的
if (null == value)
return true;

//文本只能是字母的正则
String pattern = "^[a-zA-Z]*$";
//校验传进来的是否是只包含了字母的文本
boolean isMatch = Pattern.matches(pattern, value);
//如果存在其他字符则返回校验失败
if (!isMatch)
return false;

//如果没有指定方式,则直接返回false
if (null == caseMode)
return false;

//判断是否符合大小写条件
if (caseMode == CaseMode.UPPER) {
return value.equals(value.toUpperCase());
} else {
return value.equals(value.toLowerCase());
}
}
}


自定义校验的使用
对应参数加上相关注解即可
@CaseCheck(value = CaseMode.UPPER, message = "注册方式必须是大写字母")
private String registerMode;

分组校验
使用场景
例:用户的登录和注册使用的是同一个用户对象,用户对象里面包含了所有用户相关的数据;
当用户注册的时候,我们就需要校验前端上传的号码、昵称、性别、密码、邮箱等等相关信息
当登录的时候,就只需要校验用户名和密码,其他的参数都不用校验

定义校验的分组接口

package com.lupf.springboottest.utils.validator;

/**
* 验证分组接口
*/
public class ValidationGroup {
/**
* 注册的分组
*/
public interface Register {
}

/**
* 登录的分组
*/
public interface Login {
}
}


修改用户模型对象

package com.lupf.springboottest.service.model;

import com.lupf.springboottest.utils.validator.ValidationGroup;
import com.lupf.springboottest.utils.validator.myvalidator.CaseCheck;
import com.lupf.springboottest.utils.validator.myvalidator.CaseMode;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
* 用户的业务对象
*/
public class UserModel {
private Integer id;

@NotBlank(message = "用户昵称不能为空", groups = ValidationGroup.Register.class)
private String name;

@NotNull(message = "性别选择不能为空", groups = ValidationGroup.Register.class)
private Byte sex;

@Max(value = 200, message = "年龄不能大于200岁", groups = ValidationGroup.Register.class)
@Min(value = 0, message = "年龄不能小于0岁", groups = ValidationGroup.Register.class)
private Integer age;

@NotBlank(message = "手机号码不能为空", groups = {ValidationGroup.Login.class, ValidationGroup.Register.class})
@Pattern(regexp = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$", message = "号码格式不正确!", groups = {ValidationGroup.Login.class, ValidationGroup.Register.class})
private String telphone;

@Email(message = "邮箱格式错误", groups = ValidationGroup.Register.class)
private String email;

@CaseCheck(value = CaseMode.UPPER, message = "注册方式必须是大写字母", groups = ValidationGroup.Register.class)
private String registerMode;

private String thirdPartyId;

@URL(message = "头像必须是链接地址", groups = ValidationGroup.Register.class)
private String avatar;

//这里是定义的用户加密后密码
//由于DAO层
//由于DO层用户信息和用户密码是分开的 但是从业务层面来看,这个密码确实是用户一部分,因此在这里的业务对象将用户信息和密码信息合并
//这个规则是校验明文的
@Length(min = 6, max = 18, message = "密码长度为6-18个字符", groups = {ValidationGroup.Login.class, ValidationGroup.Register.class})
private String encrptPassword;
}


使用

注册
validatorUtil.reqValidata(userModel, ValidationGroup.Register.class);
1
这里注册的时候就只会校验带有ValidationGroup.Register.class的参数
登录
validatorUtil.reqValidata(userModel, ValidationGroup.Login.class);
1
登录的时候就只会校验带有ValidationGroup.Login.class的参数

posted @ 2021-03-31 09:44  ΜΑΗΑΙΓΞ小白  阅读(180)  评论(0)    收藏  举报