科技的进步是靠懒人推动的,但是这里说的懒人不是说纯懒的那种!开发过程我想最痛苦的事情就是做那种不懂脑子的体力活,即苦逼,又无趣,那么这样的体力活,我们就得想办法偷懒去做,既能完成相关的工作,又能快速达到效果,他好我也好!这种体力活参数校验就是其中的一个;
点击下载
看下面一段注册的接口
/**
* 注册
*
* @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("参数数有误")
}
这种操作能写到人高潮迭起,全身乏力,可能这一系列的参数校验,就占用了几十行代码,导致代码的可读性极差;那有没有更好的方式呢,当然是有的;hibernate-validator
validator的优点
Validation constraint
注解 | 作用 |
---|---|
@AssertFalse | 被注释的元素必须为 false |
@AssertTrue | 被注释的元素必须为 true |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
被注释的元素必须是电子邮箱地址 | |
@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添加以下配置
org.apache.commons
commons-lang3
3.7
org.hibernate.validator
hibernate-validator
6.0.13.Final
校验响应对象
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 errMsgMap = new HashMap<>();
public Boolean getHasError() {
return hasError;
}
public void setHasError(Boolean hasError) {
this.hasError = hasError;
}
public Map getErrMsgMap() {
return errMsgMap;
}
public void setErrMsgMap(Map 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> 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方法
}
测试使用
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 {
//大小写的枚举
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);
这里注册的时候就只会校验带有ValidationGroup.Register.class的参数validatorUtil.reqValidata(userModel, ValidationGroup.Login.class);
登录的时候就只会校验带有ValidationGroup.Login.class的参数