• hibernate validator自定义校验注解以及基于服务(服务组)的校验


    hibernate validator是Bean Validation 1.1 (JSR 349) Reference Implementation,其广泛的应用在mvc的参数校验中,尤其是使用服务端spring mvc模板的时候。在这里,我们要讲的不是如何使用的问题。而是如何基于其提供更加符合项目要求以及最小化重复实现的目标,在不少情况下,我们在不同的服务中,对于相同的请求类Req,对于其中不同字段的校验要求是不同的,比如有些时候name字段是必须的,但其他情况下是非必须的,所以需要跟着服务或者服务组进行校验。再如,几乎每个系统都会使用到数据字典,使用数据字典的时候,有两种方式可以校验其取值范围,一种是直接使用java枚举类型,另外一种是人工进行判断。只不过,我们不建议使用枚举类型,但是我们也希望能够和通用的参数一样进行校验,而不是对于数据字典进行特殊校验。对于这两种情况,都可以在hibernate validator的技术上实现。对于服务分组,可以新增一个注解比如ValidServices实现,对于枚举校验,可以增加一个自定义的校验注解实现,如下:

    ValidServices.java

    package tf56.lf.base.metadata.validate;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.RetentionPolicy;
    /**
     * 参数校验分组注解
     * @author admin
     *
     */
    @Target({ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface ValidServices {
        String[] services();
    }
    package tf56.lf.base.metadata.validate;
    
    import java.util.Map;
    
    public class ValidationResult {
        // 校验结果是否有错
        private boolean success = true;
    
        // 校验错误信息
        private Map<String, String> errorPair;
    
        public boolean isSuccess() {
            return success;
        }
    
        public void setSuccess(boolean success) {
            this.success = success;
        }
    
        public Map<String, String> getErrorPair() {
            return errorPair;
        }
    
        public void setErrorPair(Map<String, String> errorPair) {
            this.errorPair = errorPair;
        }
    }

    Dict.java

    package tf56.lf.base.metadata.validate;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    
    @Target({ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = { DictValidator.class })
    @Documented
    public @interface Dict {
        
        String dictName();
        
        String message() default "{数据字典取值不合法,请参考标准数据字典管理}";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
    }

    DictValidator:

    package tf56.lf.base.metadata.validate;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    import tf56.lf.base.metadata.cons.DictUtils;
    
    public class DictValidator implements ConstraintValidator<Dict, String> {
        
        private String dictName;
        
        @Override
        public void initialize(Dict dictAnno) {
            this.dictName = dictAnno.dictName();
        }
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            if (DictUtils.isValid(dictName, value)) {
                return true;
            }
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate("该字段的当前值" + value + "不在数据字典" + dictName + "的有效取值范围内, 有效值为:[" + DictUtils.getDictKeys(dictName) + "]").addConstraintViolation();
            return false;
        }
    
    }
    DictUtils为字典取值范围校验类,每个公司的实现不同,读者自己构建一个即可。

    主类:

    package tf56.lf.common.util;
    
    import java.lang.reflect.Field;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.Validator;
    import javax.validation.constraints.AssertTrue;
    import javax.validation.constraints.Past;
    import javax.validation.constraints.Pattern;
    import javax.validation.groups.Default;
    
    import org.apache.commons.collections.CollectionUtils;
    import org.hibernate.validator.constraints.Email;
    import org.hibernate.validator.constraints.Length;
    import org.hibernate.validator.constraints.NotBlank;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import tf56.lf.base.metadata.validate.Dict;
    import tf56.lf.base.metadata.validate.ValidServices;
    import tf56.lf.base.metadata.validate.ValidationResult;
    import tf56.lf.common.cons.SpiderSystemError;
    import tf56.lf.common.exception.LfException;
    
    public class ValidationUtils {
        
        private static Map<Field,Set<String>> validFields = new ConcurrentHashMap<Field,Set<String>>();
        
        static final Logger logger = LoggerFactory.getLogger(ValidationUtils.class);
        
        private static Validator validator = Validation
                .buildDefaultValidatorFactory().getValidator();
    
        public static <T> ValidationResult validateEntity(String serviceId,T obj) {
            boolean noNeedCheck = true;
            Map<String, String> errorMsg = new HashMap<String, String>();
            Field[] fields = obj.getClass().getDeclaredFields();
            for(int i=0;i<fields.length;i++) {
                if(validFields.get(fields[i]) == null) {
                    Set<String> services = new HashSet<String>();
                    ValidServices serviceAnno = fields[i].getAnnotation(ValidServices.class);
                    if (serviceAnno != null) {
                        for (int j=0;j<serviceAnno.services().length;j++) {
                            services.add(String.valueOf(serviceAnno.services()[j]));
                        }
                    }
                    validFields.putIfAbsent(fields[i], services);
                }
                if (validFields.get(fields[i]).isEmpty() || validFields.get(fields[i]).contains(serviceId)) {
                    noNeedCheck = false;
                    Map<String, String> errorPair = validatePropertyInternal(serviceId,obj,fields[i].getName());
                    errorMsg.putAll(errorPair);
                }
            }
            if (noNeedCheck) {
                logger.warn("服务" + serviceId + "在" + obj.getClass().getCanonicalName() + "中所有字段都没有配置做任何校验.");
            }
            ValidationResult result = new ValidationResult();
            if (!errorMsg.isEmpty()) {
                result.setErrorPair(errorMsg);
                result.setSuccess(false);
            }
            return result;
        }
        
        private static <T> Map<String, String> validatePropertyInternal(String serviceId, T obj,
                String propertyName) {
            Set<ConstraintViolation<T>> set = validator.validateProperty(obj,
                    propertyName, Default.class);
            Map<String, String> errorMsg = new HashMap<String, String>();
            if (CollectionUtils.isNotEmpty(set)) {
                for (ConstraintViolation<T> cv : set) {
                    errorMsg.put(propertyName, cv.getMessage());
                }
            }
            return errorMsg;
        }
    
        public static <T> ValidationResult validateProperty(String serviceId, T obj,
                String propertyName) {
            ValidationResult result = new ValidationResult();
            Field field = null;
            try {
                field = obj.getClass().getDeclaredField(propertyName);
            } catch (NoSuchFieldException | SecurityException e) {
                throw new LfException(SpiderSystemError.ERR_NO_SUCH_FIELD_OR_FORBIDDEN);
            }
            if(validFields.get(field) == null) {
                Set<String> services = new HashSet<String>();
                ValidServices serviceAnno = field.getAnnotation(ValidServices.class);
                if (serviceAnno != null) {
                    for (int i=0;i<serviceAnno.services().length;i++) {
                        services.add(String.valueOf(serviceAnno.services()[i]));
                    }
                }
                validFields.putIfAbsent(field, services);
            }
            if (validFields.get(field).isEmpty() || validFields.get(field).contains(serviceId)) {
                Map<String, String> errorPair = validatePropertyInternal(serviceId,obj,field.getName());
                if (!errorPair.isEmpty()) {
                    result.setErrorPair(errorPair);
                    result.setSuccess(false);
                }
            }
            return result;
        }
        
        public static void main(String[] args) {
            SimpleEntity entity = new SimpleEntity();
            entity.setValid(true);
            ValidationResult result = ValidationUtils.validateEntity("1001",entity);
            if (!result.isSuccess()) {
                System.out.println(FastJsonUtil.serializeFromObject(result.getErrorPair()));
            }
            
            result = ValidationUtils.validateEntity("100",entity);
            if (!result.isSuccess()) {
                System.out.println(FastJsonUtil.serializeFromObject(result.getErrorPair()));
            }
            
            entity = new SimpleEntity();
            entity.setValid(true);
            result = ValidationUtils.validateEntity("1",entity);
            if (!result.isSuccess()) {
                System.out.println(FastJsonUtil.serializeFromObject(result.getErrorPair()));
            }
        }
        
        public static class SimpleEntity {
    
            @ValidServices(services = { "1001","1002" })
            @NotBlank(message="名字不能为空或者空串")
            @Length(min=2,max=10,message="名字必须由2~10个字组成")
            private String name;
            
            @Dict(dictName = "payType")
            private String payType;
            
            @Past(message="时间不能晚于当前时间")
            private Date date;
            
            @Email(message="邮箱格式不正确")
            private String email;
            
            @Pattern(regexp="(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{5,10}",message="密码必须是5~10位数字和字母的组合")
            private String password;
            
            @AssertTrue(message="字段必须为真")
            private boolean valid;
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public Date getDate() {
                return date;
            }
    
            public void setDate(Date date) {
                this.date = date;
            }
    
            public String getEmail() {
                return email;
            }
    
            public void setEmail(String email) {
                this.email = email;
            }
    
            public String getPassword() {
                return password;
            }
    
            public void setPassword(String password) {
                this.password = password;
            }
    
            public boolean isValid() {
                return valid;
            }
    
            public void setValid(boolean valid) {
                this.valid = valid;
            }
        }
    }

    输出如下:

    {"name":"名字不能为空或者空串","payType":"该字段的当前值null不在数据字典payType的有效取值范围内, 有效值为:[OTHER,BANK,CASH,TF_ACCOUNT]"}
    {"payType":"该字段的当前值null不在数据字典payType的有效取值范围内, 有效值为:[OTHER,BANK,CASH,TF_ACCOUNT]"}
    {"payType":"该字段的当前值null不在数据字典payType的有效取值范围内, 有效值为:[OTHER,BANK,CASH,TF_ACCOUNT]"}

  • 相关阅读:
    C语言和go语言之间的交互
    Elasticsearch笔记九之优化
    Elasticsearch笔记八之脑裂
    Elasticsearch笔记七之setting,mapping,分片查询方式
    Elasticsearch笔记六之中文分词器及自定义分词器
    Elasticsearch笔记四之配置参数与核心概念
    Elasticsearch笔记三之版本控制和插件
    Elasticsearch笔记二之Curl工具基本操作
    Elasticsearch笔记五之java操作es
    Python处理Excel表格
  • 原文地址:https://www.cnblogs.com/zhjh256/p/7078031.html
Copyright © 2020-2023  润新知