zhouhao 3 éve
szülő
commit
024c3d9649
72 módosított fájl, 2415 hozzáadás és 1119 törlés
  1. 1 1
      docker/run-all/docker-compose.yml
  2. 1 1
      jetlinks-components/common-component/pom.xml
  3. 87 1
      jetlinks-components/configure-component/pom.xml
  4. 2 4
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/ExecutorConfiguration.java
  5. 11 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/cluster/Cluster.java
  6. 130 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/cluster/ClusterConfiguration.java
  7. 58 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/cluster/ClusterProperties.java
  8. 44 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/cluster/FSTMessageCodec.java
  9. 41 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/crud/TableColumnCommentCustomizer.java
  10. 101 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/DeviceClusterConfiguration.java
  11. 128 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/PersistenceDeviceSessionManager.java
  12. 92 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/PersistentSessionEntity.java
  13. 32 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/UnknownDeviceSessionProvider.java
  14. 69 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/doc/SpringDocCustomizerConfiguration.java
  15. 0 64
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/r2dbc/R2dbcPoolConfiguration.java
  16. 0 43
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/r2dbc/R2dbcPoolProperties.java
  17. 69 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/trace/LoggingSpanExporter.java
  18. 80 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/trace/TraceConfiguration.java
  19. 33 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/trace/TraceExchangeFilterFunction.java
  20. 112 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/trace/TraceProperties.java
  21. 97 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/trace/TraceWebFilter.java
  22. 4 1
      jetlinks-components/configure-component/src/main/resources/META-INF/spring.factories
  23. 1 1
      jetlinks-components/dashboard-component/pom.xml
  24. 1 1
      jetlinks-components/elasticsearch-component/pom.xml
  25. 15 11
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/configuration/ElasticSearchConfiguration.java
  26. 3 1
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/strategies/AbstractElasticSearchIndexStrategy.java
  27. 262 116
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/DefaultReactiveElasticsearchClient.java
  28. 48 37
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/RawActionResponse.java
  29. 1 1
      jetlinks-components/gateway-component/pom.xml
  30. 113 0
      jetlinks-components/gateway-component/src/main/java/org/jetlinks/community/gateway/AbstractDeviceGateway.java
  31. 23 0
      jetlinks-components/gateway-component/src/main/java/org/jetlinks/community/gateway/DeviceGateway.java
  32. 8 0
      jetlinks-components/gateway-component/src/main/java/org/jetlinks/community/gateway/GatewayState.java
  33. 1 1
      jetlinks-components/io-component/pom.xml
  34. 1 1
      jetlinks-components/logging-component/pom.xml
  35. 1 1
      jetlinks-components/network-component/mqtt-component/pom.xml
  36. 64 105
      jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttClientDeviceGateway.java
  37. 1 1
      jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttClientDeviceGatewayProvider.java
  38. 163 134
      jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttServerDeviceGateway.java
  39. 1 1
      jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttServerDeviceGatewayProvider.java
  40. 1 1
      jetlinks-components/network-component/network-core/pom.xml
  41. 201 128
      jetlinks-components/network-component/network-core/src/main/java/org/jetlinks/community/network/utils/DeviceGatewayHelper.java
  42. 1 1
      jetlinks-components/network-component/pom.xml
  43. 1 1
      jetlinks-components/network-component/tcp-component/pom.xml
  44. 84 170
      jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/device/TcpServerDeviceGateway.java
  45. 1 1
      jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/device/TcpServerDeviceGatewayProvider.java
  46. 1 1
      jetlinks-components/notify-component/notify-core/pom.xml
  47. 1 1
      jetlinks-components/notify-component/notify-dingtalk/pom.xml
  48. 1 1
      jetlinks-components/notify-component/notify-email/pom.xml
  49. 1 1
      jetlinks-components/notify-component/notify-sms/pom.xml
  50. 1 1
      jetlinks-components/notify-component/notify-voice/pom.xml
  51. 1 1
      jetlinks-components/notify-component/notify-wechat/pom.xml
  52. 1 1
      jetlinks-components/notify-component/pom.xml
  53. 1 1
      jetlinks-components/pom.xml
  54. 1 1
      jetlinks-components/rule-engine-component/pom.xml
  55. 1 1
      jetlinks-components/timeseries-component/pom.xml
  56. 1 1
      jetlinks-manager/authentication-manager/pom.xml
  57. 1 1
      jetlinks-manager/device-manager/pom.xml
  58. 1 1
      jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/configuration/DeviceManagerConfiguration.java
  59. 51 23
      jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/message/DeviceMessageConnector.java
  60. 5 5
      jetlinks-manager/device-manager/src/main/resources/i18n/device-manager/messages_en.properties
  61. 5 5
      jetlinks-manager/device-manager/src/main/resources/i18n/device-manager/messages_zh.properties
  62. 1 1
      jetlinks-manager/logging-manager/pom.xml
  63. 1 1
      jetlinks-manager/network-manager/pom.xml
  64. 1 1
      jetlinks-manager/notify-manager/pom.xml
  65. 1 1
      jetlinks-manager/pom.xml
  66. 1 1
      jetlinks-manager/rule-engine-manager/pom.xml
  67. 1 1
      jetlinks-manager/visualization-manager/pom.xml
  68. 1 2
      jetlinks-standalone/pom.xml
  69. 0 132
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/JetLinksConfiguration.java
  70. 0 46
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/doc/SwaggerConfiguration.java
  71. 0 1
      jetlinks-standalone/src/main/resources/application.yml
  72. 146 57
      pom.xml

+ 1 - 1
docker/run-all/docker-compose.yml

@@ -59,7 +59,7 @@ services:
     links:
       - jetlinks:jetlinks
   jetlinks:
-    image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:1.13.0-SNAPSHOT
+    image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:1.20.0-SNAPSHOT
     container_name: jetlinks-ce
     ports:
       - "8848:8848" # API端口

+ 1 - 1
jetlinks-components/common-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 87 - 1
jetlinks-components/configure-component/pom.xml

@@ -5,7 +5,8 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -35,6 +36,91 @@
             <optional>true</optional>
         </dependency>
 
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework</groupId>
+            <artifactId>hsweb-easy-orm-rdb</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-commons-crud</artifactId>
+            <version>${hsweb.framework.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jetlinks</groupId>
+            <artifactId>jetlinks-supports</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>guava</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>de.ruedigermoeller</groupId>
+            <artifactId>fst</artifactId>
+            <version>2.57</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-webflux-core</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-access-logging-api</artifactId>
+            <version>${hsweb.framework.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-exporter-logging</artifactId>
+            <version>1.12.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-sdk-trace</artifactId>
+            <version>1.12.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-sdk</artifactId>
+            <version>1.12.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-exporter-jaeger</artifactId>
+            <version>1.12.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-protobuf</artifactId>
+            <version>1.45.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-netty-shaded</artifactId>
+            <version>1.45.0</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 2 - 4
jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/ExecutorConfiguration.java

@@ -1,4 +1,4 @@
-package org.jetlinks.community.standalone.configuration;
+package org.jetlinks.community.configure;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -6,13 +6,11 @@ import reactor.core.scheduler.Scheduler;
 import reactor.core.scheduler.Schedulers;
 
 @Configuration
-public class ExecutorConfiguration {
-
+public class JetLinksCommonConfiguration {
 
     @Bean
     public Scheduler reactorScheduler() {
         return Schedulers.parallel();
     }
 
-
 }

+ 11 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/cluster/Cluster.java

@@ -0,0 +1,11 @@
+package org.jetlinks.community.configure.cluster;
+
+public class Cluster {
+
+    static String ID = "default";
+
+
+    public static String id() {
+        return ID;
+    }
+}

+ 130 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/cluster/ClusterConfiguration.java

@@ -0,0 +1,130 @@
+package org.jetlinks.community.configure.cluster;
+
+import io.scalecube.cluster.ClusterConfig;
+import io.scalecube.net.Address;
+import io.scalecube.services.Microservices;
+import io.scalecube.services.ServiceInfo;
+import io.scalecube.services.ServiceProvider;
+import io.scalecube.services.transport.rsocket.RSocketClientTransportFactory;
+import io.scalecube.services.transport.rsocket.RSocketServerTransportFactory;
+import io.scalecube.services.transport.rsocket.RSocketServiceTransport;
+import io.scalecube.transport.netty.tcp.TcpTransportFactory;
+import org.jetlinks.core.cluster.ClusterManager;
+import org.jetlinks.core.event.EventBus;
+import org.jetlinks.supports.cluster.redis.RedisClusterManager;
+import org.jetlinks.supports.config.EventBusStorageManager;
+import org.jetlinks.supports.event.BrokerEventBus;
+import org.jetlinks.supports.event.EventBroker;
+import org.jetlinks.supports.scalecube.DynamicServiceRegistry;
+import org.jetlinks.supports.scalecube.ExtendedCluster;
+import org.jetlinks.supports.scalecube.ExtendedClusterImpl;
+import org.jetlinks.supports.scalecube.ExtendedServiceDiscoveryImpl;
+import org.jetlinks.supports.scalecube.event.ScalecubeEventBusBroker;
+import org.nustaq.serialization.FSTConfiguration;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.data.redis.core.ReactiveRedisTemplate;
+import reactor.core.scheduler.Scheduler;
+import reactor.core.scheduler.Schedulers;
+
+import java.util.stream.Collectors;
+
+@Configuration
+@EnableConfigurationProperties(ClusterProperties.class)
+@ConditionalOnClass(ExtendedCluster.class)
+public class ClusterConfiguration {
+
+    @Bean
+    public ExtendedClusterImpl cluster(ClusterProperties properties, ResourceLoader resourceLoader) {
+
+        FSTMessageCodec codec = new FSTMessageCodec(() -> {
+            FSTConfiguration configuration = FSTConfiguration
+                .createDefaultConfiguration()
+                .setForceSerializable(true);
+
+            configuration.setClassLoader(resourceLoader.getClassLoader());
+            return configuration;
+        });
+
+        ExtendedClusterImpl impl = new ExtendedClusterImpl(
+            new ClusterConfig()
+                .transport(conf -> conf
+                    .port(properties.getPort())
+                    .messageCodec(codec)
+                    .transportFactory(new TcpTransportFactory()))
+                .memberAlias(properties.getId())
+                .externalHost(properties.getExternalHost())
+                .externalPort(properties.getExternalPort())
+                .membership(conf -> conf
+                    .seedMembers(properties
+                                     .getSeeds()
+                                     .stream()
+                                     .map(Address::from)
+                                     .collect(Collectors.toList()))
+
+
+                )
+
+        );
+        impl.startAwait();
+        return impl;
+    }
+
+    @Bean
+    public EventBroker eventBroker(ExtendedCluster cluster) {
+        return new ScalecubeEventBusBroker(cluster);
+    }
+
+    @Bean
+    public BrokerEventBus eventBus(ObjectProvider<EventBroker> provider,
+                                   ObjectProvider<Scheduler> scheduler) {
+
+        BrokerEventBus eventBus = new BrokerEventBus();
+        eventBus.setPublishScheduler(scheduler.getIfAvailable(Schedulers::parallel));
+        for (EventBroker eventBroker : provider) {
+            eventBus.addBroker(eventBroker);
+        }
+
+        return eventBus;
+    }
+
+    @Bean
+    public EventBusStorageManager eventBusStorageManager(ClusterManager clusterManager, EventBus eventBus) {
+        return new EventBusStorageManager(clusterManager,
+                                          eventBus,
+                                          -1);
+    }
+
+    @Bean(initMethod = "startup")
+    public RedisClusterManager clusterManager(ClusterProperties properties, ReactiveRedisTemplate<Object, Object> template) {
+        return new RedisClusterManager(properties.getName(), properties.getId(), template);
+    }
+
+    @Bean
+    public Microservices microservices(ExtendedCluster cluster,
+                                       ObjectProvider<ServiceInfo> infos,
+                                       ObjectProvider<ServiceProvider> providers,
+                                       ClusterProperties properties) {
+        return Microservices
+            .builder()
+            .services(infos.stream().toArray())
+            .services(call -> providers
+                .stream()
+                .flatMap(provider -> provider.provide(call).stream())
+                .collect(Collectors.toList()))
+            .serviceRegistry(new DynamicServiceRegistry())
+            .discovery(serviceEndpoint -> new ExtendedServiceDiscoveryImpl(cluster, serviceEndpoint))
+            .externalHost(properties.getRpcExternalHost())
+            .externalPort(properties.getRpcExternalPort())
+            .transport(() -> new RSocketServiceTransport()
+                .serverTransportFactory(RSocketServerTransportFactory.tcp(properties.getRpcPort()))
+                .clientTransportFactory(RSocketClientTransportFactory.tcp())
+            )
+            .startAwait();
+    }
+
+}

+ 58 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/cluster/ClusterProperties.java

@@ -0,0 +1,58 @@
+package org.jetlinks.community.configure.cluster;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jetlinks.core.trace.TraceHolder;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@ConfigurationProperties(prefix = "jetlinks.cluster")
+@Getter
+@Setter
+public class ClusterProperties {
+
+    private static String NAME = "default";
+
+    private String id = "default";
+
+    private String name = NAME;
+
+    //集群节点对外暴露的host
+    private String externalHost;
+
+    //集群节点对外暴露的端口
+    private Integer externalPort;
+
+    //集群本地监听端口
+    private int port;
+
+    //集群rpc对外暴露的host
+    private String rpcExternalHost;
+
+    //集群rpc对外暴露的端口
+    private Integer rpcExternalPort;
+
+    //集群rpc调用端口
+    private int rpcPort;
+
+    private List<String> seeds = new ArrayList<>();
+
+    public void setId(String id) {
+        this.id = id;
+        Cluster.ID = id;
+        TraceHolder.setupGlobalName(id);
+    }
+
+    public void setName(String name) {
+        this.name = name;
+        NAME = name;
+    }
+
+    public static String globalName() {
+        return NAME;
+    }
+
+
+}

+ 44 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/cluster/FSTMessageCodec.java

@@ -0,0 +1,44 @@
+package org.jetlinks.community.configure.cluster;
+
+import io.netty.util.concurrent.FastThreadLocal;
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.MessageCodec;
+import lombok.AllArgsConstructor;
+import org.nustaq.serialization.FSTConfiguration;
+import org.nustaq.serialization.FSTObjectInput;
+import org.nustaq.serialization.FSTObjectOutput;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.function.Supplier;
+
+@AllArgsConstructor
+public class FSTMessageCodec implements MessageCodec {
+    private final FastThreadLocal<FSTConfiguration> configuration;
+
+    public FSTMessageCodec(Supplier<FSTConfiguration> supplier) {
+
+        this(new FastThreadLocal<FSTConfiguration>() {
+            @Override
+            protected FSTConfiguration initialValue() {
+                return supplier.get();
+            }
+        });
+    }
+
+    @Override
+    public Message deserialize(InputStream stream) throws Exception {
+        Message message = Message.builder().build();
+        try (FSTObjectInput input = configuration.get().getObjectInput(stream)) {
+            message.readExternal(input);
+        }
+        return message;
+    }
+
+    @Override
+    public void serialize(Message message, OutputStream stream) throws Exception {
+        try (FSTObjectOutput output = configuration.get().getObjectOutput(stream)) {
+            message.writeExternal(output);
+        }
+    }
+}

+ 41 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/crud/TableColumnCommentCustomizer.java

@@ -0,0 +1,41 @@
+package org.jetlinks.community.configure.crud;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
+import org.hswebframework.ezorm.rdb.metadata.RDBTableMetadata;
+import org.hswebframework.web.crud.configuration.TableMetadataCustomizer;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.Set;
+
+@Component
+public class TableColumnCommentCustomizer implements TableMetadataCustomizer {
+
+    @Override
+    public void customColumn(Class<?> entityType,
+                             PropertyDescriptor descriptor,
+                             Field field,
+                             Set<Annotation> annotations,
+                             RDBColumnMetadata column) {
+        if (StringUtils.isEmpty(column.getComment())) {
+            annotations
+                .stream()
+                .filter(Schema.class::isInstance)
+                .map(Schema.class::cast)
+                .findAny()
+                .ifPresent(schema -> column.setComment(schema.description()));
+        }
+    }
+
+    @Override
+    public void customTable(Class<?> entityType, RDBTableMetadata table) {
+        Schema schema = entityType.getAnnotation(Schema.class);
+        if (null != schema) {
+            table.setComment(schema.description());
+        }
+    }
+}

+ 101 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/DeviceClusterConfiguration.java

@@ -0,0 +1,101 @@
+package org.jetlinks.community.configure.device;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
+import io.scalecube.services.Microservices;
+import io.scalecube.services.ServiceInfo;
+import io.vavr.Lazy;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
+import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
+import org.jetlinks.community.configure.cluster.ClusterProperties;
+import org.jetlinks.core.ProtocolSupports;
+import org.jetlinks.core.cluster.ClusterManager;
+import org.jetlinks.core.config.ConfigStorageManager;
+import org.jetlinks.core.device.DeviceOperationBroker;
+import org.jetlinks.core.device.DeviceRegistry;
+import org.jetlinks.core.device.DeviceStateChecker;
+import org.jetlinks.core.device.session.DeviceSessionManager;
+import org.jetlinks.core.message.interceptor.DeviceMessageSenderInterceptor;
+import org.jetlinks.core.server.MessageHandler;
+import org.jetlinks.supports.cluster.ClusterDeviceOperationBroker;
+import org.jetlinks.supports.cluster.ClusterDeviceRegistry;
+import org.jetlinks.supports.device.session.MicroserviceDeviceSessionManager;
+import org.jetlinks.supports.scalecube.ExtendedCluster;
+import org.jetlinks.supports.server.ClusterSendToDeviceMessageHandler;
+import org.jetlinks.supports.server.DecodedClientMessageHandler;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableEasyormRepository("org.jetlinks.community.configure.device.PersistentSessionEntity")
+@ConditionalOnBean(ProtocolSupports.class)
+public class DeviceClusterConfiguration {
+
+    @Bean
+    public ClusterDeviceRegistry deviceRegistry(ProtocolSupports supports,
+                                                ClusterManager manager,
+                                                ConfigStorageManager storageManager,
+                                                DeviceOperationBroker handler) {
+
+        return new ClusterDeviceRegistry(supports,
+                                         storageManager,
+                                         manager,
+                                         handler,
+                                         CaffeinatedGuava.build(Caffeine.newBuilder()));
+    }
+
+
+    @Bean
+    @ConditionalOnBean(ClusterDeviceRegistry.class)
+    public BeanPostProcessor interceptorRegister(ClusterDeviceRegistry registry) {
+        return new BeanPostProcessor() {
+            @Override
+            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+                if (bean instanceof DeviceMessageSenderInterceptor) {
+                    registry.addInterceptor(((DeviceMessageSenderInterceptor) bean));
+                }
+                if (bean instanceof DeviceStateChecker) {
+                    registry.addStateChecker(((DeviceStateChecker) bean));
+                }
+                return bean;
+            }
+        };
+    }
+
+    @Bean(initMethod = "init", destroyMethod = "shutdown")
+    @ConditionalOnBean(Microservices.class)
+    public PersistenceDeviceSessionManager deviceSessionManager(ExtendedCluster cluster,
+                                                                Microservices microservices,
+                                                                ReactiveRepository<PersistentSessionEntity, String> repository) {
+
+        return new PersistenceDeviceSessionManager(cluster, microservices.call(),repository);
+    }
+
+    @Bean
+    public ServiceInfo sessionManagerServiceInfo(ClusterProperties properties, ApplicationContext context) {
+        return MicroserviceDeviceSessionManager
+            .createService(properties.getId(),
+                           Lazy.of(() -> context.getBean(MicroserviceDeviceSessionManager.class)));
+    }
+
+    @ConditionalOnBean(DecodedClientMessageHandler.class)
+    @Bean
+    public ClusterSendToDeviceMessageHandler defaultSendToDeviceMessageHandler(DeviceSessionManager sessionManager,
+                                                                               DeviceRegistry registry,
+                                                                               MessageHandler messageHandler,
+                                                                               DecodedClientMessageHandler clientMessageHandler) {
+        return new ClusterSendToDeviceMessageHandler(sessionManager, messageHandler, registry, clientMessageHandler);
+    }
+
+    @Bean
+    public ClusterDeviceOperationBroker clusterDeviceOperationBroker(ExtendedCluster cluster,
+                                                                     DeviceSessionManager sessionManager) {
+        return new ClusterDeviceOperationBroker(cluster, sessionManager);
+    }
+
+
+}

+ 128 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/PersistenceDeviceSessionManager.java

@@ -0,0 +1,128 @@
+package org.jetlinks.community.configure.device;
+
+import io.scalecube.services.ServiceCall;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
+import org.jetlinks.core.device.DeviceRegistry;
+import org.jetlinks.core.device.session.DeviceSessionEvent;
+import org.jetlinks.core.server.session.DeviceSession;
+import org.jetlinks.core.server.session.PersistentSession;
+import org.jetlinks.supports.device.session.MicroserviceDeviceSessionManager;
+import org.jetlinks.supports.scalecube.ExtendedCluster;
+import org.springframework.beans.BeansException;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.data.util.Lazy;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Nonnull;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@Slf4j
+public class PersistenceDeviceSessionManager extends MicroserviceDeviceSessionManager implements CommandLineRunner, ApplicationContextAware {
+    private Supplier<DeviceRegistry> registry;
+
+    private final ReactiveRepository<PersistentSessionEntity, String> repository;
+
+    public PersistenceDeviceSessionManager(ExtendedCluster cluster,
+                                           ServiceCall serviceCall,
+                                           ReactiveRepository<PersistentSessionEntity, String> repository) {
+        super(cluster, serviceCall);
+        this.repository = repository;
+    }
+
+    @Override
+    public void init() {
+
+        super.init();
+        disposable.add(
+            listenEvent(event -> {
+                //移除持久化的会话
+                if (event.getType() == DeviceSessionEvent.Type.unregister
+                    && event.getSession().isWrapFrom(PersistentSession.class)) {
+                    return removePersistentSession(
+                        event.getSession().unwrap(PersistentSession.class)
+                    );
+                }
+                return Mono.empty();
+            })
+        );
+    }
+
+    @Override
+    public void shutdown() {
+        super.shutdown();
+        Flux.fromIterable(localSessions.values())
+            .flatMap(Function.identity())
+            .filter(session -> session.isWrapFrom(PersistentSession.class))
+            .map(session -> session.unwrap(PersistentSession.class))
+            .as(this::tryPersistent)
+            .block();
+    }
+
+    @Override
+    protected Mono<DeviceSession> handleSessionCompute(DeviceSession old, DeviceSession newSession) {
+        if (old == newSession) {
+            return Mono.just(newSession);
+        }
+        if ((old == null || !old.isWrapFrom(PersistentSession.class))
+            && newSession.isWrapFrom(PersistentSession.class)) {
+            //todo 批量处理?
+            return this
+                .tryPersistent(Flux.just(newSession.unwrap(PersistentSession.class)))
+                .thenReturn(newSession);
+        }
+        return super.handleSessionCompute(old, newSession);
+    }
+
+    Mono<Void> tryPersistent(Flux<PersistentSession> sessions) {
+
+        return sessions
+            .flatMap(session -> PersistentSessionEntity.from(getCurrentServerId(), session, registry.get()))
+            .distinct(PersistentSessionEntity::getId)
+            .as(repository::save)
+            .onErrorResume(err -> {
+                log.warn("persistent session error", err);
+                return Mono.empty();
+            })
+            .then();
+    }
+
+    Mono<Void> resumeSession(PersistentSessionEntity entity) {
+        return entity
+            .toSession(registry.get())
+            .doOnNext(session -> {
+                log.debug("resume session[{}]", session.getDeviceId());
+                localSessions.putIfAbsent(session.getDeviceId(), Mono.just(session));
+            })
+            .onErrorResume((err) -> {
+                log.debug("resume session[{}] error", entity.getDeviceId(), err);
+                return Mono.empty();
+            })
+            .then();
+    }
+
+    Mono<Void> removePersistentSession(PersistentSession session) {
+        return repository
+            .deleteById(session.getId())
+            .then();
+    }
+
+    @Override
+    public void run(String... args) throws Exception {
+        repository
+            .createQuery()
+            .where(PersistentSessionEntity::getServerId, getCurrentServerId())
+            .fetch()
+            .flatMap(this::resumeSession)
+            .subscribe();
+    }
+
+    @Override
+    public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
+        this.registry = Lazy.of(() -> applicationContext.getBean(DeviceRegistry.class));
+    }
+}

+ 92 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/PersistentSessionEntity.java

@@ -0,0 +1,92 @@
+package org.jetlinks.community.configure.device;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Generated;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.codec.binary.Base64;
+import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType;
+import org.hswebframework.ezorm.rdb.mapping.annotation.Comment;
+import org.hswebframework.web.api.crud.entity.GenericEntity;
+import org.jetlinks.core.device.DeviceRegistry;
+import org.jetlinks.core.server.session.DeviceSessionProvider;
+import org.jetlinks.core.server.session.PersistentSession;
+import org.springframework.util.StringUtils;
+import reactor.core.publisher.Mono;
+
+import javax.persistence.Column;
+import javax.persistence.Index;
+import javax.persistence.Table;
+import java.sql.JDBCType;
+
+@Getter
+@Setter
+@Table(name = "dev_sessions", indexes = @Index(
+    name = "idx_session_server", columnList = "server_id"
+))
+@Comment("设备会话信息表")
+@Generated
+public class PersistentSessionEntity extends GenericEntity<String> {
+
+    /**
+     * @see DeviceSessionProvider#getId()
+     */
+    @Schema(description="设备会话提供商")
+    @Column(length = 32, nullable = false)
+    private String provider;
+
+    @Schema(description="设备连接的网关服务ID")
+    @Column(length = 64, nullable = false)
+    private String serverId;
+
+    @Schema(description="设备ID")
+    @Column(length = 64, nullable = false)
+    private String deviceId;
+
+    @Schema(description="会话超时时间")
+    @Column
+    private Long keepAliveTimeout;
+
+    @Schema(description="最近会话时间")
+    @Column
+    private Long lastKeepAliveTime;
+
+    @Schema(description="会话序列化")
+    @Column
+    @ColumnType(javaType = String.class, jdbcType = JDBCType.LONGVARCHAR)
+    private String sessionBase64;
+
+    public static Mono<PersistentSessionEntity> from(String serverId,
+                                                     PersistentSession session,
+                                                     DeviceRegistry registry) {
+        PersistentSessionEntity entity = new PersistentSessionEntity();
+
+        entity.setId(session.getId());
+        entity.setProvider(session.getProvider());
+        entity.setServerId(serverId);
+        entity.setDeviceId(session.getDeviceId());
+        entity.setKeepAliveTimeout(session.getKeepAliveTimeout().toMillis());
+        entity.setLastKeepAliveTime(session.lastPingTime());
+        DeviceSessionProvider provider = DeviceSessionProvider
+            .lookup(session.getProvider())
+            .orElseGet(UnknownDeviceSessionProvider::getInstance);
+
+        return provider
+            .serialize(session, registry)
+            .map(Base64::encodeBase64String)
+            .doOnNext(entity::setSessionBase64)
+            .thenReturn(entity);
+
+    }
+
+    public Mono<PersistentSession> toSession(DeviceRegistry registry) {
+        DeviceSessionProvider provider = DeviceSessionProvider
+            .lookup(getProvider())
+            .orElseGet(UnknownDeviceSessionProvider::getInstance);
+
+        if (StringUtils.hasText(sessionBase64)) {
+            return provider.deserialize(Base64.decodeBase64(sessionBase64), registry);
+        }
+        return Mono.empty();
+    }
+}

+ 32 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/UnknownDeviceSessionProvider.java

@@ -0,0 +1,32 @@
+package org.jetlinks.community.configure.device;
+
+import lombok.Generated;
+import org.jetlinks.core.device.DeviceRegistry;
+import org.jetlinks.core.server.session.DeviceSessionProvider;
+import org.jetlinks.core.server.session.PersistentSession;
+import reactor.core.publisher.Mono;
+
+@Generated
+public class UnknownDeviceSessionProvider implements DeviceSessionProvider {
+
+    public static UnknownDeviceSessionProvider instance = new UnknownDeviceSessionProvider();
+
+    public static UnknownDeviceSessionProvider getInstance() {
+        return instance;
+    }
+
+    @Override
+    public String getId() {
+        return "unknown";
+    }
+
+    @Override
+    public Mono<PersistentSession> deserialize(byte[] sessionData, DeviceRegistry registry) {
+        return Mono.empty();
+    }
+
+    @Override
+    public Mono<byte[]> serialize(PersistentSession session, DeviceRegistry registry) {
+        return Mono.empty();
+    }
+}

+ 69 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/doc/SpringDocCustomizerConfiguration.java

@@ -0,0 +1,69 @@
+package org.jetlinks.community.configure.doc;
+
+import org.hswebframework.web.api.crud.entity.EntityFactory;
+import org.hswebframework.web.crud.web.ResponseMessage;
+import org.reactivestreams.Publisher;
+import org.springdoc.core.ReturnTypeParser;
+import org.springdoc.webflux.core.SpringDocWebFluxConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ResolvableType;
+import org.springframework.http.ResponseEntity;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+
+@Configuration
+@AutoConfigureBefore(SpringDocWebFluxConfiguration.class)
+public class SpringDocCustomizerConfiguration {
+
+    @Bean
+    public ReturnTypeParser operationCustomizer(EntityFactory factory) {
+
+        return new ReturnTypeParser() {
+            @Override
+            public Type getReturnType(MethodParameter methodParameter) {
+                Type type = ReturnTypeParser.super.getReturnType(methodParameter);
+
+                if (type instanceof ParameterizedType) {
+                    ParameterizedType parameterizedType = ((ParameterizedType) type);
+                    Type rawType = parameterizedType.getRawType();
+                    if (rawType instanceof Class && Publisher.class.isAssignableFrom(((Class<?>) rawType))) {
+                        Type actualType = parameterizedType.getActualTypeArguments()[0];
+
+                        if (actualType instanceof ParameterizedType) {
+                            actualType = ((ParameterizedType) actualType).getRawType();
+                        }
+                        if (actualType == ResponseEntity.class || actualType == ResponseMessage.class) {
+                            return type;
+                        }
+                        boolean returnList = Flux.class.isAssignableFrom(((Class<?>) rawType));
+
+                        //统一返回ResponseMessage
+                        return ResolvableType
+                            .forClassWithGenerics(
+                                Mono.class,
+                                ResolvableType.forClassWithGenerics(
+                                   factory.getInstanceType(ResponseMessage.class),
+                                    returnList ?
+                                        ResolvableType.forClassWithGenerics(
+                                            List.class,
+                                            ResolvableType.forType(parameterizedType.getActualTypeArguments()[0])
+                                        ) :
+                                        ResolvableType.forType(parameterizedType.getActualTypeArguments()[0])
+                                ))
+                            .getType();
+
+                    }
+                }
+
+                return type;
+            }
+        };
+    }
+}

+ 0 - 64
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/r2dbc/R2dbcPoolConfiguration.java

@@ -1,64 +0,0 @@
-package org.jetlinks.community.configure.r2dbc;
-
-import io.r2dbc.pool.ConnectionPool;
-import io.r2dbc.pool.ConnectionPoolConfiguration;
-import io.r2dbc.spi.ConnectionFactory;
-import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.boot.autoconfigure.AutoConfigureBefore;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.r2dbc.*;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.boot.context.properties.PropertyMapper;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-import org.springframework.core.io.ResourceLoader;
-import org.springframework.util.StringUtils;
-
-@Configuration(proxyBeanMethods = false)
-@ConditionalOnClass(ConnectionPool.class)
-@ConditionalOnMissingBean(ConnectionFactory.class)
-@EnableConfigurationProperties({
-    R2dbcPoolProperties.class,
-    R2dbcProperties.class
-})
-@AutoConfigureBefore(R2dbcAutoConfiguration.class)
-public class R2dbcPoolConfiguration {
-
-    @Bean(destroyMethod = "dispose")
-    @Primary
-    ConnectionPool connectionFactory(R2dbcProperties properties,
-                                     R2dbcPoolProperties poolProperties,
-                                     ResourceLoader resourceLoader,
-                                     ObjectProvider<ConnectionFactoryOptionsBuilderCustomizer> customizers) {
-        ConnectionFactory connectionFactory = ConnectionFactoryBuilder
-            .of(properties, () -> EmbeddedDatabaseConnection.get(resourceLoader.getClassLoader()))
-            .configure((options) -> {
-                for (ConnectionFactoryOptionsBuilderCustomizer optionsCustomizer : customizers) {
-                    optionsCustomizer.customize(options);
-                }
-            })
-            .build();
-        R2dbcProperties.Pool pool = properties.getPool();
-
-        ConnectionPoolConfiguration.Builder builder = ConnectionPoolConfiguration.builder(connectionFactory);
-        PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
-        map.from(pool.getMaxIdleTime()).to(builder::maxIdleTime);
-        map.from(poolProperties.getMaxLifeTime()).to(builder::maxLifeTime);
-        map.from(poolProperties.getMaxAcquireTime()).to(builder::maxAcquireTime);
-        map.from(poolProperties.getMaxCreateConnectionTime()).to(builder::maxCreateConnectionTime);
-        map.from(pool.getInitialSize()).to(builder::initialSize);
-        map.from(pool.getMaxSize()).to(builder::maxSize);
-        map.from(pool.getValidationQuery()).whenHasText().to(builder::validationQuery);
-        map.from(poolProperties.getValidationDepth()).to(builder::validationDepth);
-        map.from(poolProperties.getAcquireRetry()).to(builder::acquireRetry);
-
-        if (StringUtils.hasText(pool.getValidationQuery())) {
-            builder.validationQuery(pool.getValidationQuery());
-        }
-
-        return new ConnectionPool(builder.build());
-    }
-
-}

+ 0 - 43
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/r2dbc/R2dbcPoolProperties.java

@@ -1,43 +0,0 @@
-package org.jetlinks.community.configure.r2dbc;
-
-import io.r2dbc.spi.ValidationDepth;
-import lombok.Getter;
-import lombok.Setter;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-import java.time.Duration;
-
-@Getter
-@Setter
-@ConfigurationProperties(prefix = "spring.r2dbc.pool")
-public class R2dbcPoolProperties {
-
-    /**
-     * Maximum lifetime of a connection in the pool. By default, connections have an
-     * infinite lifetime.
-     */
-    private Duration maxLifeTime = Duration.ofMinutes(10);
-
-    /**
-     * Maximum time to acquire a connection from the pool. By default, wait
-     * indefinitely.
-     */
-    private Duration maxAcquireTime;
-
-    private int acquireRetry = 3;
-    /**
-     * Maximum time to wait to create a new connection. By default, wait indefinitely.
-     */
-    private Duration maxCreateConnectionTime;
-
-    /**
-     * Validation query.
-     */
-    private String validationQuery;
-
-    /**
-     * Validation depth.
-     */
-    private ValidationDepth validationDepth = ValidationDepth.LOCAL;
-
-}

+ 69 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/trace/LoggingSpanExporter.java

@@ -0,0 +1,69 @@
+package org.jetlinks.community.configure.trace;
+
+import io.opentelemetry.sdk.common.CompletableResultCode;
+import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.opentelemetry.sdk.trace.export.SpanExporter;
+import lombok.AllArgsConstructor;
+import org.jetlinks.core.utils.StringBuilderUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import java.util.Collection;
+
+@AllArgsConstructor
+final class LoggingSpanExporter implements SpanExporter {
+    private final Logger logger;
+
+    public static LoggingSpanExporter create(String name) {
+        return new LoggingSpanExporter(LoggerFactory.getLogger(name));
+    }
+
+    @Override
+    public CompletableResultCode export(@Nonnull Collection<SpanData> spans) {
+        if (!logger.isTraceEnabled()) {
+            return CompletableResultCode.ofSuccess();
+        }
+        for (SpanData span : spans) {
+            String log = StringBuilderUtils.buildString(span, ((data, sb) -> {
+                InstrumentationLibraryInfo instrumentationLibraryInfo = data.getInstrumentationLibraryInfo();
+                sb.append("'")
+                  .append(data.getName())
+                  .append("' : ")
+                  .append(data.getTraceId())
+                  .append(" ")
+                  .append(data.getSpanId())
+                  .append(" ")
+                  .append(data.getKind())
+                  .append(" [tracer: ")
+                  .append(instrumentationLibraryInfo.getName())
+                  .append(":")
+                  .append(
+                      instrumentationLibraryInfo.getVersion() == null
+                          ? ""
+                          : instrumentationLibraryInfo.getVersion())
+                  .append("] ")
+                  .append(data.getAttributes());
+            }));
+
+            logger.trace(log);
+        }
+        return CompletableResultCode.ofSuccess();
+    }
+
+    /**
+     * Flushes the data.
+     *
+     * @return the result of the operation
+     */
+    @Override
+    public CompletableResultCode flush() {
+        return CompletableResultCode.ofSuccess();
+    }
+
+    @Override
+    public CompletableResultCode shutdown() {
+        return flush();
+    }
+}

+ 80 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/trace/TraceConfiguration.java

@@ -0,0 +1,80 @@
+package org.jetlinks.community.configure.trace;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
+import io.opentelemetry.context.propagation.ContextPropagators;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.resources.Resource;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
+import io.opentelemetry.sdk.trace.SpanProcessor;
+import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
+import org.jetlinks.community.configure.cluster.ClusterProperties;
+import org.jetlinks.core.event.EventBus;
+import org.jetlinks.core.trace.EventBusSpanExporter;
+import org.jetlinks.core.trace.TraceHolder;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties(TraceProperties.class)
+//@ConditionalOnProperty(prefix = "trace", name = "enabled", havingValue = "true", matchIfMissing = true)
+public class TraceConfiguration {
+
+    @Bean
+    public TraceWebFilter traceWebFilter() {
+        return new TraceWebFilter();
+    }
+
+    //推送跟踪信息到eventBus中
+    @Bean
+    public SpanProcessor eventBusSpanExporter(EventBus eventBus) {
+        return SimpleSpanProcessor.create(
+            EventBusSpanExporter.create(eventBus)
+        );
+    }
+
+    @Bean
+    public OpenTelemetry createTelemetry(ObjectProvider<SpanProcessor> spanProcessors,
+                                         ClusterProperties clusterProperties,
+                                         TraceProperties traceProperties) {
+        SdkTracerProviderBuilder sdkTracerProvider = SdkTracerProvider.builder();
+        spanProcessors.forEach(sdkTracerProvider::addSpanProcessor);
+        traceProperties.buildProcessors().forEach(sdkTracerProvider::addSpanProcessor);
+        SdkTracerProvider tracerProvider = sdkTracerProvider
+            .setResource(Resource
+                             .builder()
+                             .put("service.name", clusterProperties.getId())
+                             .build())
+            .build();
+
+        Runtime.getRuntime().addShutdownHook(new Thread(tracerProvider::close));
+
+        OpenTelemetrySdk telemetry= OpenTelemetrySdk
+            .builder()
+            .setTracerProvider(tracerProvider)
+            .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
+            .build();
+        TraceHolder.setup(telemetry);
+        try {
+            GlobalOpenTelemetry.set(telemetry);
+        }catch (Throwable ignore){
+
+        }
+        return telemetry;
+    }
+
+    @Bean
+    public WebClientCustomizer traceWebClientCustomizer(OpenTelemetry openTelemetry) {
+        return builder -> builder
+            .filters(filters -> {
+                if (!filters.contains(TraceExchangeFilterFunction.instance())) {
+                    filters.add(TraceExchangeFilterFunction.instance());
+                }
+            });
+    }
+}

+ 33 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/trace/TraceExchangeFilterFunction.java

@@ -0,0 +1,33 @@
+package org.jetlinks.community.configure.trace;
+
+import org.jetlinks.core.trace.TraceHolder;
+import org.springframework.web.reactive.function.client.ClientRequest;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.ExchangeFunction;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Nonnull;
+
+public final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
+
+    private static final TraceExchangeFilterFunction INSTANCE = new TraceExchangeFilterFunction();
+
+
+    public static ExchangeFilterFunction instance() {
+        return INSTANCE;
+    }
+
+    private TraceExchangeFilterFunction() {
+    }
+
+    @Override
+    @Nonnull
+    public Mono<ClientResponse> filter(@Nonnull ClientRequest request,
+                                       @Nonnull ExchangeFunction next) {
+        return TraceHolder
+            .writeContextTo(ClientRequest.from(request), ClientRequest.Builder::header)
+            .flatMap(builder -> next.exchange(builder.build()));
+    }
+
+}

+ 112 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/trace/TraceProperties.java

@@ -0,0 +1,112 @@
+package org.jetlinks.community.configure.trace;
+
+import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter;
+import io.opentelemetry.sdk.trace.SpanProcessor;
+import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
+import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
+import io.opentelemetry.sdk.trace.export.SpanExporter;
+import lombok.Getter;
+import lombok.Setter;
+import org.jetlinks.core.trace.TraceHolder;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+@ConfigurationProperties(prefix = "trace")
+@Getter
+@Setter
+public class TraceProperties {
+
+    private boolean enabled = true;
+
+    private Set<String> ignoreSpans;
+
+    //记录跟踪信息到Jaeger
+    private Jaeger jaeger;
+
+    //打印跟踪信息到日志
+    private Logging logging = new Logging();
+
+
+    public void setIgnoreSpans(Set<String> ignoreSpans) {
+        this.ignoreSpans = ignoreSpans;
+        for (String ignoreSpan : ignoreSpans) {
+            TraceHolder.disable(ignoreSpan, "sys-conf");
+        }
+    }
+
+    public void setEnabled(boolean enabled) {
+        if (enabled) {
+            TraceHolder.enable();
+        } else {
+            TraceHolder.disable();
+        }
+        this.enabled = enabled;
+    }
+
+    public List<SpanProcessor> buildProcessors() {
+        List<SpanProcessor> processors = new ArrayList<>();
+        if (jaeger != null && jaeger.isEnabled()) {
+            processors.add(jaeger.create());
+        }
+        return processors;
+    }
+
+    @Getter
+    @Setter
+    public static class Logging {
+        private String name = "jetlinks.trace";
+
+        public SpanProcessor create() {
+            return SimpleSpanProcessor.create(
+                LoggingSpanExporter.create(name)
+            );
+        }
+    }
+
+    //https://www.jaegertracing.io/docs/1.18/opentelemetry/
+    public static class Jaeger extends GrpcProcessor {
+        @Override
+        protected SpanExporter createExporter() {
+            return JaegerGrpcSpanExporter
+                .builder()
+                .setEndpoint(getEndpoint())
+                .setTimeout(getTimeout())
+                .build();
+        }
+    }
+
+    @Getter
+    @Setter
+    public abstract static class GrpcProcessor extends BatchProcessor {
+        private String endpoint;
+        private Duration timeout = Duration.ofSeconds(5);
+    }
+
+    @Getter
+    @Setter
+    public abstract static class BatchProcessor {
+        private boolean enabled = true;
+        private String endpoint;
+        private int maxBatchSize = 2048;
+        private int maxQueueSize = 512;
+        private Duration exporterTimeout = Duration.ofSeconds(30);
+        private Duration scheduleDelay = Duration.ofMillis(100);
+
+        protected abstract SpanExporter createExporter();
+
+        public SpanProcessor create() {
+            return BatchSpanProcessor
+                .builder(createExporter())
+                .setScheduleDelay(100, TimeUnit.MILLISECONDS)
+                .setMaxExportBatchSize(maxBatchSize)
+                .setMaxQueueSize(maxQueueSize)
+                .setExporterTimeout(exporterTimeout)
+                .build();
+        }
+    }
+}

+ 97 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/trace/TraceWebFilter.java

@@ -0,0 +1,97 @@
+package org.jetlinks.community.configure.trace;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.trace.StatusCode;
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.authorization.simple.SimpleAuthentication;
+import org.hswebframework.web.authorization.simple.SimpleUser;
+import org.hswebframework.web.logging.AccessLoggerInfo;
+import org.hswebframework.web.logging.events.AccessLoggerBeforeEvent;
+import org.jetlinks.core.trace.MonoTracer;
+import org.jetlinks.core.trace.TraceHolder;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+import java.util.concurrent.TimeUnit;
+
+public class TraceWebFilter implements WebFilter, Ordered {
+
+    static AttributeKey<String> action = AttributeKey.stringKey("request.action");
+    static AttributeKey<String> ip = AttributeKey.stringKey("request.ip");
+    static AttributeKey<String> userId = AttributeKey.stringKey("user.id");
+    static AttributeKey<String> username = AttributeKey.stringKey("user.username");
+    static AttributeKey<String> userName = AttributeKey.stringKey("user.name");
+
+    @SuppressWarnings("all")
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange,
+                             WebFilterChain chain) {
+        String spanName = exchange.getRequest().getMethodValue() +
+            ":" + exchange.getRequest().getPath().value();
+
+        return TraceHolder
+            //将追踪信息返回到响应头
+            .writeContextTo(exchange.getResponse().getHeaders(), HttpHeaders::set)
+            .then(chain.filter(exchange))
+            .as(MonoTracer.create(spanName))
+            .as(MonoTracer.createWith(exchange.getRequest().getHeaders().toSingleValueMap()));
+
+    }
+
+    public static final Authentication anonymous;
+
+    static {
+        SimpleAuthentication auth = new SimpleAuthentication();
+        auth.setUser(SimpleUser
+                         .builder()
+                         .id("anonymous")
+                         .userType("none")
+                         .name("anonymous")
+                         .username("anonymous")
+                         .build());
+        anonymous = auth;
+    }
+
+    @EventListener
+    public void handleAccessLogger(AccessLoggerBeforeEvent event) {
+        String spanName = "/java/" + event.getLogger().getTarget().getSimpleName()
+            + "/" + event.getLogger().getMethod().getName();
+
+        event.transformFirst(first -> Authentication
+            .currentReactive()
+            .defaultIfEmpty(anonymous)
+            .flatMap(auth -> first
+                .thenReturn(event.getLogger())
+                .as(MonoTracer.<AccessLoggerInfo>create(
+                    spanName,
+                    (span, log) -> {
+                        if (log.getException() != null) {
+                            span.recordException(log.getException());
+                            span.setStatus(StatusCode.ERROR);
+                        }
+                        span.setAttribute(action, log.getAction());
+                        span.setAttribute(ip, log.getIp());
+                        span.setAttribute(userId, auth.getUser().getId());
+                        span.setAttribute(username, auth.getUser().getUsername());
+                        span.setAttribute(userName, auth.getUser().getName());
+                        log
+                            .getContext()
+                            .put("traceId", span.getSpanContext().getTraceId());
+                    },
+                    builder -> builder
+                        .setStartTimestamp(event.getLogger().getRequestTime(),
+                                           TimeUnit.MILLISECONDS))
+                ))
+        );
+    }
+
+    @Override
+    public int getOrder() {
+        return HIGHEST_PRECEDENCE + 100;
+    }
+}

+ 4 - 1
jetlinks-components/configure-component/src/main/resources/META-INF/spring.factories

@@ -1,2 +1,5 @@
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-org.jetlinks.community.configure.r2dbc.R2dbcPoolConfiguration
+org.jetlinks.community.configure.cluster.ClusterConfiguration,\
+org.jetlinks.community.configure.doc.SpringDocCustomizerConfiguration,\
+org.jetlinks.community.configure.trace.TraceConfiguration,\
+org.jetlinks.community.configure.device.DeviceClusterConfiguration

+ 1 - 1
jetlinks-components/dashboard-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
jetlinks-components/elasticsearch-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 15 - 11
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/configuration/ElasticSearchConfiguration.java

@@ -25,8 +25,8 @@ import org.springframework.data.elasticsearch.client.reactive.RequestCreator;
 import org.springframework.data.elasticsearch.client.reactive.WebClientProvider;
 import org.springframework.http.client.reactive.ReactorClientHttpConnector;
 import reactor.netty.http.client.HttpClient;
-import reactor.netty.tcp.ProxyProvider;
 import reactor.netty.tcp.TcpClient;
+import reactor.netty.transport.ProxyProvider;
 
 import javax.net.ssl.SSLContext;
 import java.net.InetSocketAddress;
@@ -55,13 +55,14 @@ public class ElasticSearchConfiguration {
         this.properties = properties;
         this.embeddedProperties = embeddedProperties;
     }
+
     @Bean
     @SneakyThrows
     public DefaultReactiveElasticsearchClient reactiveElasticsearchClient(ClientConfiguration clientConfiguration) {
         if (embeddedProperties.isEnabled()) {
             log.debug("starting embedded elasticsearch on {}:{}",
-                embeddedProperties.getHost(),
-                embeddedProperties.getPort());
+                      embeddedProperties.getHost(),
+                      embeddedProperties.getPort());
 
             new EmbeddedElasticSearch(embeddedProperties).start();
         }
@@ -69,7 +70,9 @@ public class ElasticSearchConfiguration {
         WebClientProvider provider = getWebClientProvider(clientConfiguration);
 
         HostProvider hostProvider = HostProvider.provider(provider, clientConfiguration.getHeadersSupplier(),
-            clientConfiguration.getEndpoints().toArray(new InetSocketAddress[0]));
+                                                          clientConfiguration
+                                                              .getEndpoints()
+                                                              .toArray(new InetSocketAddress[0]));
 
         DefaultReactiveElasticsearchClient client =
             new DefaultReactiveElasticsearchClient(hostProvider, new RequestCreator() {
@@ -93,8 +96,8 @@ public class ElasticSearchConfiguration {
 
         if (!soTimeout.isNegative()) {
             tcpClient = tcpClient.doOnConnected(connection -> connection //
-                .addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))
-                .addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)));
+                                                                         .addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))
+                                                                         .addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)));
         }
 
         if (clientConfiguration.getProxy().isPresent()) {
@@ -104,7 +107,8 @@ public class ElasticSearchConfiguration {
             if (hostPort.length != 2) {
                 throw new IllegalArgumentException("invalid proxy configuration " + proxy + ", should be \"host:port\"");
             }
-            tcpClient = tcpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
+            tcpClient = tcpClient.proxy(proxyOptions -> proxyOptions
+                .type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
                 .port(Integer.parseInt(hostPort[1])));
         }
 
@@ -118,7 +122,7 @@ public class ElasticSearchConfiguration {
             if (sslContext.isPresent()) {
                 httpClient = httpClient.secure(sslContextSpec -> {
                     sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null, IdentityCipherSuiteFilter.INSTANCE,
-                        ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false));
+                                                                ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false));
                 });
             } else {
                 httpClient = httpClient.secure();
@@ -135,7 +139,7 @@ public class ElasticSearchConfiguration {
         }
 
         provider = provider.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
-            .withWebClientConfigurer(clientConfiguration.getWebClientConfigurer());
+                           .withWebClientConfigurer(clientConfiguration.getWebClientConfigurer());
         return provider;
     }
 
@@ -144,8 +148,8 @@ public class ElasticSearchConfiguration {
     public ElasticRestClient elasticRestClient() {
 
         RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(properties.createHosts())
-            .setRequestConfigCallback(properties::applyRequestConfigBuilder)
-            .setHttpClientConfigCallback(properties::applyHttpAsyncClientBuilder));
+                                                                       .setRequestConfigCallback(properties::applyRequestConfigBuilder)
+                                                                       .setHttpClientConfigCallback(properties::applyHttpAsyncClientBuilder));
         return new ElasticRestClient(client, client);
     }
 

+ 3 - 1
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/strategies/AbstractElasticSearchIndexStrategy.java

@@ -45,7 +45,9 @@ public abstract class AbstractElasticSearchIndexStrategy implements ElasticSearc
     }
 
     protected Mono<Void> doCreateIndex(ElasticSearchIndexMetadata metadata) {
-        return client.createIndex(createIndexRequest(metadata));
+        return client
+            .createIndex(createIndexRequest(metadata))
+            .then();
     }
 
     protected Mono<Void> doPutIndex(ElasticSearchIndexMetadata metadata,

+ 262 - 116
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/DefaultReactiveElasticsearchClient.java

@@ -19,6 +19,9 @@ import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionRequest;
 import org.elasticsearch.action.DocWriteRequest;
 import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
+import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
+import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
 import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
@@ -31,6 +34,9 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
 import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
 import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
+import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
+import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
+import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
 import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequest;
 import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
 import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
@@ -38,10 +44,7 @@ import org.elasticsearch.action.bulk.BulkRequest;
 import org.elasticsearch.action.bulk.BulkResponse;
 import org.elasticsearch.action.delete.DeleteRequest;
 import org.elasticsearch.action.delete.DeleteResponse;
-import org.elasticsearch.action.get.GetRequest;
-import org.elasticsearch.action.get.GetResponse;
-import org.elasticsearch.action.get.MultiGetRequest;
-import org.elasticsearch.action.get.MultiGetResponse;
+import org.elasticsearch.action.get.*;
 import org.elasticsearch.action.index.IndexRequest;
 import org.elasticsearch.action.index.IndexResponse;
 import org.elasticsearch.action.main.MainRequest;
@@ -53,7 +56,12 @@ import org.elasticsearch.action.support.WriteRequest;
 import org.elasticsearch.action.support.master.AcknowledgedResponse;
 import org.elasticsearch.action.update.UpdateRequest;
 import org.elasticsearch.action.update.UpdateResponse;
+import org.elasticsearch.client.GetAliasesResponse;
 import org.elasticsearch.client.Request;
+import org.elasticsearch.client.indices.GetFieldMappingsRequest;
+import org.elasticsearch.client.indices.GetFieldMappingsResponse;
+import org.elasticsearch.client.indices.GetIndexResponse;
+import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.common.Priority;
 import org.elasticsearch.common.Strings;
@@ -65,29 +73,32 @@ import org.elasticsearch.index.VersionType;
 import org.elasticsearch.index.get.GetResult;
 import org.elasticsearch.index.reindex.BulkByScrollResponse;
 import org.elasticsearch.index.reindex.DeleteByQueryRequest;
+import org.elasticsearch.index.reindex.UpdateByQueryRequest;
 import org.elasticsearch.index.seqno.SequenceNumbers;
 import org.elasticsearch.rest.BytesRestResponse;
 import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.script.mustache.SearchTemplateRequest;
+import org.elasticsearch.script.mustache.SearchTemplateResponse;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.SearchHits;
 import org.elasticsearch.search.aggregations.Aggregation;
 import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
 import org.elasticsearch.search.internal.SearchContext;
+import org.elasticsearch.search.suggest.Suggest;
 import org.elasticsearch.tasks.TaskId;
 import org.reactivestreams.Publisher;
 import org.springframework.data.elasticsearch.client.ClientLogger;
 import org.springframework.data.elasticsearch.client.ElasticsearchHost;
 import org.springframework.data.elasticsearch.client.NoReachableHostException;
 import org.springframework.data.elasticsearch.client.reactive.HostProvider;
-import org.springframework.data.elasticsearch.client.reactive.RequestBodyEncodingException;
 import org.springframework.data.elasticsearch.client.reactive.RequestCreator;
 import org.springframework.data.elasticsearch.client.util.NamedXContents;
 import org.springframework.data.elasticsearch.client.util.RequestConverters;
 import org.springframework.data.elasticsearch.client.util.ScrollState;
+import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
 import org.springframework.data.util.Lazy;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.lang.Nullable;
 import org.springframework.util.Assert;
@@ -118,7 +129,7 @@ import static org.springframework.data.elasticsearch.client.util.RequestConverte
 
 @Slf4j
 @Generated
-public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient {
+public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Cluster {
     private final HostProvider<?> hostProvider;
     private final RequestCreator requestCreator;
     private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
@@ -158,13 +169,10 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
     @Override
     public Mono<Boolean> ping(HttpHeaders headers) {
 
-        return sendRequest(new MainRequest(), requestCreator.ping(), RawActionResponse.class, headers) //
-                                                                                                       .map(response -> response
-                                                                                                           .statusCode()
-                                                                                                           .is2xxSuccessful()) //
-                                                                                                       .onErrorResume(NoReachableHostException.class, error -> Mono
-                                                                                                           .just(false))
-                                                                                                       .next();
+        return sendRequest(new MainRequest(), requestCreator.ping(), RawActionResponse.class, headers)
+            .flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful()))
+            .onErrorResume(NoReachableHostException.class, error -> Mono.just(false))
+            .next();
     }
 
     /*
@@ -196,13 +204,11 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
      * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#multiGet(org.springframework.http.HttpHeaders, org.elasticsearch.action.get.MultiGetRequest)
      */
     @Override
-    public Flux<GetResult> multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest) {
+    public Flux<MultiGetItemResponse> multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest) {
 
         return sendRequest(multiGetRequest, requestCreator.multiGet(), MultiGetResponse.class, headers)
             .map(MultiGetResponse::getResponses) //
-            .flatMap(Flux::fromArray) //
-            .filter(it -> !it.isFailed() && it.getResponse().isExists()) //
-            .map(it -> DefaultReactiveElasticsearchClient.getResponseToGetResult(it.getResponse()));
+            .flatMap(Flux::fromArray); //
     }
 
     /*
@@ -212,11 +218,11 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
     @Override
     public Mono<Boolean> exists(HttpHeaders headers, GetRequest getRequest) {
 
-        return sendRequest(getRequest, requestCreator.exists(), RawActionResponse.class, headers) //
-                                                                                                  .map(response -> response
-                                                                                                      .statusCode()
-                                                                                                      .is2xxSuccessful()) //
-                                                                                                  .next();
+        return sendRequest(getRequest, requestCreator.exists(), RawActionResponse.class, headers)
+            .flatMap(response -> response.releaseBody().thenReturn(response
+                                                                       .statusCode()
+                                                                       .is2xxSuccessful()))
+            .next();
     }
 
     /*
@@ -233,7 +239,12 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
      * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#indices()
      */
     @Override
-    public org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices indices() {
+    public Indices indices() {
+        return this;
+    }
+
+    @Override
+    public Cluster cluster() {
         return this;
     }
 
@@ -272,6 +283,12 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
             .next();
     }
 
+    @Override
+    public Flux<SearchHit> searchTemplate(HttpHeaders headers, SearchTemplateRequest searchTemplateRequest) {
+        return sendRequest(searchTemplateRequest, requestCreator.searchTemplate(), SearchTemplateResponse.class, headers)
+            .map(response -> response.getResponse().getHits()).flatMap(Flux::fromIterable);
+    }
+
     protected Request buildSearchRequest(SearchRequest request) {
         //兼容6.x版本es
         if (version.before(Version.V_7_0_0) && request
@@ -298,6 +315,17 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
                                                                                                    .flatMap(Flux::fromIterable);
     }
 
+    @Override
+    public Mono<SearchResponse> searchForResponse(HttpHeaders headers, SearchRequest searchRequest) {
+        return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers).next();
+    }
+
+    @Override
+    public Flux<Suggest> suggest(HttpHeaders headers, SearchRequest searchRequest) {
+        return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers) //
+                                                                                                  .map(SearchResponse::getSuggest);
+    }
+
     /*
      * (non-Javadoc)
      * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#aggregate(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
@@ -412,6 +440,11 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
                                                                                                                .publishNext();
     }
 
+    @Override
+    public Mono<ByQueryResponse> updateBy(HttpHeaders headers, UpdateByQueryRequest updateRequest) {
+        return null;
+    }
+
     static XContentType enforceSameContentType(IndexRequest indexRequest, @Nullable XContentType xContentType) {
         XContentType requestContentType = indexRequest.getContentType();
         if (requestContentType != XContentType.JSON && requestContentType != XContentType.SMILE) {
@@ -581,10 +614,25 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
     @Override
     public Mono<Boolean> existsIndex(HttpHeaders headers, GetIndexRequest request) {
         return sendRequest(request, requestCreator.indexExists()
-            , RawActionResponse.class, headers) //
-                                                .map(response -> response.statusCode().is2xxSuccessful())
-                                                .onErrorReturn(false)
-                                                .next();
+            , RawActionResponse.class, headers)
+            .flatMap(response -> response
+                .releaseBody()
+                .thenReturn(response
+                                .statusCode()
+                                .is2xxSuccessful()))
+            .onErrorReturn(false)
+            .next();
+    }
+
+    @Override
+    public Mono<Boolean> existsIndex(HttpHeaders headers, org.elasticsearch.client.indices.GetIndexRequest getIndexRequest) {
+        return sendRequest(getIndexRequest, requestCreator.indexExistsRequest(), RawActionResponse.class, headers)
+            .flatMap(response -> response
+                .releaseBody()
+                .thenReturn(response
+                                .statusCode()
+                                .is2xxSuccessful()))
+            .next();
     }
 
     /*
@@ -592,10 +640,11 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
      * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#deleteIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest)
      */
     @Override
-    public Mono<Void> deleteIndex(HttpHeaders headers, DeleteIndexRequest request) {
+    public Mono<Boolean> deleteIndex(HttpHeaders headers, DeleteIndexRequest request) {
 
-        return sendRequest(request, requestCreator.indexDelete(), AcknowledgedResponse.class, headers) //
-                                                                                                       .then();
+        return sendRequest(request, requestCreator.indexDelete(), AcknowledgedResponse.class, headers)
+            .map(AcknowledgedResponse::isAcknowledged)
+            .next();
     }
 
     /*
@@ -603,13 +652,19 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
      * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#createIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.create.CreateIndexRequest)
      */
     @Override
-    public Mono<Void> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest) {
+    public Mono<Boolean> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest) {
 
         return sendRequest(createIndexRequest, requestCreator.indexCreate().andThen(request -> {
             request.addParameter("include_type_name", "true");
             return request;
-        }), AcknowledgedResponse.class, headers) //
-                                                 .then();
+        }), AcknowledgedResponse.class, headers)
+            .map(AcknowledgedResponse::isAcknowledged)
+            .next();
+    }
+
+    @Override
+    public Mono<Boolean> createIndex(HttpHeaders headers, org.elasticsearch.client.indices.CreateIndexRequest createIndexRequest) {
+        return null;
     }
 
     /*
@@ -619,16 +674,8 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
     @Override
     public Mono<Void> openIndex(HttpHeaders headers, OpenIndexRequest request) {
 
-        return sendRequest(
-            request,
-            requestCreator
-                .indexOpen()
-                .andThen(r -> {
-                    r.addParameter("include_type_name", "true");
-                    return r;
-                }),
-            AcknowledgedResponse.class, headers) //
-                                                 .then();
+        return sendRequest(request, requestCreator.indexOpen(), AcknowledgedResponse.class, headers)
+            .then();
     }
 
     /*
@@ -658,12 +705,19 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
      * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#updateMapping(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest)
      */
     @Override
-    public Mono<Void> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
+    public Mono<Boolean> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
 
-        return sendRequest(putMappingRequest
-            , requestCreator.putMapping()
-            , AcknowledgedResponse.class, headers) //
-                                                   .then();
+        return putMapping(headers, putMappingRequest);
+    }
+
+    @Override
+    public Mono<Boolean> putMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
+        return null;
+    }
+
+    @Override
+    public Mono<Boolean> putMapping(HttpHeaders headers, org.elasticsearch.client.indices.PutMappingRequest putMappingRequest) {
+        return null;
     }
 
     /*
@@ -677,29 +731,49 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
                                                                                                     .then();
     }
 
+    @Override
+    public Mono<GetSettingsResponse> getSettings(HttpHeaders headers, GetSettingsRequest getSettingsRequest) {
+        return sendRequest(getSettingsRequest, requestCreator.getSettings(), GetSettingsResponse.class, headers).next();
+    }
+
     /*
      * (non-Javadoc)
      * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.ReactiveElasticsearchClientCallback)
      */
     @Override
-    public Mono<ClientResponse> execute(org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.ReactiveElasticsearchClientCallback callback) {
+    public <T> Mono<T> execute(ReactiveElasticsearchClientCallback<T> callback) {
 
-        return this.hostProvider.getActive(HostProvider.Verification.LAZY) //
-                                .flatMap(callback::doWithClient) //
-                                .onErrorResume(throwable -> {
+        return this.hostProvider
+            .getActive(HostProvider.Verification.LAZY) //
+            .flatMap(callback::doWithClient) //
+            .onErrorResume(throwable -> {
 
-                                    if (throwable instanceof ConnectException) {
+                if (isCausedByConnectionException(throwable)) {
+                    return hostProvider.getActive(HostProvider.Verification.ACTIVE) //
+                                       .flatMap(callback::doWithClient);
+                }
+
+                return Mono.error(throwable);
+            });
+    }
+
+    private boolean isCausedByConnectionException(Throwable throwable) {
+
+        Throwable t = throwable;
+        do {
 
-                                        return hostProvider.getActive(HostProvider.Verification.ACTIVE) //
-                                                           .flatMap(callback::doWithClient);
-                                    }
+            if (t instanceof ConnectException) {
+                return true;
+            }
+
+            t = t.getCause();
+        } while (t != null);
 
-                                    return Mono.error(throwable);
-                                });
+        return false;
     }
 
     @Override
-    public Mono<org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Status> status() {
+    public Mono<Status> status() {
 
         return hostProvider.clusterInfo() //
                            .map(it -> new ClientStatus(it.getNodes()));
@@ -716,6 +790,22 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
 
     // -->
 
+    private <REQ, RESP> Flux<RESP> sendRequest(REQ request, Function<REQ, Request> converter, Class<RESP> responseType,
+                                               HttpHeaders headers) {
+        return sendRequest(converter.apply(request), responseType, headers);
+    }
+
+    private <Resp> Flux<Resp> sendRequest(Request request, Class<Resp> responseType, HttpHeaders headers) {
+
+        String logId = ClientLogger.newLogId();
+
+        return Flux
+            .from(execute(webClient -> sendRequest(webClient, logId, request, headers).exchangeToMono(clientResponse -> {
+                Publisher<? extends Resp> publisher = readResponseBody(logId, request, clientResponse, responseType);
+                return Mono.from(publisher);
+            })));
+    }
+
     private <Req extends ActionRequest, Resp> Flux<Resp> sendRequest(Req request,
                                                                      Function<Req, Request> converter,
                                                                      Class<Resp> responseType,
@@ -738,54 +828,53 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
 
         String logId = ClientLogger.newLogId();
 
-        return execute(webClient -> sendRequest(webClient, logId, request, headers))
-            .flatMapMany(response -> readResponseBody(logId, request, response, responseType, decoder));
+        return execute(webClient -> Mono.just(this.sendRequest(webClient, logId, request, headers)))
+            .flatMapMany(spec -> {
+                return spec.exchangeToFlux(response -> {
+                    return Flux.from(
+                        this.readResponseBody(logId, request, response, responseType, decoder)
+                    );
+                });
+            });
     }
 
-    private Mono<ClientResponse> sendRequest(WebClient webClient, String logId, Request request, HttpHeaders headers) {
-
-        WebClient.RequestBodySpec requestBodySpec = webClient
-            .method(HttpMethod.valueOf(request.getMethod().toUpperCase())) //
-            .uri(builder -> {
-
-                builder = builder.path(request.getEndpoint());
-
-                if (!ObjectUtils.isEmpty(request.getParameters())) {
-                    for (Map.Entry<String, String> entry : request
-                        .getParameters()
-                        .entrySet()) {
-                        builder = builder.queryParam(entry.getKey(), entry
-                            .getValue());
-                    }
-                }
-                return builder.build();
-            }) //
-            .attribute(ClientRequest.LOG_ID_ATTRIBUTE, logId) //
-            .headers(theHeaders -> {
-
-                // add all the headers explicitly set
-                theHeaders.addAll(headers);
-
-                // and now those that might be set on the request.
-                if (request.getOptions() != null) {
-
-                    if (!ObjectUtils.isEmpty(request
-                                                 .getOptions()
-                                                 .getHeaders())) {
-                        request
-                            .getOptions()
-                            .getHeaders()
-                            .forEach(it -> theHeaders.add(it.getName(), it
-                                .getValue()));
-                    }
-                }
-
-                // plus the ones from the supplier
-                HttpHeaders suppliedHeaders = headersSupplier.get();
-                if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) {
-                    theHeaders.addAll(suppliedHeaders);
-                }
-            });
+    private WebClient.RequestBodySpec sendRequest(WebClient webClient, String logId, Request request, HttpHeaders headers) {
+
+        WebClient.RequestBodySpec requestBodySpec = webClient.method(HttpMethod.valueOf(request
+                                                                                            .getMethod()
+                                                                                            .toUpperCase())) //
+                                                             .uri(builder -> {
+
+                                                                 builder = builder.path(request.getEndpoint());
+
+                                                                 if (!ObjectUtils.isEmpty(request.getParameters())) {
+                                                                     for (Map.Entry<String, String> entry : request
+                                                                         .getParameters()
+                                                                         .entrySet()) {
+                                                                         builder = builder.queryParam(entry.getKey(), entry.getValue());
+                                                                     }
+                                                                 }
+                                                                 return builder.build();
+                                                             }) //
+                                                             .attribute(ClientRequest.LOG_ID_ATTRIBUTE, logId) //
+                                                             .headers(theHeaders -> {
+
+                                                                 // add all the headers explicitly set
+                                                                 theHeaders.addAll(headers);
+
+                                                                 // and now those that might be set on the request.
+                                                                 if (request.getOptions() != null) {
+
+                                                                     if (!ObjectUtils.isEmpty(request
+                                                                                                  .getOptions()
+                                                                                                  .getHeaders())) {
+                                                                         request
+                                                                             .getOptions()
+                                                                             .getHeaders()
+                                                                             .forEach(it -> theHeaders.add(it.getName(), it.getValue()));
+                                                                     }
+                                                                 }
+                                                             });
 
         if (request.getEntity() != null) {
 
@@ -796,19 +885,15 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
                                         .toUpperCase(), request.getEndpoint(), request.getParameters(),
                                     body::get);
 
-            requestBodySpec.contentType(MediaType.valueOf(request.getEntity().getContentType().getValue()));
-            requestBodySpec.body(Mono.fromSupplier(body), String.class);
+            requestBodySpec.contentType(MediaType.valueOf(request.getEntity().getContentType().getValue()))
+                           .body(Mono.fromSupplier(body), String.class);
         } else {
             ClientLogger.logRequest(logId, request
                 .getMethod()
                 .toUpperCase(), request.getEndpoint(), request.getParameters());
         }
 
-        return requestBodySpec //
-                               .exchange() //
-                               .onErrorReturn(ConnectException.class, ClientResponse
-                                   .create(HttpStatus.SERVICE_UNAVAILABLE)
-                                   .build());
+        return requestBodySpec;
     }
 
     private Lazy<String> bodyExtractor(Request request) {
@@ -821,6 +906,11 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
         return EntityUtils.toString(request.getEntity());
     }
 
+    private <T> Publisher<? extends T> readResponseBody(String logId, Request request, ClientResponse response,
+                                                        Class<T> responseType) {
+        return readResponseBody(logId, request, response, responseType, DefaultReactiveElasticsearchClient::doDecode);
+    }
+
     private <T> Publisher<? extends T> readResponseBody(String logId,
                                                         Request request,
                                                         ClientResponse response,
@@ -1014,7 +1104,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
             .sendRequest(request, this::buildSearchRequest, SearchResponse.class, HttpHeaders.EMPTY)
             .singleOrEmpty()
             .doOnSuccess(res -> log
-                .trace("execute search {} {} : {}", request.indices(),res.getTook(), request.source()))
+                .trace("execute search {} {} : {}", request.indices(), res.getTook(), request.source()))
             .doOnError(err -> log.warn("execute search {} error : {}", request.indices(), request.source(), err));
     }
 
@@ -1076,6 +1166,56 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
             .singleOrEmpty();
     }
 
+    @Override
+    public Mono<GetMappingsResponse> getMapping(HttpHeaders headers, GetMappingsRequest getMappingsRequest) {
+        return null;
+    }
+
+    @Override
+    public Mono<org.elasticsearch.client.indices.GetMappingsResponse> getMapping(HttpHeaders headers, org.elasticsearch.client.indices.GetMappingsRequest getMappingsRequest) {
+        return null;
+    }
+
+    @Override
+    public Mono<GetFieldMappingsResponse> getFieldMapping(HttpHeaders headers, GetFieldMappingsRequest getFieldMappingsRequest) {
+        return null;
+    }
+
+    @Override
+    public Mono<Boolean> updateAliases(HttpHeaders headers, IndicesAliasesRequest indicesAliasesRequest) {
+        return null;
+    }
+
+    @Override
+    public Mono<GetAliasesResponse> getAliases(HttpHeaders headers, GetAliasesRequest getAliasesRequest) {
+        return null;
+    }
+
+    @Override
+    public Mono<Boolean> putTemplate(HttpHeaders headers, org.elasticsearch.client.indices.PutIndexTemplateRequest putIndexTemplateRequest) {
+        return null;
+    }
+
+    @Override
+    public Mono<org.elasticsearch.client.indices.GetIndexTemplatesResponse> getTemplate(HttpHeaders headers, org.elasticsearch.client.indices.GetIndexTemplatesRequest getIndexTemplatesRequest) {
+        return null;
+    }
+
+    @Override
+    public Mono<Boolean> existsTemplate(HttpHeaders headers, IndexTemplatesExistRequest indexTemplatesExistRequest) {
+        return null;
+    }
+
+    @Override
+    public Mono<Boolean> deleteTemplate(HttpHeaders headers, DeleteIndexTemplateRequest deleteIndexTemplateRequest) {
+        return null;
+    }
+
+    @Override
+    public Mono<GetIndexResponse> getIndex(HttpHeaders headers, org.elasticsearch.client.indices.GetIndexRequest getIndexRequest) {
+        return null;
+    }
+
     Request convertGetIndexTemplateRequest(GetIndexTemplatesRequest getIndexTemplatesRequest) {
         Request request = new Request(HttpGet.METHOD_NAME, "/_template/" + String.join(",", getIndexTemplatesRequest.names()));
         Params params = new Params(request);
@@ -1119,17 +1259,23 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
         return version;
     }
 
+    @Override
+    public Mono<ClusterHealthResponse> health(HttpHeaders headers, ClusterHealthRequest clusterHealthRequest) {
+        return sendRequest(clusterHealthRequest, requestCreator.clusterHealth(), ClusterHealthResponse.class, headers)
+            .next();
+    }
+
     // endregion
 
     // region internal classes
 
     /**
-     * Reactive client {@link ReactiveElasticsearchClient.Status} implementation.
+     * Reactive client {@link Status} implementation.
      *
      * @author Christoph Strobl
      */
     @Generated
-    static class ClientStatus implements ReactiveElasticsearchClient.Status {
+    static class ClientStatus implements Status {
 
         private final Collection<ElasticsearchHost> connectedHosts;
 

+ 48 - 37
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/RawActionResponse.java

@@ -1,11 +1,13 @@
 package org.jetlinks.community.elastic.search.service.reactive;
 
+import lombok.Generated;
 import org.elasticsearch.action.ActionResponse;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.client.reactive.ClientHttpResponse;
 import org.springframework.web.reactive.function.BodyExtractor;
 import org.springframework.web.reactive.function.client.ClientResponse;
+import reactor.core.publisher.Mono;
 
 import java.io.IOException;
 
@@ -17,43 +19,52 @@ import java.io.IOException;
  * @author Mark Paluch
  * @since 3.2
  */
+@Generated
 class RawActionResponse extends ActionResponse {
 
-	private final ClientResponse delegate;
-
-	private RawActionResponse(ClientResponse delegate) {
-		this.delegate = delegate;
-	}
-
-	static RawActionResponse create(ClientResponse response) {
-		return new RawActionResponse(response);
-	}
-
-	public HttpStatus statusCode() {
-		return delegate.statusCode();
-	}
-
-	/*
-	 * (non-Javadoc)
-	 * @see org.springframework.web.reactive.function.client.ClientResponse#headers()
-	 */
-	public ClientResponse.Headers headers() {
-		return delegate.headers();
-	}
-
-	/*
-	 * (non-Javadoc)
-	 * @see org.springframework.web.reactive.function.client.ClientResponse#body(org.springframework.web.reactive.function.BodyExtractor)
-	 */
-	public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
-		return delegate.body(extractor);
-	}
-
-	/*
-	 * (non-Javadoc)
-	 * until Elasticsearch 7.4 this empty implementation was available in the abstract base class
-	 */
-	@Override
-	public void writeTo(StreamOutput out) throws IOException {
-	}
+    private final ClientResponse delegate;
+
+    private RawActionResponse(ClientResponse delegate) {
+        this.delegate = delegate;
+    }
+
+    static RawActionResponse create(ClientResponse response) {
+        return new RawActionResponse(response);
+    }
+
+    public HttpStatus statusCode() {
+        return delegate.statusCode();
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see org.springframework.web.reactive.function.client.ClientResponse#headers()
+     */
+    public ClientResponse.Headers headers() {
+        return delegate.headers();
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see org.springframework.web.reactive.function.client.ClientResponse#body(org.springframework.web.reactive.function.BodyExtractor)
+     */
+    public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
+        return delegate.body(extractor);
+    }
+
+    /*
+     * (non-Javadoc)
+     * until Elasticsearch 7.4 this empty implementation was available in the abstract base class
+     */
+    @Override
+    public void writeTo(StreamOutput out) throws IOException {
+    }
+
+    /**
+     * Ensure the response body is released to properly release the underlying connection.
+     *
+     */
+    public Mono<Void> releaseBody() {
+        return delegate.releaseBody();
+    }
 }

+ 1 - 1
jetlinks-components/gateway-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 113 - 0
jetlinks-components/gateway-component/src/main/java/org/jetlinks/community/gateway/AbstractDeviceGateway.java

@@ -0,0 +1,113 @@
+package org.jetlinks.community.gateway;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.community.gateway.monitor.DeviceGatewayMonitor;
+import org.jetlinks.community.gateway.monitor.GatewayMonitors;
+import org.jetlinks.core.message.Message;
+import reactor.core.Disposable;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import java.util.function.BiConsumer;
+
+@Slf4j
+public abstract class AbstractDeviceGateway implements DeviceGateway {
+    private final static AtomicReferenceFieldUpdater<AbstractDeviceGateway, GatewayState>
+        STATE = AtomicReferenceFieldUpdater.newUpdater(AbstractDeviceGateway.class, GatewayState.class, "state");
+
+    private final String id;
+
+    private final List<BiConsumer<GatewayState, GatewayState>> stateListener = new CopyOnWriteArrayList<>();
+
+    private volatile GatewayState state = GatewayState.shutdown;
+
+    protected final DeviceGatewayMonitor monitor;
+
+    public AbstractDeviceGateway(String id) {
+        this.id = id;
+        this.monitor = GatewayMonitors.getDeviceGatewayMonitor(id);
+    }
+
+    @Override
+    public final String getId() {
+        return id;
+    }
+
+    @Override
+    public Flux<Message> onMessage() {
+        return Flux.empty();
+    }
+
+    @Override
+    public final synchronized Mono<Void> startup() {
+        if (state == GatewayState.paused) {
+            changeState(GatewayState.started);
+            return Mono.empty();
+        }
+        if (state == GatewayState.started || state == GatewayState.starting) {
+            return Mono.empty();
+        }
+        changeState(GatewayState.starting);
+        return this
+            .doStartup()
+            .doOnSuccess(ignore -> changeState(GatewayState.started));
+    }
+
+    @Override
+    public final Mono<Void> pause() {
+        changeState(GatewayState.paused);
+        return Mono.empty();
+    }
+
+    @Override
+    public final Mono<Void> shutdown() {
+        GatewayState old = STATE.getAndSet(this, GatewayState.shutdown);
+
+        if (old == GatewayState.shutdown) {
+            return Mono.empty();
+        }
+        changeState(GatewayState.shutdown);
+        return doShutdown();
+    }
+
+    protected abstract Mono<Void> doShutdown();
+
+    protected abstract Mono<Void> doStartup();
+
+    protected synchronized final void changeState(GatewayState target) {
+        GatewayState old = STATE.getAndSet(this, target);
+        if (target == old) {
+            return;
+        }
+        for (BiConsumer<GatewayState, GatewayState> consumer : stateListener) {
+            try {
+                consumer.accept(old, this.state);
+            } catch (Throwable error) {
+                log.warn("fire gateway {} state listener error", getId(), error);
+            }
+        }
+    }
+
+    @Override
+    public final GatewayState getState() {
+        return state;
+    }
+
+    @Override
+    public final void doOnStateChange(BiConsumer<GatewayState, GatewayState> listener) {
+        stateListener.add(listener);
+    }
+
+    @Override
+    public final void doOnShutdown(Disposable disposable) {
+        DeviceGateway.super.doOnShutdown(disposable);
+    }
+
+    @Override
+    public final boolean isAlive() {
+        return state == GatewayState.started || state == GatewayState.starting;
+    }
+}

+ 23 - 0
jetlinks-components/gateway-component/src/main/java/org/jetlinks/community/gateway/DeviceGateway.java

@@ -3,9 +3,12 @@ package org.jetlinks.community.gateway;
 import org.jetlinks.community.network.NetworkType;
 import org.jetlinks.core.message.Message;
 import org.jetlinks.core.message.codec.Transport;
+import reactor.core.Disposable;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
+import java.util.function.BiConsumer;
+
 /**
  * 设备网关,用于统一管理设备连接,状态以及消息收发
  *
@@ -63,4 +66,24 @@ public interface DeviceGateway {
     default boolean isAlive() {
         return true;
     }
+
+    default boolean isStarted() {
+        return getState() == GatewayState.started;
+    }
+
+    default GatewayState getState() {
+        return GatewayState.started;
+    }
+
+    default void doOnStateChange(BiConsumer<GatewayState, GatewayState> listener) {
+
+    }
+
+    default void doOnShutdown(Disposable disposable) {
+        doOnStateChange((before, after) -> {
+            if (after == GatewayState.shutdown) {
+                disposable.dispose();
+            }
+        });
+    }
 }

+ 8 - 0
jetlinks-components/gateway-component/src/main/java/org/jetlinks/community/gateway/GatewayState.java

@@ -0,0 +1,8 @@
+package org.jetlinks.community.gateway;
+
+public enum GatewayState {
+    starting,
+    started,
+    paused,
+    shutdown
+}

+ 1 - 1
jetlinks-components/io-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
jetlinks-components/logging-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
jetlinks-components/network-component/mqtt-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>network-component</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 64 - 105
jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttClientDeviceGateway.java

@@ -1,10 +1,7 @@
 package org.jetlinks.community.network.mqtt.gateway.device;
 
-import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
-import org.jetlinks.community.gateway.DeviceGateway;
-import org.jetlinks.community.gateway.monitor.DeviceGatewayMonitor;
-import org.jetlinks.community.gateway.monitor.GatewayMonitors;
+import org.jetlinks.community.gateway.AbstractDeviceGateway;
 import org.jetlinks.community.network.DefaultNetworkType;
 import org.jetlinks.community.network.NetworkType;
 import org.jetlinks.community.network.mqtt.client.MqttClient;
@@ -15,33 +12,25 @@ import org.jetlinks.core.ProtocolSupport;
 import org.jetlinks.core.ProtocolSupports;
 import org.jetlinks.core.device.DeviceOperator;
 import org.jetlinks.core.device.DeviceRegistry;
+import org.jetlinks.core.device.session.DeviceSessionManager;
 import org.jetlinks.core.message.DeviceMessage;
-import org.jetlinks.core.message.Message;
 import org.jetlinks.core.message.codec.DefaultTransport;
 import org.jetlinks.core.message.codec.EncodedMessage;
 import org.jetlinks.core.message.codec.FromDeviceMessageContext;
 import org.jetlinks.core.message.codec.Transport;
-import org.jetlinks.core.server.session.DeviceSessionManager;
 import org.jetlinks.supports.server.DecodedClientMessageHandler;
 import reactor.core.Disposable;
-import reactor.core.publisher.EmitterProcessor;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.FluxSink;
 import reactor.core.publisher.Mono;
 
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 @Slf4j
-public class MqttClientDeviceGateway implements DeviceGateway {
-
-    @Getter
-    private final String id;
+public class MqttClientDeviceGateway extends AbstractDeviceGateway {
 
     private final MqttClient mqttClient;
 
@@ -49,21 +38,15 @@ public class MqttClientDeviceGateway implements DeviceGateway {
 
     private final List<String> topics;
 
-    private final String protocol;
-
     private final int qos;
 
-    private final ProtocolSupports protocolSupport;
-
-    private final EmitterProcessor<Message> processor = EmitterProcessor.create(false);
+    private final String protocol;
 
-    private final FluxSink<Message> sink = processor.sink(FluxSink.OverflowStrategy.BUFFER);
+    private final ProtocolSupports protocolSupport;
 
     private final AtomicBoolean started = new AtomicBoolean();
 
-    private final List<Disposable> disposable = new CopyOnWriteArrayList<>();
-
-    private final DeviceGatewayMonitor gatewayMonitor;
+    private Disposable disposable = null;
 
     private final DeviceGatewayHelper helper;
 
@@ -76,9 +59,7 @@ public class MqttClientDeviceGateway implements DeviceGateway {
                                    DecodedClientMessageHandler clientMessageHandler,
                                    List<String> topics,
                                    int qos) {
-        this.gatewayMonitor = GatewayMonitors.getDeviceGatewayMonitor(id);
-
-        this.id = Objects.requireNonNull(id, "id");
+        super(id);
         this.mqttClient = Objects.requireNonNull(mqttClient, "mqttClient");
         this.registry = Objects.requireNonNull(registry, "registry");
         this.protocolSupport = Objects.requireNonNull(protocolSupport, "protocolSupport");
@@ -94,65 +75,61 @@ public class MqttClientDeviceGateway implements DeviceGateway {
     }
 
     private void doStart() {
-        if (started.getAndSet(true) || !disposable.isEmpty()) {
-            return;
+        if (disposable != null) {
+            disposable.dispose();
         }
-        disposable
-            .add(mqttClient
-                     .subscribe(topics,qos)
-                     .filter((msg) -> started.get())
-                     .flatMap(mqttMessage -> {
-                         AtomicReference<Duration> timeoutRef = new AtomicReference<>();
-                         return this
-                             .getProtocol()
-                             .flatMap(codec -> codec.getMessageCodec(getTransport()))
-                             .flatMapMany(codec -> codec.decode(FromDeviceMessageContext.of(
-                                 new UnknownDeviceMqttClientSession(id + ":unknown", mqttClient) {
-                                     @Override
-                                     public Mono<Boolean> send(EncodedMessage encodedMessage) {
-                                         return super
-                                             .send(encodedMessage)
-                                             .doOnSuccess(r -> gatewayMonitor.sentMessage());
-                                     }
-
-                                     @Override
-                                     public void setKeepAliveTimeout(Duration timeout) {
-                                         timeoutRef.set(timeout);
-                                     }
-                                 }
-                                 , mqttMessage, registry)
-                             ))
-                             .doOnError((err) -> log.error("解码MQTT客户端消息失败 {}:{}",
-                                                           mqttMessage.getTopic(),
-                                                           mqttMessage
-                                                               .getPayload()
-                                                               .toString(StandardCharsets.UTF_8),
-                                                           err))
-                             .cast(DeviceMessage.class)
-                             .flatMap(message -> {
-                                 if (processor.hasDownstreams()) {
-                                     sink.next(message);
-                                 }
-                                 gatewayMonitor.receivedMessage();
-                                 return helper
-                                     .handleDeviceMessage(message,
-                                                          device -> createDeviceSession(device, mqttClient),
-                                                          DeviceGatewayHelper.applySessionKeepaliveTimeout(message, timeoutRef::get),
-                                                          () -> log.warn("无法从MQTT[{}]消息中获取设备信息:{}", mqttMessage.print(), message)
-                                     );
-                             })
-                             .then()
-                             .onErrorResume((err) -> {
-                                 log.error("处理MQTT消息失败:{}", mqttMessage, err);
-                                 return Mono.empty();
-                             });
-                     }, Integer.MAX_VALUE)
-                     .onErrorContinue((err, ms) -> log.error("处理MQTT客户端消息失败", err))
-                     .subscribe());
+        disposable = mqttClient
+            .subscribe(topics, qos)
+            .filter((msg) -> started.get())
+            .flatMap(mqttMessage -> {
+                AtomicReference<Duration> timeoutRef = new AtomicReference<>();
+                return this
+                    .getProtocol()
+                    .flatMap(codec -> codec.getMessageCodec(getTransport()))
+                    .flatMapMany(codec -> codec.decode(FromDeviceMessageContext.of(
+                        new UnknownDeviceMqttClientSession(getId() + ":unknown", mqttClient) {
+                            @Override
+                            public Mono<Boolean> send(EncodedMessage encodedMessage) {
+                                return super
+                                    .send(encodedMessage)
+                                    .doOnSuccess(r -> monitor.sentMessage());
+                            }
+
+                            @Override
+                            public void setKeepAliveTimeout(Duration timeout) {
+                                timeoutRef.set(timeout);
+                            }
+                        }
+                        , mqttMessage, registry)
+                    ))
+                    .doOnError((err) -> log.error("解码MQTT客户端消息失败 {}:{}",
+                                                  mqttMessage.getTopic(),
+                                                  mqttMessage
+                                                      .getPayload()
+                                                      .toString(StandardCharsets.UTF_8),
+                                                  err))
+                    .cast(DeviceMessage.class)
+                    .flatMap(message -> {
+                        monitor.receivedMessage();
+                        return helper
+                            .handleDeviceMessage(message,
+                                                 device -> createDeviceSession(device, mqttClient),
+                                                 DeviceGatewayHelper.applySessionKeepaliveTimeout(message, timeoutRef::get),
+                                                 () -> log.warn("无法从MQTT[{}]消息中获取设备信息:{}", mqttMessage.print(), message)
+                            );
+                    })
+                    .then()
+                    .onErrorResume((err) -> {
+                        log.error("处理MQTT消息失败:{}", mqttMessage, err);
+                        return Mono.empty();
+                    });
+            }, Integer.MAX_VALUE)
+            .onErrorContinue((err, ms) -> log.error("处理MQTT客户端消息失败", err))
+            .subscribe();
     }
 
     private MqttClientSession createDeviceSession(DeviceOperator device, MqttClient client) {
-        return new MqttClientSession(device.getDeviceId(), device, client, gatewayMonitor);
+        return new MqttClientSession(device.getDeviceId(), device, client, monitor);
     }
 
     @Override
@@ -166,33 +143,15 @@ public class MqttClientDeviceGateway implements DeviceGateway {
     }
 
     @Override
-    public Flux<Message> onMessage() {
-        return processor;
-    }
-
-    @Override
-    public Mono<Void> pause() {
-        return Mono.fromRunnable(() -> started.set(false));
+    protected Mono<Void> doShutdown() {
+        if (disposable != null) {
+            disposable.dispose();
+        }
+        return Mono.empty();
     }
 
     @Override
-    public Mono<Void> startup() {
+    protected Mono<Void> doStartup() {
         return Mono.fromRunnable(this::doStart);
     }
-
-    @Override
-    public Mono<Void> shutdown() {
-        return Mono.fromRunnable(() -> {
-            started.set(false);
-
-            disposable.forEach(Disposable::dispose);
-
-            disposable.clear();
-        });
-    }
-
-    @Override
-    public boolean isAlive() {
-        return started.get();
-    }
 }

+ 1 - 1
jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttClientDeviceGatewayProvider.java

@@ -9,7 +9,7 @@ import org.jetlinks.community.network.NetworkType;
 import org.jetlinks.community.network.mqtt.client.MqttClient;
 import org.jetlinks.core.ProtocolSupports;
 import org.jetlinks.core.device.DeviceRegistry;
-import org.jetlinks.core.server.session.DeviceSessionManager;
+import org.jetlinks.core.device.session.DeviceSessionManager;
 import org.jetlinks.supports.server.DecodedClientMessageHandler;
 import org.springframework.stereotype.Component;
 import reactor.core.publisher.Mono;

+ 163 - 134
jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttServerDeviceGateway.java

@@ -4,6 +4,7 @@ import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import org.hswebframework.web.logger.ReactiveLogger;
+import org.jetlinks.community.gateway.AbstractDeviceGateway;
 import org.jetlinks.community.gateway.DeviceGateway;
 import org.jetlinks.community.gateway.monitor.DeviceGatewayMonitor;
 import org.jetlinks.community.gateway.monitor.GatewayMonitors;
@@ -14,11 +15,10 @@ import org.jetlinks.community.network.mqtt.gateway.device.session.MqttConnection
 import org.jetlinks.community.network.mqtt.server.MqttConnection;
 import org.jetlinks.community.network.mqtt.server.MqttServer;
 import org.jetlinks.community.network.utils.DeviceGatewayHelper;
+import org.jetlinks.community.utils.SystemUtils;
 import org.jetlinks.core.ProtocolSupport;
-import org.jetlinks.core.device.AuthenticationResponse;
-import org.jetlinks.core.device.DeviceOperator;
-import org.jetlinks.core.device.DeviceRegistry;
-import org.jetlinks.core.device.MqttAuthenticationRequest;
+import org.jetlinks.core.device.*;
+import org.jetlinks.core.device.session.DeviceSessionManager;
 import org.jetlinks.core.message.CommonDeviceMessage;
 import org.jetlinks.core.message.CommonDeviceMessageReply;
 import org.jetlinks.core.message.DeviceMessage;
@@ -28,8 +28,11 @@ import org.jetlinks.core.message.codec.FromDeviceMessageContext;
 import org.jetlinks.core.message.codec.MqttMessage;
 import org.jetlinks.core.message.codec.Transport;
 import org.jetlinks.core.server.session.DeviceSession;
-import org.jetlinks.core.server.session.DeviceSessionManager;
+import org.jetlinks.core.server.session.KeepOnlineSession;
 import org.jetlinks.core.server.session.ReplaceableDeviceSession;
+import org.jetlinks.core.trace.DeviceTracer;
+import org.jetlinks.core.trace.FluxTracer;
+import org.jetlinks.core.trace.MonoTracer;
 import org.jetlinks.supports.server.DecodedClientMessageHandler;
 import org.springframework.util.StringUtils;
 import reactor.core.Disposable;
@@ -46,33 +49,33 @@ import java.util.concurrent.atomic.LongAdder;
 import java.util.function.Function;
 
 @Slf4j
-class MqttServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGateway {
-
-    @Getter
-    private final String id;
+class MqttServerDeviceGateway extends AbstractDeviceGateway implements MonitorSupportDeviceGateway {
 
+    //设备注册中心
     private final DeviceRegistry registry;
 
-    private final DeviceSessionManager sessionManager;
+    //设备会话管理器
+    private final org.jetlinks.core.device.session.DeviceSessionManager sessionManager;
 
+    //Mqtt 服务
     private final MqttServer mqttServer;
 
+    //解码后的设备消息处理器
     private final DecodedClientMessageHandler messageHandler;
 
-    private final DeviceGatewayMonitor gatewayMonitor;
-
+    //连接计数器
     private final LongAdder counter = new LongAdder();
 
-    private final EmitterProcessor<Message> messageProcessor = EmitterProcessor.create(false);
-
-    private final FluxSink<Message> sink = messageProcessor.sink(FluxSink.OverflowStrategy.BUFFER);
-
-    private final AtomicBoolean started = new AtomicBoolean();
-
+    //自定义的认证协议,在设备网关里配置自定义的认证协议来进行统一的设备认证处理
+    //场景: 默认情况下时使用mqtt的clientId作为设备ID来进行设备与连接的绑定的,如果clientId的规则比较复杂
+    //或者需要使用其他的clientId规则,则可以指定自定义的认证协议来进行认证.
+    //指定了自定义协议的局限是: 所有使用同一个mqtt服务接入的设备,认证规则都必须一致才行.
     private final Mono<ProtocolSupport> supportMono;
 
+    //注销监听器
     private Disposable disposable;
 
+    //设备网关消息处理工具类
     private final DeviceGatewayHelper helper;
 
     public MqttServerDeviceGateway(String id,
@@ -81,8 +84,7 @@ class MqttServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGate
                                    MqttServer mqttServer,
                                    DecodedClientMessageHandler messageHandler,
                                    Mono<ProtocolSupport> customProtocol) {
-        this.gatewayMonitor = GatewayMonitors.getDeviceGatewayMonitor(id);
-        this.id = id;
+        super(id);
         this.registry = registry;
         this.sessionManager = sessionManager;
         this.mqttServer = mqttServer;
@@ -97,58 +99,78 @@ class MqttServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGate
     }
 
     private void doStart() {
-        if (started.getAndSet(true) || disposable != null) {
-            return;
+        if (disposable != null) {
+            disposable.dispose();
         }
         disposable = mqttServer
+            //监听连接
             .handleConnection()
             .filter(conn -> {
-                if (!started.get()) {
+                //暂停或者已停止时.
+                if (!isStarted()) {
+                    //直接响应SERVER_UNAVAILABLE
                     conn.reject(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE);
-                    gatewayMonitor.rejected();
+                    monitor.rejected();
                 }
-                return started.get();
+                return isStarted();
             })
             .publishOn(Schedulers.parallel())
+            //处理mqtt连接请求
             .flatMap(this::handleConnection)
+            //处理认证结果
             .flatMap(tuple3 -> handleAuthResponse(tuple3.getT1(), tuple3.getT2(), tuple3.getT3()))
             .flatMap(tp -> handleAcceptedMqttConnection(tp.getT1(), tp.getT2(), tp.getT3()), Integer.MAX_VALUE)
-            .subscriberContext(ReactiveLogger.start("network", mqttServer.getId()))
+            .contextWrite(ReactiveLogger.start("network", mqttServer.getId()))
             .subscribe();
 
     }
 
     //处理连接,并进行认证
     private Mono<Tuple3<DeviceOperator, AuthenticationResponse, MqttConnection>> handleConnection(MqttConnection connection) {
+        //内存不够了
+        if (SystemUtils.memoryIsOutOfWatermark()) {
+            //直接拒绝,响应SERVER_UNAVAILABLE,不再处理此连接
+            connection.reject(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE);
+            return Mono.empty();
+        }
         return Mono
             .justOrEmpty(connection.getAuth())
             .flatMap(auth -> {
-                MqttAuthenticationRequest request = new MqttAuthenticationRequest(connection.getClientId(), auth.getUsername(), auth
-                    .getPassword(), getTransport());
+                MqttAuthenticationRequest request = new MqttAuthenticationRequest(connection.getClientId(),
+                                                                                  auth.getUsername(),
+                                                                                  auth.getPassword(),
+                                                                                  getTransport());
                 return supportMono
                     //使用自定义协议来认证
                     .map(support -> support.authenticate(request, registry))
+                    //没有指定自定义协议,则使用clientId对应的设备进行认证.
                     .defaultIfEmpty(Mono.defer(() -> registry
                         .getDevice(connection.getClientId())
                         .flatMap(device -> device.authenticate(request))))
                     .flatMap(Function.identity())
+                    //如果认证结果返回空,说明协议没有设置认证,或者认证返回不对,默认返回BAD_USER_NAME_OR_PASSWORD,防止由于协议编写不当导致mqtt任意访问的安全问题.
                     .switchIfEmpty(Mono.fromRunnable(() -> connection.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD)));
             })
             .flatMap(resp -> {
+                //认证响应可以自定义设备ID,如果没有则使用mqtt的clientId
                 String deviceId = StringUtils.isEmpty(resp.getDeviceId()) ? connection.getClientId() : resp.getDeviceId();
                 //认证返回了新的设备ID,则使用新的设备
                 return registry
                     .getDevice(deviceId)
                     .map(operator -> Tuples.of(operator, resp, connection))
+                    //设备不存在,应答IDENTIFIER_REJECTED
                     .switchIfEmpty(Mono.fromRunnable(() -> connection.reject(MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED)))
                     ;
             })
-            //设备注册信息不存在,拒绝连接
+            //设备认证错误,拒绝连接
             .onErrorResume((err) -> Mono.fromRunnable(() -> {
-                gatewayMonitor.rejected();
-                connection.reject(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE);
                 log.error("MQTT连接认证[{}]失败", connection.getClientId(), err);
-            }));
+                //监控信息
+                monitor.rejected();
+                //应答SERVER_UNAVAILABLE
+                connection.reject(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE);
+            }))
+            .subscribeOn(Schedulers.parallel());
     }
 
     //处理认证结果
@@ -156,76 +178,101 @@ class MqttServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGate
                                                                                                    AuthenticationResponse resp,
                                                                                                    MqttConnection connection) {
         return Mono
-            .fromCallable(() -> {
-                try {
-                    String deviceId = device.getDeviceId();
-                    if (resp.isSuccess()) {
-                        counter.increment();
-                        DeviceSession session = sessionManager.getSession(deviceId);
-                        MqttConnectionSession newSession = new MqttConnectionSession(deviceId, device, getTransport(), connection, gatewayMonitor);
-                        if (null == session) {
-                            sessionManager.register(newSession);
-                        } else if (session instanceof ReplaceableDeviceSession) {
-                            ((ReplaceableDeviceSession) session).replaceWith(newSession);
-                        }
-                        gatewayMonitor.connected();
-                        gatewayMonitor.totalConnection(counter.sum());
-                        //监听断开连接
-                        connection.onClose(conn -> {
-                            counter.decrement();
-                            DeviceSession _tmp = sessionManager.getSession(newSession.getId());
-                            //只有与创建的会话相同才移除(下线),因为有可能设置了keepOnline,
-                            //或者设备通过其他方式注册了会话,这里断开连接不能影响到以上情况.
-                            if (_tmp != null && _tmp.isWrapFrom(MqttConnectionSession.class)) {
-                                MqttConnectionSession connectionSession = _tmp.unwrap(MqttConnectionSession.class);
-                                if (connectionSession.getConnection() == conn) {
-                                    sessionManager.unregister(deviceId);
-                                }
+            .defer(() -> {
+                String deviceId = device.getDeviceId();
+                //认证通过
+                if (resp.isSuccess()) {
+                    counter.increment();
+                    return sessionManager
+                        .compute(deviceId, old -> {
+                            MqttConnectionSession newSession = new MqttConnectionSession(deviceId, device, getTransport(), connection, monitor);
+                            return old
+                                .doOnNext(session -> {
+                                    if (session instanceof ReplaceableDeviceSession) {
+                                        //如果是可替换的会话,则替换为新的会话
+                                        //通常是设置了keepOnline或者之前的会话还没有来得及移除时,直接更新为新的会话.
+                                        ((ReplaceableDeviceSession) session).replaceWith(newSession);
+                                    }
+                                })
+                                .defaultIfEmpty(newSession);
+                        })
+                        .flatMap(session -> Mono.fromCallable(() -> {
+                            try {
+                                //监听断开连接
+                                connection.onClose(conn -> {
+                                    counter.decrement();
+                                    //监控信息
+                                    monitor.disconnected();
+                                    monitor.totalConnection(counter.sum());
+
+                                    sessionManager
+                                        .getSession(session.getDeviceId())
+                                        .flatMap(_tmp -> {
+                                            //只有与创建的会话相同才移除(下线),因为有可能设置了keepOnline,
+                                            //或者设备通过其他方式注册了会话,这里断开连接不能影响到以上情况.
+                                            if (_tmp != null && _tmp.isWrapFrom(MqttConnectionSession.class) && !(_tmp instanceof KeepOnlineSession)) {
+                                                MqttConnectionSession connectionSession = _tmp.unwrap(MqttConnectionSession.class);
+                                                if (connectionSession.getConnection() == conn) {
+                                                    return sessionManager.remove(deviceId, true);
+                                                }
+                                            }
+                                            return Mono.empty();
+                                        })
+                                        .subscribe();
+                                });
+                                return Tuples.of(connection.accept(), device, session.unwrap(MqttConnectionSession.class));
+                            } catch (IllegalStateException ignore) {
+                                //忽略错误,偶尔可能会出现网络异常,导致accept时,连接已经中断.还有其他更好的处理方式?
+                                return null;
                             }
-                            gatewayMonitor.disconnected();
-                            gatewayMonitor.totalConnection(counter.sum());
+                        }))
+                        .doOnNext(o -> {
+                            //监控信息
+                            monitor.connected();
+                            monitor.totalConnection(counter.sum());
                         });
-                        return Tuples.of(connection.accept(), device, newSession);
-                    } else {
-                        log.warn("MQTT客户端认证[{}]失败:{}", deviceId, resp.getMessage());
-                        connection.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD);
-                        gatewayMonitor.rejected();
-                    }
-                } catch (IllegalStateException ignore) {
-
+                } else {
+                    //认证失败返回 0x04 BAD_USER_NAME_OR_PASSWORD
+                    connection.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD);
+                    monitor.rejected();
+                    log.warn("MQTT客户端认证[{}]失败:{}", deviceId, resp.getMessage());
                 }
-                return null;
+                return Mono.empty();
             })
             .onErrorResume(error -> Mono.fromRunnable(() -> {
                 log.error(error.getMessage(), error);
-                gatewayMonitor.rejected();
+                monitor.rejected();
+                //发生错误时应答 SERVER_UNAVAILABLE
                 connection.reject(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE);
-            }));
+            }))
+            ;
     }
 
     //处理已经建立连接的MQTT连接
-    private Mono<Void> handleAcceptedMqttConnection(MqttConnection connection, DeviceOperator operator, MqttConnectionSession session) {
-
-        return connection
-            .handleMessage()
-            .filter(pb -> started.get())
-            .doOnCancel(() -> {
-                //流被取消时(可能网关关闭了)断开连接
-                connection.close().subscribe();
-            })
-            .publishOn(Schedulers.parallel())
-            .doOnNext(msg -> gatewayMonitor.receivedMessage())
-            .flatMap(publishing ->
-                         this.decodeAndHandleMessage(operator, session, publishing.getMessage(), connection)
-                             //ack
-                             .doOnSuccess(s -> publishing.acknowledge())
+    private Mono<Void> handleAcceptedMqttConnection(MqttConnection connection,
+                                                    DeviceOperator operator,
+                                                    MqttConnectionSession session) {
+
+
+        return Flux
+            .usingWhen(Mono.just(connection),
+                       MqttConnection::handleMessage,
+                       MqttConnection::close)
+            //网关暂停或者已停止时,则不处理消息
+            .filter(pb -> isStarted())
+            .doOnNext(msg -> monitor.receivedMessage())
+            //解码收到的mqtt报文
+            .flatMap(publishing -> this
+                .decodeAndHandleMessage(operator, session, publishing.getMessage(), connection)
+                //应答MQTT(QoS1,2的场景)
+                .doOnSuccess(s -> publishing.acknowledge())
             )
             //合并遗言消息
             .mergeWith(
                 Mono.justOrEmpty(connection.getWillMessage())
+                    //解码遗言消息
                     .flatMap(mqttMessage -> this.decodeAndHandleMessage(operator, session, mqttMessage, connection))
             )
-            .subscriberContext(ReactiveLogger.start("network", mqttServer.getId()))
             .then();
     }
 
@@ -237,53 +284,53 @@ class MqttServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGate
         return operator
             .getProtocol()
             .flatMap(protocol -> protocol.getMessageCodec(getTransport()))
+            //解码
             .flatMapMany(codec -> codec.decode(FromDeviceMessageContext.of(session, message, registry)))
             .cast(DeviceMessage.class)
             .flatMap(msg -> {
-                if (messageProcessor.hasDownstreams()) {
-                    sink.next(msg);
-                }
-                if (msg instanceof CommonDeviceMessage) {
-                    CommonDeviceMessage _msg = ((CommonDeviceMessage) msg);
-                    if (StringUtils.isEmpty(_msg.getDeviceId())) {
-                        _msg.setDeviceId(operator.getDeviceId());
-                    }
-                }
-                if (msg instanceof CommonDeviceMessageReply) {
-                    CommonDeviceMessageReply<?> _msg = ((CommonDeviceMessageReply<?>) msg);
-                    if (StringUtils.isEmpty(_msg.getDeviceId())) {
-                        _msg.setDeviceId(operator.getDeviceId());
-                    }
+                //回填deviceId,有的场景协议包不能或者没有解析出deviceId,则直接使用连接对应的设备id进行填充.
+                if (!StringUtils.hasText(msg.getDeviceId())) {
+                    msg.thingId(DeviceThingType.device, operator.getDeviceId());
                 }
-                return handleMessage(operator, msg, connection);
+                return this
+                    .handleMessage(operator, msg, connection);
             })
-            .then()
             .doOnEach(ReactiveLogger.onError(err -> log.error("处理MQTT连接[{}]消息失败:{}", operator.getDeviceId(), message, err)))
-            .onErrorResume((err) -> Mono.empty())//发生错误不中断流
-            ;
+            .as(FluxTracer
+                    .create(DeviceTracer.SpanName.decode(operator.getDeviceId()),
+                            (span, msg) -> span.setAttribute(DeviceTracer.SpanKey.message, msg
+                                .toJson()
+                                .toJSONString())))
+            //发生错误不中断流
+            .onErrorResume((err) -> Mono.empty())
+            .then()
+            .subscribeOn(Schedulers.parallel());
     }
 
-    private Mono<Void> handleMessage(DeviceOperator mainDevice,
-                                     DeviceMessage message,
-                                     MqttConnection connection) {
+    private Mono<DeviceMessage> handleMessage(DeviceOperator mainDevice,
+                                              DeviceMessage message,
+                                              MqttConnection connection) {
+        //连接已经断开,直接处理消息,不再处理会话
+        //有的场景下,设备发送了消息,立即就断开了连接,这是会话已经失效了,如果还继续创建会话的话会出现多次上线的问题.
         if (!connection.isAlive()) {
             return messageHandler
                 .handleMessage(mainDevice, message)
-                .then();
+                .thenReturn(message);
         }
+        //统一处理解码后的设备消息
         return helper.handleDeviceMessage(message,
                                           device -> new MqttConnectionSession(device.getDeviceId(),
                                                                               device,
                                                                               getTransport(),
                                                                               connection,
-                                                                              gatewayMonitor),
+                                                                              monitor),
                                           session -> {
 
                                           },
                                           () -> {
                                               log.warn("无法从MQTT[{}]消息中获取设备信息:{}", connection.getClientId(), message);
                                           })
-                     .then();
+                     .thenReturn(message);
     }
 
     @Override
@@ -297,34 +344,16 @@ class MqttServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGate
     }
 
     @Override
-    public Flux<Message> onMessage() {
-        return messageProcessor;
-    }
-
-    @Override
-    public Mono<Void> pause() {
-        return Mono.fromRunnable(() -> started.set(false));
+    protected Mono<Void> doShutdown() {
+        if(disposable!=null){
+            disposable.dispose();
+        }
+        return Mono.empty();
     }
 
     @Override
-    public Mono<Void> startup() {
+    protected Mono<Void> doStartup() {
         return Mono.fromRunnable(this::doStart);
     }
 
-    @Override
-    public Mono<Void> shutdown() {
-        return Mono.fromRunnable(() -> {
-            started.set(false);
-            if (disposable != null && !disposable.isDisposed()) {
-                disposable.dispose();
-            }
-            disposable = null;
-        });
-    }
-
-    @Override
-    public boolean isAlive() {
-        return started.get();
-    }
-
 }

+ 1 - 1
jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttServerDeviceGatewayProvider.java

@@ -9,7 +9,7 @@ import org.jetlinks.community.network.NetworkType;
 import org.jetlinks.community.network.mqtt.server.MqttServer;
 import org.jetlinks.core.ProtocolSupports;
 import org.jetlinks.core.device.DeviceRegistry;
-import org.jetlinks.core.server.session.DeviceSessionManager;
+import org.jetlinks.core.device.session.DeviceSessionManager;
 import org.jetlinks.supports.server.DecodedClientMessageHandler;
 import org.springframework.stereotype.Component;
 import reactor.core.publisher.Mono;

+ 1 - 1
jetlinks-components/network-component/network-core/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>network-component</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 201 - 128
jetlinks-components/network-component/network-core/src/main/java/org/jetlinks/community/network/utils/DeviceGatewayHelper.java

@@ -5,16 +5,18 @@ import org.jetlinks.community.PropertyConstants;
 import org.jetlinks.core.device.DeviceConfigKey;
 import org.jetlinks.core.device.DeviceOperator;
 import org.jetlinks.core.device.DeviceRegistry;
+import org.jetlinks.core.device.session.DeviceSessionManager;
 import org.jetlinks.core.message.*;
 import org.jetlinks.core.message.state.DeviceStateCheckMessage;
 import org.jetlinks.core.message.state.DeviceStateCheckMessageReply;
 import org.jetlinks.core.server.session.ChildrenDeviceSession;
 import org.jetlinks.core.server.session.DeviceSession;
-import org.jetlinks.core.server.session.DeviceSessionManager;
 import org.jetlinks.core.server.session.KeepOnlineSession;
+import org.jetlinks.core.server.session.LostDeviceSession;
 import org.jetlinks.supports.server.DecodedClientMessageHandler;
 import org.springframework.util.StringUtils;
 import reactor.core.publisher.Mono;
+import reactor.util.context.Context;
 
 import java.time.Duration;
 import java.util.function.Consumer;
@@ -36,8 +38,10 @@ public class DeviceGatewayHelper {
     private final DeviceSessionManager sessionManager;
     private final DecodedClientMessageHandler messageHandler;
 
+
     public static Consumer<DeviceSession> applySessionKeepaliveTimeout(DeviceMessage msg, Supplier<Duration> timeoutSupplier) {
         return session -> {
+            //从消息头里获取keepOnlineTimeoutSeconds来设置会话有效期
             Duration timeout = msg
                 .getHeader(Headers.keepOnlineTimeoutSeconds)
                 .map(Duration::ofSeconds)
@@ -48,15 +52,26 @@ public class DeviceGatewayHelper {
         };
     }
 
+    /**
+     * 处理设备消息
+     *
+     * @param message                设备消息
+     * @param sessionBuilder         会话构造器,在会话不存在时,创建会话
+     * @param sessionConsumer        会话自定义回调,处理会话时用来自定义会话,比如重置连接信息
+     * @param deviceNotFoundCallback 设备不存在的监听器回调
+     * @return 设备操作接口
+     */
     public Mono<DeviceOperator> handleDeviceMessage(DeviceMessage message,
                                                     Function<DeviceOperator, DeviceSession> sessionBuilder,
                                                     Consumer<DeviceSession> sessionConsumer,
-                                                    Runnable deviceNotFoundListener) {
+                                                    Runnable deviceNotFoundCallback) {
 
-        return handleDeviceMessage(message, sessionBuilder, sessionConsumer, () -> Mono.fromRunnable(deviceNotFoundListener));
+        return handleDeviceMessage(message, sessionBuilder, sessionConsumer, () -> Mono.fromRunnable(deviceNotFoundCallback));
     }
 
     protected Mono<Void> handleChildrenDeviceMessage(String deviceId, DeviceMessage children) {
+        //设备状态检查,断开设备连接的消息都忽略
+        //这些消息属于状态管理,通常是用来自定义子设备状态的,所以这些消息都忽略处理会话
         if (deviceId == null
             || children instanceof DeviceStateCheckMessage
             || children instanceof DeviceStateCheckMessageReply
@@ -64,41 +79,51 @@ public class DeviceGatewayHelper {
             || children instanceof DisconnectDeviceMessageReply) {
             return Mono.empty();
         }
+        //子设备回复失败的也忽略
         if (children instanceof DeviceMessageReply) {
             DeviceMessageReply reply = ((DeviceMessageReply) children);
             if (!reply.isSuccess()) {
                 return Mono.empty();
             }
         }
-        ChildrenDeviceSession deviceSession = sessionManager.getSession(deviceId, children.getDeviceId());
-        if (deviceSession != null) {
-            deviceSession.keepAlive();
-            applySessionKeepaliveTimeout(children, () -> null)
-                .accept(deviceSession);
-        }
+        String childrenId = children.getDeviceId();
+
         //子设备离线或者注销
         if (children instanceof DeviceOfflineMessage || children instanceof DeviceUnRegisterMessage) {
             //注销会话,这里子设备可能会收到多次离线消息
             //注销会话一次离线,消息网关转发子设备消息一次
-            if (deviceSession != null && children instanceof DeviceOfflineMessage) {
-                //忽略离线消息,因为注销会话时,会自动发送一个离线消息
-                children.addHeader(Headers.ignore, true);
-            }
             return sessionManager
-                .unRegisterChildren(deviceId, children.getDeviceId())
+                .remove(childrenId, children.getHeaderOrDefault(Headers.clearAllSession))
+                .doOnNext(total -> {
+                    if (total > 0) {
+                        children.addHeader(Headers.ignore, true);
+                    }
+                })
                 .then();
-        }
-        if (deviceSession == null && null != children.getDeviceId()) {
-            //忽略上线消息,因为注册会话时,会自动发送一个上线消息
+        } else {
+            //子设备上线
             if (children instanceof DeviceOnlineMessage) {
                 children.addHeader(Headers.ignore, true);
             }
-            Mono<Void> registerSession = sessionManager
-                .registerChildren(deviceId, children.getDeviceId())
-                .then();
+            Mono<DeviceSession> registerSession = sessionManager
+                .getSession(deviceId)
+                .flatMap(parentSession -> sessionManager
+                    .compute(childrenId, old -> old
+                        .switchIfEmpty(Mono.defer(() -> registry
+                            .getDevice(childrenId)
+                            .map(child -> new ChildrenDeviceSession(childrenId, parentSession, child)))))
+                )
+                .doOnNext(session -> {
+                    session.keepAlive();
+                    applySessionKeepaliveTimeout(children, () -> null)
+                        .accept(session);
+                });
+
+
             //子设备注册
             if (isDoRegister(children)) {
                 return Mono
+                    //延迟2秒,因为自动注册是异步的,收到消息后并不能保证马上可以注册成功.
                     .delay(Duration.ofSeconds(2))
                     .then(registry
                               .getDevice(children.getDeviceId())
@@ -106,138 +131,185 @@ public class DeviceGatewayHelper {
                                   //没有配置状态自管理才自动上线
                                   .getSelfConfig(DeviceConfigKey.selfManageState)
                                   .defaultIfEmpty(false)
-                                  .filter(Boolean.FALSE::equals)
-                                  .flatMap(ignore -> registerSession))
-                    );
+                                  .filter(Boolean.FALSE::equals))
+                              .flatMap(ignore -> registerSession))
+                    .then();
             }
-            return registerSession;
+            return registerSession.then();
         }
-        return Mono.empty();
     }
 
-    /**
-     * 处理来自设备网关的设备消息
-     *
-     * @param message                设备消息
-     * @param sessionBuilder         设备操作
-     * @param sessionConsumer        设备消费
-     * @param deviceNotFoundListener 异常监听
-     * @return 设备操作
-     */
     public Mono<DeviceOperator> handleDeviceMessage(DeviceMessage message,
-                                                    Function<DeviceOperator, DeviceSession> sessionBuilder,
-                                                    Consumer<DeviceSession> sessionConsumer,
-                                                    Supplier<Mono<DeviceOperator>> deviceNotFoundListener) {
+                                                    Function<DeviceOperator, Mono<DeviceSession>> sessionBuilder,
+                                                    Function<DeviceSession, Mono<Void>> sessionConsumer,
+                                                    Supplier<Mono<DeviceOperator>> deviceNotFoundCallback) {
         String deviceId = message.getDeviceId();
         if (StringUtils.isEmpty(deviceId)) {
             return Mono.empty();
         }
         Mono<Void> then = Mono.empty();
         boolean doHandle = true;
+        //子设备消息
         if (message instanceof ChildDeviceMessage) {
             DeviceMessage childrenMessage = (DeviceMessage) ((ChildDeviceMessage) message).getChildDeviceMessage();
             then = handleChildrenDeviceMessage(deviceId, childrenMessage);
-        } else if (message instanceof ChildDeviceMessageReply) {
+        }
+        //子设备消息回复
+        else if (message instanceof ChildDeviceMessageReply) {
             DeviceMessage childrenMessage = (DeviceMessage) ((ChildDeviceMessageReply) message).getChildDeviceMessage();
             then = handleChildrenDeviceMessage(deviceId, childrenMessage);
-        } else if (message instanceof DeviceOfflineMessage) {
-            //设备离线消息
-            DeviceSession session = sessionManager.unregister(deviceId);
-            if (null == session) {
-                //如果session不存在,则将离线消息转发到
-                return registry
-                    .getDevice(deviceId)
-                    .flatMap(device -> messageHandler
-                        .handleMessage(device, message)
-                        .thenReturn(device));
-            }
-            return registry.getDevice(deviceId);
-        } else if (message instanceof DeviceOnlineMessage) {
-            //设备在线消息
-            doHandle = false;
         }
-        DeviceSession session = sessionManager.getSession(deviceId);
-        //session不存在,可能是同一个连接返回多个设备消息
-        if (session == null) {
-            return registry
-                .getDevice(deviceId)
-                .switchIfEmpty(Mono.defer(() -> {
-                    //设备注册
-                    if (isDoRegister(message)) {
-                        return messageHandler
-                            .handleMessage(null, message)
-                            //延迟2秒后尝试重新获取设备并上线
-                            .then(Mono.delay(Duration.ofSeconds(2)))
-                            .then(registry.getDevice(deviceId));
-                    }
-                    if (deviceNotFoundListener != null) {
-                        return deviceNotFoundListener.get();
+        //设备离线消息
+        else if (message instanceof DeviceOfflineMessage) {
+            return sessionManager
+                .remove(deviceId, message.getHeaderOrDefault(Headers.clearAllSession))
+                .flatMap(l -> {
+                    if (l == 0) {
+                        return registry
+                            .getDevice(deviceId)
+                            .flatMap(device -> messageHandler.handleMessage(device, message));
                     }
                     return Mono.empty();
-                }))
-                .flatMap(device -> {
-                    //忽略会话管理,比如一个设备存在多种接入方式时,其中一种接入方式收到的消息设置忽略会话来防止会话冲突
-                    if (message.getHeader(Headers.ignoreSession).orElse(false)) {
-                        if (!isDoRegister(message)) {
-                            return messageHandler
-                                .handleMessage(device, message)
-                                .thenReturn(device);
+                })
+                .then(
+                    registry.getDevice(deviceId)
+                )
+                .contextWrite(Context.of(DeviceMessage.class, message));
+        }
+        //设备在线消息
+        else if (message instanceof DeviceOnlineMessage) {
+
+            doHandle = false;
+        }
+
+        boolean fHandle = doHandle;
+        return sessionManager
+            .compute(deviceId, (old) -> old
+                .map(session -> {
+                    //会话已存在
+                    Mono<Void> after = Mono.empty();
+                    //没有忽略会话
+                    if (!message.getHeader(Headers.ignoreSession).orElse(false)) {
+                        //消息中指定保存在线
+                        if (message.getHeader(Headers.keepOnline).orElse(false)
+                            && !(session instanceof KeepOnlineSession)) {
+                            Duration timeout = message
+                                .getHeader(Headers.keepOnlineTimeoutSeconds)
+                                .map(Duration::ofSeconds)
+                                .orElse(Duration.ofHours(1));
+                            //替换session
+                            session = new KeepOnlineSession(session, timeout);
                         }
-                        return Mono.just(device);
+                        //KeepOnline的连接丢失时,重新创建会话,并替换丢失的会话。
+                        if (session.isWrapFrom(KeepOnlineSession.class) && session.isWrapFrom(LostDeviceSession.class)) {
+                            after = sessionBuilder
+                                .apply(session.getOperator())
+                                .doOnNext(session.unwrap(KeepOnlineSession.class)::replaceWith)
+                                .then();
+                        }
+                        after = after.then(
+                            sessionConsumer.apply(session)
+                        );
+
                     }
-                    //session已经存在了,可能是并发创建.
-                    DeviceSession trySession = sessionManager.getSession(deviceId);
-                    if (trySession != null) {
-                        trySession.keepAlive();
-                        return Mono.just(device);
+                    session.keepAlive();
+                    if (fHandle) {
+                        //处理消息
+                        return messageHandler
+                            .handleMessage(session.getOperator(), message)
+                            .then(after)
+                            .thenReturn(session);
                     }
-                    DeviceSession newSession = sessionBuilder.apply(device);
-                    if (null != newSession) {
-                        //保持会话,在低功率设备上,可能无法保持长连接.
-                        if (message.getHeader(Headers.keepOnline).orElse(false)) {
-                            int timeout = message.getHeaderOrDefault(Headers.keepOnlineTimeoutSeconds);
-                            newSession = new KeepOnlineSession(newSession, Duration.ofSeconds(timeout));
-                        }
-                        //注册会话
-                        sessionManager.register(newSession);
-                        //执行自定义会话回调
-                        sessionConsumer.accept(newSession);
-                        //保活
-                        newSession.keepAlive();
-                        if (!(message instanceof DeviceRegisterMessage) &&
-                            !(message instanceof DeviceOnlineMessage)) {
+                    return after
+                        .thenReturn(session);
+                })
+                .defaultIfEmpty(Mono.defer(() -> registry
+                    .getDevice(deviceId)
+                    .switchIfEmpty(Mono.defer(() -> {
+                        //设备注册
+                        if (isDoRegister(message)) {
                             return messageHandler
-                                .handleMessage(device, message)
-                                .thenReturn(device);
+                                .handleMessage(null, message)
+                                //延迟2秒后尝试重新获取设备并上线
+                                .then(Mono.delay(Duration.ofSeconds(2)))
+                                .then(registry.getDevice(deviceId));
                         }
-                    }
-                    return Mono.just(device);
-                })
-                .switchIfEmpty(then.then(Mono.empty()))
-                .flatMap(then::thenReturn)
-                ;
-        } else {
-            //消息中指定保存在线
-            if (message.getHeader(Headers.keepOnline).orElse(false)
-                && !(session instanceof KeepOnlineSession)) {
-                Duration timeout = message
-                    .getHeader(Headers.keepOnlineTimeoutSeconds)
-                    .map(Duration::ofSeconds)
-                    .orElse(Duration.ofHours(1));
-                //替换session
-                session = sessionManager.replace(session, new KeepOnlineSession(session, timeout));
-            }
-            sessionConsumer.accept(session);
-            session.keepAlive();
-            if (doHandle) {
-                return messageHandler
-                    .handleMessage(session.getOperator(), message)
-                    .then(then)
-                    .then(registry.getDevice(deviceId));
-            }
-            return then
-                .then(registry.getDevice(deviceId));
-        }
+                        if (deviceNotFoundCallback != null) {
+                            return deviceNotFoundCallback.get();
+                        }
+                        return Mono.empty();
+                    }))
+                    .flatMap(device -> {
+                        //忽略会话管理,比如一个设备存在多种接入方式时,其中一种接入方式收到的消息设置忽略会话来防止会话冲突
+                        if (message.getHeader(Headers.ignoreSession).orElse(false)) {
+                            if (!isDoRegister(message)) {
+                                return messageHandler
+                                    .handleMessage(device, message)
+                                    .then(Mono.empty());
+                            }
+                            return Mono.empty();
+                        }
+                        return sessionBuilder
+                            .apply(device)
+                            .flatMap(newSession -> {
+                                //保持会话,在低功率设备上,可能无法保持长连接.
+                                if (message.getHeader(Headers.keepOnline).orElse(false)) {
+                                    int timeout = message.getHeaderOrDefault(Headers.keepOnlineTimeoutSeconds);
+                                    newSession = new KeepOnlineSession(newSession, Duration.ofSeconds(timeout));
+                                }
+                                //执行自定义会话回调
+                                sessionConsumer.apply(newSession);
+                                //保活
+                                newSession.keepAlive();
+                                if (!(message instanceof DeviceRegisterMessage) &&
+                                    !(message instanceof DeviceOnlineMessage)) {
+                                    return
+                                        sessionConsumer
+                                            .apply(newSession)
+                                            .then(
+                                                messageHandler
+                                                    .handleMessage(device, message)
+                                            )
+                                            .thenReturn(newSession);
+                                } else {
+                                    return sessionConsumer
+                                        .apply(newSession)
+                                        .thenReturn(newSession);
+                                }
+                            });
+                    })))
+                .flatMap(Function.identity()))
+            .then(then)
+            .then(registry.getDevice(deviceId))
+            .contextWrite(Context.of(DeviceMessage.class,message));
+
+
+    }
+
+
+    /**
+     * 处理设备消息
+     *
+     * @param message                设备消息
+     * @param sessionBuilder         会话构造器,在会话不存在时,创建会话
+     * @param sessionConsumer        会话自定义回调,处理会话时用来自定义会话,比如重置连接信息
+     * @param deviceNotFoundCallback 设备不存在的监听器回调
+     * @return 设备操作接口
+     */
+    public Mono<DeviceOperator> handleDeviceMessage(DeviceMessage message,
+                                                    Function<DeviceOperator, DeviceSession> sessionBuilder,
+                                                    Consumer<DeviceSession> sessionConsumer,
+                                                    Supplier<Mono<DeviceOperator>> deviceNotFoundCallback) {
+        return this
+            .handleDeviceMessage(
+                message,
+                device -> Mono.justOrEmpty(sessionBuilder.apply(device)),
+                session -> {
+                    sessionConsumer.accept(session);
+                    return Mono.empty();
+                },
+                deviceNotFoundCallback
+            );
 
     }
 
@@ -248,4 +320,5 @@ public class DeviceGatewayHelper {
     }
 
 
+
 }

+ 1 - 1
jetlinks-components/network-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <packaging>pom</packaging>

+ 1 - 1
jetlinks-components/network-component/tcp-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>network-component</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 84 - 170
jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/device/TcpServerDeviceGateway.java

@@ -1,11 +1,8 @@
 package org.jetlinks.community.network.tcp.device;
 
-import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import org.hswebframework.web.logger.ReactiveLogger;
-import org.jetlinks.community.gateway.DeviceGateway;
-import org.jetlinks.community.gateway.monitor.DeviceGatewayMonitor;
-import org.jetlinks.community.gateway.monitor.GatewayMonitors;
+import org.jetlinks.community.gateway.AbstractDeviceGateway;
 import org.jetlinks.community.gateway.monitor.MonitorSupportDeviceGateway;
 import org.jetlinks.community.network.DefaultNetworkType;
 import org.jetlinks.community.network.NetworkType;
@@ -18,39 +15,29 @@ import org.jetlinks.core.ProtocolSupports;
 import org.jetlinks.core.device.DeviceOperator;
 import org.jetlinks.core.device.DeviceProductOperator;
 import org.jetlinks.core.device.DeviceRegistry;
+import org.jetlinks.core.device.session.DeviceSessionManager;
 import org.jetlinks.core.message.DeviceMessage;
-import org.jetlinks.core.message.Message;
 import org.jetlinks.core.message.codec.DefaultTransport;
-import org.jetlinks.core.message.codec.EncodedMessage;
 import org.jetlinks.core.message.codec.FromDeviceMessageContext;
 import org.jetlinks.core.message.codec.Transport;
 import org.jetlinks.core.server.DeviceGatewayContext;
 import org.jetlinks.core.server.session.DeviceSession;
-import org.jetlinks.core.server.session.DeviceSessionManager;
+import org.jetlinks.core.trace.DeviceTracer;
+import org.jetlinks.core.trace.MonoTracer;
 import org.jetlinks.supports.server.DecodedClientMessageHandler;
 import reactor.core.Disposable;
-import reactor.core.publisher.EmitterProcessor;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.FluxSink;
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
 
 import java.net.InetSocketAddress;
 import java.time.Duration;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.atomic.LongAdder;
 
 @Slf4j(topic = "system.tcp.gateway")
-class TcpServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGateway {
+class TcpServerDeviceGateway extends AbstractDeviceGateway implements  MonitorSupportDeviceGateway {
 
-    @Getter
-    private final String id;
-
-    /**
-     * 维护所有创建的tcp server
-     */
     private final TcpServer tcpServer;
 
     private final String protocol;
@@ -59,25 +46,13 @@ class TcpServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGatew
 
     private final DeviceRegistry registry;
 
-    private final DeviceSessionManager sessionManager;
-
-    private final DeviceGatewayMonitor gatewayMonitor;
+    private final org.jetlinks.core.device.session.DeviceSessionManager sessionManager;
 
-    /**
-     * 连接计数器
-     */
     private final LongAdder counter = new LongAdder();
 
-    private final EmitterProcessor<Message> processor = EmitterProcessor.create(false);
-
-    private final FluxSink<Message> sink = processor.sink(FluxSink.OverflowStrategy.BUFFER);
+    private Disposable disposable;
 
-    private final AtomicBoolean started = new AtomicBoolean();
     private final DeviceGatewayHelper helper;
-    /**
-     * 数据流控开关
-     */
-    private Disposable disposable;
 
     public TcpServerDeviceGateway(String id,
                                   String protocol,
@@ -86,8 +61,7 @@ class TcpServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGatew
                                   DecodedClientMessageHandler clientMessageHandler,
                                   DeviceSessionManager sessionManager,
                                   TcpServer tcpServer) {
-        this.gatewayMonitor = GatewayMonitors.getDeviceGatewayMonitor(id);
-        this.id = id;
+        super(id);
         this.protocol = protocol;
         this.registry = deviceRegistry;
         this.supports = supports;
@@ -100,90 +74,22 @@ class TcpServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGatew
         return supports.getProtocol(protocol);
     }
 
-    /**
-     * 当前总链接
-     *
-     * @return 当前总链接
-     */
     @Override
     public long totalConnection() {
         return counter.sum();
     }
 
-    /**
-     * 传输协议
-     *
-     * @return {@link org.jetlinks.core.message.codec.DefaultTransport}
-     */
     @Override
     public Transport getTransport() {
         return DefaultTransport.TCP;
     }
 
-    /**
-     * 网络类型
-     *
-     * @return {@link  org.jetlinks.community.network.DefaultNetworkType}
-     */
     @Override
     public NetworkType getNetworkType() {
         return DefaultNetworkType.TCP_SERVER;
     }
 
-    /**
-     * 启动网关
-     */
-    private void doStart() {
-        if (started.getAndSet(true) || disposable != null) {
-            return;
-        }
-        // 从TCPServer中获取连接的client
-        // client实例化为TcpConnection之后处理消息
-        disposable = tcpServer
-            .handleConnection()
-            .publishOn(Schedulers.parallel())
-            .flatMap(client -> new TcpConnection(client).accept(), Integer.MAX_VALUE)
-            .onErrorContinue((err, obj) -> log.error(err.getMessage(), err))
-            .subscriberContext(ReactiveLogger.start("network", tcpServer.getId()))
-            .subscribe(
-                ignore -> {
-                },
-                error -> log.error(error.getMessage(), error)
-            );
-    }
-
-    @Override
-    public Flux<Message> onMessage() {
-        return processor;
-    }
-
-    @Override
-    public Mono<Void> pause() {
-        return Mono.fromRunnable(() -> started.set(false));
-    }
-
-    @Override
-    public Mono<Void> startup() {
-        return Mono.fromRunnable(this::doStart);
-    }
-
-    @Override
-    public Mono<Void> shutdown() {
-        return Mono.fromRunnable(() -> {
-            started.set(false);
-            disposable.dispose();
-            disposable = null;
-        });
-    }
 
-    @Override
-    public boolean isAlive() {
-        return started.get();
-    }
-
-    /**
-     * TCP 客户端连接
-     */
     class TcpConnection implements DeviceGatewayContext {
         final TcpClient client;
         final AtomicReference<Duration> keepaliveTimeout = new AtomicReference<>();
@@ -193,51 +99,27 @@ class TcpServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGatew
         TcpConnection(TcpClient client) {
             this.client = client;
             this.address = client.getRemoteAddress();
-            gatewayMonitor.totalConnection(counter.sum());
+            monitor.totalConnection(counter.sum());
             client.onDisconnect(() -> {
                 counter.decrement();
-                gatewayMonitor.disconnected();
-                gatewayMonitor.totalConnection(counter.sum());
+                monitor.disconnected();
+                monitor.totalConnection(counter.sum());
+                //check session
+                sessionManager
+                    .getSession(client.getId())
+                    .subscribe();
             });
-            gatewayMonitor.connected();
-            DeviceSession session = sessionManager.getSession(client.getId());
-            if (session == null) {
-                session = new UnknownTcpDeviceSession(client.getId(), client, getTransport()) {
-                    @Override
-                    public Mono<Boolean> send(EncodedMessage encodedMessage) {
-                        return super.send(encodedMessage).doOnSuccess(r -> gatewayMonitor.sentMessage());
-                    }
-
-                    @Override
-                    public void setKeepAliveTimeout(Duration timeout) {
-                        keepaliveTimeout.set(timeout);
-                        client.setKeepAliveTimeout(timeout);
-                    }
-
-                    @Override
-                    public Optional<InetSocketAddress> getClientAddress() {
-                        return Optional.of(address);
-                    }
-                };
-            }
-
-            sessionRef.set(session);
-
+            monitor.connected();
+            sessionRef.set(new UnknownTcpDeviceSession(client.getId(), client, getTransport()));
         }
 
-        /**
-         * 接收消息
-         *
-         * @return void
-         */
         Mono<Void> accept() {
             return getProtocol()
                 .flatMap(protocol -> protocol.onClientConnect(getTransport(), client, this))
                 .then(
                     client
                         .subscribe()
-                        .filter(tcp -> started.get())
-                        .publishOn(Schedulers.parallel())
+                        .filter(tcp -> isStarted())
                         .flatMap(this::handleTcpMessage)
                         .onErrorResume((err) -> {
                             log.error(err.getMessage(), err);
@@ -249,50 +131,46 @@ class TcpServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGatew
                 .doOnCancel(client::shutdown);
         }
 
-        /**
-         * 处理TCP消息 ==>> 设备消息
-         *
-         * @param message tcp消息
-         * @return void
-         */
         Mono<Void> handleTcpMessage(TcpMessage message) {
+            long time = System.nanoTime();
             return getProtocol()
                 .flatMap(pt -> pt.getMessageCodec(getTransport()))
                 .flatMapMany(codec -> codec.decode(FromDeviceMessageContext.of(sessionRef.get(), message, registry)))
                 .cast(DeviceMessage.class)
-                .doOnNext(msg -> gatewayMonitor.receivedMessage())
-                .flatMap(this::handleDeviceMessage)
-                .doOnEach(ReactiveLogger.onError(err -> log.error("处理TCP[{}]消息失败:\n{}",
-                    address,
-                    message
-                    , err)))
+                .flatMap(msg -> this
+                    .handleDeviceMessage(msg)
+                    .as(MonoTracer.create(
+                        DeviceTracer.SpanName.decode(msg.getDeviceId()),
+                        builder -> {
+                            builder.setAttribute(DeviceTracer.SpanKey.message, msg.toString());
+                            builder.setStartTimestamp(time, TimeUnit.NANOSECONDS);
+                        })))
+                .doOnEach(ReactiveLogger
+                              .onError(err -> log.error("Handle TCP[{}] message failed:\n{}",
+                                                        address,
+                                                        message
+                                  , err)))
+
                 .onErrorResume((err) -> Mono.fromRunnable(client::reset))
+                .subscribeOn(Schedulers.parallel())
                 .then();
         }
 
-        /**
-         * 处理设备消息
-         *
-         * @param message 设备消息
-         * @return void
-         */
-        Mono<Void> handleDeviceMessage(DeviceMessage message) {
-            if (processor.hasDownstreams()) {
-                sink.next(message);
-            }
+        Mono<DeviceMessage> handleDeviceMessage(DeviceMessage message) {
+            monitor.receivedMessage();
             return helper
                 .handleDeviceMessage(message,
-                    device -> new TcpDeviceSession(device, client, getTransport(), gatewayMonitor),
-                    DeviceGatewayHelper
-                        .applySessionKeepaliveTimeout(message, keepaliveTimeout::get)
-                        .andThen(session -> {
-                            TcpDeviceSession deviceSession = session.unwrap(TcpDeviceSession.class);
-                            deviceSession.setClient(client);
-                            sessionRef.set(deviceSession);
-                        }),
-                    () -> log.warn("无法从tcp[{}]消息中获取设备信息:{}", address, message)
+                                     device -> new TcpDeviceSession(device, client, getTransport(), monitor),
+                                     DeviceGatewayHelper
+                                         .applySessionKeepaliveTimeout(message, keepaliveTimeout::get)
+                                         .andThen(session -> {
+                                             TcpDeviceSession deviceSession = session.unwrap(TcpDeviceSession.class);
+                                             deviceSession.setClient(client);
+                                             sessionRef.set(deviceSession);
+                                         }),
+                                     () -> log.warn("TCP{}: The device[{}] in the message body does not exist:{}", address, message.getDeviceId(), message)
                 )
-                .then();
+                .thenReturn(message);
         }
 
         @Override
@@ -307,7 +185,43 @@ class TcpServerDeviceGateway implements DeviceGateway, MonitorSupportDeviceGatew
 
         @Override
         public Mono<Void> onMessage(DeviceMessage message) {
-            return handleDeviceMessage(message);
+            return handleDeviceMessage(message).then();
         }
     }
+
+    private void doStart() {
+        if ( disposable != null) {
+            disposable.dispose();
+        }
+        disposable = tcpServer
+            .handleConnection()
+            .publishOn(Schedulers.parallel())
+            .flatMap(client -> new TcpConnection(client)
+                         .accept()
+                         .onErrorResume(err -> {
+                             log.error("handle tcp client[{}] error", client.getRemoteAddress(), err);
+                             return Mono.empty();
+                         })
+                , Integer.MAX_VALUE)
+            .onErrorContinue((err, obj) -> log.error(err.getMessage(), err))
+            .contextWrite(ReactiveLogger.start("network", tcpServer.getId()))
+            .subscribe(
+                ignore -> {
+                },
+                error -> log.error(error.getMessage(), error)
+            );
+    }
+
+    @Override
+    protected Mono<Void> doShutdown() {
+        if(disposable!=null){
+            disposable.dispose();
+        }
+        return Mono.empty();
+    }
+
+    @Override
+    protected Mono<Void> doStartup() {
+        return Mono.fromRunnable(this::doStart);
+    }
 }

+ 1 - 1
jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/device/TcpServerDeviceGatewayProvider.java

@@ -9,7 +9,7 @@ import org.jetlinks.community.network.NetworkType;
 import org.jetlinks.community.network.tcp.server.TcpServer;
 import org.jetlinks.core.ProtocolSupports;
 import org.jetlinks.core.device.DeviceRegistry;
-import org.jetlinks.core.server.session.DeviceSessionManager;
+import org.jetlinks.core.device.session.DeviceSessionManager;
 import org.jetlinks.supports.server.DecodedClientMessageHandler;
 import org.springframework.stereotype.Component;
 import org.springframework.util.Assert;

+ 1 - 1
jetlinks-components/notify-component/notify-core/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>notify-component</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
jetlinks-components/notify-component/notify-dingtalk/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>notify-component</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
jetlinks-components/notify-component/notify-email/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>notify-component</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
jetlinks-components/notify-component/notify-sms/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>notify-component</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
jetlinks-components/notify-component/notify-voice/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>notify-component</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
jetlinks-components/notify-component/notify-wechat/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>notify-component</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
jetlinks-components/notify-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
jetlinks-components/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-community</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
jetlinks-components/rule-engine-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
jetlinks-components/timeseries-component/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
jetlinks-manager/authentication-manager/pom.xml

@@ -7,7 +7,7 @@
     <parent>
         <groupId>org.jetlinks.community</groupId>
         <artifactId>jetlinks-manager</artifactId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>authentication-manager</artifactId>

+ 1 - 1
jetlinks-manager/device-manager/pom.xml

@@ -7,7 +7,7 @@
     <parent>
         <groupId>org.jetlinks.community</groupId>
         <artifactId>jetlinks-manager</artifactId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>device-manager</artifactId>

+ 1 - 1
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/configuration/DeviceManagerConfiguration.java

@@ -4,9 +4,9 @@ import org.jetlinks.community.device.message.DeviceMessageConnector;
 import org.jetlinks.community.device.message.writer.TimeSeriesMessageWriterConnector;
 import org.jetlinks.community.device.service.data.DeviceDataService;
 import org.jetlinks.core.device.DeviceRegistry;
+import org.jetlinks.core.device.session.DeviceSessionManager;
 import org.jetlinks.core.event.EventBus;
 import org.jetlinks.core.server.MessageHandler;
-import org.jetlinks.core.server.session.DeviceSessionManager;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;

+ 51 - 23
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/message/DeviceMessageConnector.java

@@ -6,16 +6,20 @@ import org.jetlinks.community.PropertyConstants;
 import org.jetlinks.core.Values;
 import org.jetlinks.core.device.DeviceOperator;
 import org.jetlinks.core.device.DeviceRegistry;
+import org.jetlinks.core.device.session.DeviceSessionEvent;
+import org.jetlinks.core.device.session.DeviceSessionManager;
 import org.jetlinks.core.event.EventBus;
 import org.jetlinks.core.message.*;
 import org.jetlinks.core.message.event.EventMessage;
 import org.jetlinks.core.server.MessageHandler;
-import org.jetlinks.core.server.session.DeviceSessionManager;
+import org.jetlinks.core.server.session.ChildrenDeviceSession;
+import org.jetlinks.core.server.session.DeviceSession;
 import org.jetlinks.supports.server.DecodedClientMessageHandler;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 import javax.annotation.Nonnull;
+import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -37,7 +41,10 @@ public class DeviceMessageConnector implements DecodedClientMessageHandler {
         PropertyConstants.deviceName.getKey(),
         PropertyConstants.orgId.getKey()
     };
-    private final static BiConsumer<Throwable, Object> doOnError = (error, val) -> DeviceMessageConnector.log.error(error.getMessage(), error);
+    private final static Function<Throwable, Mono<Void>> doOnError = (error) -> {
+        DeviceMessageConnector.log.error(error.getMessage(), error);
+        return Mono.empty();
+    };
     private final static Function<DeviceOperator, Mono<Values>> configGetter = operator -> operator.getSelfConfigs(allConfigHeader);
     private final static Values emptyValues = Values.of(Collections.emptyMap());
     private static final BiConsumer<Message, StringBuilder>[] fastTopicBuilder;
@@ -135,27 +142,48 @@ public class DeviceMessageConnector implements DecodedClientMessageHandler {
         this.registry = registry;
         this.eventBus = eventBus;
         this.messageHandler = messageHandler;
-        sessionManager
-            .onRegister()
-            .flatMap(session -> {
-                DeviceOnlineMessage message = new DeviceOnlineMessage();
-                message.setDeviceId(session.getDeviceId());
-                message.setTimestamp(session.connectTime());
-                return onMessage(message);
-            })
-            .onErrorContinue(doOnError)
-            .subscribe();
+        sessionManager.listenEvent(event->{
+            if(event.isClusterExists()){
+                return Mono.empty();
+            }
+            //从会话管理器里监听会话注册,转发为设备上线消息
+            if(event.getType()== DeviceSessionEvent.Type.unregister){
+                return this.handleSessionUnregister(event.getSession());
+            }
+            //从会话管理器里监听会话注销,转发为设备离线消息
+            if(event.getType()== DeviceSessionEvent.Type.register){
+                return this.handleSessionRegister(event.getSession());
+            }
+            return Mono.empty();
+        });
+    }
+
 
-        sessionManager
-            .onUnRegister()
-            .flatMap(session -> {
-                DeviceOfflineMessage message = new DeviceOfflineMessage();
-                message.setDeviceId(session.getDeviceId());
-                message.setTimestamp(System.currentTimeMillis());
-                return onMessage(message);
-            })
-            .onErrorContinue(doOnError)
-            .subscribe();
+    protected Mono<Void> handleSessionRegister(DeviceSession session) {
+        DeviceOnlineMessage message = new DeviceOnlineMessage();
+        message.addHeader("from", "session-register");
+        //添加客户端地址信息
+        message.addHeader("address", session.getClientAddress().map(InetSocketAddress::toString).orElse(""));
+        message.setDeviceId(session.getDeviceId());
+        message.setTimestamp(System.currentTimeMillis());
+        return this
+            .onMessage(message)
+            .onErrorResume(doOnError);
+    }
+
+    protected Mono<Void> handleSessionUnregister(DeviceSession session) {
+        DeviceOfflineMessage message = new DeviceOfflineMessage();
+        message.addHeader("from", "session-unregister");
+        message.setDeviceId(session.getDeviceId());
+        message.setTimestamp(System.currentTimeMillis());
+        //子设备会话时添加上级设备id到header中,下游可以直接通过获取header来获取上级设备id
+        if (session.isWrapFrom(ChildrenDeviceSession.class)) {
+            ChildrenDeviceSession child = session.unwrap(ChildrenDeviceSession.class);
+            message.addHeader("parentId", child.getParentDevice().getDeviceId());
+        }
+        return this
+            .onMessage(message)
+            .onErrorResume(doOnError);
     }
 
     public static Flux<String> createDeviceMessageTopic(DeviceRegistry deviceRegistry, Message message) {
@@ -226,7 +254,7 @@ public class DeviceMessageConnector implements DecodedClientMessageHandler {
         return this
             .getTopic(message)
             .flatMap(topic -> eventBus.publish(topic, message).then())
-            .onErrorContinue(doOnError)
+            .onErrorResume(doOnError)
             .then();
     }
 

+ 5 - 5
jetlinks-manager/device-manager/src/main/resources/i18n/device-manager/messages_en.properties

@@ -1,8 +1,8 @@
-# org.jetlinks.pro.device.entity.DeviceInstanceEntity
-#org.jetlinks.pro.device.entity.DeviceInstanceEntity.productName=Product Name
+# org.jetlinks.community.device.entity.DeviceInstanceEntity
+#org.jetlinks.community.device.entity.DeviceInstanceEntity.productName=Product Name
 
 # enums
-org.jetlinks.pro.device.enums.DeviceState.notActive=Disabled
-org.jetlinks.pro.device.enums.DeviceState.offline=Offline
-org.jetlinks.pro.device.enums.DeviceState.online=Online
+org.jetlinks.community.device.enums.DeviceState.notActive=Disabled
+org.jetlinks.community.device.enums.DeviceState.offline=Offline
+org.jetlinks.community.device.enums.DeviceState.online=Online
 

+ 5 - 5
jetlinks-manager/device-manager/src/main/resources/i18n/device-manager/messages_zh.properties

@@ -1,9 +1,9 @@
-# org.jetlinks.pro.device.entity.DeviceInstanceEntity
-#org.jetlinks.pro.device.entity.DeviceInstanceEntity.productName=产品名称
+# org.jetlinks.community.device.entity.DeviceInstanceEntity
+#org.jetlinks.community.device.entity.DeviceInstanceEntity.productName=产品名称
 
 ##枚举
 
-org.jetlinks.pro.device.enums.DeviceState.notActive=未启用
-org.jetlinks.pro.device.enums.DeviceState.offline=离线
-org.jetlinks.pro.device.enums.DeviceState.online=在线
+org.jetlinks.community.device.enums.DeviceState.notActive=未启用
+org.jetlinks.community.device.enums.DeviceState.offline=离线
+org.jetlinks.community.device.enums.DeviceState.online=在线
 

+ 1 - 1
jetlinks-manager/logging-manager/pom.xml

@@ -7,7 +7,7 @@
     <parent>
         <groupId>org.jetlinks.community</groupId>
         <artifactId>jetlinks-manager</artifactId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
     </parent>
     <artifactId>logging-manager</artifactId>
 

+ 1 - 1
jetlinks-manager/network-manager/pom.xml

@@ -7,7 +7,7 @@
     <parent>
         <groupId>org.jetlinks.community</groupId>
         <artifactId>jetlinks-manager</artifactId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>network-manager</artifactId>

+ 1 - 1
jetlinks-manager/notify-manager/pom.xml

@@ -7,7 +7,7 @@
     <parent>
         <groupId>org.jetlinks.community</groupId>
         <artifactId>jetlinks-manager</artifactId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>notify-manager</artifactId>

+ 1 - 1
jetlinks-manager/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-community</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
     </parent>
     <packaging>pom</packaging>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
jetlinks-manager/rule-engine-manager/pom.xml

@@ -7,7 +7,7 @@
     <parent>
         <groupId>org.jetlinks.community</groupId>
         <artifactId>jetlinks-manager</artifactId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>rule-engine-manager</artifactId>

+ 1 - 1
jetlinks-manager/visualization-manager/pom.xml

@@ -7,7 +7,7 @@
     <parent>
         <groupId>org.jetlinks.community</groupId>
         <artifactId>jetlinks-manager</artifactId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>visualization-manager</artifactId>

+ 1 - 2
jetlinks-standalone/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-community</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>1.20.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -231,7 +231,6 @@
         <dependency>
             <groupId>org.springdoc</groupId>
             <artifactId>springdoc-openapi-webflux-ui</artifactId>
-            <version>1.5.3</version>
         </dependency>
 
         <dependency>

+ 0 - 132
jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/JetLinksConfiguration.java

@@ -1,45 +1,16 @@
 package org.jetlinks.community.standalone.configuration;
 
-import com.github.benmanes.caffeine.cache.Caffeine;
-import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
 import io.vertx.core.Vertx;
 import io.vertx.core.VertxOptions;
 import lombok.extern.slf4j.Slf4j;
-import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
 import org.hswebframework.web.authorization.token.UserTokenManager;
 import org.hswebframework.web.authorization.token.redis.RedisUserTokenManager;
-import org.jetlinks.community.device.entity.DeviceInstanceEntity;
-import org.jetlinks.community.device.entity.DeviceProductEntity;
-import org.jetlinks.community.device.service.AutoDiscoverDeviceRegistry;
-import org.jetlinks.community.device.timeseries.DeviceTimeSeriesMetric;
-import org.jetlinks.community.micrometer.MeterRegistryManager;
-import org.jetlinks.core.ProtocolSupports;
 import org.jetlinks.core.cluster.ClusterManager;
-import org.jetlinks.core.config.ConfigStorageManager;
-import org.jetlinks.core.device.DeviceOperationBroker;
-import org.jetlinks.core.device.DeviceRegistry;
-import org.jetlinks.core.device.StandaloneDeviceMessageBroker;
-import org.jetlinks.core.event.EventBus;
-import org.jetlinks.core.message.interceptor.DeviceMessageSenderInterceptor;
-import org.jetlinks.core.server.MessageHandler;
-import org.jetlinks.core.server.monitor.GatewayServerMetrics;
-import org.jetlinks.core.server.monitor.GatewayServerMonitor;
-import org.jetlinks.core.server.session.DeviceSessionManager;
 import org.jetlinks.core.spi.ServiceContext;
-import org.jetlinks.supports.cluster.ClusterDeviceRegistry;
-import org.jetlinks.supports.cluster.EventBusDeviceOperationBroker;
-import org.jetlinks.supports.cluster.redis.RedisClusterManager;
-import org.jetlinks.supports.config.EventBusStorageManager;
-import org.jetlinks.supports.event.BrokerEventBus;
 import org.jetlinks.supports.protocol.ServiceLoaderProtocolSupports;
 import org.jetlinks.supports.protocol.management.ClusterProtocolSupportManager;
 import org.jetlinks.supports.protocol.management.ProtocolSupportLoader;
 import org.jetlinks.supports.protocol.management.ProtocolSupportManager;
-import org.jetlinks.supports.server.DecodedClientMessageHandler;
-import org.jetlinks.supports.server.DefaultSendToDeviceMessageHandler;
-import org.jetlinks.supports.server.monitor.MicrometerGatewayServerMetrics;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -47,11 +18,7 @@ import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory
 import org.springframework.boot.web.server.WebServerFactoryCustomizer;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
 import org.springframework.data.redis.core.ReactiveRedisOperations;
-import org.springframework.data.redis.core.ReactiveRedisTemplate;
-
-import java.util.Optional;
 
 @Configuration
 @EnableConfigurationProperties(JetLinksProperties.class)
@@ -79,105 +46,6 @@ public class JetLinksConfiguration {
         return Vertx.vertx(vertxOptions);
     }
 
-    @Bean
-    public EventBus eventBus() {
-        return new BrokerEventBus();
-    }
-
-    @Bean(initMethod = "start", destroyMethod = "dispose")
-    public EventBusDeviceOperationBroker eventBusDeviceOperationBroker(ClusterManager clusterManager,EventBus eventBus) {
-        return new EventBusDeviceOperationBroker(clusterManager.getCurrentServerId(),eventBus);
-    }
-
-    @Bean
-    public EventBusStorageManager eventBusStorageManager(ClusterManager clusterManager, EventBus eventBus) {
-        return new EventBusStorageManager(clusterManager,
-                                          eventBus,
-                                          () -> CaffeinatedGuava.build(Caffeine.newBuilder()));
-    }
-
-    @Bean(initMethod = "startup")
-    public RedisClusterManager clusterManager(JetLinksProperties properties, ReactiveRedisTemplate<Object, Object> template) {
-        return new RedisClusterManager(properties.getClusterName(), properties.getServerId(), template);
-    }
-
-    @Bean
-    public ClusterDeviceRegistry clusterDeviceRegistry(ProtocolSupports supports,
-                                                       ClusterManager manager,
-                                                       ConfigStorageManager storageManager,
-                                                       DeviceOperationBroker handler) {
-
-        return new ClusterDeviceRegistry(supports,
-                                         storageManager,
-                                         manager,
-                                         handler,
-                                         CaffeinatedGuava.build(Caffeine.newBuilder()));
-    }
-
-    @Bean
-    @Primary
-    @ConditionalOnProperty(prefix = "jetlinks.device.registry", name = "auto-discover", havingValue = "enabled", matchIfMissing = true)
-    public AutoDiscoverDeviceRegistry deviceRegistry(ClusterDeviceRegistry registry,
-                                                     ReactiveRepository<DeviceInstanceEntity, String> instanceRepository,
-                                                     ReactiveRepository<DeviceProductEntity, String> productRepository) {
-        return new AutoDiscoverDeviceRegistry(registry, instanceRepository, productRepository);
-    }
-
-
-    @Bean
-    public BeanPostProcessor interceptorRegister(ClusterDeviceRegistry registry) {
-        return new BeanPostProcessor() {
-            @Override
-            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
-                if (bean instanceof DeviceMessageSenderInterceptor) {
-                    registry.addInterceptor(((DeviceMessageSenderInterceptor) bean));
-                }
-                return bean;
-            }
-        };
-    }
-
-    @Bean(initMethod = "startup")
-    public DefaultSendToDeviceMessageHandler defaultSendToDeviceMessageHandler(JetLinksProperties properties,
-                                                                               DeviceSessionManager sessionManager,
-                                                                               DeviceRegistry registry,
-                                                                               MessageHandler messageHandler,
-                                                                               DecodedClientMessageHandler clientMessageHandler) {
-        return new DefaultSendToDeviceMessageHandler(properties.getServerId(), sessionManager, messageHandler, registry, clientMessageHandler);
-    }
-
-    @Bean
-    public GatewayServerMonitor gatewayServerMonitor(JetLinksProperties properties, MeterRegistryManager registry) {
-        GatewayServerMetrics metrics = new MicrometerGatewayServerMetrics(properties.getServerId(),
-                                                                          registry.getMeterRegister(DeviceTimeSeriesMetric
-                                                                                                        .deviceMetrics().getId()));
-
-        return new GatewayServerMonitor() {
-            @Override
-            public String getCurrentServerId() {
-                return properties.getServerId();
-            }
-
-            @Override
-            public GatewayServerMetrics metrics() {
-                return metrics;
-            }
-        };
-    }
-
-
-    @Bean(initMethod = "init", destroyMethod = "shutdown")
-    public DefaultDeviceSessionManager deviceSessionManager(JetLinksProperties properties,
-                                                            GatewayServerMonitor monitor,
-                                                            DeviceRegistry registry) {
-        DefaultDeviceSessionManager sessionManager = new DefaultDeviceSessionManager();
-        sessionManager.setGatewayServerMonitor(monitor);
-        sessionManager.setRegistry(registry);
-        Optional.ofNullable(properties.getTransportLimit()).ifPresent(sessionManager::setTransportLimits);
-
-        return sessionManager;
-    }
-
     @Bean(initMethod = "init")
     @ConditionalOnProperty(prefix = "jetlinks.protocol.spi", name = "enabled", havingValue = "true")
     public ServiceLoaderProtocolSupports serviceLoaderProtocolSupports(ServiceContext serviceContext) {

+ 0 - 46
jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/doc/SwaggerConfiguration.java

@@ -47,50 +47,4 @@ import java.util.List;
 @AutoConfigureBefore(SpringDocWebFluxConfiguration.class)
 public class SwaggerConfiguration {
 
-
-    @Bean
-    public ReturnTypeParser operationCustomizer() {
-
-        return new ReturnTypeParser() {
-            @Override
-            public Type getReturnType(MethodParameter methodParameter) {
-                Type type = ReturnTypeParser.super.getReturnType(methodParameter);
-
-                if (type instanceof ParameterizedType) {
-                    ParameterizedType parameterizedType = ((ParameterizedType) type);
-                    Type rawType = parameterizedType.getRawType();
-                    if (rawType instanceof Class && Publisher.class.isAssignableFrom(((Class<?>) rawType))) {
-                        Type actualType = parameterizedType.getActualTypeArguments()[0];
-
-                        if (actualType instanceof ParameterizedType) {
-                            actualType = ((ParameterizedType) actualType).getRawType();
-                        }
-                        if (actualType == ResponseEntity.class || actualType == ResponseMessage.class) {
-                            return type;
-                        }
-                        boolean returnList = Flux.class.isAssignableFrom(((Class<?>) rawType));
-
-                        //统一返回ResponseMessage
-                        return ResolvableType
-                            .forClassWithGenerics(
-                                Mono.class,
-                                ResolvableType.forClassWithGenerics(
-                                    ResponseMessage.class,
-                                    returnList ?
-                                        ResolvableType.forClassWithGenerics(
-                                            List.class,
-                                            ResolvableType.forType(parameterizedType.getActualTypeArguments()[0])
-                                        ) :
-                                        ResolvableType.forType(parameterizedType.getActualTypeArguments()[0])
-                                ))
-                            .getType();
-
-                    }
-                }
-
-                return type;
-            }
-        };
-    }
-
 }

+ 0 - 1
jetlinks-standalone/src/main/resources/application.yml

@@ -136,7 +136,6 @@ logging:
     io.vertx.mqtt.impl: warn
     #    org.springframework.data.elasticsearch.client: trace
     #    org.elasticsearch: error
-    org.jetlinks.pro.influx: trace
     org.elasticsearch: error
     org.elasticsearch.deprecation.search.aggregations.bucket.histogram: error
   config: classpath:logback-spring.xml

+ 146 - 57
pom.xml

@@ -6,7 +6,7 @@
 
     <groupId>org.jetlinks.community</groupId>
     <artifactId>jetlinks-community</artifactId>
-    <version>1.13.0-SNAPSHOT</version>
+    <version>1.20.0-SNAPSHOT</version>
     <modules>
         <module>jetlinks-components</module>
         <module>jetlinks-manager</module>
@@ -16,24 +16,29 @@
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.build.locales>zh_CN</project.build.locales>
-        <spring.boot.version>2.3.11.RELEASE</spring.boot.version>
+        <spring.boot.version>2.5.12</spring.boot.version>
         <java.version>1.8</java.version>
         <project.build.jdk>${java.version}</project.build.jdk>
         <hsweb.framework.version>4.0.14-SNAPSHOT</hsweb.framework.version>
         <easyorm.version>4.0.14-SNAPSHOT</easyorm.version>
         <hsweb.expands.version>3.0.2</hsweb.expands.version>
-        <jetlinks.version>1.1.10-SNAPSHOT</jetlinks.version>
+        <jetlinks.version>1.2.0-SNAPSHOT</jetlinks.version>
         <r2dbc.version>Arabba-SR10</r2dbc.version>
-        <vertx.version>4.2.3</vertx.version>
         <netty.version>4.1.73.Final</netty.version>
         <elasticsearch.version>7.11.2</elasticsearch.version>
-        <reactor.excel.version>1.0.1</reactor.excel.version>
-        <reactor.ql.version>1.0.13</reactor.ql.version>
+        <reactor.excel.version>1.0.2</reactor.excel.version>
+        <reactor.ql.version>1.0.14-SNAPSHOT</reactor.ql.version>
+        <californium.version>3.3.1</californium.version>
         <fastjson.version>1.2.70</fastjson.version>
+        <reactor.version>2020.0.6</reactor.version>
+        <vertx.version>4.2.3</vertx.version>
         <log4j.version>2.17.1</log4j.version>
         <logback.version>1.2.9</logback.version>
+        <springdoc.version>1.6.6</springdoc.version>
+        <jackson.version>2.13.2.20220328</jackson.version>
     </properties>
 
+
     <build>
         <finalName>${project.artifactId}</finalName>
         <resources>
@@ -79,12 +84,15 @@
             <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
-                <version>0.8.0</version>
+                <version>0.8.7</version>
                 <executions>
                     <execution>
                         <goals>
                             <goal>prepare-agent</goal>
                         </goals>
+                        <configuration>
+                            <propertyName>jacocoArgLine</propertyName>
+                        </configuration>
                     </execution>
                     <execution>
                         <id>report</id>
@@ -126,8 +134,8 @@
                 <dependencies>
                     <dependency>
                         <groupId>org.codehaus.groovy</groupId>
-                        <artifactId>groovy-all</artifactId>
-                        <version>2.4.15</version>
+                        <artifactId>groovy</artifactId>
+                        <version>2.5.14</version>
                     </dependency>
                 </dependencies>
             </plugin>
@@ -135,44 +143,42 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <version>2.17</version>
+                <version>2.22.0</version>
                 <configuration>
                     <includes>
                         <include>**/*Test.java</include>
-                        <include>**/*Test.groovy</include>
                         <include>**/*Tests.java</include>
-                        <include>**/*Test.groovy</include>
-                        <include>**/*Spec.java</include>
+                        <include>**/*TestCase.java</include>
                     </includes>
+                    <argLine>-Dfile.encoding=UTF-8 ${jacocoArgLine}</argLine>
                 </configuration>
             </plugin>
 
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-javadoc-plugin</artifactId>
-                <version>3.0.0</version>
-            </plugin>
         </plugins>
-
     </build>
 
-    <profiles>
-        <profile>
-            <id>build</id>
-            <repositories>
-                <repository>
-                    <id>maven-central</id>
-                    <name>central</name>
-                    <url>https://repo1.maven.org/maven2/</url>
-                </repository>
-            </repositories>
-        </profile>
-    </profiles>
-
     <dependencyManagement>
 
         <dependencies>
 
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-common</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-webflux-core</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-webflux-ui</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>org.apache.logging.log4j</groupId>
                 <artifactId>log4j-to-slf4j</artifactId>
@@ -191,6 +197,33 @@
                 <version>${log4j.version}</version>
             </dependency>
 
+            <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcprov-jdk15on</artifactId>
+                <version>1.70</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcpkix-jdk15on</artifactId>
+                <version>1.70</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.fasterxml.jackson</groupId>
+                <artifactId>jackson-bom</artifactId>
+                <version>${jackson.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <dependency>
+                <groupId>commons-codec</groupId>
+                <artifactId>commons-codec</artifactId>
+                <version>1.15</version>
+            </dependency>
+
             <dependency>
                 <groupId>io.netty</groupId>
                 <artifactId>netty-bom</artifactId>
@@ -200,19 +233,39 @@
             </dependency>
 
             <dependency>
-                <groupId>org.jetlinks</groupId>
-                <artifactId>reactor-ql</artifactId>
-                <version>${reactor.ql.version}</version>
+                <groupId>io.projectreactor</groupId>
+                <artifactId>reactor-bom</artifactId>
+                <version>${reactor.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
             </dependency>
 
             <dependency>
-                <groupId>com.fasterxml.jackson</groupId>
-                <artifactId>jackson-bom</artifactId>
-                <version>2.10.4</version>
+                <groupId>io.vertx</groupId>
+                <artifactId>vertx-dependencies</artifactId>
+                <version>${vertx.version}</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
 
+            <dependency>
+                <groupId>org.eclipse.californium</groupId>
+                <artifactId>californium-core</artifactId>
+                <version>${californium.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.eclipse.californium</groupId>
+                <artifactId>scandium</artifactId>
+                <version>${californium.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.jetlinks</groupId>
+                <artifactId>reactor-ql</artifactId>
+                <version>${reactor.ql.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>org.hswebframework</groupId>
                 <artifactId>reactor-excel</artifactId>
@@ -228,7 +281,13 @@
             <dependency>
                 <groupId>ch.qos.logback</groupId>
                 <artifactId>logback-classic</artifactId>
-                <version>1.2.3</version>
+                <version>${logback.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>ch.qos.logback</groupId>
+                <artifactId>logback-core</artifactId>
+                <version>${logback.version}</version>
             </dependency>
 
             <dependency>
@@ -260,6 +319,10 @@
                 <type>pom</type>
                 <scope>import</scope>
                 <exclusions>
+                    <exclusion>
+                        <groupId>io.r2dbc</groupId>
+                        <artifactId>r2dbc-bom</artifactId>
+                    </exclusion>
                     <exclusion>
                         <groupId>org.springframework.boot</groupId>
                         <artifactId>spring-boot-dependencies</artifactId>
@@ -267,6 +330,12 @@
                 </exclusions>
             </dependency>
 
+            <dependency>
+                <groupId>org.hswebframework.web</groupId>
+                <artifactId>hsweb-core</artifactId>
+                <version>${hsweb.framework.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>org.jetlinks</groupId>
                 <artifactId>rule-engine-support</artifactId>
@@ -279,16 +348,11 @@
                 <version>${jetlinks.version}</version>
             </dependency>
 
-            <dependency>
-                <groupId>io.vertx</groupId>
-                <artifactId>vertx-core</artifactId>
-                <version>${vertx.version}</version>
-            </dependency>
 
             <dependency>
-                <groupId>io.vertx</groupId>
-                <artifactId>vertx-mqtt</artifactId>
-                <version>${vertx.version}</version>
+                <groupId>com.google.guava</groupId>
+                <artifactId>guava</artifactId>
+                <version>28.0-jre</version>
             </dependency>
 
             <dependency>
@@ -325,12 +389,21 @@
                 <groupId>org.hswebframework</groupId>
                 <artifactId>hsweb-easy-orm-rdb</artifactId>
                 <version>${easyorm.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>io.r2dbc</groupId>
+                        <artifactId>r2dbc-bom</artifactId>
+                    </exclusion>
+                </exclusions>
             </dependency>
+
             <dependency>
                 <groupId>org.hswebframework</groupId>
                 <artifactId>hsweb-easy-orm-elasticsearch</artifactId>
                 <version>${easyorm.version}</version>
             </dependency>
+
+
         </dependencies>
     </dependencyManagement>
 
@@ -343,13 +416,35 @@
         </dependency>
 
         <dependency>
-            <groupId>dev.miku</groupId>
-            <artifactId>r2dbc-mysql</artifactId>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
         </dependency>
 
         <dependency>
-            <groupId>io.projectreactor</groupId>
-            <artifactId>reactor-tools</artifactId>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <version>1.16.2</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>1.16.2</version>
+            <scope>test</scope>
         </dependency>
 
         <dependency>
@@ -382,7 +477,6 @@
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
-            <version>1.7.32</version>
         </dependency>
 
         <dependency>
@@ -402,11 +496,6 @@
             <optional>true</optional>
         </dependency>
 
-        <dependency>
-            <groupId>org.hswebframework</groupId>
-            <artifactId>hsweb-utils</artifactId>
-            <version>3.0.3</version>
-        </dependency>
 
     </dependencies>