增加逻辑主键验证功能

This commit is contained in:
zhou-hao
2018-06-11 23:54:13 +08:00
parent 1859f9b2e1
commit 0285effcec
7 changed files with 326 additions and 1 deletions

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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());
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

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

View File

@@ -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() {