Browse Source

增加逻辑主键验证功能

zhou-hao 7 years ago
parent
commit
0285effcec

+ 7 - 0
hsweb-commons/hsweb-commons-controller/src/main/java/org/hswebframework/web/controller/message/ResponseMessage.java

@@ -42,6 +42,8 @@ public class ResponseMessage<T> implements Serializable {
 
     private Long timestamp;
 
+    private String code;
+
     @ApiModelProperty("调用结果消息")
     public String getMessage() {
         return message;
@@ -62,6 +64,11 @@ public class ResponseMessage<T> implements Serializable {
         return timestamp;
     }
 
+    @ApiModelProperty(value = "业务代码")
+    public String getCode() {
+        return code;
+    }
+
     public static <T> ResponseMessage<T> error(String message) {
         return error(500, message);
     }

+ 204 - 0
hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/DefaultLogicPrimaryKeyValidator.java

@@ -0,0 +1,204 @@
+package org.hswebframework.web.service;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.ezorm.core.dsl.Query;
+import org.hswebframework.ezorm.core.param.TermType;
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.hswebframework.web.commons.entity.param.QueryParamEntity;
+import org.hswebframework.web.validator.DuplicateKeyException;
+import org.hswebframework.web.validator.LogicPrimaryKey;
+import org.hswebframework.web.validator.LogicPrimaryKeyValidator;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@SuppressWarnings("all")
+@Slf4j
+public class DefaultLogicPrimaryKeyValidator implements LogicPrimaryKeyValidator {
+
+    private static final Map<Class, Map<Class, Validator>> validatorCache = new HashMap<>();
+
+
+    private static final Validator EMPTY_VALIDATOR = bean -> {
+    };
+
+    public <T> void registerQuerySuppiler(Class type, Function<T, Query<T, QueryParamEntity>> querySupplier) {
+        validatorCache.computeIfAbsent(type, this::createValidator)
+                .values()
+                .stream()
+                .filter(DefaultValidator.class::isInstance)
+                .map(DefaultValidator.class::cast)
+                .forEach(validator -> validator.querySupplier = querySupplier);
+    }
+
+    @Override
+    public void validate(Object bean, Class... groups) {
+
+        Class target = ClassUtils.getUserClass(bean);
+        if (null != groups && groups.length > 0) {
+            for (Class group : groups) {
+                validatorCache.computeIfAbsent(target, this::createValidator)
+                        .getOrDefault(group, EMPTY_VALIDATOR)
+                        .doValidate(bean);
+            }
+        } else {
+            validatorCache.computeIfAbsent(target, this::createValidator)
+                    .getOrDefault(Void.class, EMPTY_VALIDATOR)
+                    .doValidate(bean);
+        }
+
+    }
+
+    protected Map<Class, Validator> createValidator(Class target) {
+        //属性名:注解
+        Map<String, LogicPrimaryKey> keys = new HashMap<>();
+
+        ReflectionUtils.doWithFields(target, field -> {
+            LogicPrimaryKey primaryKey = field.getAnnotation(LogicPrimaryKey.class);
+            if (primaryKey != null) {
+                keys.put(field.getName(), primaryKey);
+            }
+        });
+
+        //获取类上的注解
+        Class tempClass = target;
+        LogicPrimaryKey classAnn = null;
+
+        while (classAnn == null) {
+            classAnn = AnnotationUtils.findAnnotation(tempClass, LogicPrimaryKey.class);
+            if (null != classAnn) {
+                if (classAnn.value().length > 0) {
+                    for (String field : classAnn.value()) {
+                        keys.put(field, classAnn);
+                    }
+                    break;
+                } else {
+                    //如果注解没有指定字段则从group中获取
+                    tempClass = classAnn.group();
+                    if (tempClass == Void.class) {
+                        log.warn("类{}的注解{}无效,请设置value属性或者group属性", classAnn, tempClass);
+                        break;
+                    }
+                }
+            } else {
+                break;
+            }
+        }
+
+        if (keys.isEmpty()) {
+            return Collections.emptyMap();
+        }
+
+        return keys.entrySet()
+                .stream()
+                .collect(
+                        Collectors.groupingBy(
+
+                                //按注解中的group分组
+                                e -> e.getValue().group()
+                                //将分组后的注解转为字段配置
+                                , Collectors.collectingAndThen(
+                                        Collectors.mapping(e -> LogicPrimaryKeyField
+                                                .builder()
+                                                .field(e.getKey())
+                                                .termType(e.getValue().termType())
+                                                .condition(e.getValue().condition())
+                                                .matchNullOrEmpty(e.getValue().matchNullOrEmpty())
+                                                .build(), Collectors.toList())
+                                        //将每一组的字段集合构造为验证器对象
+                                        , list -> DefaultValidator
+                                                .builder()
+                                                .infos(list)
+                                                .build())
+                        )
+                );
+
+    }
+
+    interface Validator<T> {
+        void doValidate(T bean);
+    }
+
+    @Builder
+    static class DefaultValidator<T> implements Validator<T> {
+        private List<LogicPrimaryKeyField> infos = new ArrayList<>();
+
+        private volatile Function<T, Query<T, QueryParamEntity>> querySupplier;
+
+        public void doValidate(T bean) {
+            if (querySupplier == null) {
+                return;
+            }
+
+            Query<T, QueryParamEntity> query = querySupplier.apply(bean);
+
+            Map<String, Object> mapBean = FastBeanCopier.copy(bean, new HashMap<>());
+
+            for (LogicPrimaryKeyField info : infos) {
+                String field = info.getField();
+                Object value = mapBean.get(field);
+                if (value == null) {
+                    String tmpField = field;
+                    Object tmpValue = null;
+                    Map<String, Object> tempMapBean = mapBean;
+                    while (tmpValue == null && tmpField.contains(".")) {
+                        String[] nest = tmpField.split("[.]", 2);
+                        Object nestObject = tempMapBean.get(nest[0]);
+                        if (nestObject == null) {
+                            break;
+                        }
+                        if (nestObject instanceof Map) {
+                            tempMapBean = ((Map) nestObject);
+                        } else {
+                            tempMapBean = FastBeanCopier.copy(nestObject, new HashMap<>());
+                        }
+                        tmpField = nest[1];
+                        tmpValue = tempMapBean.get(tmpField);
+                    }
+                    value = tmpValue;
+                }
+
+                if (StringUtils.isEmpty(value)) {
+                    if (info.matchNullOrEmpty) {
+                        if (value == null) {
+                            query.isNull(info.getField());
+                        } else {
+                            query.isEmpty(info.getField());
+                        }
+                    }
+                } else {
+                    String termType = StringUtils.isEmpty(info.termType) ? TermType.eq : info.termType;
+                    query.and(info.getField(), termType, value);
+                }
+            }
+
+            T result = query.single();
+
+            if (result != null) {
+
+                throw new DuplicateKeyException(result, "存在重复数据");
+            }
+        }
+    }
+
+    @Getter
+    @Setter
+    @Builder
+    private static class LogicPrimaryKeyField {
+        private String field;
+
+        private String condition;
+
+        private boolean matchNullOrEmpty;
+
+        private String termType;
+    }
+}

+ 25 - 0
hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/GenericEntityService.java

@@ -22,11 +22,15 @@ import org.hswebframework.web.commons.entity.GenericEntity;
 import org.hswebframework.web.commons.entity.RecordCreationEntity;
 import org.hswebframework.web.dao.CrudDao;
 import org.hswebframework.web.id.IDGenerator;
+import org.hswebframework.web.validator.DuplicateKeyException;
+import org.hswebframework.web.validator.LogicPrimaryKeyValidator;
 import org.hswebframework.web.validator.group.CreateGroup;
 import org.hswebframework.web.validator.group.UpdateGroup;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.Assert;
 
+import javax.annotation.PostConstruct;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -53,6 +57,17 @@ public abstract class GenericEntityService<E extends GenericEntity<PK>, PK>
      */
     protected abstract IDGenerator<PK> getIDGenerator();
 
+    @Autowired(required = false)
+    public LogicPrimaryKeyValidator logicPrimaryKeyValidator;
+
+    @PostConstruct
+    public void init() {
+        if (null != logicPrimaryKeyValidator && logicPrimaryKeyValidator instanceof DefaultLogicPrimaryKeyValidator) {
+            ((DefaultLogicPrimaryKeyValidator) logicPrimaryKeyValidator)
+                    .<E>registerQuerySuppiler(getEntityInstanceType(), bean -> this.createQuery().not("id", bean.getId()));
+        }
+    }
+
     @Override
     public int deleteByPk(PK pk) {
         Assert.notNull(pk, "parameter can not be null");
@@ -95,7 +110,17 @@ public abstract class GenericEntityService<E extends GenericEntity<PK>, PK>
         return entity.getId();
     }
 
+    @SuppressWarnings("unchecked")
     protected boolean dataExisted(E entity) {
+        try {
+            logicPrimaryKeyValidator.validate(entity);
+        } catch (DuplicateKeyException e) {
+            if (getEntityType().isInstance(e.getData())) {
+                PK id = ((E) e.getData()).getId();
+                entity.setId(id);
+                return true;
+            }
+        }
         return null != entity.getId() && null != selectByPk(entity.getId());
     }
 

+ 19 - 0
hsweb-core/src/main/java/org/hswebframework/web/validator/DuplicateKeyException.java

@@ -0,0 +1,19 @@
+package org.hswebframework.web.validator;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.web.BusinessException;
+
+import java.util.Map;
+
+@Getter
+@Setter
+public class DuplicateKeyException extends BusinessException {
+
+    private Object data;
+
+    public DuplicateKeyException(Object data, String message) {
+        super(message, 400);
+        this.data = data;
+    }
+}

+ 56 - 0
hsweb-core/src/main/java/org/hswebframework/web/validator/LogicPrimaryKey.java

@@ -0,0 +1,56 @@
+package org.hswebframework.web.validator;
+
+
+import java.lang.annotation.*;
+
+/**
+ * 逻辑主键,用于在新增或者修改前进行重复数据判断.
+ * 可在类或者字段上进行注解
+ *
+ * @see DuplicateKeyException
+ * @since 3.0.0-RC
+ */
+@Target({ElementType.TYPE, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface LogicPrimaryKey {
+
+    /**
+     * 属性名称,如果注解在类上,值为空的时候必须指定{@link this#group()},并且group对应的类上必须有合法的LogicPrimaryKey注解
+     *
+     * @return 属性名集合, 在字段上此属性无效
+     */
+    String[] value() default "";
+
+    /**
+     * 验证条件,值为一个spel表达式,可在验证的时候根据实体中不同的属性,使用不同的规则,例如:
+     * <pre>
+     *     #object.name!=null
+     * </pre>
+     *
+     * @return spel表达式, 为空时不进行判断
+     */
+    String condition() default "";
+
+    /**
+     * 查询条件类型,基于hsweb-commons-dao中的termType,可指定此字段查询的方式,默认为:'eq'(=)
+     *
+     * @return 自定义查询条件类型
+     */
+    String termType() default "";
+
+    /**
+     * 是否匹配空值,如果为true,并且值为<code>null</code>时,进行where field is null,如果值为<code>""</code>,则进行where field =''。否则将跳过该字段验证
+     *
+     * @return 是否匹配空值
+     */
+    boolean matchNullOrEmpty() default false;
+
+    /**
+     * 验证分组,如果在验证的时候指定了分组,则只会验证对应分组的规则
+     *
+     * @return 分组
+     */
+    Class group() default Void.class;
+
+}

+ 5 - 0
hsweb-core/src/main/java/org/hswebframework/web/validator/LogicPrimaryKeyValidator.java

@@ -0,0 +1,5 @@
+package org.hswebframework.web.validator;
+
+public interface LogicPrimaryKeyValidator {
+    void validate(Object bean, Class... group);
+}

+ 10 - 1
hsweb-starter/hsweb-spring-boot-starter/src/main/java/org/hswebframework/web/starter/HswebAutoConfiguration.java

@@ -30,12 +30,14 @@ import org.hswebframework.web.commons.entity.factory.EntityFactory;
 import org.hswebframework.web.commons.entity.factory.MapperEntityFactory;
 import org.hswebframework.web.convert.CustomMessageConverter;
 import org.hswebframework.web.dict.EnumDict;
+import org.hswebframework.web.service.DefaultLogicPrimaryKeyValidator;
 import org.hswebframework.web.starter.convert.FastJsonGenericHttpMessageConverter;
 import org.hswebframework.web.starter.convert.FastJsonHttpMessageConverter;
 import org.hswebframework.web.starter.entity.EntityFactoryInitConfiguration;
 import org.hswebframework.web.starter.entity.EntityProperties;
 import org.hswebframework.web.starter.resolver.AuthorizationArgumentResolver;
 import org.hswebframework.web.starter.resolver.JsonParamResolver;
+import org.hswebframework.web.validator.LogicPrimaryKeyValidator;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -174,10 +176,17 @@ public class HswebAutoConfiguration {
     @ConditionalOnMissingBean(EntityFactory.class)
     public MapperEntityFactory mapperEntityFactory() {
         MapperEntityFactory entityFactory = new MapperEntityFactory(entityProperties.createMappers());
-        FastBeanCopier.setBeanFactory(entityFactory); ;
+        FastBeanCopier.setBeanFactory(entityFactory);
+        ;
         return entityFactory;
     }
 
+    @Bean
+    @ConditionalOnMissingBean(LogicPrimaryKeyValidator.class)
+    public LogicPrimaryKeyValidator logicPrimaryKeyValidator() {
+        return new DefaultLogicPrimaryKeyValidator();
+    }
+
     @Bean
     @ConditionalOnBean(MapperEntityFactory.class)
     public EntityFactoryInitConfiguration entityFactoryInitConfiguration() {