Browse Source

实现实体变更事件

zhou-hao 5 years ago
parent
commit
a716e05974

+ 16 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/annotation/EnableEntityEvent.java

@@ -0,0 +1,16 @@
+package org.hswebframework.web.crud.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @see org.hswebframework.web.crud.events.EntityModifyEvent
+ * @see org.hswebframework.web.crud.events.EntityDeletedEvent
+ * @see org.hswebframework.web.crud.events.EntityCreatedEvent
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface EnableEntityEvent {
+
+}

+ 13 - 4
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/EasyOrmConfiguration.java

@@ -18,6 +18,7 @@ import org.hswebframework.web.api.crud.entity.EntityFactory;
 import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
 import org.hswebframework.web.crud.entity.factory.MapperEntityFactory;
 import org.hswebframework.web.crud.events.CompositeEventListener;
+import org.hswebframework.web.crud.events.EntityEventListener;
 import org.hswebframework.web.crud.events.ValidateEventListener;
 import org.hswebframework.web.crud.generator.CurrentTimeGenerator;
 import org.hswebframework.web.crud.generator.DefaultIdGenerator;
@@ -43,6 +44,9 @@ public class EasyOrmConfiguration {
     @Autowired
     private EasyormProperties properties;
 
+    static {
+
+    }
     @Bean
     @ConditionalOnMissingBean
     public EntityFactory entityFactory() {
@@ -62,7 +66,7 @@ public class EasyOrmConfiguration {
             @Override
             public EntityColumnMapping getMapping(Class entity) {
 
-                return resolver.resolve(entityFactory.getInstanceType(entity,true))
+                return resolver.resolve(entityFactory.getInstanceType(entity, true))
                         .getFeature(MappingFeatureType.columnPropertyMapping.createFeatureId(entity))
                         .map(EntityColumnMapping.class::cast)
                         .orElse(null);
@@ -118,17 +122,22 @@ public class EasyOrmConfiguration {
         return new BeanPostProcessor() {
             @Override
             public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
-                if (bean instanceof Feature) {
-                    metadata.addFeature(((Feature) bean));
-                }
+
                 if (bean instanceof EventListener) {
                     eventListener.addListener(((EventListener) bean));
+                } else if (bean instanceof Feature) {
+                    metadata.addFeature(((Feature) bean));
                 }
+
                 return bean;
             }
         };
     }
 
+    @Bean
+    public EntityEventListener entityEventListener(){
+        return new EntityEventListener();
+    }
 
     @Bean
     public ValidateEventListener validateEventListener() {

+ 6 - 1
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/EntityCreatedEvent.java

@@ -4,12 +4,17 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 
 import java.io.Serializable;
+import java.util.List;
 
+/**
+ * @see org.hswebframework.web.crud.annotation.EnableEntityEvent
+ * @param <E>
+ */
 @AllArgsConstructor
 @Getter
 public class EntityCreatedEvent<E> implements Serializable {
 
-    private E entity;
+    private List<E> entity;
 
     private Class<E> entityType;
 }

+ 24 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/EntityDeletedEvent.java

@@ -0,0 +1,24 @@
+package org.hswebframework.web.crud.events;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @param <E>
+ * @see org.hswebframework.web.crud.annotation.EnableEntityEvent
+ */
+@AllArgsConstructor
+@Getter
+public class EntityDeletedEvent<E> implements Serializable {
+
+    private static final long serialVersionUID = -7158901204884303777L;
+
+    private List<E> entity;
+
+    private Class<E> entityType;
+
+}

+ 179 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/EntityEventListener.java

@@ -0,0 +1,179 @@
+package org.hswebframework.web.crud.events;
+
+
+import org.hswebframework.ezorm.core.GlobalConfig;
+import org.hswebframework.ezorm.rdb.events.*;
+import org.hswebframework.ezorm.rdb.mapping.*;
+import org.hswebframework.ezorm.rdb.mapping.events.MappingContextKeys;
+import org.hswebframework.ezorm.rdb.mapping.events.MappingEventTypes;
+import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
+import org.hswebframework.ezorm.rdb.metadata.TableOrViewMetadata;
+import org.hswebframework.web.api.crud.entity.Entity;
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.hswebframework.web.crud.annotation.EnableEntityEvent;
+import org.hswebframework.web.event.GenericsPayloadApplicationEvent;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SuppressWarnings("all")
+public class EntityEventListener implements EventListener {
+
+    @Autowired
+    ApplicationEventPublisher eventPublisher;
+
+    @Override
+    public String getId() {
+        return "entity-listener";
+    }
+
+    @Override
+    public String getName() {
+        return "实体变更事件监听器";
+    }
+
+    @Override
+    public void onEvent(EventType type, EventContext context) {
+
+        if (context.get(MappingContextKeys.error).isPresent()) {
+            return;
+        }
+        EntityColumnMapping mapping = context.get(MappingContextKeys.columnMapping).orElse(null);
+        if (mapping == null ||
+                !Entity.class.isAssignableFrom(mapping.getEntityType()) ||
+                mapping.getEntityType().getAnnotation(EnableEntityEvent.class) == null) {
+            return;
+        }
+
+        if (type == MappingEventTypes.insert_after) {
+            boolean single = context.get(MappingContextKeys.type).map("single"::equals).orElse(false);
+            if (single) {
+                handleInsertSingle(mapping.getEntityType(), context);
+            } else {
+                handleInsertBatch(mapping.getEntityType(), context);
+            }
+        }
+        if (type == MappingEventTypes.update_before) {
+            handleUpdateBefore(context);
+        }
+
+        if (type == MappingEventTypes.delete_before) {
+            handleDeleteBefore(context);
+        }
+    }
+
+    protected void sendUpdateEvent(List<?> olds, EventContext context) {
+        List<Object> newValues = new ArrayList<>(olds.size());
+        EntityColumnMapping mapping = context.get(MappingContextKeys.columnMapping).orElseThrow(UnsupportedOperationException::new);
+        TableOrViewMetadata table = context.get(ContextKeys.table).orElseThrow(UnsupportedOperationException::new);
+        RDBColumnMetadata idColumn = table.getColumns().stream().filter(RDBColumnMetadata::isPrimaryKey).findFirst().orElse(null);
+        if (idColumn == null) {
+            return;
+        }
+        for (Object old : olds) {
+            Object newValue = context.get(MappingContextKeys.instance)
+                    .filter(Entity.class::isInstance)
+                    .map(Entity.class::cast)
+                    .orElseGet(() -> {
+                        return context.get(MappingContextKeys.updateColumnInstance)
+                                .map(map -> {
+                                    return FastBeanCopier.copy(map, FastBeanCopier.copy(old, mapping.getEntityType()));
+                                })
+                                .map(Entity.class::cast)
+                                .orElse(null);
+                    });
+            if (newValue != null) {
+                FastBeanCopier.copy(old, newValue, FastBeanCopier.include(idColumn.getAlias()));
+            }
+            newValues.add(newValue);
+        }
+        eventPublisher.publishEvent(new GenericsPayloadApplicationEvent<>(this, new EntityModifyEvent(olds, newValues, mapping.getEntityType()), mapping.getEntityType()));
+    }
+
+    protected void sendDeleteEvent(List<?> olds, EventContext context) {
+
+        EntityColumnMapping mapping = context.get(MappingContextKeys.columnMapping).orElseThrow(UnsupportedOperationException::new);
+        TableOrViewMetadata table = context.get(ContextKeys.table).orElseThrow(UnsupportedOperationException::new);
+        eventPublisher.publishEvent(new GenericsPayloadApplicationEvent<>(this, new EntityDeletedEvent(olds, mapping.getEntityType()), mapping.getEntityType()));
+
+    }
+
+    protected void handleReactiveUpdateBefore(DSLUpdate<?, ?> update, EventContext context) {
+        Object repo = context.get(MappingContextKeys.repository).orElse(null);
+        if (repo instanceof ReactiveRepository) {
+            context.get(MappingContextKeys.reactiveResultHolder)
+                    .ifPresent(holder -> {
+                        holder.before(
+                                ((ReactiveRepository<?, ?>) repo).createQuery()
+                                        .setParam(update.toQueryParam())
+                                        .fetch()
+                                        .collectList()
+                                        .doOnSuccess(list -> {
+                                            sendUpdateEvent(list, context);
+                                        })
+                                        .then()
+                        );
+                    });
+        }
+    }
+
+    protected void handleUpdateBefore(EventContext context) {
+        context.<DSLUpdate<?, ?>>get(ContextKeys.source())
+                .ifPresent(dslUpdate -> {
+                    if (context.get(MappingContextKeys.reactive).orElse(false)) {
+                        handleReactiveUpdateBefore(dslUpdate, context);
+                    } else {
+                        // TODO: 2019-11-09
+                    }
+                });
+
+    }
+
+    protected void handleDeleteBefore(EventContext context) {
+        context.<DSLDelete>get(ContextKeys.source())
+                .ifPresent(dslUpdate -> {
+                    Object repo = context.get(MappingContextKeys.repository).orElse(null);
+                    if (repo instanceof ReactiveRepository) {
+                        context.get(MappingContextKeys.reactiveResultHolder)
+                                .ifPresent(holder -> {
+                                    holder.before(
+                                            ((ReactiveRepository<?, ?>) repo).createQuery()
+                                                    .setParam(dslUpdate.toQueryParam())
+                                                    .fetch()
+                                                    .collectList()
+                                                    .doOnSuccess(list -> {
+                                                        sendDeleteEvent(list, context);
+                                                    })
+                                                    .then()
+                                    );
+                                });
+                    }
+                });
+    }
+
+    protected void handleUpdateAfter(EventContext context) {
+
+    }
+
+    protected void handleInsertBatch(Class clazz, EventContext context) {
+
+        context.get(MappingContextKeys.instance)
+                .filter(List.class::isInstance)
+                .map(List.class::cast)
+                .ifPresent(lst -> {
+                    eventPublisher.publishEvent(new GenericsPayloadApplicationEvent<>(this, new EntityCreatedEvent(lst, clazz), clazz));
+                });
+    }
+
+    protected void handleInsertSingle(Class clazz, EventContext context) {
+        context.get(MappingContextKeys.instance)
+                .filter(Entity.class::isInstance)
+                .map(Entity.class::cast)
+                .ifPresent(entity -> {
+                    eventPublisher.publishEvent(new GenericsPayloadApplicationEvent<>(this, new EntityCreatedEvent(Collections.singletonList(entity),clazz), clazz));
+                });
+    }
+}

+ 7 - 2
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/EntityModifyEvent.java

@@ -4,16 +4,21 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 
 import java.io.Serializable;
+import java.util.List;
 
+/**
+ * @see org.hswebframework.web.crud.annotation.EnableEntityEvent
+ * @param <E>
+ */
 @AllArgsConstructor
 @Getter
 public class EntityModifyEvent<E> implements Serializable{
 
     private static final long serialVersionUID = -7158901204884303777L;
 
-    private E before;
+    private List<E> before;
 
-    private E after;
+    private List<E> after;
 
     private Class<E> entityType;
 

+ 9 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/ValidateEventListener.java

@@ -13,6 +13,15 @@ import java.util.List;
 
 public class ValidateEventListener implements EventListener {
 
+    @Override
+    public String getId() {
+        return "validate-listener";
+    }
+
+    @Override
+    public String getName() {
+        return "验证器监听器";
+    }
 
     @Override
     @SuppressWarnings("all")

+ 2 - 2
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/service/EnableCacheReactiveCrudService.java

@@ -57,12 +57,12 @@ public interface EnableCacheReactiveCrudService<E, K> extends ReactiveCrudServic
     @Override
     default ReactiveUpdate<E> createUpdate() {
         return ReactiveCrudService.super.createUpdate()
-                .onExecute(s -> s.doFinally((__) -> getCache().clear().subscribe()));
+                .onExecute((update,s) -> s.doFinally((__) -> getCache().clear().subscribe()));
     }
 
     @Override
     default ReactiveDelete createDelete() {
         return ReactiveCrudService.super.createDelete()
-                .onExecute(s -> s.doFinally((__) -> getCache().clear().subscribe()));
+                .onExecute((update,s) -> s.doFinally((__) -> getCache().clear().subscribe()));
     }
 }

+ 34 - 0
hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/entity/EventTestEntity.java

@@ -0,0 +1,34 @@
+package org.hswebframework.web.crud.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hswebframework.web.api.crud.entity.GenericEntity;
+import org.hswebframework.web.crud.annotation.EnableEntityEvent;
+import org.hswebframework.web.crud.generator.Generators;
+
+import javax.persistence.Column;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Table;
+
+@Getter
+@Setter
+@Table(name = "s_test_event")
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+@EnableEntityEvent
+public class EventTestEntity extends GenericEntity<String> {
+
+    @Column(length = 32)
+    private String name;
+
+    @Column
+    private Integer age;
+
+    @Override
+    @GeneratedValue(generator = Generators.DEFAULT_ID_GENERATOR)
+    public String getId() {
+        return super.getId();
+    }
+}

+ 90 - 0
hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/events/EntityEventListenerTest.java

@@ -0,0 +1,90 @@
+package org.hswebframework.web.crud.events;
+
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
+import org.hswebframework.web.crud.TestApplication;
+import org.hswebframework.web.crud.entity.EventTestEntity;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.transaction.reactive.TransactionalOperator;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import javax.annotation.PostConstruct;
+
+import static org.junit.Assert.*;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = TestApplication.class)
+public class EntityEventListenerTest {
+
+    @Autowired
+    private ReactiveRepository<EventTestEntity, String> reactiveRepository;
+
+    @Autowired
+    private TransactionalOperator transactionalOperator;
+
+    @Autowired
+    private TestEntityListener listener;
+
+    @Test
+    public void test() {
+        Mono.just(EventTestEntity.of("test", 1))
+                .as(reactiveRepository::insert)
+                .as(StepVerifier::create)
+                .expectNext(1)
+                .verifyComplete();
+        Assert.assertEquals(listener.created.getAndSet(0), 1);
+
+
+    }
+
+    @Test
+    public void testInsertBatch() {
+        Flux.just(EventTestEntity.of("test2", 1), EventTestEntity.of("test3", 2))
+                .as(reactiveRepository::insert)
+                .as(StepVerifier::create)
+                .expectNext(2)
+                .verifyComplete();
+        Assert.assertEquals(listener.created.getAndSet(0), 2);
+
+        reactiveRepository.createUpdate().set("age",3).where().in("name","test2","test3").execute()
+                .as(StepVerifier::create)
+                .expectNext(2)
+                .verifyComplete();
+
+        Assert.assertEquals(listener.modified.getAndSet(0), 2);
+
+        reactiveRepository.createDelete().where().in("name","test2","test3").execute()
+                .as(StepVerifier::create)
+                .expectNext(2)
+                .verifyComplete();
+
+        Assert.assertEquals(listener.deleted.getAndSet(0), 2);
+
+    }
+
+    @Test
+    @Ignore
+    public void testInsertError() {
+        Flux.just(EventTestEntity.of("test2", 1), EventTestEntity.of("test3", 2))
+                .as(reactiveRepository::insert)
+                .flatMap(i -> Mono.error(new RuntimeException()))
+                .as(transactionalOperator::transactional)
+                .as(StepVerifier::create)
+                .verifyError();
+
+        Assert.assertEquals(listener.created.getAndSet(0), 0);
+    }
+
+
+}

+ 36 - 0
hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/events/TestEntityListener.java

@@ -0,0 +1,36 @@
+package org.hswebframework.web.crud.events;
+
+import org.hswebframework.web.crud.entity.EventTestEntity;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Component
+public class TestEntityListener {
+
+    AtomicInteger created = new AtomicInteger();
+    AtomicInteger deleted = new AtomicInteger();
+
+    AtomicInteger modified = new AtomicInteger();
+
+
+    @EventListener
+    public void handleCreated(EntityCreatedEvent<EventTestEntity> event) {
+        System.out.println(event);
+        created.addAndGet(event.getEntity().size());
+    }
+
+    @EventListener
+    public void handleCreated(EntityDeletedEvent<EventTestEntity> event) {
+        System.out.println(event);
+        deleted.addAndGet(event.getEntity().size());
+    }
+
+    @EventListener
+    public void handleModify(EntityModifyEvent<EventTestEntity> event) {
+        System.out.println(event);
+        modified.addAndGet(event.getAfter().size());
+    }
+}