Przeglądaj źródła

Merge branch '1.20'

# Conflicts:
#	jetlinks-standalone/pom.xml
#	pom.xml
zhouhao 3 lat temu
rodzic
commit
426394328b
82 zmienionych plików z 2857 dodań i 2048 usunięć
  1. 4 1
      docker/run-all/docker-compose.yml
  2. 1 1
      jetlinks-components/common-component/pom.xml
  3. 104 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. 118 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. 85 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/DeviceClusterConfiguration.java
  11. 171 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/PersistenceDeviceSessionManager.java
  12. 85 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 6
      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. 286 117
      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. 14 2
      jetlinks-components/io-component/pom.xml
  34. 62 57
      jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/file/DefaultFileManager.java
  35. 3 3
      jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/file/FileManagerConfiguration.java
  36. 1 61
      jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/file/FileProperties.java
  37. 0 43
      jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/file/web/FileManagerController.java
  38. 1 1
      jetlinks-components/logging-component/pom.xml
  39. 1 1
      jetlinks-components/network-component/mqtt-component/pom.xml
  40. 64 107
      jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttClientDeviceGateway.java
  41. 1 1
      jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttClientDeviceGatewayProvider.java
  42. 163 134
      jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttServerDeviceGateway.java
  43. 1 1
      jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttServerDeviceGatewayProvider.java
  44. 1 1
      jetlinks-components/network-component/network-core/pom.xml
  45. 247 146
      jetlinks-components/network-component/network-core/src/main/java/org/jetlinks/community/network/utils/DeviceGatewayHelper.java
  46. 1 1
      jetlinks-components/network-component/pom.xml
  47. 1 1
      jetlinks-components/network-component/tcp-component/pom.xml
  48. 82 170
      jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/device/TcpServerDeviceGateway.java
  49. 1 1
      jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/device/TcpServerDeviceGatewayProvider.java
  50. 4 4
      jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/parser/strateies/DelimitedPayloadParserBuilder.java
  51. 1 1
      jetlinks-components/notify-component/notify-core/pom.xml
  52. 1 1
      jetlinks-components/notify-component/notify-dingtalk/pom.xml
  53. 1 2
      jetlinks-components/notify-component/notify-email/pom.xml
  54. 25 18
      jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifier.java
  55. 1 1
      jetlinks-components/notify-component/notify-sms/pom.xml
  56. 1 1
      jetlinks-components/notify-component/notify-voice/pom.xml
  57. 1 1
      jetlinks-components/notify-component/notify-wechat/pom.xml
  58. 1 1
      jetlinks-components/notify-component/pom.xml
  59. 1 1
      jetlinks-components/pom.xml
  60. 1 1
      jetlinks-components/rule-engine-component/pom.xml
  61. 1 1
      jetlinks-components/timeseries-component/pom.xml
  62. 1 1
      jetlinks-manager/authentication-manager/pom.xml
  63. 1 1
      jetlinks-manager/device-manager/pom.xml
  64. 1 1
      jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/configuration/DeviceManagerConfiguration.java
  65. 51 23
      jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/message/DeviceMessageConnector.java
  66. 5 5
      jetlinks-manager/device-manager/src/main/resources/i18n/device-manager/messages_en.properties
  67. 5 5
      jetlinks-manager/device-manager/src/main/resources/i18n/device-manager/messages_zh.properties
  68. 1 1
      jetlinks-manager/logging-manager/pom.xml
  69. 1 1
      jetlinks-manager/network-manager/pom.xml
  70. 1 1
      jetlinks-manager/notify-manager/pom.xml
  71. 1 1
      jetlinks-manager/pom.xml
  72. 1 1
      jetlinks-manager/rule-engine-manager/pom.xml
  73. 1 1
      jetlinks-manager/visualization-manager/pom.xml
  74. 2 3
      jetlinks-standalone/pom.xml
  75. 0 132
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/JetLinksConfiguration.java
  76. 208 0
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/RenameProtocolSupport.java
  77. 5 2
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/SpringProtocolSupportLoader.java
  78. 0 46
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/doc/SwaggerConfiguration.java
  79. 0 0
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/protocol/AutoDownloadJarProtocolSupportLoader.java
  80. 0 704
      jetlinks-standalone/src/main/java/org/springframework/data/redis/connection/ReactiveHashCommands.java
  81. 5 2
      jetlinks-standalone/src/main/resources/application.yml
  82. 165 64
      pom.xml

+ 4 - 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端口
@@ -67,6 +67,7 @@ services:
       - "8000-8010:8000-8010" # 预留
     volumes:
       - "jetlinks-volume:/application/static/upload"  # 持久化上传的文件
+      - "jetlinks-file-volume:/application/data/files"
       - "jetlinks-protocol-volume:/application/data/protocols"
     environment:
       - "JAVA_OPTS=-Duser.language=zh -XX:+UseG1GC"
@@ -81,6 +82,7 @@ services:
 #        - "spring.reactor.debug-agent.enabled=false" #设置为false能提升性能
       - "spring.redis.host=redis"
       - "spring.redis.port=6379"
+      - "file.manager.storage-base-path=/application/data/files"
       - "spring.redis.password=JetLinks@redis"
       - "logging.level.io.r2dbc=warn"
       - "logging.level.org.springframework.data=warn"
@@ -101,4 +103,5 @@ volumes:
   redis-volume:
   elasticsearch-volume:
   jetlinks-volume:
+  jetlinks-file-volume:
   jetlinks-protocol-volume:

+ 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>
 

+ 104 - 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,108 @@
             <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.14.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-sdk-trace</artifactId>
+            <version>1.14.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-sdk</artifactId>
+            <version>1.14.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-exporter-jaeger</artifactId>
+            <version>1.14.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-protobuf</artifactId>
+            <version>1.47.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-netty-shaded</artifactId>
+            <version>1.47.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jetlinks.community</groupId>
+            <artifactId>common-component</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+        </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;
+    }
+}

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

@@ -0,0 +1,118 @@
+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.jetlinks.supports.scalecube.rpc.ScalecubeRpcManager;
+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(proxyBeanMethods = false)
+@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(initMethod = "startAwait", destroyMethod = "stopAwait")
+    public ScalecubeRpcManager rpcManager(ExtendedCluster cluster, ClusterProperties properties) {
+        return new ScalecubeRpcManager(cluster,
+                                       () -> new RSocketServiceTransport()
+                                           .serverTransportFactory(RSocketServerTransportFactory.tcp(properties.getRpcPort()))
+                                           .clientTransportFactory(RSocketClientTransportFactory.tcp()))
+            .externalHost(properties.getRpcExternalHost())
+            .externalPort(properties.getRpcExternalPort());
+    }
+
+}

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

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

@@ -0,0 +1,85 @@
+package org.jetlinks.community.configure.device;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
+import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
+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.rpc.RpcManager;
+import org.jetlinks.core.server.MessageHandler;
+import org.jetlinks.supports.cluster.ClusterDeviceOperationBroker;
+import org.jetlinks.supports.cluster.ClusterDeviceRegistry;
+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.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration(proxyBeanMethods = false)
+@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(RpcManager.class)
+    public PersistenceDeviceSessionManager deviceSessionManager(RpcManager rpcManager) {
+
+        return new PersistenceDeviceSessionManager(rpcManager);
+    }
+
+    @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);
+    }
+
+
+}

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

@@ -0,0 +1,171 @@
+package org.jetlinks.community.configure.device;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.h2.mvstore.MVMap;
+import org.h2.mvstore.MVStore;
+import org.h2.mvstore.MVStoreException;
+import org.jetlinks.core.device.DeviceRegistry;
+import org.jetlinks.core.device.session.DeviceSessionEvent;
+import org.jetlinks.core.rpc.RpcManager;
+import org.jetlinks.core.server.session.DeviceSession;
+import org.jetlinks.core.server.session.PersistentSession;
+import org.jetlinks.community.configure.cluster.Cluster;
+import org.jetlinks.supports.device.session.ClusterDeviceSessionManager;
+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.io.File;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * 支持会话持久化的设备会话管理器,将{@link PersistentSession}持久化到本地磁盘,在服务器重启后
+ * 将会对会话进行恢复.
+ *
+ * @author zhouhao
+ * @since 1.20
+ */
+@Slf4j
+public class PersistenceDeviceSessionManager extends ClusterDeviceSessionManager implements CommandLineRunner, ApplicationContextAware {
+    private Supplier<DeviceRegistry> registry;
+
+    private MVMap<String, PersistentSessionEntity> repository;
+
+    @Getter
+    @Setter
+    private String filePath;
+
+    public PersistenceDeviceSessionManager(RpcManager rpcManager) {
+        super(rpcManager);
+    }
+
+    static MVMap<String, PersistentSessionEntity> initStore(String file) {
+        File f = new File(file);
+        if (!f.getParentFile().exists()) {
+            f.getParentFile().mkdirs();
+        }
+        Supplier<MVMap<String, PersistentSessionEntity>>
+            builder = () -> {
+            MVStore store = new MVStore.Builder()
+                .fileName(file)
+                .cacheSize(1)
+                .open();
+            return store.openMap("device-session");
+        };
+        try {
+            return builder.get();
+        } catch (MVStoreException e) {
+            log.warn("load session from {} error,delete it and init.", file, e);
+            f.delete();
+            return builder.get();
+        }
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        if (filePath == null) {
+            filePath = "./data/sessions-" + (Cluster
+                .id()
+                .replace(":", "_")
+                .replace("/", ""));
+        }
+        repository = initStore(filePath);
+
+        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();
+        repository.store.compactMoveChunks();
+        repository.store.close();
+    }
+
+    @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)) {
+            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)
+            .doOnNext(e -> {
+                log.debug("persistent device[{}] session", e.getDeviceId());
+                repository.put(e.getDeviceId(), e);
+            })
+            .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) {
+        repository.remove(session.getId());
+        return Mono.empty();
+    }
+
+    @Override
+    public void run(String... args) throws Exception {
+
+        Flux.fromIterable(repository.values())
+            .flatMap(this::resumeSession)
+            .subscribe();
+    }
+
+    @Override
+    public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
+        this.registry = Lazy.of(() -> applicationContext.getBean(DeviceRegistry.class));
+    }
+}

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

@@ -0,0 +1,85 @@
+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.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 java.sql.JDBCType;
+
+@Getter
+@Setter
+@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 - 6
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>
@@ -34,11 +34,6 @@
             <artifactId>reactor-core</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>org.hswebframework</groupId>
-            <artifactId>hsweb-easy-orm-elasticsearch</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>org.elasticsearch.client</groupId>
             <artifactId>elasticsearch-rest-high-level-client</artifactId>

+ 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,

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

@@ -3,7 +3,6 @@ package org.jetlinks.community.elastic.search.service.reactive;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.serializer.SerializerFeature;
-import io.vavr.Function3;
 import lombok.Generated;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
@@ -19,6 +18,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 +33,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 +43,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 +55,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 +72,33 @@ 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.ReactiveElasticsearchClient;
 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;
@@ -101,6 +112,7 @@ import reactor.core.publisher.EmitterProcessor;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.FluxSink;
 import reactor.core.publisher.Mono;
+import reactor.function.Function3;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -118,7 +130,7 @@ import static org.springframework.data.elasticsearch.client.util.RequestConverte
 
 @Slf4j
 @Generated
-public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient {
+public class DefaultReactiveElasticsearchClient implements org.jetlinks.community.elastic.search.service.reactive.ReactiveElasticsearchClient, ReactiveElasticsearchClient.Cluster {
     private final HostProvider<?> hostProvider;
     private final RequestCreator requestCreator;
     private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
@@ -158,13 +170,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 +205,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 +219,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 +240,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 +284,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 +316,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 +441,13 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
                                                                                                                .publishNext();
     }
 
+    @Override
+    public Mono<ByQueryResponse> updateBy(HttpHeaders headers, UpdateByQueryRequest updateRequest) {
+        return sendRequest(updateRequest, requestCreator.updateByQuery(), BulkByScrollResponse.class, headers)
+            .next()
+            .map(ByQueryResponse::of);
+    }
+
     static XContentType enforceSameContentType(IndexRequest indexRequest, @Nullable XContentType xContentType) {
         XContentType requestContentType = indexRequest.getContentType();
         if (requestContentType != XContentType.JSON && requestContentType != XContentType.SMILE) {
@@ -581,10 +617,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 +643,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 +655,21 @@ 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 sendRequest(createIndexRequest, requestCreator.createIndexRequest(), AcknowledgedResponse.class, headers)
+            .map(AcknowledgedResponse::isAcknowledged)
+            .next();
     }
 
     /*
@@ -619,16 +679,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 +710,23 @@ 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 sendRequest(putMappingRequest, requestCreator.putMapping(), AcknowledgedResponse.class, headers)
+            .map(AcknowledgedResponse::isAcknowledged)
+            .next();
+    }
+
+    @Override
+    public Mono<Boolean> putMapping(HttpHeaders headers, org.elasticsearch.client.indices.PutMappingRequest putMappingRequest) {
+        return sendRequest(putMappingRequest, requestCreator.putMappingRequest(), AcknowledgedResponse.class, headers)
+            .map(AcknowledgedResponse::isAcknowledged)
+            .next();
     }
 
     /*
@@ -677,29 +740,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 (isCausedByConnectionException(throwable)) {
+                    return hostProvider.getActive(HostProvider.Verification.ACTIVE) //
+                                       .flatMap(callback::doWithClient);
+                }
+
+                return Mono.error(throwable);
+            });
+    }
 
-                                    if (throwable instanceof ConnectException) {
+    private boolean isCausedByConnectionException(Throwable throwable) {
 
-                                        return hostProvider.getActive(HostProvider.Verification.ACTIVE) //
-                                                           .flatMap(callback::doWithClient);
-                                    }
+        Throwable t = throwable;
+        do {
 
-                                    return Mono.error(throwable);
-                                });
+            if (t instanceof ConnectException) {
+                return true;
+            }
+
+            t = t.getCause();
+        } while (t != null);
+
+        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 +799,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 +837,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 +894,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 +915,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 +1113,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 +1175,70 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
             .singleOrEmpty();
     }
 
+    @Override
+    public Mono<GetMappingsResponse> getMapping(HttpHeaders headers, GetMappingsRequest getMappingsRequest) {
+        return sendRequest(getMappingsRequest, requestCreator.getMapping(),
+                           GetMappingsResponse.class, headers).next();
+    }
+
+    @Override
+    public Mono<org.elasticsearch.client.indices.GetMappingsResponse> getMapping(HttpHeaders headers, org.elasticsearch.client.indices.GetMappingsRequest getMappingsRequest) {
+        return sendRequest(getMappingsRequest, requestCreator.getMappingRequest(), org.elasticsearch.client.indices.GetMappingsResponse.class, headers) //
+                                                                                                                                                        .next();
+    }
+
+    @Override
+    public Mono<GetFieldMappingsResponse> getFieldMapping(HttpHeaders headers,
+                                                          GetFieldMappingsRequest getFieldMappingsRequest) {
+        return sendRequest(getFieldMappingsRequest, requestCreator.getFieldMapping(), GetFieldMappingsResponse.class,
+                           headers).next();
+    }
+
+    @Override
+    public Mono<Boolean> updateAliases(HttpHeaders headers, IndicesAliasesRequest indicesAliasesRequest) {
+        return sendRequest(indicesAliasesRequest, requestCreator.updateAlias(), AcknowledgedResponse.class, headers)
+            .map(AcknowledgedResponse::isAcknowledged).next();
+    }
+
+    @Override
+    public Mono<GetAliasesResponse> getAliases(HttpHeaders headers, GetAliasesRequest getAliasesRequest) {
+        return sendRequest(getAliasesRequest, requestCreator.getAlias(), GetAliasesResponse.class, headers).next();
+    }
+
+    @Override
+    public Mono<Boolean> putTemplate(HttpHeaders headers, org.elasticsearch.client.indices.PutIndexTemplateRequest putIndexTemplateRequest) {
+        return sendRequest(putIndexTemplateRequest, requestCreator.putTemplate(), AcknowledgedResponse.class, headers)
+            .map(AcknowledgedResponse::isAcknowledged).next();
+    }
+
+    @Override
+    public Mono<org.elasticsearch.client.indices.GetIndexTemplatesResponse> getTemplate(HttpHeaders headers,
+                                                                                        org.elasticsearch.client.indices.GetIndexTemplatesRequest getIndexTemplatesRequest) {
+        return (sendRequest(getIndexTemplatesRequest, requestCreator.getTemplates(), org.elasticsearch.client.indices.GetIndexTemplatesResponse.class,
+                            headers)).next();
+    }
+
+    @Override
+    public Mono<Boolean> existsTemplate(HttpHeaders headers, IndexTemplatesExistRequest indexTemplatesExistRequest) {
+        return sendRequest(indexTemplatesExistRequest, requestCreator.templatesExist(),
+                           RawActionResponse.class, headers)
+            .flatMap(response -> response
+                .releaseBody()
+                .thenReturn(response.statusCode().is2xxSuccessful()))
+            .next();
+    }
+
+    @Override
+    public Mono<Boolean> deleteTemplate(HttpHeaders headers, DeleteIndexTemplateRequest deleteIndexTemplateRequest) {
+        return sendRequest(deleteIndexTemplateRequest, requestCreator.deleteTemplate(), AcknowledgedResponse.class, headers)
+            .map(AcknowledgedResponse::isAcknowledged).next();
+    }
+
+    @Override
+    public Mono<GetIndexResponse> getIndex(HttpHeaders headers, org.elasticsearch.client.indices.GetIndexRequest getIndexRequest) {
+        return sendRequest(getIndexRequest, requestCreator.getIndex(), GetIndexResponse.class, headers).next();
+    }
+
     Request convertGetIndexTemplateRequest(GetIndexTemplatesRequest getIndexTemplatesRequest) {
         Request request = new Request(HttpGet.METHOD_NAME, "/_template/" + String.join(",", getIndexTemplatesRequest.names()));
         Params params = new Params(request);
@@ -1119,17 +1282,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
+}

+ 14 - 2
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>
@@ -28,7 +28,7 @@
         <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>easyexcel</artifactId>
-            <version>3.1.0</version>
+            <version>3.1.1</version>
             <exclusions>
                 <exclusion>
                     <groupId>org.apache.poi</groupId>
@@ -42,6 +42,11 @@
             <artifactId>spring-webflux</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-buffer</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.hswebframework.web</groupId>
             <artifactId>hsweb-core</artifactId>
@@ -53,5 +58,12 @@
             <artifactId>hsweb-commons-crud</artifactId>
             <version>${hsweb.framework.version}</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.jetlinks</groupId>
+            <artifactId>jetlinks-supports</artifactId>
+            <version>${jetlinks.version}</version>
+        </dependency>
+
     </dependencies>
 </project>

+ 62 - 57
jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/file/DefaultFileManager.java

@@ -1,60 +1,57 @@
 package org.jetlinks.community.io.file;
 
+import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufAllocator;
 import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.scalecube.services.annotations.Service;
+import io.scalecube.services.annotations.ServiceMethod;
 import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
 import org.hswebframework.web.exception.BusinessException;
+import org.hswebframework.web.exception.NotFoundException;
 import org.hswebframework.web.id.IDGenerator;
+import org.jetlinks.core.rpc.RpcManager;
 import org.springframework.core.io.FileSystemResource;
-import org.springframework.core.io.buffer.DataBuffer;
-import org.springframework.core.io.buffer.DataBufferFactory;
-import org.springframework.core.io.buffer.DataBufferUtils;
-import org.springframework.core.io.buffer.NettyDataBufferFactory;
-import org.springframework.http.ContentDisposition;
-import org.springframework.http.HttpRange;
-import org.springframework.http.MediaType;
-import org.springframework.http.client.MultipartBodyBuilder;
+import org.springframework.core.io.buffer.*;
 import org.springframework.http.codec.multipart.FilePart;
-import org.springframework.web.reactive.function.BodyInserters;
-import org.springframework.web.reactive.function.client.WebClient;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 import java.io.File;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
 import java.security.MessageDigest;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
-import java.util.Collections;
 import java.util.Objects;
 import java.util.function.Function;
 
 
-public class DefaultFileManager implements FileManager {
+public class ClusterFileManager implements FileManager {
 
     private final FileProperties properties;
 
-    private final DataBufferFactory bufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
+    private final NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
 
     private final ReactiveRepository<FileEntity, String> repository;
 
+    private final RpcManager rpcManager;
 
-    private final WebClient client;
-
-    public DefaultFileManager(WebClient.Builder builder,
+    public ClusterFileManager(RpcManager rpcManager,
                               FileProperties properties,
                               ReactiveRepository<FileEntity, String> repository) {
         new File(properties.getStorageBasePath()).mkdirs();
         this.properties = properties;
-        this.client = builder
-            .clone()
-            .filter(this.properties.createWebClientRute())
-            .build();
+        this.rpcManager = rpcManager;
         this.repository = repository;
+        rpcManager.registerService(new ServiceImpl());
     }
 
     @Override
@@ -69,27 +66,6 @@ public class DefaultFileManager implements FileManager {
         return dataBuffer;
     }
 
-    public Mono<FileInfo> saveFileToCluster(String name, Flux<DataBuffer> stream) {
-        String serverId = properties.selectServerNode();
-        MultipartBodyBuilder builder = new MultipartBodyBuilder();
-        builder.asyncPart("file", stream, DataBuffer.class)
-               .headers(header -> header
-                   .setContentDisposition(ContentDisposition
-                                              .builder("form-data")
-                                              .name("file")
-                                              .filename(name)
-                                              .build()))
-               .contentType(MediaType.APPLICATION_OCTET_STREAM);
-        return client
-            .post()
-            .uri("http://" + serverId + "/file/" +serverId)
-            .attribute(FileProperties.serverNodeIdAttr, serverId)
-            .contentType(MediaType.MULTIPART_FORM_DATA)
-            .body(BodyInserters.fromMultipartData(builder.build()))
-            .retrieve()
-            .bodyToMono(FileInfo.class);
-    }
-
     public Mono<FileInfo> doSaveFile(String name, Flux<DataBuffer> stream) {
         LocalDate now = LocalDate.now();
         FileInfo fileInfo = new FileInfo();
@@ -102,7 +78,7 @@ public class DefaultFileManager implements FileManager {
         MessageDigest md5 = DigestUtils.getMd5Digest();
         MessageDigest sha256 = DigestUtils.getSha256Digest();
         String storageBasePath = properties.getStorageBasePath();
-        String serverNodeId = properties.getServerNodeId();
+        String serverNodeId = rpcManager.currentServerId();
         Path path = Paths.get(storageBasePath, storagePath);
         path.toFile().getParentFile().mkdirs();
         return stream
@@ -130,12 +106,7 @@ public class DefaultFileManager implements FileManager {
 
     @Override
     public Mono<FileInfo> saveFile(String name, Flux<DataBuffer> stream) {
-        if (properties.getClusterRute().isEmpty()
-            || properties.getClusterRute().containsKey(properties.getServerNodeId())) {
-            return doSaveFile(name, stream);
-        }
-        //配置里集群,但是并不支持本节点,则保存到其他节点
-        return saveFileToCluster(name, stream);
+        return doSaveFile(name, stream);
     }
 
     @Override
@@ -150,24 +121,25 @@ public class DefaultFileManager implements FileManager {
             .read(new FileSystemResource(Paths.get(properties.getStorageBasePath(), filePath)),
                   position,
                   bufferFactory,
-                  properties.getReadBufferSize());
+                  (int) properties.getReadBufferSize().toBytes())
+            .onErrorMap(NoSuchFileException.class, e -> new NotFoundException());
     }
 
     private Flux<DataBuffer> readFile(FileEntity file, long position) {
-        if (Objects.equals(file.getServerNodeId(), properties.getServerNodeId())) {
+        if (Objects.equals(file.getServerNodeId(), rpcManager.currentServerId())) {
             return readFile(file.getStoragePath(), position);
         }
         return readFromAnotherServer(file, position);
     }
 
     protected Flux<DataBuffer> readFromAnotherServer(FileEntity file, long position) {
-        return client
-            .get()
-            .uri("http://" + file.getServerNodeId() + "/file/{serverNodeId}/{fileId}", file.getServerNodeId(), file.getId())
-            .attribute(FileProperties.serverNodeIdAttr, file.getServerNodeId())
-            .headers(header -> header.setRange(Collections.singletonList(HttpRange.createByteRange(position))))
-            .retrieve()
-            .bodyToFlux(DataBuffer.class);
+
+        return rpcManager
+            .getService(file.getServerNodeId(), Service.class)
+            .switchIfEmpty(Mono.error(NotFoundException::new))
+            .flatMapMany(service -> service.read(new ReadRequest(file.getId(), position)))
+            .<DataBuffer>map(bufferFactory::wrap)
+            .doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
     }
 
     @Override
@@ -179,6 +151,7 @@ public class DefaultFileManager implements FileManager {
     public Flux<DataBuffer> read(String id, long position) {
         return repository
             .findById(id)
+            .switchIfEmpty(Mono.error(NotFoundException::new))
             .flatMapMany(file -> readFile(file, position));
     }
 
@@ -186,6 +159,7 @@ public class DefaultFileManager implements FileManager {
     public Flux<DataBuffer> read(String id, Function<ReaderContext, Mono<Void>> beforeRead) {
         return repository
             .findById(id)
+            .switchIfEmpty(Mono.error(NotFoundException::new))
             .flatMapMany(file -> {
                 DefaultReaderContext context = new DefaultReaderContext(file.toInfo(), 0);
                 return beforeRead
@@ -210,4 +184,35 @@ public class DefaultFileManager implements FileManager {
         }
     }
 
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class ReadRequest {
+        private String id;
+        private long position;
+    }
+
+    @io.scalecube.services.annotations.Service
+    public interface Service {
+
+        @ServiceMethod
+        Flux<ByteBuf> read(ReadRequest request);
+    }
+
+
+    public class ServiceImpl implements Service {
+        @Override
+        public Flux<ByteBuf> read(ReadRequest request) {
+            return ClusterFileManager
+                .this
+                .read(request.id, request.position)
+                .map(buf -> {
+                    if (buf instanceof NettyDataBuffer) {
+                        return ((NettyDataBuffer) buf).getNativeBuffer();
+                    }
+                    return Unpooled.wrappedBuffer(buf.asByteBuffer());
+                });
+        }
+    }
 }

+ 3 - 3
jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/file/FileManagerConfiguration.java

@@ -2,10 +2,10 @@ package org.jetlinks.community.io.file;
 
 import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
 import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
+import org.jetlinks.core.rpc.RpcManager;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.web.reactive.function.client.WebClient;
 
 @Configuration
 @EnableConfigurationProperties(FileProperties.class)
@@ -14,10 +14,10 @@ public class FileManagerConfiguration {
 
 
     @Bean
-    public FileManager fileManager(WebClient.Builder builder,
+    public FileManager fileManager(RpcManager rpcManager,
                                    FileProperties properties,
                                    ReactiveRepository<FileEntity, String> repository){
-        return new DefaultFileManager(builder,properties,repository);
+        return new ClusterFileManager(rpcManager,properties,repository);
     }
 
 }

+ 1 - 61
jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/file/FileProperties.java

@@ -2,76 +2,16 @@ package org.jetlinks.community.io.file;
 
 import lombok.Getter;
 import lombok.Setter;
-import org.hswebframework.web.exception.NotFoundException;
-import org.hswebframework.web.utils.DigestUtils;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.util.unit.DataSize;
-import org.springframework.web.reactive.function.client.ClientRequest;
-import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
-import org.springframework.web.util.UriComponentsBuilder;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ThreadLocalRandom;
 
 @Getter
 @Setter
 @ConfigurationProperties("file.manager")
 public class FileProperties {
 
-    public static final String clusterKeyHeader = "cluster-key";
-
-    public static final String serverNodeIdAttr = "server-node-id";
-
-    private String clusterKey = DigestUtils.md5Hex("_JetLinks_FM_K");
-
     private String storageBasePath = "./data/files";
 
-    private int readBufferSize = (int) DataSize.ofKilobytes(64).toBytes();
-
-    private String serverNodeId = "default";
-
-    /**
-     * server1: 192.168.33.222:3322
-     */
-    private Map<String, String> clusterRute = new HashMap<>();
-
-
-    public String selectServerNode() {
-        int size = clusterRute.size();
-        if (size == 0) {
-            throw new NotFoundException("error.server_node_notfound");
-        }
-        return new ArrayList<>(clusterRute.keySet())
-            .get(ThreadLocalRandom.current().nextInt(size));
-    }
-
-    public ExchangeFilterFunction createWebClientRute() {
-        return (clientRequest, exchangeFunction) -> {
-            String target = clientRequest
-                .attribute(serverNodeIdAttr)
-                .map(String::valueOf)
-                .map(clusterRute::get)
-                .orElseThrow(() -> new NotFoundException("error.server_node_notfound"));
-            int idx = target.lastIndexOf(":");
-            String host = target.substring(0, idx).trim();
-            String port = target.substring(idx + 1).trim();
-            return exchangeFunction
-                .exchange(
-                    ClientRequest
-                        .from(clientRequest)
-                        .header(clusterKeyHeader, clusterKey)
-                        .url(UriComponentsBuilder
-                                 .fromUri(clientRequest.url())
-                                 .host(host)
-                                 .port(port)
-                                 .build()
-                                 .toUri())
-                        .build()
-                );
-        };
-    }
-
+    private DataSize readBufferSize = DataSize.ofKilobytes(64);
 
 }

+ 0 - 43
jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/file/web/FileManagerController.java

@@ -3,14 +3,11 @@ package org.jetlinks.community.io.file.web;
 import io.swagger.v3.oas.annotations.Operation;
 import lombok.AllArgsConstructor;
 import org.hswebframework.web.authorization.annotation.Authorize;
-import org.hswebframework.web.authorization.exception.AccessDenyException;
 import org.jetlinks.community.io.file.FileInfo;
 import org.jetlinks.community.io.file.FileManager;
-import org.jetlinks.community.io.file.FileProperties;
 import org.springframework.http.ContentDisposition;
 import org.springframework.http.HttpRange;
 import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
 import org.springframework.http.codec.multipart.FilePart;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.server.ServerWebExchange;
@@ -18,15 +15,12 @@ import reactor.core.publisher.Mono;
 
 import java.nio.charset.StandardCharsets;
 import java.util.List;
-import java.util.Objects;
 
 @RestController
 @RequestMapping("/file")
 @AllArgsConstructor
 public class FileManagerController {
 
-    private final FileProperties properties;
-
     private final FileManager fileManager;
 
     @PostMapping("/upload")
@@ -70,41 +64,4 @@ public class FileManagerController {
                                return Mono.empty();
                            }));
     }
-
-    //用于集群间获取文件
-    @GetMapping("/{clusterNodeId}/{fileId}")
-    @Authorize(ignore = true)
-    @Operation(summary = "集群间获取文件", hidden = true)
-    public Mono<Void> readFromCluster(@PathVariable String clusterNodeId,
-                                      @PathVariable String fileId,
-                                      ServerWebExchange exchange) {
-        if (Objects.equals(clusterNodeId, properties.getServerNodeId())) {
-            //读取自己
-            return Mono.error(new IllegalArgumentException("error.file_read_loop"));
-        }
-        //校验key
-        if (!Objects.equals(exchange.getRequest().getHeaders().getFirst(FileProperties.clusterKeyHeader),
-                            properties.getClusterKey())) {
-            return Mono.error(new AccessDenyException());
-        }
-        return read(fileId, exchange);
-    }
-
-    //用于集群间保存文件
-    @PostMapping("/{clusterNodeId}")
-    @Authorize(ignore = true)
-    @Operation(summary = "集群间获取文件", hidden = true)
-    public Mono<ResponseEntity<FileInfo>> saveFromCluster(@PathVariable String clusterNodeId,
-                                                          @RequestPart("file") Mono<FilePart> partMono,
-                                                          @RequestHeader(FileProperties.clusterKeyHeader) String key) {
-        if (!Objects.equals(clusterNodeId, properties.getServerNodeId())) {
-            return Mono.error(new IllegalArgumentException("error.file_read_loop"));
-        }
-        //校验key
-        if (!Objects.equals(key, properties.getClusterKey())) {
-            return Mono.error(new AccessDenyException());
-        }
-        return upload(partMono)
-            .map(ResponseEntity::ok);
-    }
 }

+ 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 - 107
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,13 @@ 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 FluxSink<Message> sink = processor.sink(FluxSink.OverflowStrategy.BUFFER);
-
-    private final AtomicBoolean started = new AtomicBoolean();
+    private final String protocol;
 
-    private final List<Disposable> disposable = new CopyOnWriteArrayList<>();
+    private final ProtocolSupports protocolSupport;
 
-    private final DeviceGatewayMonitor gatewayMonitor;
+    private Disposable disposable = null;
 
     private final DeviceGatewayHelper helper;
 
@@ -76,9 +57,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 +73,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) -> isStarted())
+            .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),
+                                                 ignore->{},
+                                                 () -> 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 +141,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>

+ 247 - 146
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;
@@ -22,12 +24,12 @@ import java.util.function.Function;
 import java.util.function.Supplier;
 
 /**
- * 设备网关处理工具
- * <p>
- * 封装常用的设备消息处理操作
- * </p>
+ * 设备网关消息处理,会话管理工具类,用于统一封装对设备消息和会话的处理逻辑
  *
  * @author zhouhao
+ * @see DeviceRegistry
+ * @see DecodedClientMessageHandler
+ * @since 1.5
  */
 @AllArgsConstructor
 public class DeviceGatewayHelper {
@@ -36,69 +38,88 @@ public class DeviceGatewayHelper {
     private final DeviceSessionManager sessionManager;
     private final DecodedClientMessageHandler messageHandler;
 
+    @Deprecated
     public static Consumer<DeviceSession> applySessionKeepaliveTimeout(DeviceMessage msg, Supplier<Duration> timeoutSupplier) {
         return session -> {
-            Duration timeout = msg
-                .getHeader(Headers.keepOnlineTimeoutSeconds)
-                .map(Duration::ofSeconds)
-                .orElseGet(timeoutSupplier);
-            if (null != timeout) {
-                session.setKeepAliveTimeout(timeout);
-            }
+            //do nothing
+
         };
     }
 
+    public Mono<DeviceOperator> handleDeviceMessage(DeviceMessage message,
+                                                    Function<DeviceOperator, DeviceSession> sessionBuilder) {
+
+        return handleDeviceMessage(message, sessionBuilder, (ignore)->{}, () -> {});
+    }
+
+    /**
+     * 处理设备消息
+     *
+     * @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) {
+    private Mono<Void> handleChildrenDeviceMessage(String deviceId, DeviceMessage children) {
+        //设备状态检查,断开设备连接的消息都忽略
+        //这些消息属于状态管理,通常是用来自定义子设备状态的,所以这些消息都忽略处理会话
         if (deviceId == null
             || children instanceof DeviceStateCheckMessage
             || children instanceof DeviceStateCheckMessageReply
             || children instanceof DisconnectDeviceMessage
-            || children instanceof DisconnectDeviceMessageReply) {
+            || children instanceof DisconnectDeviceMessageReply
+            || children.getHeaderOrDefault(Headers.ignoreSession)) {
             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> sessionHandler = sessionManager
+                .getSession(deviceId)
+                .flatMap(parentSession -> this
+                    .createOrUpdateSession(childrenId,
+                                           children,
+                                           child -> Mono.just(new ChildrenDeviceSession(childrenId, parentSession, child)),
+                                           Mono::empty));
+
+
             //子设备注册
             if (isDoRegister(children)) {
                 return Mono
+                    //延迟2秒,因为自动注册是异步的,收到消息后并不能保证马上可以注册成功.
                     .delay(Duration.ofSeconds(2))
                     .then(registry
                               .getDevice(children.getDeviceId())
@@ -106,146 +127,226 @@ public class DeviceGatewayHelper {
                                   //没有配置状态自管理才自动上线
                                   .getSelfConfig(DeviceConfigKey.selfManageState)
                                   .defaultIfEmpty(false)
-                                  .filter(Boolean.FALSE::equals)
-                                  .flatMap(ignore -> registerSession))
-                    );
+                                  .filter(Boolean.FALSE::equals))
+                              .flatMap(ignore -> sessionHandler))
+                    .then();
             }
-            return registerSession;
+            return sessionHandler.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)) {
+        if (!StringUtils.hasText(deviceId)) {
             return Mono.empty();
         }
-        Mono<Void> then = Mono.empty();
+        Mono<DeviceOperator> then = null;
         boolean doHandle = true;
+        //子设备消息
         if (message instanceof ChildDeviceMessage) {
             DeviceMessage childrenMessage = (DeviceMessage) ((ChildDeviceMessage) message).getChildDeviceMessage();
-            then = handleChildrenDeviceMessage(deviceId, childrenMessage);
-        } else if (message instanceof ChildDeviceMessageReply) {
+            then = handleChildrenDeviceMessage(deviceId, childrenMessage)
+                .then(registry.getDevice(deviceId));
+        }
+        //子设备消息回复
+        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) {
-            //设备在线消息
+            then = handleChildrenDeviceMessage(deviceId, childrenMessage)
+                .then(registry.getDevice(deviceId));
+        }
+        //设备离线消息
+        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();
+                })
+                .then(registry.getDevice(deviceId))
+                .contextWrite(Context.of(DeviceMessage.class, message));
+        }
+        //设备上线消息,不发送到messageHandler,防止设备上线存在重复消息
+        else if (message instanceof DeviceOnlineMessage) {
             doHandle = false;
         }
-        DeviceSession session = sessionManager.getSession(deviceId);
-        //session不存在,可能是同一个连接返回多个设备消息
-        if (session == null) {
+
+        //忽略会话管理,比如一个设备存在多种接入方式时,其中一种接入方式收到的消息设置忽略会话来防止会话冲突
+        if (message.getHeaderOrDefault(Headers.ignoreSession)) {
             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();
-                    }
-                    return Mono.empty();
-                }))
                 .flatMap(device -> {
-                    //忽略会话管理,比如一个设备存在多种接入方式时,其中一种接入方式收到的消息设置忽略会话来防止会话冲突
-                    if (message.getHeader(Headers.ignoreSession).orElse(false)) {
-                        if (!isDoRegister(message)) {
-                            return messageHandler
-                                .handleMessage(device, message)
-                                .thenReturn(device);
-                        }
-                        return Mono.just(device);
-                    }
-                    //session已经存在了,可能是并发创建.
-                    DeviceSession trySession = sessionManager.getSession(deviceId);
-                    if (trySession != null) {
-                        trySession.keepAlive();
-                        return Mono.just(device);
-                    }
-                    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 messageHandler
-                                .handleMessage(device, message)
-                                .thenReturn(device);
-                        }
+                    if (!isDoRegister(message)) {
+                        return messageHandler
+                            .handleMessage(device, message)
+                            .thenReturn(device);
                     }
                     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 (then == null) {
+            then = registry.getDevice(deviceId);
+        }
+
+        if (doHandle) {
+            then = then.flatMap(opt -> messageHandler.handleMessage(opt, message).thenReturn(opt));
         }
 
+        return this
+            .createOrUpdateSession(deviceId, message, sessionBuilder, deviceNotFoundCallback)
+            .flatMap(sessionConsumer)
+            .then(then)
+            .contextWrite(Context.of(DeviceMessage.class, message));
+
+    }
+
+    private Mono<DeviceSession> createOrUpdateSession(String deviceId,
+                                                      DeviceMessage message,
+                                                      Function<DeviceOperator, Mono<DeviceSession>> sessionBuilder,
+                                                      Supplier<Mono<DeviceOperator>> deviceNotFoundCallback) {
+        return sessionManager
+            .getSession(deviceId)
+            .map(old -> {
+                //需要更新会话时才进行更新
+                if (needUpdateSession(old, message)) {
+                    return sessionManager
+                        .compute(deviceId, null, session -> updateSession(session, message, sessionBuilder));
+                }
+                applySessionKeepaliveTimeout(message, old);
+                old.keepAlive();
+                return Mono.just(old);
+            })
+            //会话不存在则尝试创建或者更新
+            .defaultIfEmpty(Mono.defer(() -> sessionManager
+                .compute(deviceId,
+                         registerNewSession(
+                             deviceId,
+                             message,
+                             sessionBuilder,
+                             () -> {
+                                 //设备注册
+                                 if (isDoRegister(message)) {
+                                     return messageHandler
+                                         .handleMessage(null, message)
+                                         //延迟2秒后尝试重新获取设备并上线
+                                         .then(Mono.delay(Duration.ofSeconds(2)))
+                                         .then(registry.getDevice(deviceId));
+                                 }
+                                 if (deviceNotFoundCallback != null) {
+                                     return deviceNotFoundCallback.get();
+                                 }
+                                 return Mono.empty();
+                             }),
+                         session -> updateSession(session, message, sessionBuilder))))
+            .flatMap(Function.identity());
+    }
+
+    private Mono<DeviceSession> registerNewSession(String deviceId,
+                                                   DeviceMessage message,
+                                                   Function<DeviceOperator, Mono<DeviceSession>> sessionBuilder,
+                                                   Supplier<Mono<DeviceOperator>> deviceNotFoundCallback) {
+        return registry
+            .getDevice(deviceId)
+            .switchIfEmpty(Mono.defer(deviceNotFoundCallback))
+            .flatMap(device -> sessionBuilder
+                .apply(device)
+                .map(newSession -> {
+                    //保持在线,在低功率设备上,可能无法保持长连接,通过keepOnline的header来标识让设备保持在线
+                    if (message.getHeader(Headers.keepOnline).orElse(false)) {
+                        int timeout = message.getHeaderOrDefault(Headers.keepOnlineTimeoutSeconds);
+                        newSession = new KeepOnlineSession(newSession, Duration.ofSeconds(timeout));
+                    }
+                    return newSession;
+                }));
+    }
+
+    private Mono<DeviceSession> updateSession(DeviceSession session,
+                                              DeviceMessage message,
+                                              Function<DeviceOperator, Mono<DeviceSession>> sessionBuilder) {
+        Mono<Void> after = null;
+        //消息中指定保持在线,并且之前的会话不是保持在线,则需要替换之前的会话
+        if (isNewKeeOnline(session, message)) {
+            Integer timeoutSeconds = message.getHeaderOrDefault(Headers.keepOnlineTimeoutSeconds);
+            //替换session
+            session = new KeepOnlineSession(session, Duration.ofSeconds(timeoutSeconds));
+        }
+        //KeepOnline的连接丢失时(服务重启等操作),设备上线后替换丢失的会话,让其能恢复下行能力。
+        if (isKeeOnlineLost(session)) {
+            after = sessionBuilder
+                .apply(session.getOperator())
+                .doOnNext(((KeepOnlineSession) session)::replaceWith)
+                .then();
+        }
+        applySessionKeepaliveTimeout(message, session);
+        session.keepAlive();
+        return after == null
+            ? Mono.just(session)
+            : after.thenReturn(session);
     }
 
-    private boolean isDoRegister(DeviceMessage message) {
+    private static void applySessionKeepaliveTimeout(DeviceMessage msg, DeviceSession session) {
+        Integer timeout = msg.getHeaderOrElse(Headers.keepOnlineTimeoutSeconds, () -> null);
+        if (null != timeout) {
+            session.setKeepAliveTimeout(Duration.ofSeconds(timeout));
+        }
+    }
+
+    //判断是否需要更新会话
+    private static boolean needUpdateSession(DeviceSession session, DeviceMessage message) {
+        return isNewKeeOnline(session, message) || isKeeOnlineLost(session);
+    }
+
+    //判断是否为新的保持在线消息
+    private static boolean isNewKeeOnline(DeviceSession session, DeviceMessage message) {
+        return message.getHeaderOrDefault(Headers.keepOnline) && !(session instanceof KeepOnlineSession);
+    }
+
+    //判断保持在线的会话是否以及丢失(服务重启后可能出现)
+    private static boolean isKeeOnlineLost(DeviceSession session) {
+        return session instanceof KeepOnlineSession && session.isWrapFrom(LostDeviceSession.class);
+    }
+
+    //判断是否为设备注册
+    private static boolean isDoRegister(DeviceMessage message) {
         return message instanceof DeviceRegisterMessage
             && message.getHeader(PropertyConstants.deviceName).isPresent()
             && message.getHeader(PropertyConstants.productId).isPresent();
     }
 
 
+    /**
+     * 处理设备消息
+     *
+     * @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
+            );
+
+    }
+
+
 }

+ 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>

+ 82 - 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,44 @@ 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),
+                                     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 +183,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;

+ 4 - 4
jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/parser/strateies/DelimitedPayloadParserBuilder.java

@@ -14,10 +14,10 @@ public class DelimitedPayloadParserBuilder extends VertxPayloadParserBuilder {
     @Override
     protected RecordParser createParser(ValueObject config) {
 
-        return RecordParser.newDelimited(StringEscapeUtils
-                                             .unescapeJava(config
-                                                               .getString("delimited")
-                                                               .orElseThrow(() -> new IllegalArgumentException("delimited can not be null"))));
+        return RecordParser.newDelimited(StringEscapeUtils.unescapeJava(
+            config
+                .getString("delimited")
+                .orElseThrow(() -> new IllegalArgumentException("delimited can not be null"))));
     }
 
 

+ 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 - 2
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>
 
@@ -50,7 +50,6 @@
             <version>${project.version}</version>
         </dependency>
 
-
     </dependencies>
 
 </project>

+ 25 - 18
jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifier.java

@@ -1,7 +1,6 @@
 package org.jetlinks.community.notify.email.embedded;
 
 import com.alibaba.fastjson.JSONObject;
-import io.vavr.control.Try;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
@@ -11,16 +10,20 @@ import org.hswebframework.web.id.IDGenerator;
 import org.hswebframework.web.utils.ExpressionUtils;
 import org.hswebframework.web.utils.TemplateParser;
 import org.hswebframework.web.validator.ValidatorUtils;
+import org.jetlinks.core.Values;
 import org.jetlinks.community.io.file.FileManager;
 import org.jetlinks.community.notify.*;
 import org.jetlinks.community.notify.email.EmailProvider;
 import org.jetlinks.community.notify.template.TemplateManager;
-import org.jetlinks.core.Values;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
 import org.jsoup.nodes.Element;
 import org.springframework.core.io.*;
 import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.InputStreamSource;
+import org.springframework.core.io.Resource;
 import org.springframework.http.MediaType;
 import org.springframework.mail.javamail.JavaMailSender;
 import org.springframework.mail.javamail.JavaMailSenderImpl;
@@ -35,8 +38,6 @@ import reactor.core.scheduler.Schedulers;
 import javax.annotation.Nonnull;
 import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeUtility;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.List;
@@ -140,17 +141,24 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
                 return Flux
                     .fromIterable(template.getAttachments().entrySet())
                     .flatMap(entry -> Mono.zip(Mono.just(entry.getKey()), convertResource(entry.getValue())))
-                    .doOnNext(tp -> Try
-                        .run(() -> helper.addAttachment(MimeUtility.encodeText(tp.getT1()), tp.getT2())).get())
-                    .then(Flux
-                              .fromIterable(template.getImages().entrySet())
-                              .flatMap(entry -> Mono.zip(Mono.just(entry.getKey()), convertResource(entry.getValue())))
-                              .doOnNext(tp -> Try
-                                  .run(() -> helper.addInline(tp.getT1(), tp.getT2(), MediaType.APPLICATION_OCTET_STREAM_VALUE))
-                                  .get())
-                              .then()
-                    ).thenReturn(mimeMessage)
-                    ;
+                    .flatMap(tp2 -> Mono
+                        .fromCallable(() -> {
+                            //添加附件
+                            helper.addAttachment(MimeUtility.encodeText(tp2.getT1()), tp2.getT2());
+                            return helper;
+                        }))
+                    .then(
+                        Flux.fromIterable(template.getImages().entrySet())
+                            .flatMap(entry -> Mono.zip(Mono.just(entry.getKey()), convertResource(entry.getValue())))
+                            .flatMap(tp2 -> Mono
+                                .fromCallable(() -> {
+                                    //添加图片资源
+                                    helper.addInline(tp2.getT1(), tp2.getT2(), MediaType.APPLICATION_OCTET_STREAM_VALUE);
+                                    return helper;
+                                }))
+                            .then()
+                    )
+                    .thenReturn(mimeMessage);
 
             })
             .flatMap(Function.identity())
@@ -168,8 +176,7 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
                 .get()
                 .uri(resource)
                 .accept(MediaType.APPLICATION_OCTET_STREAM)
-                .exchange()
-                .flatMap(rep -> rep.bodyToMono(Resource.class));
+                .exchangeToMono(res->res.bodyToMono(Resource.class));
         } else if (resource.startsWith("data:") && resource.contains(";base64,")) {
             String base64 = resource.substring(resource.indexOf(";base64,") + 8);
             return Mono.just(
@@ -200,7 +207,7 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
         if (StringUtils.isEmpty(subject) || StringUtils.isEmpty(text)) {
             throw new BusinessException("模板内容错误,text 或者 subject 不能为空.");
         }
-        String sendText = render(text, context, true);
+        String sendText = render(text, context,true);
         List<EmailTemplate.Attachment> tempAttachments = template.getAttachments();
         Map<String, String> attachments = new HashMap<>();
 

+ 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>

+ 2 - 3
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>
@@ -58,7 +58,7 @@
             <groupId>com.fasterxml.jackson.dataformat</groupId>
             <artifactId>jackson-dataformat-cbor</artifactId>
         </dependency>
-             
+
         <dependency>
             <groupId>com.github.ben-manes.caffeine</groupId>
             <artifactId>caffeine</artifactId>
@@ -236,7 +236,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) {

+ 208 - 0
jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/RenameProtocolSupport.java

@@ -0,0 +1,208 @@
+package org.jetlinks.community.standalone.configuration;
+
+import lombok.AllArgsConstructor;
+import lombok.Generated;
+import lombok.Getter;
+import org.jetlinks.core.ProtocolSupport;
+import org.jetlinks.core.device.*;
+import org.jetlinks.core.event.EventBus;
+import org.jetlinks.core.message.codec.DeviceMessageCodec;
+import org.jetlinks.core.message.codec.TraceDeviceMessageCodec;
+import org.jetlinks.core.message.codec.Transport;
+import org.jetlinks.core.message.interceptor.DeviceMessageSenderInterceptor;
+import org.jetlinks.core.metadata.*;
+import org.jetlinks.core.route.Route;
+import org.jetlinks.core.server.ClientConnection;
+import org.jetlinks.core.server.DeviceGatewayContext;
+import org.jetlinks.supports.official.JetLinksDeviceMetadataCodec;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Nonnull;
+import java.util.Map;
+
+/**
+ * 重命名协议,将协议包里的协议使用进行重命名
+ *
+ * @author zhouhao
+ * @since 1.2
+ */
+@AllArgsConstructor
+@Generated
+public class RenameProtocolSupport implements ProtocolSupport {
+
+    public static final JetLinksDeviceMetadataCodec metadataCodec = new JetLinksDeviceMetadataCodec();
+    @Getter
+    private final String id;
+
+    @Getter
+    private final String name;
+
+    @Getter
+    private final String description;
+
+    private final ProtocolSupport target;
+
+    @Override
+    public Flux<? extends Transport> getSupportedTransport() {
+        return target.getSupportedTransport();
+    }
+
+    @Nonnull
+    @Override
+    public Mono<? extends DeviceMessageCodec> getMessageCodec(Transport transport) {
+        return target
+            .getMessageCodec(transport)
+            .map(codec-> new TraceDeviceMessageCodec(id,codec));
+    }
+
+    @Override
+    public Mono<DeviceMessageSenderInterceptor> getSenderInterceptor() {
+        return target.getSenderInterceptor();
+    }
+
+    @Nonnull
+    @Override
+    @SuppressWarnings("all")
+    public DeviceMetadataCodec getMetadataCodec() {
+        return target.getMetadataCodec() == null ? metadataCodec : target.getMetadataCodec();
+    }
+
+    @Nonnull
+    @Override
+    public Mono<AuthenticationResponse> authenticate(@Nonnull AuthenticationRequest request,
+                                                     @Nonnull DeviceOperator deviceOperation) {
+        return target.authenticate(request, deviceOperation);
+    }
+
+    @Nonnull
+    @Override
+    public Mono<AuthenticationResponse> authenticate(@Nonnull AuthenticationRequest request,
+                                                     @Nonnull DeviceRegistry registry) {
+        return target.authenticate(request, registry);
+    }
+
+    @Override
+    public Mono<DeviceMetadata> getDefaultMetadata(Transport transport) {
+        return target.getDefaultMetadata(transport);
+    }
+
+    @Override
+    public Flux<ConfigMetadata> getMetadataExpandsConfig(Transport transport,
+                                                         DeviceMetadataType metadataType,
+                                                         String metadataId,
+                                                         String dataTypeId) {
+        return target.getMetadataExpandsConfig(transport, metadataType, metadataId, dataTypeId);
+    }
+
+    @Override
+    public Flux<DeviceMetadataCodec> getMetadataCodecs() {
+        return target.getMetadataCodecs();
+    }
+
+    @Override
+    public Mono<ConfigMetadata> getInitConfigMetadata() {
+        return target.getInitConfigMetadata();
+    }
+
+    @Nonnull
+    @Override
+    public Mono<DeviceStateChecker> getStateChecker() {
+        return target.getStateChecker();
+    }
+
+    @Override
+    public Mono<ConfigMetadata> getConfigMetadata(Transport transport) {
+        return target.getConfigMetadata(transport);
+    }
+
+    @Override
+    public void init(Map<String, Object> configuration) {
+        target.init(configuration);
+    }
+
+    @Override
+    public void dispose() {
+        target.dispose();
+    }
+
+    @Override
+    public boolean isDisposed() {
+        return target.isDisposed();
+    }
+
+    @Override
+    public Mono<Void> onDeviceUnRegister(DeviceOperator operator) {
+        return target.onDeviceUnRegister(operator);
+    }
+
+    @Override
+    public Mono<Void> onDeviceRegister(DeviceOperator operator) {
+        return target.onDeviceRegister(operator);
+    }
+
+    @Override
+    public Mono<Void> onProductRegister(DeviceProductOperator operator) {
+        return target.onProductRegister(operator);
+    }
+
+    @Override
+    public Mono<Void> onProductUnRegister(DeviceProductOperator operator) {
+        return target.onProductUnRegister(operator);
+    }
+
+    @Override
+    public Mono<Void> onDeviceMetadataChanged(DeviceOperator operator) {
+        return target.onDeviceMetadataChanged(operator);
+    }
+
+    @Override
+    public Mono<Void> onProductMetadataChanged(DeviceProductOperator operator) {
+        return target.onProductMetadataChanged(operator);
+    }
+
+    @Override
+    public Mono<Void> onChildBind(DeviceOperator gateway, Flux<DeviceOperator> child) {
+        return target.onChildBind(gateway, child);
+    }
+
+    @Override
+    public Mono<Void> onChildUnbind(DeviceOperator gateway, Flux<DeviceOperator> child) {
+        return target.onChildUnbind(gateway, child);
+    }
+
+    @Override
+    public Mono<Void> onClientConnect(Transport transport, ClientConnection connection, DeviceGatewayContext context) {
+        return target.onClientConnect(transport, connection, context);
+    }
+
+    @Override
+    public Flux<Feature> getFeatures(Transport transport) {
+        return target.getFeatures(transport);
+    }
+
+    @Override
+    public Mono<DeviceInfo> doBeforeDeviceCreate(Transport transport, DeviceInfo deviceInfo) {
+        return target.doBeforeDeviceCreate(transport, deviceInfo);
+    }
+
+    @Override
+    public int getOrder() {
+        return target.getOrder();
+    }
+
+    @Override
+    public int compareTo(ProtocolSupport o) {
+        return target.compareTo(o);
+    }
+
+    @Override
+    public Flux<Route> getRoutes(Transport transport) {
+        return target.getRoutes(transport);
+    }
+
+    @Override
+    public String getDocument(Transport transport) {
+        return target.getDocument(transport);
+    }
+}

+ 5 - 2
jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/SpringProtocolSupportLoader.java

@@ -21,11 +21,14 @@ public class SpringProtocolSupportLoader implements ProtocolSupportLoader,BeanPo
     public void register(ProtocolSupportLoaderProvider provider) {
         this.providers.put(provider.getProvider(), provider);
     }
+
     @Override
     public Mono<? extends ProtocolSupport> load(ProtocolSupportDefinition definition) {
-        return Mono.justOrEmpty(this.providers.get(definition.getProvider()))
+        return Mono
+            .justOrEmpty(this.providers.get(definition.getProvider()))
             .switchIfEmpty(Mono.error(() -> new UnsupportedOperationException("unsupported provider:" + definition.getProvider())))
-            .flatMap((provider) -> provider.load(definition));
+            .flatMap((provider) -> provider.load(definition))
+            .map(loaded -> new RenameProtocolSupport(definition.getId(), definition.getName(), definition.getDescription(), loaded));
     }
 
     @Override

+ 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 - 0
jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/protocol/AutoDownloadJarProtocolSupportLoader.java


+ 0 - 704
jetlinks-standalone/src/main/java/org/springframework/data/redis/connection/ReactiveHashCommands.java

@@ -1,704 +0,0 @@
-/*
- * Copyright 2016-2020 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.data.redis.connection;
-
-import org.reactivestreams.Publisher;
-import org.springframework.dao.InvalidDataAccessApiUsageException;
-import org.springframework.data.redis.connection.ReactiveRedisConnection.*;
-import org.springframework.data.redis.core.ScanOptions;
-import org.springframework.lang.Nullable;
-import org.springframework.util.Assert;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-
-import java.nio.ByteBuffer;
-import java.util.*;
-import java.util.function.Function;
-
-/**
- * Redis Hash commands executed using reactive infrastructure.
- *
- * @author Christoph Strobl
- * @author Mark Paluch
- * @since 2.0
- */
-public interface ReactiveHashCommands {
-
-	/**
-	 * {@literal HSET} {@link Command}.
-	 *
-	 * @author Christoph Strobl
-	 * @see <a href="https://redis.io/commands/hset">Redis Documentation: HSET</a>
-	 */
-	class HSetCommand extends KeyCommand {
-
-		private static final ByteBuffer SINGLE_VALUE_KEY = ByteBuffer.allocate(0);
-		private final Map<ByteBuffer, ByteBuffer> fieldValueMap;
-		private final boolean upsert;
-
-		private HSetCommand(@Nullable ByteBuffer key, Map<ByteBuffer, ByteBuffer> keyValueMap, boolean upsert) {
-
-			super(key);
-
-			this.fieldValueMap = keyValueMap;
-			this.upsert = upsert;
-		}
-
-		/**
-		 * Creates a new {@link HSetCommand} given a {@link ByteBuffer key}.
-		 *
-		 * @param value must not be {@literal null}.
-		 * @return a new {@link HSetCommand} for {@link ByteBuffer key}.
-		 */
-		public static HSetCommand value(ByteBuffer value) {
-
-			Assert.notNull(value, "Value must not be null!");
-
-			return new HSetCommand(null, Collections.singletonMap(SINGLE_VALUE_KEY, value), Boolean.TRUE);
-		}
-
-		/**
-		 * Creates a new {@link HSetCommand} given a {@link Map} of field values.
-		 *
-		 * @param fieldValueMap must not be {@literal null}.
-		 * @return a new {@link HSetCommand} for a {@link Map} of field values.
-		 */
-		public static HSetCommand fieldValues(Map<ByteBuffer, ByteBuffer> fieldValueMap) {
-
-			Assert.notNull(fieldValueMap, "Field values map must not be null!");
-
-			return new HSetCommand(null, fieldValueMap, Boolean.TRUE);
-		}
-
-		/**
-		 * Applies a field. Constructs a new command instance with all previously configured properties.
-		 *
-		 * @param field must not be {@literal null}.
-		 * @return a new {@link HSetCommand} with {@literal field} applied.
-		 */
-		public HSetCommand ofField(ByteBuffer field) {
-
-			if (!fieldValueMap.containsKey(SINGLE_VALUE_KEY)) {
-				throw new InvalidDataAccessApiUsageException("Value has not been set.");
-			}
-
-			Assert.notNull(field, "Field not be null!");
-
-			return new HSetCommand(getKey(), Collections.singletonMap(field, fieldValueMap.get(SINGLE_VALUE_KEY)), upsert);
-		}
-
-		/**
-		 * Applies the {@literal key}. Constructs a new command instance with all previously configured properties.
-		 *
-		 * @param key must not be {@literal null}.
-		 * @return a new {@link HSetCommand} with {@literal key} applied.
-		 */
-		public HSetCommand forKey(ByteBuffer key) {
-
-			Assert.notNull(key, "Key not be null!");
-
-			return new HSetCommand(key, fieldValueMap, upsert);
-		}
-
-		/**
-		 * Disable upsert. Constructs a new command instance with all previously configured properties.
-		 *
-		 * @return a new {@link HSetCommand} with upsert disabled.
-		 */
-		public HSetCommand ifValueNotExists() {
-			return new HSetCommand(getKey(), fieldValueMap, Boolean.FALSE);
-		}
-
-		/**
-		 * @return
-		 */
-		public boolean isUpsert() {
-			return upsert;
-		}
-
-		/**
-		 * @return never {@literal null}.
-		 */
-		public Map<ByteBuffer, ByteBuffer> getFieldValueMap() {
-			return fieldValueMap;
-		}
-	}
-
-	/**
-	 * Set the {@literal value} of a hash {@literal field}.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @param field must not be {@literal null}.
-	 * @param value must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hset">Redis Documentation: HSET</a>
-	 */
-	default Mono<Boolean> hSet(ByteBuffer key, ByteBuffer field, ByteBuffer value) {
-
-		Assert.notNull(key, "Key must not be null!");
-		Assert.notNull(field, "Field must not be null!");
-		Assert.notNull(value, "Value must not be null!");
-
-		return hSet(Mono.just(HSetCommand.value(value).ofField(field).forKey(key)))
-				.next()
-				.flatMap(response->Mono.justOrEmpty(response.getOutput()));
-	}
-
-	/**
-	 * Set the {@literal value} of a hash {@literal field}.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @param field must not be {@literal null}.
-	 * @param value must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hsetnx">Redis Documentation: HSETNX</a>
-	 */
-	default Mono<Boolean> hSetNX(ByteBuffer key, ByteBuffer field, ByteBuffer value) {
-
-		Assert.notNull(key, "Key must not be null!");
-		Assert.notNull(field, "Field must not be null!");
-		Assert.notNull(value, "Value must not be null!");
-
-		return hSet(Mono.just(HSetCommand.value(value).ofField(field).forKey(key).ifValueNotExists()))
-				.next()
-				.flatMap(response->Mono.justOrEmpty(response.getOutput()));
-	}
-
-	/**
-	 * Set multiple hash fields to multiple values using data provided in {@literal fieldValueMap}.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @param fieldValueMap must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hmset">Redis Documentation: HMSET</a>
-	 */
-	default Mono<Boolean> hMSet(ByteBuffer key, Map<ByteBuffer, ByteBuffer> fieldValueMap) {
-
-		Assert.notNull(key, "Key must not be null!");
-		Assert.notNull(fieldValueMap, "Field must not be null!");
-
-		return hSet(Mono.just(HSetCommand.fieldValues(fieldValueMap).forKey(key))).next().map(it -> true);
-	}
-
-	/**
-	 * Set the {@literal value} of a hash {@literal field}.
-	 *
-	 * @param commands must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hset">Redis Documentation: HSET</a>
-	 */
-	Flux<BooleanResponse<HSetCommand>> hSet(Publisher<HSetCommand> commands);
-
-	/**
-	 * {@literal HGET} {@link Command}.
-	 *
-	 * @author Christoph Strobl
-	 * @see <a href="https://redis.io/commands/hget">Redis Documentation: HGET</a>
-	 */
-	class HGetCommand extends KeyCommand {
-
-		private List<ByteBuffer> fields;
-
-		private HGetCommand(@Nullable ByteBuffer key, List<ByteBuffer> fields) {
-
-			super(key);
-
-			this.fields = fields;
-		}
-
-		/**
-		 * Creates a new {@link HGetCommand} given a {@link ByteBuffer field name}.
-		 *
-		 * @param field must not be {@literal null}.
-		 * @return a new {@link HGetCommand} for a {@link ByteBuffer field name}.
-		 */
-		public static HGetCommand field(ByteBuffer field) {
-
-			Assert.notNull(field, "Field must not be null!");
-
-			return new HGetCommand(null, Collections.singletonList(field));
-		}
-
-		/**
-		 * Creates a new {@link HGetCommand} given a {@link Collection} of field names.
-		 *
-		 * @param fields must not be {@literal null}.
-		 * @return a new {@link HGetCommand} for a {@link Collection} of field names.
-		 */
-		public static HGetCommand fields(Collection<ByteBuffer> fields) {
-
-			Assert.notNull(fields, "Fields must not be null!");
-
-			return new HGetCommand(null, new ArrayList<>(fields));
-		}
-
-		/**
-		 * Applies the hash {@literal key}. Constructs a new command instance with all previously configured properties.
-		 *
-		 * @param key must not be {@literal null}.
-		 * @return a new {@link HGetCommand} with {@literal key} applied.
-		 */
-		public HGetCommand from(ByteBuffer key) {
-
-			Assert.notNull(key, "Key must not be null!");
-
-			return new HGetCommand(key, fields);
-		}
-
-		/**
-		 * @return never {@literal null}.
-		 */
-		public List<ByteBuffer> getFields() {
-			return fields;
-		}
-	}
-
-	/**
-	 * Get value for given {@literal field} from hash at {@literal key}.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @param field must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hget">Redis Documentation: HGET</a>
-	 */
-	default Mono<ByteBuffer> hGet(ByteBuffer key, ByteBuffer field) {
-		return hMGet(key, Collections.singletonList(field)).flatMapIterable(Function.identity()).next();
-	}
-
-	/**
-	 * Get values for given {@literal fields} from hash at {@literal key}.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @param fields must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hmget">Redis Documentation: HMGET</a>
-	 */
-	default Mono<List<ByteBuffer>> hMGet(ByteBuffer key, Collection<ByteBuffer> fields) {
-
-		Assert.notNull(key, "Key must not be null!");
-		Assert.notNull(fields, "Fields must not be null!");
-
-		return hMGet(Mono.just(HGetCommand.fields(fields).from(key)))
-				.next()
-				.flatMap(res->Mono.justOrEmpty(res.getOutput()));
-	}
-
-	/**
-	 * Get values for given {@literal fields} from hash at {@literal key}.
-	 *
-	 * @param commands must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hmget">Redis Documentation: HMGET</a>
-	 */
-	Flux<MultiValueResponse<HGetCommand, ByteBuffer>> hMGet(Publisher<HGetCommand> commands);
-
-	/**
-	 * {@literal HEXISTS} {@link Command}.
-	 *
-	 * @author Christoph Strobl
-	 * @see <a href="https://redis.io/commands/hexists">Redis Documentation: HEXISTS</a>
-	 */
-	class HExistsCommand extends KeyCommand {
-
-		private final ByteBuffer field;
-
-		private HExistsCommand(@Nullable ByteBuffer key, ByteBuffer field) {
-
-			super(key);
-
-			this.field = field;
-		}
-
-		/**
-		 * Creates a new {@link HExistsCommand} given a {@link ByteBuffer field name}.
-		 *
-		 * @param field must not be {@literal null}.
-		 * @return a new {@link HExistsCommand} for a {@link ByteBuffer field name}.
-		 */
-		public static HExistsCommand field(ByteBuffer field) {
-
-			Assert.notNull(field, "Field must not be null!");
-
-			return new HExistsCommand(null, field);
-		}
-
-		/**
-		 * Applies the hash {@literal key}. Constructs a new command instance with all previously configured properties.
-		 *
-		 * @param key must not be {@literal null}.
-		 * @return a new {@link HExistsCommand} with {@literal key} applied.
-		 */
-		public HExistsCommand in(ByteBuffer key) {
-
-			Assert.notNull(key, "Key must not be null!");
-
-			return new HExistsCommand(key, field);
-		}
-
-		/**
-		 * @return never {@literal null}.
-		 */
-		public ByteBuffer getField() {
-			return field;
-		}
-	}
-
-	/**
-	 * Determine if given hash {@literal field} exists.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @param field must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hexists">Redis Documentation: HEXISTS</a>
-	 */
-	default Mono<Boolean> hExists(ByteBuffer key, ByteBuffer field) {
-
-		Assert.notNull(key, "Key must not be null!");
-		Assert.notNull(field, "Field must not be null!");
-
-		return hExists(Mono.just(HExistsCommand.field(field).in(key)))
-				.next()
-				.flatMap(response->Mono.justOrEmpty(response.getOutput()));
-	}
-
-	/**
-	 * Determine if given hash {@literal field} exists.
-	 *
-	 * @param commands
-	 * @return
-	 * @see <a href="https://redis.io/commands/hexists">Redis Documentation: HEXISTS</a>
-	 */
-	Flux<BooleanResponse<HExistsCommand>> hExists(Publisher<HExistsCommand> commands);
-
-	/**
-	 * @author Christoph Strobl
-	 * @see <a href="https://redis.io/commands/hdel">Redis Documentation: HDEL</a>
-	 */
-	class HDelCommand extends KeyCommand {
-
-		private final List<ByteBuffer> fields;
-
-		private HDelCommand(@Nullable ByteBuffer key, List<ByteBuffer> fields) {
-
-			super(key);
-
-			this.fields = fields;
-		}
-
-		/**
-		 * Creates a new {@link HDelCommand} given a {@link ByteBuffer field name}.
-		 *
-		 * @param field must not be {@literal null}.
-		 * @return a new {@link HDelCommand} for a {@link ByteBuffer field name}.
-		 */
-		public static HDelCommand field(ByteBuffer field) {
-
-			Assert.notNull(field, "Field must not be null!");
-
-			return new HDelCommand(null, Collections.singletonList(field));
-		}
-
-		/**
-		 * Creates a new {@link HDelCommand} given a {@link Collection} of field names.
-		 *
-		 * @param fields must not be {@literal null}.
-		 * @return a new {@link HDelCommand} for a {@link Collection} of field names.
-		 */
-		public static HDelCommand fields(Collection<ByteBuffer> fields) {
-
-			Assert.notNull(fields, "Fields must not be null!");
-
-			return new HDelCommand(null, new ArrayList<>(fields));
-		}
-
-		/**
-		 * Applies the hash {@literal key}. Constructs a new command instance with all previously configured properties.
-		 *
-		 * @param key must not be {@literal null}.
-		 * @return a new {@link HDelCommand} with {@literal key} applied.
-		 */
-		public HDelCommand from(ByteBuffer key) {
-
-			Assert.notNull(key, "Key must not be null!");
-
-			return new HDelCommand(key, fields);
-		}
-
-		/**
-		 * @return never {@literal null}.
-		 */
-		public List<ByteBuffer> getFields() {
-			return fields;
-		}
-	}
-
-	/**
-	 * Delete given hash {@literal field}.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @param field must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hdel">Redis Documentation: HDEL</a>
-	 */
-	default Mono<Boolean> hDel(ByteBuffer key, ByteBuffer field) {
-
-		Assert.notNull(field, "Field must not be null!");
-
-		return hDel(key, Collections.singletonList(field)).map(val -> val > 0 ? Boolean.TRUE : Boolean.FALSE);
-	}
-
-	/**
-	 * Delete given hash {@literal fields}.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @param fields must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hdel">Redis Documentation: HDEL</a>
-	 */
-	default Mono<Long> hDel(ByteBuffer key, Collection<ByteBuffer> fields) {
-
-		Assert.notNull(key, "Key must not be null!");
-		Assert.notNull(fields, "Fields must not be null!");
-
-		return hDel(Mono.just(HDelCommand.fields(fields).from(key)))
-				.next()
-				.flatMap(response->Mono.justOrEmpty(response.getOutput()));
-	}
-
-	/**
-	 * Delete given hash {@literal fields}.
-	 *
-	 * @param commands must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hdel">Redis Documentation: HDEL</a>
-	 */
-	Flux<NumericResponse<HDelCommand, Long>> hDel(Publisher<HDelCommand> commands);
-
-	/**
-	 * Get size of hash at {@literal key}.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hlen">Redis Documentation: HLEN</a>
-	 */
-	default Mono<Long> hLen(ByteBuffer key) {
-
-		Assert.notNull(key, "Key must not be null!");
-
-		return hLen(Mono.just(new KeyCommand(key)))
-				.next()
-				.flatMap(response->Mono.justOrEmpty(response.getOutput()));
-	}
-
-	/**
-	 * Get size of hash at {@literal key}.
-	 *
-	 * @param commands must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hlen">Redis Documentation: HLEN</a>
-	 */
-	Flux<NumericResponse<KeyCommand, Long>> hLen(Publisher<KeyCommand> commands);
-
-	/**
-	 * Get key set (fields) of hash at {@literal key}.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hkeys">Redis Documentation: HKEYS</a>
-	 */
-	default Flux<ByteBuffer> hKeys(ByteBuffer key) {
-
-		Assert.notNull(key, "Key must not be null!");
-
-		return hKeys(Mono.just(new KeyCommand(key)))
-				.flatMap(CommandResponse::getOutput);
-	}
-
-	/**
-	 * Get key set (fields) of hash at {@literal key}.
-	 *
-	 * @param commands must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hkeys">Redis Documentation: HKEYS</a>
-	 */
-	Flux<CommandResponse<KeyCommand, Flux<ByteBuffer>>> hKeys(Publisher<KeyCommand> commands);
-
-	/**
-	 * Get entry set (values) of hash at {@literal key}.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hvals">Redis Documentation: HVALS</a>
-	 */
-	default Flux<ByteBuffer> hVals(ByteBuffer key) {
-
-		Assert.notNull(key, "Key must not be null!");
-
-		return hVals(Mono.just(new KeyCommand(key))).flatMap(CommandResponse::getOutput);
-	}
-
-	/**
-	 * Get entry set (values) of hash at {@literal key}.
-	 *
-	 * @param commands must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hvals">Redis Documentation: HVALS</a>
-	 */
-	Flux<CommandResponse<KeyCommand, Flux<ByteBuffer>>> hVals(Publisher<KeyCommand> commands);
-
-	/**
-	 * Get entire hash stored at {@literal key}.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hgetall">Redis Documentation: HGETALL</a>
-	 */
-	default Flux<Map.Entry<ByteBuffer, ByteBuffer>> hGetAll(ByteBuffer key) {
-
-		Assert.notNull(key, "Key must not be null!");
-
-		return hGetAll(Mono.just(new KeyCommand(key))).flatMap(CommandResponse::getOutput);
-	}
-
-	/**
-	 * Get entire hash stored at {@literal key}.
-	 *
-	 * @param commands must not be {@literal null}.
-	 * @return
-	 * @see <a href="https://redis.io/commands/hgetall">Redis Documentation: HGETALL</a>
-	 */
-	Flux<CommandResponse<KeyCommand, Flux<Map.Entry<ByteBuffer, ByteBuffer>>>> hGetAll(Publisher<KeyCommand> commands);
-
-	/**
-	 * Use a {@link Flux} to iterate over entries in the hash at {@code key}. The resulting {@link Flux} acts as a cursor
-	 * and issues {@code HSCAN} commands itself as long as the subscriber signals demand.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @return the {@link Flux} emitting {@link Map.Entry entries} one by one.
-	 * @throws IllegalArgumentException in case the given key is {@literal null}.
-	 * @see <a href="https://redis.io/commands/hscan">Redis Documentation: HSCAN</a>
-	 * @since 2.1
-	 */
-	default Flux<Map.Entry<ByteBuffer, ByteBuffer>> hScan(ByteBuffer key) {
-		return hScan(key, ScanOptions.NONE);
-	}
-
-	/**
-	 * Use a {@link Flux} to iterate over entries in the hash at {@code key} given {@link ScanOptions}. The resulting
-	 * {@link Flux} acts as a cursor and issues {@code HSCAN} commands itself as long as the subscriber signals demand.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @param options must not be {@literal null}. Use {@link ScanOptions#NONE} instead.
-	 * @return the {@link Flux} emitting the raw {@link Map.Entry entries} one by one.
-	 * @throws IllegalArgumentException in case one of the required arguments is {@literal null}.
-	 * @see <a href="https://redis.io/commands/hscan">Redis Documentation: HSCAN</a>
-	 * @since 2.1
-	 */
-	default Flux<Map.Entry<ByteBuffer, ByteBuffer>> hScan(ByteBuffer key, ScanOptions options) {
-
-		return hScan(Mono.just(KeyScanCommand.key(key).withOptions(options))).map(CommandResponse::getOutput)
-				.flatMap(it -> it);
-	}
-
-	/**
-	 * Use a {@link Flux} to iterate over entries in the hash at {@code key}. The resulting {@link Flux} acts as a cursor
-	 * and issues {@code HSCAN} commands itself as long as the subscriber signals demand.
-	 *
-	 * @param commands must not be {@literal null}.
-	 * @return the {@link Flux} emitting {@link CommandResponse} one by one.
-	 * @see <a href="https://redis.io/commands/hscan">Redis Documentation: HSCAN</a>
-	 * @since 2.1
-	 */
-	Flux<CommandResponse<KeyCommand, Flux<Map.Entry<ByteBuffer, ByteBuffer>>>> hScan(Publisher<KeyScanCommand> commands);
-
-	/**
-	 * @author Christoph Strobl
-	 * @see <a href="https://redis.io/commands/hstrlen">Redis Documentation: HSTRLEN</a>
-	 * @since 2.1
-	 */
-	class HStrLenCommand extends KeyCommand {
-
-		private ByteBuffer field;
-
-		/**
-		 * Creates a new {@link HStrLenCommand} given a {@code key}.
-		 *
-		 * @param key can be {@literal null}.
-		 * @param field must not be {@literal null}.
-		 */
-		private HStrLenCommand(@Nullable ByteBuffer key, ByteBuffer field) {
-
-			super(key);
-			this.field = field;
-		}
-
-		/**
-		 * Specify the {@code field} within the hash to get the length of the {@code value} of.ø
-		 *
-		 * @param field must not be {@literal null}.
-		 * @return new instance of {@link HStrLenCommand}.
-		 */
-		public static HStrLenCommand lengthOf(ByteBuffer field) {
-
-			Assert.notNull(field, "Field must not be null!");
-			return new HStrLenCommand(null, field);
-		}
-
-		/**
-		 * Define the {@code key} the hash is stored at.
-		 *
-		 * @param key must not be {@literal null}.
-		 * @return new instance of {@link HStrLenCommand}.
-		 */
-		public HStrLenCommand from(ByteBuffer key) {
-			return new HStrLenCommand(key, field);
-		}
-
-		/**
-		 * @return the field.
-		 */
-		public ByteBuffer getField() {
-			return field;
-		}
-	}
-
-	/**
-	 * Get the length of the value associated with {@code field}. If either the {@code key} or the {@code field} do not
-	 * exist, {@code 0} is emitted.
-	 *
-	 * @param key must not be {@literal null}.
-	 * @param field must not be {@literal null}.
-	 * @return never {@literal null}.
-	 * @since 2.1
-	 */
-	default Mono<Long> hStrLen(ByteBuffer key, ByteBuffer field) {
-
-		Assert.notNull(key, "Key must not be null!");
-		Assert.notNull(field, "Field must not be null!");
-
-		return hStrLen(Mono.just(HStrLenCommand.lengthOf(field).from(key))).next().map(NumericResponse::getOutput);
-	}
-
-	/**
-	 * Get the length of the value associated with {@code field}. If either the {@code key} or the {@code field} do not
-	 * exist, {@code 0} is emitted.
-	 *
-	 * @param commands must not be {@literal null}.
-	 * @return never {@literal null}.
-	 * @since 2.1
-	 */
-	Flux<NumericResponse<HStrLenCommand, Long>> hStrLen(Publisher<HStrLenCommand> commands);
-}

+ 5 - 2
jetlinks-standalone/src/main/resources/application.yml

@@ -76,7 +76,7 @@ hsweb:
         allowed-headers: "*"
         allowed-methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
         allowed-origins: ["*"]
-        allow-credentials: true
+#        allow-credentials: true
         max-age: 1800
   dict:
     enum-packages: org.jetlinks
@@ -106,6 +106,10 @@ hsweb:
     type: redis
     redis:
       local-cache-type: guava
+file:
+  manager:
+    storage-base-path: ./data/files
+
 jetlinks:
   server-id: ${spring.application.name}:${server.port} #设备服务网关服务ID,不同服务请设置不同的ID
   logging:
@@ -136,7 +140,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

+ 165 - 64
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,30 @@
     <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.13</spring.boot.version>
         <java.version>1.8</java.version>
         <project.build.jdk>${java.version}</project.build.jdk>
         <hsweb.framework.version>4.0.14</hsweb.framework.version>
-        <easyorm.version>4.0.14</easyorm.version>
+        <easyorm.version>4.1.0-SNAPSHOT</easyorm.version>
         <hsweb.expands.version>3.0.2</hsweb.expands.version>
-        <jetlinks.version>1.1.10</jetlinks.version>
-        <r2dbc.version>Arabba-SR10</r2dbc.version>
-        <vertx.version>4.2.3</vertx.version>
-        <netty.version>4.1.73.Final</netty.version>
+        <jetlinks.version>1.2.0-SNAPSHOT</jetlinks.version>
+        <r2dbc.version>Borca-SR1</r2dbc.version>
+        <netty.version>4.1.74.Final</netty.version>
         <elasticsearch.version>7.11.2</elasticsearch.version>
         <reactor.excel.version>1.0.3</reactor.excel.version>
-        <reactor.ql.version>1.0.13</reactor.ql.version>
+        <reactor.ql.version>1.0.14</reactor.ql.version>
+        <californium.version>3.3.1</californium.version>
         <fastjson.version>1.2.83</fastjson.version>
+        <reactor.version>2020.0.18</reactor.version>
+        <vertx.version>4.3.0</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>
+        <opentelemetry.version>1.13.0</opentelemetry.version>
     </properties>
 
+
     <build>
         <finalName>${project.artifactId}</finalName>
         <resources>
@@ -79,12 +85,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 +135,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 +144,48 @@
             <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>com.h2database</groupId>
+                <artifactId>h2</artifactId>
+                <version>2.1.210</version>
+            </dependency>
+
+            <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 +204,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 +240,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 +288,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 +326,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 +337,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 +355,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>31.0.1-jre</version>
             </dependency>
 
             <dependency>
@@ -325,17 +396,31 @@
                 <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>
 
     <dependencies>
 
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-text</artifactId>
+            <version>1.9</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
@@ -343,13 +428,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.17.2</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>1.17.2</version>
+            <scope>test</scope>
         </dependency>
 
         <dependency>
@@ -382,7 +489,6 @@
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
-            <version>1.7.32</version>
         </dependency>
 
         <dependency>
@@ -402,11 +508,6 @@
             <optional>true</optional>
         </dependency>
 
-        <dependency>
-            <groupId>org.hswebframework</groupId>
-            <artifactId>hsweb-utils</artifactId>
-            <version>3.0.3</version>
-        </dependency>
 
     </dependencies>