refactor: 优化

This commit is contained in:
zhouhao
2025-07-16 16:25:33 +08:00
parent b43123cabc
commit 821cc5f934
17 changed files with 425 additions and 350 deletions

View File

@@ -34,13 +34,13 @@ public class ValidateEventListener implements EventListener, Ordered {
if (resultHolder.isPresent()) {
resultHolder
.ifPresent(holder -> holder
.invoke(LocaleUtils
.doInReactive(() -> {
tryValidate(type, context);
return null;
})
));
.ifPresent(holder -> holder
.invoke(LocaleUtils
.doInReactive(() -> {
tryValidate(type, context);
return null;
})
));
} else {
tryValidate(type, context);
}
@@ -48,7 +48,8 @@ public class ValidateEventListener implements EventListener, Ordered {
@SuppressWarnings("all")
public void tryValidate(EventType type, EventContext context) {
if (type == MappingEventTypes.insert_before || type == MappingEventTypes.save_before) {
if (type == MappingEventTypes.insert_before
|| type == MappingEventTypes.save_before) {
boolean single = context.get(MappingContextKeys.type).map("single"::equals).orElse(false);
if (single) {
@@ -60,10 +61,11 @@ public class ValidateEventListener implements EventListener, Ordered {
context.get(MappingContextKeys.instance)
.filter(List.class::isInstance)
.map(List.class::cast)
.ifPresent(lst -> lst.stream()
.filter(Entity.class::isInstance)
.map(Entity.class::cast)
.forEach(e -> ((Entity) e).tryValidate(CreateGroup.class))
.ifPresent(lst -> lst
.stream()
.filter(Entity.class::isInstance)
.map(Entity.class::cast)
.forEach(e -> ((Entity) e).tryValidate(CreateGroup.class))
);
}

View File

@@ -32,7 +32,7 @@ public interface CrudService<E, K> {
return getRepository().createDelete();
}
@Transactional(readOnly = true, transactionManager = TransactionManagers.jdbcTransactionManager)
@Transactional( readOnly = true, transactionManager = TransactionManagers.jdbcTransactionManager)
default Optional<E> findById(K id) {
return getRepository()
.findById(id);
@@ -48,48 +48,48 @@ public interface CrudService<E, K> {
.findById(id);
}
@Transactional(transactionManager = TransactionManagers.jdbcTransactionManager)
@Transactional(rollbackFor = Throwable.class,transactionManager = TransactionManagers.jdbcTransactionManager)
default SaveResult save(Collection<E> entityArr) {
return getRepository()
.save(entityArr);
}
@Transactional(transactionManager = TransactionManagers.jdbcTransactionManager)
@Transactional(rollbackFor = Throwable.class,transactionManager = TransactionManagers.jdbcTransactionManager)
default int insert(Collection<E> entityArr) {
return getRepository()
.insertBatch(entityArr);
}
@Transactional(transactionManager = TransactionManagers.jdbcTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.jdbcTransactionManager)
default void insert(E entityArr) {
getRepository()
.insert(entityArr);
}
@Transactional(transactionManager = TransactionManagers.jdbcTransactionManager)
@Transactional(rollbackFor = Throwable.class,transactionManager = TransactionManagers.jdbcTransactionManager)
default int updateById(K id, E entityArr) {
return getRepository()
.updateById(id, entityArr);
}
@Transactional(transactionManager = TransactionManagers.jdbcTransactionManager)
@Transactional(rollbackFor = Throwable.class,transactionManager = TransactionManagers.jdbcTransactionManager)
default SaveResult save(E entity) {
return getRepository()
.save(Collections.singletonList(entity));
}
@Transactional(transactionManager = TransactionManagers.jdbcTransactionManager)
@Transactional(rollbackFor = Throwable.class,transactionManager = TransactionManagers.jdbcTransactionManager)
default SaveResult save(List<E> entities) {
return getRepository()
.save(entities);
}
@Transactional(transactionManager = TransactionManagers.jdbcTransactionManager)
@Transactional(rollbackFor = Throwable.class,transactionManager = TransactionManagers.jdbcTransactionManager)
default int deleteById(Collection<K> idArr) {
return getRepository().deleteById(idArr);
}
@Transactional(transactionManager = TransactionManagers.jdbcTransactionManager)
@Transactional(rollbackFor = Throwable.class,transactionManager = TransactionManagers.jdbcTransactionManager)
default int deleteById(K idArr) {
return deleteById(Collections.singletonList(idArr));
}

View File

@@ -34,48 +34,55 @@ public interface EnableCacheReactiveCrudService<E, K> extends ReactiveCrudServic
}
@Override
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> updateById(K id, E data) {
return updateById(id, Mono.just(data));
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> updateById(K id, Mono<E> entityPublisher) {
return registerClearCache(Collections.singleton("id:" + id))
.then(ReactiveCrudService.super.updateById(id, entityPublisher));
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<SaveResult> save(Collection<E> collection) {
return registerClearCache()
.then(ReactiveCrudService.super.save(collection));
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<SaveResult> save(E data) {
return registerClearCache()
.then(ReactiveCrudService.super.save(data));
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<SaveResult> save(Publisher<E> entityPublisher) {
return registerClearCache()
.then(ReactiveCrudService.super.save(entityPublisher));
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> insert(E data) {
return registerClearCache()
.then(ReactiveCrudService.super.insert(data));
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> insert(Publisher<E> entityPublisher) {
return registerClearCache()
.then(ReactiveCrudService.super.insert(entityPublisher));
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> insertBatch(Publisher<? extends Collection<E>> entityPublisher) {
return registerClearCache()
.then(ReactiveCrudService.super.insertBatch(entityPublisher));
@@ -106,12 +113,13 @@ public interface EnableCacheReactiveCrudService<E, K> extends ReactiveCrudServic
@Override
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> deleteById(K id) {
return deleteById(Mono.just(id));
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> deleteById(Publisher<K> idPublisher) {
Flux<K> cache = Flux.from(idPublisher).cache();
return cache

View File

@@ -1,7 +1,9 @@
package org.hswebframework.web.crud.service;
import org.hswebframework.ezorm.rdb.mapping.SyncRepository;
import org.hswebframework.web.api.crud.entity.TransactionManagers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
public abstract class GenericCrudService<E,K> implements CrudService<E,K> {

View File

@@ -90,13 +90,13 @@ public interface ReactiveCrudService<E, K> {
}
@Transactional(readOnly = true, transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<E> findById(K id) {
return getRepository()
.findById(id);
}
@Transactional(readOnly = true, transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Flux<E> findById(Collection<K> publisher) {
return getRepository()
.findById(publisher);
@@ -114,61 +114,61 @@ public interface ReactiveCrudService<E, K> {
.findById(publisher);
}
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<SaveResult> save(Publisher<E> entityPublisher) {
return getRepository()
.save(entityPublisher);
}
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<SaveResult> save(E data) {
return getRepository()
.save(data);
}
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<SaveResult> save(Collection<E> collection) {
return getRepository()
.save(collection);
}
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> updateById(K id, Mono<E> entityPublisher) {
return getRepository()
.updateById(id, entityPublisher);
}
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> updateById(K id, E data) {
return getRepository()
.updateById(id, Mono.just(data));
}
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> insertBatch(Publisher<? extends Collection<E>> entityPublisher) {
return getRepository()
.insertBatch(entityPublisher);
}
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> insert(Publisher<E> entityPublisher) {
return getRepository()
.insert(entityPublisher);
}
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> insert(E data) {
return getRepository()
.insert(Mono.just(data));
}
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> deleteById(Publisher<K> idPublisher) {
return getRepository()
.deleteById(idPublisher);
}
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> deleteById(K id) {
return getRepository()
.deleteById(Mono.just(id));

View File

@@ -174,23 +174,23 @@ public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K
}
@Override
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> insert(Publisher<E> entityPublisher) {
return insertBatch(Flux.from(entityPublisher).collectList());
}
@Override
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> insert(E data) {
return this.insertBatch(Flux.just(Collections.singletonList(data)));
}
@Override
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<Integer> insertBatch(Publisher<? extends Collection<E>> entityPublisher) {
return this
.getRepository()
.insertBatch(new TreeSortServiceHelper<>(this)
.insertBatch(new ReactiveTreeSortServiceHelper<>(this)
.prepare(Flux.from(entityPublisher)
.flatMapIterable(Function.identity()))
// .doOnNext(e -> e.tryValidate(CreateGroup.class))
@@ -201,105 +201,11 @@ public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K
return 200;
}
@Deprecated
default Mono<E> applyTreeProperty(E ele) {
if (StringUtils.hasText(ele.getPath()) ||
ObjectUtils.isEmpty(ele.getParentId())) {
return Mono.just(ele);
}
return this.checkCyclicDependency(ele.getId(), ele)
.then(this.findById(ele.getParentId())
.doOnNext(parent -> ele.setPath(parent.getPath() + "-" + RandomUtil.randomChar(4))))
.thenReturn(ele);
}
@Deprecated
//校验是否有循环依赖,修改父节点为自己的子节点?
default Mono<E> checkCyclicDependency(K id, E ele) {
if (ObjectUtils.isEmpty(id)) {
return Mono.empty();
}
return this
.queryIncludeChildren(Collections.singletonList(id))
.doOnNext(e -> {
if (Objects.equals(ele.getParentId(), e.getId())) {
throw new ValidationException.NoStackTrace("parentId", "error.tree_entity_cyclic_dependency");
}
})
.then(Mono.just(ele));
}
@Deprecated
default Mono<Collection<E>> checkParentId(Collection<E> source) {
Set<K> idSet = source
.stream()
.map(TreeSupportEntity::getId)
.filter(e -> !ObjectUtils.isEmpty(e))
.collect(Collectors.toSet());
if (idSet.isEmpty()) {
return Mono.just(source);
}
Set<K> readyToCheck = source
.stream()
.map(TreeSupportEntity::getParentId)
.filter(e -> !ObjectUtils.isEmpty(e) && !idSet.contains(e))
.collect(Collectors.toSet());
if (readyToCheck.isEmpty()) {
return Mono.just(source);
}
return this
.createQuery()
.select("id")
.in("id", readyToCheck)
.fetch()
.doOnNext(e -> readyToCheck.remove(e.getId()))
.then(Mono.fromSupplier(() -> {
if (!readyToCheck.isEmpty()) {
throw new ValidationException(
"error.tree_entity_parent_id_not_exist",
Collections.singletonList(
new ValidationException.Detail(
"parentId",
"error.tree_entity_parent_id_not_exist",
readyToCheck))
);
}
return source;
}));
}
@Deprecated
//重构子节点的path
default void refactorChildPath(K id, Function<K, Collection<E>> childGetter, String path, Consumer<E> pathAccepter) {
Collection<E> children = childGetter.apply(id);
if (CollectionUtils.isEmpty(children)) {
return;
}
for (E child : children) {
if (ObjectUtils.isEmpty(path)) {
child.setPath(RandomUtil.randomChar(4));
} else {
child.setPath(path + "-" + RandomUtil.randomChar(4));
}
pathAccepter.accept(child);
this.refactorChildPath(child.getId(), childGetter, child.getPath(), pathAccepter);
}
}
@Override
@Transactional(rollbackFor = Throwable.class,
transactionManager = TransactionManagers.reactiveTransactionManager)
default Mono<SaveResult> save(Publisher<E> entityPublisher) {
return new TreeSortServiceHelper<>(this)
return new ReactiveTreeSortServiceHelper<>(this)
.prepare(Flux.from(entityPublisher))
// .doOnNext(e -> e.tryValidate(CreateGroup.class))
.buffer(getBufferSize())
@@ -310,7 +216,7 @@ public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K
@Deprecated
default Flux<E> tryRefactorPath(Flux<E> stream) {
return new TreeSortServiceHelper<>(this).prepare(stream);
return new ReactiveTreeSortServiceHelper<>(this).prepare(stream);
}
@Override

View File

@@ -0,0 +1,41 @@
package org.hswebframework.web.crud.service;
import org.hswebframework.web.api.crud.entity.TreeSortSupportEntity;
import org.hswebframework.web.id.IDGenerator;
import reactor.core.publisher.Flux;
import java.util.*;
public class ReactiveTreeSortServiceHelper<E extends TreeSortSupportEntity<PK>, PK> extends TreeSortServiceHelper<E, PK> {
private final ReactiveTreeSortEntityService<E, PK> service;
public ReactiveTreeSortServiceHelper(ReactiveTreeSortEntityService<E, PK> service) {
this.service = service;
}
@Override
protected IDGenerator<PK> getIdGenerator() {
return service.getIDGenerator();
}
@Override
protected void applyChildren(E parent, List<E> children) {
service.setChildren(parent, children);
}
@Override
protected boolean isRootNode(E node) {
return service.isRootNode(node);
}
@Override
protected Flux<E> queryIncludeChildren(Collection<PK> idList) {
return service.queryIncludeChildren(idList);
}
@Override
protected Flux<E> queryById(Collection<PK> idList) {
return service.findById(idList);
}
}

View File

@@ -0,0 +1,52 @@
package org.hswebframework.web.crud.service;
import org.hswebframework.web.api.crud.entity.TreeSortSupportEntity;
import org.hswebframework.web.id.IDGenerator;
import reactor.core.publisher.Flux;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class SyncTreeSortServiceHelper<E extends TreeSortSupportEntity<PK>, PK> extends TreeSortServiceHelper<E, PK> {
private final TreeSortEntityService<E, PK> service;
public SyncTreeSortServiceHelper(TreeSortEntityService<E, PK> service) {
this.service = service;
}
@Override
protected IDGenerator<PK> getIdGenerator() {
return service.getIDGenerator();
}
@Override
protected void applyChildren(E parent, List<E> children) {
service.setChildren(parent, children);
}
@Override
protected boolean isRootNode(E node) {
return service.isRootNode(node);
}
public List<E> prepare(Collection<E> source) {
return super
.prepare(Flux.fromIterable(source))
.toStream()
.collect(Collectors.toList());
}
@Override
@SuppressWarnings("all")
protected Flux<E> queryIncludeChildren(Collection<PK> idList) {
return Flux.fromIterable(service.queryIncludeChildren(idList));
}
@Override
@SuppressWarnings("all")
protected Flux<E> queryById(Collection<PK> idList) {
return Flux.fromIterable(service.findById(idList));
}
}

View File

@@ -25,124 +25,98 @@ import java.util.stream.Stream;
* @see GenericReactiveTreeSupportCrudService
*/
public interface TreeSortEntityService<E extends TreeSortSupportEntity<K>, K>
extends CrudService<E, K> {
extends CrudService<E, K> {
@Transactional(readOnly = true, transactionManager = TransactionManagers.jdbcTransactionManager)
default List<E> queryResultToTree(QueryParamEntity paramEntity) {
return TreeSupportEntity
.list2tree(query(paramEntity),
this::setChildren,
this::createRootNodePredicate);
.list2tree(query(paramEntity),
this::setChildren,
this::createRootNodePredicate);
}
@Transactional(readOnly = true, transactionManager = TransactionManagers.jdbcTransactionManager)
default List<E> queryIncludeChildrenTree(QueryParamEntity paramEntity) {
return TreeSupportEntity
.list2tree(queryIncludeChildren(paramEntity),
this::setChildren,
this::createRootNodePredicate);
.list2tree(queryIncludeChildren(paramEntity),
this::setChildren,
this::createRootNodePredicate);
}
@Transactional(readOnly = true, transactionManager = TransactionManagers.jdbcTransactionManager)
default List<E> queryIncludeChildren(Collection<K> idList) {
return findById(idList)
.stream()
.flatMap(e -> createQuery()
.where()
.like$("path", e.getPath())
.fetch()
.stream())
.collect(Collectors.toList());
.stream()
.flatMap(e -> createQuery()
.where()
.like$("path", e.getPath())
.fetch()
.stream())
.collect(Collectors.toList());
}
@Transactional(readOnly = true, transactionManager = TransactionManagers.jdbcTransactionManager)
default List<E> queryIncludeChildren(QueryParamEntity queryParam) {
return query(queryParam)
.stream()
.flatMap(e -> createQuery()
.where()
.like$("path", e.getPath())
.fetch()
.stream())
.collect(Collectors.toList());
.stream()
.flatMap(e -> createQuery()
.where()
.like$("path", e.getPath())
.fetch()
.stream())
.collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.jdbcTransactionManager)
default void insert(E entityPublisher) {
insert(Collections.singletonList(entityPublisher));
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.jdbcTransactionManager)
default int insert(Collection<E> entityPublisher) {
return this
.getRepository()
.insertBatch(entityPublisher
.stream()
.flatMap(this::applyTreeProperty)
.flatMap(e -> TreeSupportEntity
.expandTree2List(e, getIDGenerator())
.stream())
.collect(Collectors.toList())
);
}
default Stream<E> applyTreeProperty(E ele) {
if (StringUtils.hasText(ele.getPath()) ||
ObjectUtils.isEmpty(ele.getParentId())) {
return Stream.of(ele);
}
this.checkCyclicDependency(ele.getId(), ele);
this.findById(ele.getParentId())
.ifPresent(parent -> ele.setPath(parent.getPath() + "-" + RandomUtil.randomChar(4)));
return Stream.of(ele);
}
//校验是否有循环依赖,修改父节点为自己的子节点?
default void checkCyclicDependency(K id, E ele) {
if (ObjectUtils.isEmpty(id)) {
return;
}
for (E e : this.queryIncludeChildren(Collections.singletonList(id))) {
if (Objects.equals(ele.getParentId(), e.getId())) {
throw new IllegalArgumentException("不能修改父节点为自己或者自己的子节点");
}
}
return new SyncTreeSortServiceHelper<>(this)
.prepare(Flux.fromIterable(entityPublisher))
.buffer(getBufferSize())
.map(this.getRepository()::insertBatch)
.reduce(Math::addExact)
.blockOptional()
.orElse(0);
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.jdbcTransactionManager)
default SaveResult save(List<E> entities) {
return this.getRepository()
.save(entities
.stream()
.flatMap(this::applyTreeProperty)
//把树结构平铺
.flatMap(e -> TreeSupportEntity
.expandTree2List(e, getIDGenerator())
.stream())
.collect(Collectors.toList())
);
return new SyncTreeSortServiceHelper<>(this)
.prepare(Flux.fromIterable(entities))
.buffer(getBufferSize())
.map(this.getRepository()::save)
.reduce(SaveResult::merge)
.blockOptional()
.orElse(SaveResult.of(0,0));
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.jdbcTransactionManager)
default int updateById(K id, E entity) {
entity.setId(id);
return this.save(entity).getTotal();
}
@Override
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.jdbcTransactionManager)
default int deleteById(Collection<K> idPublisher) {
List<E> dataList = findById(idPublisher);
return dataList
.stream()
.map(e -> createDelete()
.where()
.like$(e::getPath)
.execute())
.mapToInt(Integer::intValue)
.sum();
.stream()
.map(e -> createDelete()
.where()
.like$(e::getPath)
.execute())
.mapToInt(Integer::intValue)
.sum();
}
IDGenerator<K> getIDGenerator();
@@ -153,6 +127,10 @@ public interface TreeSortEntityService<E extends TreeSortSupportEntity<K>, K>
return entity.getChildren();
}
default int getBufferSize() {
return 200;
}
default Predicate<E> createRootNodePredicate(TreeSupportEntity.TreeHelper<E, K> helper) {
return node -> {
if (isRootNode(node)) {

View File

@@ -6,6 +6,7 @@ import org.hswebframework.utils.RandomUtil;
import org.hswebframework.web.api.crud.entity.TreeSortSupportEntity;
import org.hswebframework.web.api.crud.entity.TreeSupportEntity;
import org.hswebframework.web.exception.ValidationException;
import org.hswebframework.web.id.IDGenerator;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -16,54 +17,59 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class TreeSortServiceHelper<E extends TreeSortSupportEntity<PK>, PK> {
public abstract class TreeSortServiceHelper<E extends TreeSortSupportEntity<PK>, PK> {
//包含子节点的数据
private Map<PK, E> allData;
protected Map<PK, E> allData;
private Map<PK, E> oldData;
protected Map<PK, E> oldData;
private Map<PK, E> thisTime;
protected Map<PK, E> thisTime;
private Map<PK, E> readyToSave;
protected Map<PK, E> readyToSave;
private final Map<PK, Map<PK, E>> childrenMapping = new LinkedHashMap<>();
protected final Map<PK, Map<PK, E>> childrenMapping = new LinkedHashMap<>();
private final ReactiveTreeSortEntityService<E, PK> service;
protected abstract IDGenerator<PK> getIdGenerator();
TreeSortServiceHelper(ReactiveTreeSortEntityService<E, PK> service) {
this.service = service;
}
protected abstract void applyChildren(E parent, List<E> children);
Flux<E> prepare(Flux<E> source) {
protected abstract boolean isRootNode(E node);
protected abstract Flux<E> queryIncludeChildren(Collection<PK> idList);
protected abstract Flux<E> queryById(Collection<PK> idList);
public Flux<E> prepare(Flux<E> source) {
Flux<E> cache = source
.flatMapIterable(e -> TreeSupportEntity.expandTree2List(e, service.getIDGenerator()))
.collectList()
.flatMapIterable(list -> {
.flatMapIterable(e -> TreeSupportEntity.expandTree2List(e, getIdGenerator()))
.collectList()
.flatMapIterable(list -> {
Map<PK, E> map = list
.stream()
.filter(e -> e.getId() != null)
.collect(Collectors.toMap(
TreeSupportEntity::getId,
Function.identity(),
(a, b) -> a
));
//重新组装树结构
TreeSupportEntity.list2tree(list,
service::setChildren,
(Predicate<E>) e -> service.isRootNode(e) || map.get(e.getParentId()) == null);
Map<PK, E> map = list
.stream()
.filter(e -> e.getId() != null)
.collect(Collectors.toMap(
TreeSupportEntity::getId,
Function.identity(),
(a, b) -> a
));
//重新组装树结构
TreeSupportEntity.list2tree(list,
this::applyChildren,
(Predicate<E>) e -> isRootNode(e) || map.get(e.getParentId()) == null);
return list;
})
.cache();
return list;
})
.cache();
return init(cache)
.then(Mono.defer(this::checkParentId))
.then(Mono.fromRunnable(this::checkCyclicDependency))
.then(Mono.fromRunnable(this::refactorPath))
.thenMany(Flux.defer(() -> Flux.fromIterable(readyToSave.values())))
.doOnNext(this::refactor);
.then(Mono.defer(this::checkParentId))
.then(Mono.fromRunnable(this::checkCyclicDependency))
.then(Mono.fromRunnable(this::refactorPath))
.thenMany(Flux.defer(() -> Flux.fromIterable(readyToSave.values())))
.doOnNext(this::refactor);
}
private Mono<Void> init(Flux<E> source) {
@@ -73,49 +79,49 @@ public class TreeSortServiceHelper<E extends TreeSortSupportEntity<PK>, PK> {
readyToSave = new LinkedHashMap<>();
Mono<Map<PK, E>> allDataFetcher =
source
.mapNotNull(e -> {
source
.mapNotNull(e -> {
if (e.getId() != null) {
thisTime.put(e.getId(), e);
}
return e.getId();
})
.collect(Collectors.toSet())
.flatMap(list -> service
.queryIncludeChildren(list)
.collectMap(TreeSupportEntity::getId, Function.identity()));
return allDataFetcher
.doOnNext(includeChildren -> {
//旧的数据
for (E value : thisTime.values()) {
E old = includeChildren.get(value.getId());
if (null != old) {
this.oldData.put(value.getId(), old);
}
if (e.getId() != null) {
thisTime.put(e.getId(), e);
}
readyToSave.putAll(thisTime);
allData.putAll(includeChildren);
allData.putAll(this.thisTime);
initChildren();
return e.getId();
})
.then();
.collect(Collectors.toSet())
.flatMap(list ->
queryIncludeChildren(list)
.collectMap(TreeSupportEntity::getId, Function.identity()));
return allDataFetcher
.doOnNext(includeChildren -> {
//旧的数据
for (E value : thisTime.values()) {
E old = includeChildren.get(value.getId());
if (null != old) {
this.oldData.put(value.getId(), old);
}
}
readyToSave.putAll(thisTime);
allData.putAll(includeChildren);
allData.putAll(this.thisTime);
initChildren();
})
.then();
}
private void initChildren() {
childrenMapping.clear();
for (E value : allData.values()) {
if (service.isRootNode(value) || value.getId() == null) {
if (isRootNode(value) || value.getId() == null) {
continue;
}
childrenMapping
.computeIfAbsent(value.getParentId(), ignore -> new LinkedHashMap<>())
.put(value.getId(), value);
.computeIfAbsent(value.getParentId(), ignore -> new LinkedHashMap<>())
.put(value.getId(), value);
}
}
@@ -144,43 +150,40 @@ public class TreeSortServiceHelper<E extends TreeSortSupportEntity<PK>, PK> {
}
Set<PK> readyToCheck = thisTime
.values()
.stream()
.map(TreeSupportEntity::getParentId)
.filter(e -> !ObjectUtils.isEmpty(e) && !allData.containsKey(e))
.collect(Collectors.toSet());
.values()
.stream()
.map(TreeSupportEntity::getParentId)
.filter(e -> !ObjectUtils.isEmpty(e) && !allData.containsKey(e))
.collect(Collectors.toSet());
if (readyToCheck.isEmpty()) {
return Mono.empty();
}
return service
.createQuery()
.in("id", readyToCheck)
.fetch()
.doOnNext(e -> {
allData.put(e.getId(), e);
readyToCheck.remove(e.getId());
})
.then(Mono.fromRunnable(() -> {
if (!readyToCheck.isEmpty()) {
throw new ValidationException(
return queryById(readyToCheck)
.doOnNext(e -> {
allData.put(e.getId(), e);
readyToCheck.remove(e.getId());
})
.then(Mono.fromRunnable(() -> {
if (!readyToCheck.isEmpty()) {
throw new ValidationException(
"error.tree_entity_parent_id_not_exist",
Collections.singletonList(
new ValidationException.Detail(
"parentId",
"error.tree_entity_parent_id_not_exist",
Collections.singletonList(
new ValidationException.Detail(
"parentId",
"error.tree_entity_parent_id_not_exist",
readyToCheck))
);
}
initChildren();
}));
readyToCheck))
);
}
initChildren();
}));
}
private void refactorPath() {
Function<PK, Collection<E>> childGetter
= id -> childrenMapping
.getOrDefault(id, Collections.emptyMap())
.values();
= id -> childrenMapping
.getOrDefault(id, Collections.emptyMap())
.values();
for (E data : thisTime.values()) {
E old = data.getId() == null ? null : oldData.get(data.getId());
@@ -200,7 +203,7 @@ public class TreeSortServiceHelper<E extends TreeSortSupportEntity<PK>, PK> {
};
//变更到了顶级节点
if (service.isRootNode(data)) {
if (isRootNode(data)) {
data.setPath(RandomUtil.randomChar(4));
this.refactorChildPath(old.getId(), data.getPath(), childConsumer);
//重新保存所有子节点
@@ -240,11 +243,11 @@ public class TreeSortServiceHelper<E extends TreeSortSupportEntity<PK>, PK> {
private void putChildToReadyToSave(Function<PK, Collection<E>> childGetter, E data) {
childGetter
.apply(data.getId())
.forEach(e -> {
readyToSave.put(e.getId(), e);
putChildToReadyToSave(childGetter, e);
});
.apply(data.getId())
.forEach(e -> {
readyToSave.put(e.getId(), e);
putChildToReadyToSave(childGetter, e);
});
}
private void refactor(E e) {

View File

@@ -146,31 +146,31 @@ public class DefaultR2dbcExecutor extends R2dbcReactiveSqlExecutor {
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRES_NEW, transactionManager = TransactionManagers.reactiveTransactionManager)
public Mono<Void> execute(SqlRequest request) {
return super.execute(request);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRES_NEW, transactionManager = TransactionManagers.reactiveTransactionManager)
public Mono<Void> execute(Publisher<SqlRequest> request) {
return super.execute(request);
}
@Override
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
public Mono<Integer> update(Publisher<SqlRequest> request) {
return super.update(request);
}
@Override
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
public Mono<Integer> update(SqlRequest request) {
return super.update(request);
}
@Override
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
@Transactional(rollbackFor = Throwable.class, transactionManager = TransactionManagers.reactiveTransactionManager)
public Mono<Integer> update(String sql, Object... args) {
return super.update(sql, args);
}

View File

@@ -176,7 +176,7 @@ public class CommonErrorControllerAdvice {
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Mono<ResponseMessage<?>> handleException(jakarta.validation.ValidationException e) {
return Mono.just(ResponseMessage.error(400, CodeConstants.Error.illegal_argument, e.getMessage()));
return Mono.just(ResponseMessage.error(400, CodeConstants.Error.illegal_argument, e.getLocalizedMessage()));
}
@ExceptionHandler
@@ -196,7 +196,7 @@ public class CommonErrorControllerAdvice {
log.warn(e.getLocalizedMessage(), e);
return LocaleUtils
.resolveMessageReactive("error.internal_server_error")
.map(msg -> ResponseMessage.error(500, "internal_server_error", msg));
.map(msg -> ResponseMessage.error(500, CodeConstants.Error.internal_server_error, msg));
}
@ExceptionHandler
@@ -206,7 +206,7 @@ public class CommonErrorControllerAdvice {
return LocaleUtils
.resolveMessageReactive("error.internal_server_error")
.map(msg -> ResponseMessage.error(500, "internal_server_error", msg));
.map(msg -> ResponseMessage.error(500, CodeConstants.Error.internal_server_error, msg));
}
@ExceptionHandler

View File

@@ -14,6 +14,7 @@ import org.hswebframework.web.i18n.LocaleUtils;
import org.hswebframework.web.logger.ReactiveLogger;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -28,6 +29,7 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import reactor.core.publisher.Mono;
import jakarta.validation.ConstraintViolationException;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
@@ -39,7 +41,7 @@ public class CommonWebMvcErrorControllerAdvice {
private String resolveMessage(Throwable e) {
if (e instanceof I18nSupportException) {
return LocaleUtils.resolveMessage(((I18nSupportException) e).getI18nCode());
return LocaleUtils.resolveMessage(((I18nSupportException) e).getI18nCode(),((I18nSupportException) e).getArgs());
}
return e.getMessage() == null ? null : LocaleUtils.resolveMessage(e.getMessage());
}
@@ -63,8 +65,8 @@ public class CommonWebMvcErrorControllerAdvice {
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ResponseMessage<TokenState> handleException(UnAuthorizedException e) {
return ResponseMessage
.<TokenState>error(401, CodeConstants.Error.unauthorized, resolveMessage(e))
.result(e.getState());
.<TokenState>error(401, CodeConstants.Error.unauthorized, resolveMessage(e))
.result(e.getState());
}
@@ -85,9 +87,8 @@ public class CommonWebMvcErrorControllerAdvice {
public ResponseMessage<List<ValidationException.Detail>> handleException(ValidationException e) {
return ResponseMessage
.<List<ValidationException.Detail>>error(400, CodeConstants.Error.illegal_argument, resolveMessage(e))
.result(e.getDetails())
;
.<List<ValidationException.Detail>>error(400, CodeConstants.Error.illegal_argument, resolveMessage(e))
.result(e.getDetails());
}
@ExceptionHandler
@@ -100,24 +101,24 @@ public class CommonWebMvcErrorControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseMessage<List<ValidationException.Detail>> handleException(BindException e) {
return handleException(new ValidationException(e.getMessage(), e
.getBindingResult().getAllErrors()
.stream()
.filter(FieldError.class::isInstance)
.map(FieldError.class::cast)
.map(err -> new ValidationException.Detail(err.getField(), err.getDefaultMessage(), null))
.collect(Collectors.toList())));
.getBindingResult().getAllErrors()
.stream()
.filter(FieldError.class::isInstance)
.map(FieldError.class::cast)
.map(err -> new ValidationException.Detail(err.getField(), err.getDefaultMessage(), null))
.collect(Collectors.toList())));
}
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseMessage<List<ValidationException.Detail>> handleException(WebExchangeBindException e) {
return handleException(new ValidationException(e.getMessage(), e
.getBindingResult().getAllErrors()
.stream()
.filter(FieldError.class::isInstance)
.map(FieldError.class::cast)
.map(err -> new ValidationException.Detail(err.getField(), err.getDefaultMessage(), null))
.collect(Collectors.toList())));
.getBindingResult().getAllErrors()
.stream()
.filter(FieldError.class::isInstance)
.map(FieldError.class::cast)
.map(err -> new ValidationException.Detail(err.getField(), err.getDefaultMessage(), null))
.collect(Collectors.toList())));
}
@@ -125,18 +126,18 @@ public class CommonWebMvcErrorControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseMessage<List<ValidationException.Detail>> handleException(MethodArgumentNotValidException e) {
return handleException(new ValidationException(e.getMessage(), e
.getBindingResult().getAllErrors()
.stream()
.filter(FieldError.class::isInstance)
.map(FieldError.class::cast)
.map(err -> new ValidationException.Detail(err.getField(), err.getDefaultMessage(), null))
.collect(Collectors.toList())));
.getBindingResult().getAllErrors()
.stream()
.filter(FieldError.class::isInstance)
.map(FieldError.class::cast)
.map(err -> new ValidationException.Detail(err.getField(), err.getDefaultMessage(), null))
.collect(Collectors.toList())));
}
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseMessage<?> handleException(jakarta.validation.ValidationException e) {
return ResponseMessage.error(400, CodeConstants.Error.illegal_argument, e.getMessage());
return ResponseMessage.error(400, CodeConstants.Error.illegal_argument, e.getLocalizedMessage());
}
@ExceptionHandler
@@ -150,7 +151,18 @@ public class CommonWebMvcErrorControllerAdvice {
@Order
public ResponseMessage<Object> handleException(RuntimeException e) {
log.warn(e.getLocalizedMessage(), e);
return ResponseMessage.error(resolveMessage(e));
return ResponseMessage.error(CodeConstants.Error.internal_server_error,
LocaleUtils.resolveMessage("error.internal_server_error"));
}
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
@Order
public ResponseMessage<Object> handleException(HttpMessageNotReadableException e) {
return ResponseMessage
.error(400,
"missing_request_body",
LocaleUtils.resolveMessage("error.missing_request_body"));
}
@ExceptionHandler
@@ -182,8 +194,8 @@ public class CommonWebMvcErrorControllerAdvice {
log.warn(e.getLocalizedMessage(), e);
return ResponseMessage
.error(415, "unsupported_media_type", LocaleUtils.resolveMessage("error.unsupported_media_type"))
.result(e.getSupportedMediaTypes());
.error(415, "unsupported_media_type", LocaleUtils.resolveMessage("error.unsupported_media_type"))
.result(e.getSupportedMediaTypes());
}
@ExceptionHandler
@@ -192,9 +204,9 @@ public class CommonWebMvcErrorControllerAdvice {
log.warn(e.getLocalizedMessage(), e);
return ResponseMessage
.error(406, "not_acceptable_media_type", LocaleUtils
.resolveMessage("error.not_acceptable_media_type"))
.result(e.getSupportedMediaTypes());
.error(406, "not_acceptable_media_type", LocaleUtils
.resolveMessage("error.not_acceptable_media_type"))
.result(e.getSupportedMediaTypes());
}
@ExceptionHandler
@@ -203,8 +215,8 @@ public class CommonWebMvcErrorControllerAdvice {
log.warn(e.getLocalizedMessage(), e);
return ResponseMessage
.error(406, "method_not_allowed", LocaleUtils.resolveMessage("error.method_not_allowed"))
.result(e.getSupportedMethods());
.error(406, "method_not_allowed", LocaleUtils.resolveMessage("error.method_not_allowed"))
.result(e.getSupportedMethods());
}
@@ -220,7 +232,7 @@ public class CommonWebMvcErrorControllerAdvice {
} while (exception != null && exception != e);
if (exception == null) {
return ResponseMessage.error(400, CodeConstants.Error.illegal_argument, e.getMessage());
return ResponseMessage.error(400, CodeConstants.Error.illegal_argument, e.getMessage());
}
return ResponseMessage.error(400, CodeConstants.Error.illegal_argument, resolveMessage(exception));
}

View File

@@ -0,0 +1,67 @@
package org.hswebframework.web.crud.web;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import org.hswebframework.web.api.crud.entity.QueryOperation;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.api.crud.entity.TreeSortSupportEntity;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.authorization.annotation.QueryAction;
import org.hswebframework.web.crud.service.ReactiveTreeSortEntityService;
import org.hswebframework.web.crud.service.TreeSortEntityService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
public interface TreeServiceQueryController<E extends TreeSortSupportEntity<K>, K> {
@Authorize(ignore = true)
TreeSortEntityService<E, K> getService();
@GetMapping("/_query/tree")
@QueryAction
@QueryOperation(summary = "使用GET动态查询并返回树形结构")
default List<E> findAllTree(@Parameter(hidden = true) QueryParamEntity param) {
return getService().queryResultToTree(param);
}
@GetMapping("/_query/_children")
@QueryAction
@QueryOperation(summary = "使用GET动态查询并返回子节点数据")
default List<E> findAllChildren(@Parameter(hidden = true) QueryParamEntity param) {
return getService().queryIncludeChildren(param);
}
@GetMapping("/_query/_children/tree")
@QueryAction
@QueryOperation(summary = "使用GET动态查询并返回子节点树形结构数据")
default List<E> findAllChildrenTree(@Parameter(hidden = true) QueryParamEntity param) {
return getService().queryIncludeChildrenTree(param);
}
@PostMapping("/_query/tree")
@QueryAction
@Operation(summary = "使用POST动态查询并返回树形结构")
default List<E> findAllTreePost(@RequestBody QueryParamEntity param) {
return getService().queryResultToTree(param);
}
@PostMapping("/_query/_children")
@QueryAction
@Operation(summary = "使用POST动态查询并返回子节点数据")
default List<E> findAllChildrenPost(@RequestBody QueryParamEntity param) {
return getService().queryIncludeChildren(param);
}
@PostMapping("/_query/_children/tree")
@QueryAction
@Operation(summary = "使用POST动态查询并返回子节点树形结构数据")
default List<E> findAllChildrenTreePost(@RequestBody QueryParamEntity param) {
return getService().queryIncludeChildrenTree(param);
}
}

View File

@@ -8,4 +8,5 @@ error.tree_entity_cyclic_dependency=Cannot modify parent node as oneself or one'
error.tree_entity_parent_id_not_exist=Parent node does not exist or has been deleted
error.resource_not_found=Resource not found
error.data.find.not_found=Data not found
error.sql.prepare.failed.IndexOutOfBoundsException=Execute SQL failed, try check config: `easyorm.dialect`.
error.sql.prepare.failed.IndexOutOfBoundsException=Execute SQL failed, try check config: `easyorm.dialect`.
error.missing_request_body=Required request body is missing

View File

@@ -7,4 +7,5 @@ error.internal_server_error=\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF
error.tree_entity_cyclic_dependency=\u4E0D\u80FD\u4FEE\u6539\u7236\u8282\u70B9\u4E3A\u81EA\u5DF1\u6216\u8005\u81EA\u5DF1\u7684\u5B50\u8282\u70B9
error.tree_entity_parent_id_not_exist=\u7236\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u5220\u9664
error.data.find.not_found=\u6570\u636E\u4E0D\u5B58\u5728
error.sql.prepare.failed.IndexOutOfBoundsException=SQL\u6267\u884C\u5931\u8D25,\u8BF7\u5C1D\u8BD5\u68C0\u67E5`easyorm.dialect`\u914D\u7F6E.
error.sql.prepare.failed.IndexOutOfBoundsException=SQL\u6267\u884C\u5931\u8D25,\u8BF7\u5C1D\u8BD5\u68C0\u67E5`easyorm.dialect`\u914D\u7F6E.
error.missing_request_body=\u8BF7\u6C42\u4F53\u7F3A\u5931

View File

@@ -12,6 +12,8 @@ public interface CodeConstants {
String unauthorized = "unauthorized";
String not_found="not_found";
String internal_server_error="internal_server_error";
}
}