mirror of
https://github.com/hs-web/hsweb-framework.git
synced 2026-05-22 17:07:12 +08:00
增加逻辑主键验证功能
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.hswebframework.web.validator;
|
||||
|
||||
public interface LogicPrimaryKeyValidator {
|
||||
void validate(Object bean, Class... group);
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user