diff --git a/hsweb-commons/hsweb-commons-controller/src/main/java/org/hswebframework/web/controller/message/ResponseMessage.java b/hsweb-commons/hsweb-commons-controller/src/main/java/org/hswebframework/web/controller/message/ResponseMessage.java index d83649133..aca79f4ab 100644 --- a/hsweb-commons/hsweb-commons-controller/src/main/java/org/hswebframework/web/controller/message/ResponseMessage.java +++ b/hsweb-commons/hsweb-commons-controller/src/main/java/org/hswebframework/web/controller/message/ResponseMessage.java @@ -42,6 +42,8 @@ public class ResponseMessage implements Serializable { private Long timestamp; + private String code; + @ApiModelProperty("调用结果消息") public String getMessage() { return message; @@ -62,6 +64,11 @@ public class ResponseMessage implements Serializable { return timestamp; } + @ApiModelProperty(value = "业务代码") + public String getCode() { + return code; + } + public static ResponseMessage error(String message) { return error(500, message); } diff --git a/hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/DefaultLogicPrimaryKeyValidator.java b/hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/DefaultLogicPrimaryKeyValidator.java new file mode 100644 index 000000000..7cbcecc82 --- /dev/null +++ b/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> validatorCache = new HashMap<>(); + + + private static final Validator EMPTY_VALIDATOR = bean -> { + }; + + public void registerQuerySuppiler(Class type, Function> 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 createValidator(Class target) { + //属性名:注解 + Map 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 { + void doValidate(T bean); + } + + @Builder + static class DefaultValidator implements Validator { + private List infos = new ArrayList<>(); + + private volatile Function> querySupplier; + + public void doValidate(T bean) { + if (querySupplier == null) { + return; + } + + Query query = querySupplier.apply(bean); + + Map 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 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; + } +} diff --git a/hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/GenericEntityService.java b/hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/GenericEntityService.java index e33a5fcbe..6b44b7d1d 100644 --- a/hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/GenericEntityService.java +++ b/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, PK> */ protected abstract IDGenerator getIDGenerator(); + @Autowired(required = false) + public LogicPrimaryKeyValidator logicPrimaryKeyValidator; + + @PostConstruct + public void init() { + if (null != logicPrimaryKeyValidator && logicPrimaryKeyValidator instanceof DefaultLogicPrimaryKeyValidator) { + ((DefaultLogicPrimaryKeyValidator) logicPrimaryKeyValidator) + .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, 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()); } diff --git a/hsweb-core/src/main/java/org/hswebframework/web/validator/DuplicateKeyException.java b/hsweb-core/src/main/java/org/hswebframework/web/validator/DuplicateKeyException.java new file mode 100644 index 000000000..725c09d71 --- /dev/null +++ b/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; + } +} diff --git a/hsweb-core/src/main/java/org/hswebframework/web/validator/LogicPrimaryKey.java b/hsweb-core/src/main/java/org/hswebframework/web/validator/LogicPrimaryKey.java new file mode 100644 index 000000000..3d9452b4e --- /dev/null +++ b/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表达式,可在验证的时候根据实体中不同的属性,使用不同的规则,例如: + *
+     *     #object.name!=null
+     * 
+ * + * @return spel表达式, 为空时不进行判断 + */ + String condition() default ""; + + /** + * 查询条件类型,基于hsweb-commons-dao中的termType,可指定此字段查询的方式,默认为:'eq'(=) + * + * @return 自定义查询条件类型 + */ + String termType() default ""; + + /** + * 是否匹配空值,如果为true,并且值为null时,进行where field is null,如果值为"",则进行where field =''。否则将跳过该字段验证 + * + * @return 是否匹配空值 + */ + boolean matchNullOrEmpty() default false; + + /** + * 验证分组,如果在验证的时候指定了分组,则只会验证对应分组的规则 + * + * @return 分组 + */ + Class group() default Void.class; + +} diff --git a/hsweb-core/src/main/java/org/hswebframework/web/validator/LogicPrimaryKeyValidator.java b/hsweb-core/src/main/java/org/hswebframework/web/validator/LogicPrimaryKeyValidator.java new file mode 100644 index 000000000..9fd8fd555 --- /dev/null +++ b/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); +} diff --git a/hsweb-starter/hsweb-spring-boot-starter/src/main/java/org/hswebframework/web/starter/HswebAutoConfiguration.java b/hsweb-starter/hsweb-spring-boot-starter/src/main/java/org/hswebframework/web/starter/HswebAutoConfiguration.java index af72e90d6..ebc3b00b2 100644 --- a/hsweb-starter/hsweb-spring-boot-starter/src/main/java/org/hswebframework/web/starter/HswebAutoConfiguration.java +++ b/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() {