Procházet zdrojové kódy

Merge branch '2.0'

# Conflicts:
#	jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/client/VertxMqttClient.java
#	jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttServerDeviceGateway.java
#	jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/server/vertx/VertxMqttConnection.java
#	jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/server/vertx/VertxMqttServerProvider.java
#	jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/client/VertxTcpClient.java
#	jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/server/TcpServerProvider.java
#	jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/measurements/message/DeviceMessageMeasurement.java
#	jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/service/DeviceMessageBusinessHandler.java
#	pom.xml
zhouhao před 2 roky
rodič
revize
8172baf52a
100 změnil soubory, kde provedl 3762 přidání a 1488 odebrání
  1. 15 12
      README.md
  2. binární
      device-flow.png
  3. 2 2
      docker/dev-env/docker-compose.yml
  4. 35 24
      docker/run-all/docker-compose.yml
  5. 0 1
      flow.svg
  6. 19 1
      jetlinks-components/common-component/pom.xml
  7. 1 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/ConfigMetadataConstants.java
  8. 12 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/PropertyConstants.java
  9. 35 12
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/PropertyMetadataConstants.java
  10. 64 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/PropertyMetric.java
  11. 10 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/TimerIterable.java
  12. 440 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/TimerSpec.java
  13. 1 1
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/Version.java
  14. 56 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigManager.java
  15. 28 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigPropertyDef.java
  16. 23 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScope.java
  17. 18 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScopeCustomizer.java
  18. 9 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScopeManager.java
  19. 31 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScopeProperties.java
  20. 89 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/SimpleConfigManager.java
  21. 46 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/entity/ConfigEntity.java
  22. 130 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/web/SystemConfigManagerController.java
  23. 48 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/configuration/CommonConfiguration.java
  24. 27 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/doc/QueryConditionOnly.java
  25. 19 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistryCustomizer.java
  26. 23 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistrySettings.java
  27. 26 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/EnableReactorQL.java
  28. 37 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/ReactorQL.java
  29. 59 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/ReactorQLBeanDefinitionRegistrar.java
  30. 164 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/ReactorQLFactoryBean.java
  31. 22 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/ReactorQLOperation.java
  32. 170 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/term/FixedTermTypeSupport.java
  33. 19 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/term/TermType.java
  34. 20 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/term/TermTypeSupport.java
  35. 40 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/term/TermTypes.java
  36. 26 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reference/DataReferenceInfo.java
  37. 72 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reference/DataReferenceManager.java
  38. 36 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reference/DataReferenceProvider.java
  39. 30 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reference/DataReferencedException.java
  40. 26 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/reference/DefaultDataReferenceManager.java
  41. 75 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/ClassPathJsonResourceProvider.java
  42. 37 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/DefaultResourceManager.java
  43. 16 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/Resource.java
  44. 13 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/ResourceManager.java
  45. 15 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/ResourceProvider.java
  46. 34 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/SimpleResource.java
  47. 42 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/initialize/PermissionResourceProvider.java
  48. 51 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/strategy/StaticStrategyManager.java
  49. 17 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/strategy/Strategy.java
  50. 12 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/strategy/StrategyManager.java
  51. 123 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/topic/Topics.java
  52. 209 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/utils/ReactorUtils.java
  53. 12 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/web/ErrorControllerAdvice.java
  54. 37 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/web/SystemResourcesController.java
  55. 36 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/web/response/ValidationResult.java
  56. 2 1
      jetlinks-components/common-component/src/main/resources/i18n/common-component/messages_en.properties
  57. 1 1
      jetlinks-components/common-component/src/main/resources/i18n/common-component/messages_zh.properties
  58. 1 1
      jetlinks-components/configure-component/pom.xml
  59. 0 11
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/cluster/ClusterConfiguration.java
  60. 67 0
      jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/redis/ObjectRedisSerializer.java
  61. 8 16
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/JetLinksRedisConfiguration.java
  62. 0 5
      jetlinks-components/configure-component/src/main/resources/META-INF/spring.factories
  63. 5 0
      jetlinks-components/configure-component/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  64. 6 1
      jetlinks-components/dashboard-component/pom.xml
  65. 0 81
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/JvmCpuMeasurementProvider.java
  66. 0 132
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/JvmMemoryMeasurementProvider.java
  67. 3 2
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/MonitorObjectDefinition.java
  68. 0 79
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/SystemCpuMeasurementProvider.java
  69. 0 128
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/SystemMemoryMeasurementProvider.java
  70. 18 7
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/SystemMonitor.java
  71. 36 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/CpuInfo.java
  72. 41 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/DiskInfo.java
  73. 73 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/MemoryInfo.java
  74. 13 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/MonitorInfo.java
  75. 15 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/MonitorUtils.java
  76. 34 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/SystemInfo.java
  77. 148 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/SystemMonitorMeasurementProvider.java
  78. 14 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/SystemMonitorService.java
  79. 66 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/SystemMonitorServiceImpl.java
  80. 6 1
      jetlinks-components/elasticsearch-component/pom.xml
  81. 0 20
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/ElasticRestClient.java
  82. 0 268
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/DefaultAggregationService.java
  83. 58 96
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/configuration/ElasticSearchConfiguration.java
  84. 37 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/configuration/ElasticSearchThingDataConfiguration.java
  85. 0 1
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/DefaultElasticSearchIndexManager.java
  86. 5 8
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/strategies/TimeByMonthElasticSearchIndexStrategy.java
  87. 6 2
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/parser/DefaultLinkTypeParser.java
  88. 9 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/AggregationService.java
  89. 0 444
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/DefaultElasticSearchService.java
  90. 19 9
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/AggType.java
  91. 62 68
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/DefaultReactiveElasticsearchClient.java
  92. 20 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/ElasticSearchBufferProperties.java
  93. 45 42
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/ReactiveAggregationService.java
  94. 20 10
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/ReactiveElasticSearchService.java
  95. 0 1
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/ReactiveElasticsearchClient.java
  96. 38 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/things/ElasticSearchColumnModeDDLOperations.java
  97. 130 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/things/ElasticSearchColumnModeQueryOperations.java
  98. 33 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/things/ElasticSearchColumnModeSaveOperations.java
  99. 66 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/things/ElasticSearchColumnModeStrategy.java
  100. 0 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/things/ElasticSearchRowModeDDLOperations.java

+ 15 - 12
README.md

@@ -1,7 +1,7 @@
 # JetLinks 物联网基础平台
 
 ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/jetlinks/jetlinks-community/Auto%20Deploy%20Docker?label=docker)
-![Version](https://img.shields.io/badge/version-1.13--RELEASE-brightgreen)
+![Version](https://img.shields.io/badge/version-2.0--RELEASE-brightgreen)
 [![Codacy Badge](https://api.codacy.com/project/badge/Grade/e8d527d692c24633aba4f869c1c5d6ad)](https://app.codacy.com/gh/jetlinks/jetlinks-community?utm_source=github.com&utm_medium=referral&utm_content=jetlinks/jetlinks-community&utm_campaign=Badge_Grade_Settings)
 [![OSCS Status](https://www.oscs1024.com/platform/badge/jetlinks/jetlinks-community.svg?size=small)](https://www.oscs1024.com/project/jetlinks/jetlinks-community?ref=badge_small)
 ![jetlinks](https://visitor-badge.glitch.me/badge?page_id=jetlinks)
@@ -18,34 +18,37 @@ JetLinks 基于Java8,Spring Boot 2.x,WebFlux,Netty,Vert.x,Reactor等开发,
 
 ## 核心特性
 
-支持统一物模型管理,多种设备,多种厂家,统一管理。
+#### 开放源代码
 
-统一设备连接管理,多协议适配(TCP,MQTT,UDP,CoAP,HTTP等),屏蔽网络编程复杂性,灵活接入不同厂家不同协议的设备
+全部源代码开放,可自由拓展功能,不再受制于人.前后端分离,接口全开放
 
-灵活的规则引擎,设备告警,消息通知,数据转发.
+#### 统一设备接入,海量设备管理
+TCP/UDP/MQTT/HTTP、TLS/DTLS、不同厂商、不同设备、不同报文、统一接入,统一管理。
 
-强大的ReactorQL引擎,使用SQL来处理实时数据.
+#### 规则引擎
+灵活的规则模型配置,支持多种规则模型以及自定义规则模型. 设备告警,场景联动,均由统一的规则引擎管理。
 
-地理位置:统一管理地理位置信息,支持区域搜索. 
+#### 数据权限控制
+灵活的非侵入数据权限控制。可实现菜单、按钮、数据三维维度的数据权限控制。可控制单条数据的操作权限。
 
 ## 技术栈
 
-1. [Spring Boot 2.3.x](https://spring.io/projects/spring-boot)
+1. [Spring Boot 2.7.x](https://spring.io/projects/spring-boot)
 2. [Spring WebFlux](https://spring.io/) 响应式Web支持
 3. [R2DBC](https://r2dbc.io/) 响应式关系型数据库驱动
 4. [Project Reactor](https://projectreactor.io/) 响应式编程框架
-4. [Netty](https://netty.io/) ,[Vert.x](https://vertx.io/) 高性能网络编程框架
+4. [Netty](https://netty.io/),[Vert.x](https://vertx.io/) 高性能网络编程框架
 5. [ElasticSearch](https://www.elastic.co/cn/products/enterprise-search) 全文检索,日志,时序数据存储
 6. [PostgreSQL](https://www.postgresql.org) 业务功能数据管理
 7. [hsweb framework 4](https://github.com/hs-web) 业务功能基础框架
 
 ## 架构
 
-![platform](./platform.svg)
+![platform](./platform.png)
 
 ## 设备接入流程
 
-![flow](./flow.svg)
+![device-flow](./device-flow.png)
 
 ## 模块
 
@@ -62,6 +65,6 @@ JetLinks 基于Java8,Spring Boot 2.x,WebFlux,Netty,Vert.x,Reactor等开发,
 
 ## 文档
 
-[快速开始](http://doc.jetlinks.cn/basics-guide/quick-start.html) 
+[快速开始](http://doc.jetlinks.cn/install-deployment/start-with-source.html) 
 [开发文档](http://doc.jetlinks.cn/dev-guide/start.html) 
-[常见问题](http://doc.jetlinks.cn/common-problems/network-components.html) 
+[常见问题](http://doc.jetlinks.cn/common-problems/install.html) 

binární
device-flow.png


+ 2 - 2
docker/dev-env/docker-compose.yml

@@ -6,7 +6,7 @@ services:
     ports:
       - "6379:6379"
     volumes:
-      - "redis-volume:/data"
+      - "./data/redis:/data"
     command: redis-server --appendonly yes
     environment:
       - TZ=Asia/Shanghai
@@ -40,7 +40,7 @@ services:
     ports:
       - "5432:5432"
     volumes:
-      - "postgres-volume:/var/lib/postgresql/data"
+      - "./data/pg:/var/lib/postgresql/data"
     environment:
       POSTGRES_PASSWORD: jetlinks
       POSTGRES_DB: jetlinks

+ 35 - 24
docker/run-all/docker-compose.yml

@@ -6,7 +6,7 @@ services:
     #    ports:
     #      - "6379:6379"
     volumes:
-      - "redis-volume:/data"
+      - "./data/redis:/data"
     command: redis-server --appendonly yes --requirepass "JetLinks@redis"
     environment:
       - TZ=Asia/Shanghai
@@ -20,8 +20,8 @@ services:
       bootstrap.memory_lock: "true"
       discovery.zen.minimum_master_nodes: 1
       discovery.zen.ping.unicast.hosts: elasticsearch
-    volumes:
-      - elasticsearch-volume:/usr/share/elasticsearch/data
+  #  volumes:
+  #    - ./data/elasticsearch:/usr/share/elasticsearch/data
   #    ports:
   #      - "9200:9200"
   #      - "9300:9300"
@@ -33,14 +33,14 @@ services:
     links:
       - elasticsearch:elasticsearch
     ports:
-      - "5602:5601"
+      - "5601:5601"
     depends_on:
       - elasticsearch
   postgres:
     image: postgres:11-alpine
     container_name: jetlinks-ce-postgres
     volumes:
-      - "postgres-volume:/var/lib/postgresql/data"
+      - "./data/postgres:/var/lib/postgresql/data"
     ports:
       - "5432:5432"
     environment:
@@ -48,38 +48,43 @@ services:
       POSTGRES_DB: jetlinks
       TZ: Asia/Shanghai
   ui:
-    image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-antd:1.13.0
+    image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-pro:2.0.0
     container_name: jetlinks-ce-ui
     ports:
       - 9000:80
     environment:
       - "API_BASE_PATH=http://jetlinks:8848/" #API根路径
     volumes:
-      - "jetlinks-volume:/usr/share/nginx/html/upload"
+      - "./data/jetlinks-ui:/usr/share/nginx/html/upload"
     links:
       - jetlinks:jetlinks
   jetlinks:
-    image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:1.20.0-SNAPSHOT
+    image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:2.0.0-SNAPSHOT
     container_name: jetlinks-ce
+
     ports:
       - "8848:8848" # API端口
       - "1883-1890:1883-1890" # 预留
-      - "8000-8010:8000-8010" # 预留
+      - "8800-8810:8800-8810" # 预留
+      - "5060-5061:5060-5061" # 预留
     volumes:
-      - "jetlinks-volume:/application/static/upload"  # 持久化上传的文件
-      - "jetlinks-file-volume:/application/data/files"
-      - "jetlinks-protocol-volume:/application/data/protocols"
+      - "./data/jetlinks:/application/static/upload"  # 持久化上传的文件
+      - "./data/jetlinks/:/application/data/files"
+      - "./data/jetlinks/:/application/data/protocols"
+      - "./entrypoint.sh:/entrypoint.sh"
+    #entrypoint: /entrypoint.sh -d redis:5601,postgres:5432,elasticsearch:9200 'echo "start jetlinks service here"';
     environment:
+     # - "SLEEP_SECOND=4"
       - "JAVA_OPTS=-Duser.language=zh -XX:+UseG1GC"
       - "TZ=Asia/Shanghai"
       - "hsweb.file.upload.static-location=http://127.0.0.1:8848/upload"  #上传的静态文件访问根地址,为ui的地址.
       - "spring.r2dbc.url=r2dbc:postgresql://postgres:5432/jetlinks" #数据库连接地址
       - "spring.r2dbc.username=postgres"
       - "spring.r2dbc.password=jetlinks"
-      - "spring.data.elasticsearch.client.reactive.endpoints=elasticsearch:9200"
-#        - "spring.data.elasticsearch.client.reactive.username=admin"
-#        - "spring.data.elasticsearch.client.reactive.password=admin"
-#        - "spring.reactor.debug-agent.enabled=false" #设置为false能提升性能
+      - "spring.elasticsearch.uris=elasticsearch:9200"
+      #        - "spring.elasticsearch.username=admin"
+      #        - "spring.elasticsearch.password=admin"
+      #        - "spring.reactor.debug-agent.enabled=false" #设置为false能提升性能
       - "spring.redis.host=redis"
       - "spring.redis.port=6379"
       - "file.manager.storage-base-path=/application/data/files"
@@ -90,6 +95,19 @@ services:
       - "logging.level.org.jetlinks=warn"
       - "logging.level.org.hswebframework=warn"
       - "logging.level.org.springframework.data.r2dbc.connectionfactory=warn"
+      - "network.resources[0]=0.0.0.0:8800-8810/tcp"
+      - "network.resources[1]=0.0.0.0:1883-1890"
+      - "hsweb.cors.enable=true"
+      - "hsweb.cors.configs[0].path=/**"
+      - "hsweb.cors.configs[0].allowed-credentials=true"
+      - "hsweb.cors.configs[0].allowed-headers=*"
+      - "hsweb.cors.configs[0].allowed-origins=*"
+      - "hsweb.cors.configs[0].allowed-methods[0]=GET"
+      - "hsweb.cors.configs[0].allowed-methods[1]=POST"
+      - "hsweb.cors.configs[0].allowed-methods[2]=PUT"
+      - "hsweb.cors.configs[0].allowed-methods[3]=PATCH"
+      - "hsweb.cors.configs[0].allowed-methods[4]=DELETE"
+      - "hsweb.cors.configs[0].allowed-methods[5]=OPTIONS"
     links:
       - redis:redis
       - postgres:postgres
@@ -97,11 +115,4 @@ services:
     depends_on:
       - postgres
       - redis
-      - elasticsearch
-volumes:
-  postgres-volume:
-  redis-volume:
-  elasticsearch-volume:
-  jetlinks-volume:
-  jetlinks-file-volume:
-  jetlinks-protocol-volume:
+      - elasticsearch

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 1
flow.svg


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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.20.0-SNAPSHOT</version>
+        <version>2.0.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -55,5 +55,23 @@
             <groupId>com.h2database</groupId>
             <artifactId>h2</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.cronutils</groupId>
+            <artifactId>cron-utils</artifactId>
+            <version>9.2.0</version>
+            <scope>compile</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.glassfish</groupId>
+                    <artifactId>javax.el</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
     </dependencies>
 </project>

+ 1 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/ConfigMetadataConstants.java

@@ -18,5 +18,6 @@ public interface ConfigMetadataConstants {
     ConfigKey<Boolean> allowInput = ConfigKey.of("allowInput", "允许输入", Boolean.TYPE);
     ConfigKey<Boolean> required = ConfigKey.of("required", "是否必填", Boolean.TYPE);
 
+    ConfigKey<String> format = ConfigKey.of("format", "格式", String.class);
 
 }

+ 12 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/PropertyConstants.java

@@ -17,10 +17,22 @@ public interface PropertyConstants {
     Key<String> orgId = Key.of("orgId");
 
     Key<String> deviceName = Key.of("deviceName");
+    //产品名称
+    Key<String> productName = Key.of("productName");
 
     Key<String> productId = Key.of("productId");
     Key<String> uid = Key.of("_uid");
+    //设备创建者
+    Key<String> creatorId = Key.of("creatorId");
 
+    //设备接入网关ID
+    Key<String> accessId = Key.of("accessId");
+
+    /**
+     * 设备接入方式
+     * @see org.jetlinks.community.gateway.supports.DeviceGatewayProvider#getId
+     */
+    Key<String> accessProvider = Key.of("accessProvider");
 
     @SuppressWarnings("all")
     static <T> Optional<T> getFromMap(ConfigKey<T> key, Map<String, Object> map) {

+ 35 - 12
jetlinks-components/common-component/src/main/java/org/jetlinks/community/PropertyMetadataConstants.java

@@ -1,16 +1,21 @@
 package org.jetlinks.community;
 
+import org.jetlinks.community.utils.ConverterUtils;
 import org.jetlinks.core.message.DeviceMessage;
 import org.jetlinks.core.message.HeaderKey;
 import org.jetlinks.core.metadata.PropertyMetadata;
 import org.jetlinks.reactor.ql.utils.CastUtils;
 
+import java.util.*;
+
 public interface PropertyMetadataConstants {
 
+
     /**
      * 属性来源
      */
     interface Source {
+
         //数据来源
         String id = "source";
 
@@ -45,18 +50,6 @@ public interface PropertyMetadataConstants {
                            .orElse(false);
         }
 
-        /**
-         * 判断属性是否为规则
-         *
-         * @param metadata 物模型
-         * @return 是否规则
-         */
-        static boolean isRule(PropertyMetadata metadata) {
-            return  metadata
-                .getExpand(id)
-                .map(rule::equals)
-                .orElse(false);
-        }
     }
 
     /**
@@ -97,4 +90,34 @@ public interface PropertyMetadataConstants {
                 .orElse(true);
         }
     }
+
+    interface Metrics {
+        String id = "metrics";
+
+
+        static Map<String,Object> metricsToExpands(List<PropertyMetric> metrics) {
+            return Collections.singletonMap(id, metrics);
+        }
+
+        static List<PropertyMetric> getMetrics(PropertyMetadata metadata) {
+            return metadata
+                .getExpand(id)
+                .map(obj -> ConverterUtils.convertToList(obj, PropertyMetric::of))
+                .orElseGet(Collections::emptyList);
+        }
+
+        static Optional<PropertyMetric> getMetric(PropertyMetadata metadata, String metric) {
+            return metadata
+                .getExpand(id)
+                .map(obj -> {
+                    for (PropertyMetric propertyMetric : ConverterUtils.convertToList(obj, PropertyMetric::of)) {
+                        if(Objects.equals(metric, propertyMetric.getId())){
+                            return propertyMetric;
+                        }
+                    }
+                    return null;
+                });
+        }
+
+    }
 }

+ 64 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/PropertyMetric.java

@@ -0,0 +1,64 @@
+package org.jetlinks.community;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.jetlinks.community.utils.ConverterUtils;
+import org.springframework.util.StringUtils;
+
+import javax.validation.constraints.NotBlank;
+import java.util.Map;
+import java.util.function.Function;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PropertyMetric {
+    @Schema(description = "指标ID")
+    @NotBlank
+    private String id;
+
+    @Schema(description = "名称")
+    @NotBlank
+    private String name;
+
+    @Schema(description = "值,范围值使用逗号分隔")
+    private Object value;
+
+    @Schema(description = "是否为范围值")
+    private boolean range;
+
+    @Schema(description = "其他拓展配置")
+    private Map<String, Object> expands;
+
+    public Object castValue() {
+        if (value == null) {
+            return null;
+        }
+        if (range) {
+            return ConverterUtils.tryConvertToList(value, Function.identity());
+        }
+        return value;
+    }
+
+    public PropertyMetric merge(PropertyMetric another) {
+        if (!StringUtils.hasText(this.name)) {
+            this.setValue(another.value);
+        }
+        return this;
+    }
+
+    public static PropertyMetric of(String id, String name, Object value) {
+        PropertyMetric metric = new PropertyMetric();
+        metric.setId(id);
+        metric.setName(name);
+        metric.setValue(value);
+        return metric;
+    }
+
+    public static PropertyMetric of(Object mapMetric) {
+        return FastBeanCopier.copy(mapMetric, new PropertyMetric());
+    }
+}

+ 10 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/TimerIterable.java

@@ -0,0 +1,10 @@
+package org.jetlinks.community;
+
+import java.time.ZonedDateTime;
+import java.util.Iterator;
+
+public interface TimerIterable {
+
+    Iterator<ZonedDateTime> iterator(ZonedDateTime baseTime);
+
+}

+ 440 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/TimerSpec.java

@@ -0,0 +1,440 @@
+package org.jetlinks.community;
+
+import com.cronutils.builder.CronBuilder;
+import com.cronutils.model.Cron;
+import com.cronutils.model.definition.CronConstraintsFactory;
+import com.cronutils.model.definition.CronDefinition;
+import com.cronutils.model.definition.CronDefinitionBuilder;
+import com.cronutils.model.field.expression.FieldExpression;
+import com.cronutils.model.field.expression.FieldExpressionFactory;
+import com.cronutils.model.time.ExecutionTime;
+import com.cronutils.parser.CronParser;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.commons.collections4.CollectionUtils;
+import org.hswebframework.web.exception.ValidationException;
+import org.springframework.util.Assert;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+@Getter
+@Setter
+public class TimerSpec implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "触发方式")
+    @NotNull
+    private Trigger trigger;
+
+    //Cron表达式
+    @Schema(description = "触发方式为[cron]时不能为空")
+    private String cron;
+
+    @Schema(description = "执行的时间.为空则表示每天,触发方式为[week]则为1-7,触发方式为[month]时则为1-31")
+    private Set<Integer> when;
+
+    @Schema(description = "执行模式,一次还是周期执行")
+    private ExecuteMod mod;
+
+    @Schema(description = "执行模式为[period]时不能为空")
+    private Period period;
+
+    @Schema(description = "执行模式为[once]时不能为空")
+    private Once once;
+
+    public static TimerSpec cron(String cron) {
+        TimerSpec spec = new TimerSpec();
+        spec.cron = cron;
+        spec.trigger = Trigger.cron;
+        return spec;
+    }
+
+    public Predicate<LocalDateTime> createRangeFilter() {
+        if (CollectionUtils.isEmpty(when)) {
+            return ignore -> true;
+        }
+        if (trigger == Trigger.week) {
+            return date -> when.contains(date.getDayOfWeek().getValue());
+        } else if (trigger == Trigger.month) {
+            return date -> when.contains(date.getDayOfMonth());
+        }
+        return ignore -> true;
+    }
+
+    public Predicate<LocalDateTime> createTimeFilter() {
+        Predicate<LocalDateTime> range = createRangeFilter();
+        //周期执行指定了to,表示只在时间范围段内执行
+        if (mod == ExecuteMod.period) {
+            LocalTime to = period.toLocalTime();
+            LocalTime from = period.fromLocalTime();
+            Predicate<LocalDateTime> predicate
+                = time -> time.toLocalTime().compareTo(from) >= 0;
+            if (to != null) {
+                predicate = predicate.and(time -> time.toLocalTime().compareTo(to) <= 0);
+            }
+            return predicate.and(range);
+        }
+        if (mod == ExecuteMod.once){
+            LocalTime onceTime = once.localTime();
+            Predicate<LocalDateTime> predicate
+                = time -> time.toLocalTime().compareTo(onceTime) == 0;
+            return predicate.and(range);
+        }
+        return range;
+    }
+
+    public String toCronExpression() {
+        return toCron().asString();
+    }
+
+    private static CronDefinition quartz() {
+        return CronDefinitionBuilder
+            .defineCron()
+            .withSeconds()
+            .withValidRange(0, 59)
+            .and()
+            .withMinutes()
+            .withValidRange(0, 59)
+            .and()
+            .withHours()
+            .withValidRange(0, 23)
+            .and()
+            .withDayOfMonth()
+            .withValidRange(1, 31)
+            .supportsL()
+            .supportsW()
+            .supportsLW()
+            .supportsQuestionMark()
+            .and()
+            .withMonth()
+            .withValidRange(1, 12)
+            .and()
+            .withDayOfWeek()
+            .withValidRange(1, 7)
+            .withMondayDoWValue(1)
+            .supportsHash()
+            .supportsL()
+            .supportsQuestionMark()
+            .and()
+            .withYear()
+            .withValidRange(1970, 2099)
+            .withStrictRange()
+            .optional()
+            .and()
+            .withCronValidation(CronConstraintsFactory.ensureEitherDayOfWeekOrDayOfMonth())
+            .instance();
+    }
+
+    public Cron toCron() {
+        CronDefinition definition = quartz();
+        if (trigger == Trigger.cron || trigger == null) {
+            Assert.hasText(cron, "error.scene_rule_timer_cron_cannot_be_empty");
+            return new CronParser(definition).parse(cron).validate();
+        }
+
+        CronBuilder builder = CronBuilder.cron(definition);
+        builder.withYear(FieldExpression.always());
+        builder.withMonth(FieldExpression.always());
+
+        FieldExpression range;
+        if (CollectionUtils.isNotEmpty(when)) {
+            FieldExpression expr = null;
+            for (Integer integer : when) {
+                if (expr == null) {
+                    expr = FieldExpressionFactory.on(integer);
+                } else {
+                    expr = expr.and(FieldExpressionFactory.on(integer));
+                }
+            }
+            range = expr;
+        } else {
+            range = FieldExpressionFactory.questionMark();
+        }
+
+        if (trigger == Trigger.week) {
+            builder.withDoM(FieldExpressionFactory.questionMark())
+                   .withDoW(range);
+        } else if (trigger == Trigger.month) {
+            builder.withDoM(range)
+                   .withDoW(FieldExpressionFactory.questionMark());
+        }
+
+        //执行一次
+        if (mod == ExecuteMod.once) {
+            LocalTime time = once.localTime();
+            builder.withHour(FieldExpressionFactory.on(time.getHour()));
+            builder.withMinute(FieldExpressionFactory.on(time.getMinute()));
+            builder.withSecond(FieldExpressionFactory.on(time.getSecond()));
+        }
+        //周期执行
+        if (mod == ExecuteMod.period) {
+            LocalTime time = period.fromLocalTime();
+            PeriodUnit unit = period.unit;
+            if (unit == PeriodUnit.hours) {
+                builder.withHour(FieldExpressionFactory.every(FieldExpressionFactory.on(time.getHour()), period.every))
+                       .withMinute(FieldExpressionFactory.on(time.getMinute()))
+                       .withSecond(FieldExpressionFactory.on(time.getSecond()));
+            } else if (unit == PeriodUnit.minutes) {
+                builder
+                    .withHour(FieldExpressionFactory.always())
+                    .withMinute(FieldExpressionFactory.every(FieldExpressionFactory.on(time.getMinute()), period.every))
+                    .withSecond(FieldExpressionFactory.on(time.getSecond()));
+            } else if (unit == PeriodUnit.seconds) {
+                builder
+                    .withHour(FieldExpressionFactory.always())
+                    .withMinute(FieldExpressionFactory.always())
+                    .withSecond(FieldExpressionFactory.every(FieldExpressionFactory.on(time.getSecond()), period.every));
+            }
+        }
+        return builder.instance().validate();
+    }
+
+    public void validate() {
+        if (trigger == null) {
+            Assert.hasText(cron, "error.scene_rule_timer_cron_cannot_be_empty");
+        }
+        if (trigger == Trigger.cron) {
+            try {
+                toCronExpression();
+            } catch (Throwable e) {
+                ValidationException exception = new ValidationException("cron", "error.cron_format_error", cron);
+                exception.addSuppressed(e);
+                throw exception;
+            }
+        } else {
+            nextDurationBuilder().apply(ZonedDateTime.now());
+        }
+
+    }
+
+    @Getter
+    @Setter
+    @AllArgsConstructor(staticName = "of")
+    @NoArgsConstructor
+    public static class Once implements Serializable {
+        private static final long serialVersionUID = 1L;
+        //时间点
+        @Schema(description = "时间点.格式:[hh:mm],或者[hh:mm:ss]")
+        @NotBlank
+        private String time;
+
+        public LocalTime localTime() {
+            return parsTime(time);
+        }
+    }
+
+    @Getter
+    @Setter
+    public static class Period implements Serializable {
+        private static final long serialVersionUID = 1L;
+        //周期执行的时间区间
+        @Schema(description = "执行时间范围从.格式:[hh:mm],或者[hh:mm:ss]")
+        private String from;
+        @Schema(description = "执行时间范围止.格式:[hh:mm],或者[hh:mm:ss]")
+        private String to;
+
+        @Schema(description = "周期值,如:每[every][unit]执行一次")
+        private int every;
+
+        @Schema(description = "周期执行单位")
+        private PeriodUnit unit;
+
+        public LocalTime fromLocalTime() {
+            return parsTime(from);
+        }
+
+        public LocalTime toLocalTime() {
+            return parsTime(to);
+        }
+
+    }
+
+    private static LocalTime parsTime(String time) {
+        return LocalTime.parse(time);
+    }
+
+    /**
+     * 创建一个下一次执行时间间隔构造器,通过构造器来获取基准时间间隔
+     * <pre>{@code
+     *
+     *   Function<ZonedDateTime, Duration> builder = nextDurationBuilder();
+     *
+     *   Duration duration =  builder.apply(ZonedDateTime.now());
+     *
+     * }</pre>
+     *
+     * @return 构造器
+     */
+    public Function<ZonedDateTime, Duration> nextDurationBuilder() {
+        Function<ZonedDateTime, ZonedDateTime> nextTime = nextTimeBuilder();
+        return time -> Duration.between(time, nextTime.apply(time));
+    }
+
+    /**
+     * 创建一个时间构造器,通过构造器来获取下一次时间
+     * <pre>{@code
+     *
+     *   Function<ZonedDateTime, ZonedDateTime> builder = nextTimeBuilder();
+     *
+     *   ZonedDateTime nextTime =  builder.apply(ZonedDateTime.now());
+     *
+     * }</pre>
+     *
+     * @return 构造器
+     */
+    public Function<ZonedDateTime, ZonedDateTime> nextTimeBuilder() {
+        TimerIterable it = iterable();
+        return time -> it.iterator(time).next();
+    }
+
+    static int MAX_IT_TIMES = 10000;
+
+    private TimerIterable cronIterable() {
+        Cron cron = this.toCron();
+        ExecutionTime executionTime = ExecutionTime.forCron(cron);
+        Predicate<LocalDateTime> filter = createTimeFilter();
+        return baseTime -> new Iterator<ZonedDateTime>() {
+            ZonedDateTime current = baseTime;
+
+            @Override
+            public boolean hasNext() {
+                return current != null;
+            }
+
+            @Override
+            public ZonedDateTime next() {
+                if (!hasNext()) {
+                    throw new NoSuchElementException();
+                }
+                ZonedDateTime dateTime = current;
+                int i = 0;
+                do {
+                    dateTime = executionTime
+                        .nextExecution(dateTime)
+                        .orElse(null);
+                    if (dateTime == null) {
+                        i++;
+                        continue;
+                    }
+                    if (filter.test(dateTime.toLocalDateTime())) {
+                        break;
+                    }
+                } while (i < MAX_IT_TIMES);
+                return current = dateTime;
+            }
+        };
+    }
+
+    private TimerIterable periodIterable() {
+        Assert.notNull(period, "period can not be null");
+        Predicate<LocalDateTime> filter = createTimeFilter();
+
+        Duration duration = Duration.of(period.every, period.unit.temporal);
+        LocalTime time = period.fromLocalTime();
+        return baseTime -> new Iterator<ZonedDateTime>() {
+            ZonedDateTime current = baseTime;
+
+            @Override
+            public boolean hasNext() {
+                return true;
+            }
+
+            @Override
+            public ZonedDateTime next() {
+                ZonedDateTime dateTime = current;
+                int max = MAX_IT_TIMES;
+                do {
+                    dateTime = dateTime.plus(duration);
+                    if (filter.test(dateTime.toLocalDateTime())) {
+                        break;
+                    }
+                    max--;
+                } while (max > 0);
+
+                return current = dateTime;
+            }
+        };
+    }
+
+    private TimerIterable onceIterable() {
+        Assert.notNull(once, "once can not be null");
+        Predicate<LocalDateTime> filter = createTimeFilter();
+        LocalTime onceTime = once.localTime();
+        return baseTime -> new Iterator<ZonedDateTime>() {
+            ZonedDateTime current = baseTime;
+
+            @Override
+            public boolean hasNext() {
+                return true;
+            }
+
+            @Override
+            public ZonedDateTime next() {
+                ZonedDateTime dateTime = current;
+                int max = MAX_IT_TIMES;
+                if (dateTime.toLocalTime().compareTo(onceTime) != 0){
+                    dateTime = onceTime.atDate(dateTime.toLocalDate()).atZone(dateTime.getZone());
+                }
+                do {
+                    if (filter.test(dateTime.toLocalDateTime()) && current.compareTo(dateTime) <= 0) {
+                        current = dateTime.plusDays(1);
+                        break;
+                    }
+                    dateTime = dateTime.plusDays(1);
+                    max--;
+                } while (max > 0);
+                return dateTime;
+            }
+        };
+    }
+
+    public TimerIterable iterable() {
+        if ((trigger == Trigger.cron || trigger == null) && cron != null){
+            return cronIterable();
+        }
+        return mod == ExecuteMod.period ? periodIterable() : onceIterable();
+    }
+
+    public List<ZonedDateTime> getNextExecuteTimes(ZonedDateTime from, long times) {
+        List<ZonedDateTime> timeList = new ArrayList<>((int) times);
+        Iterator<ZonedDateTime> it = iterable().iterator(from);
+        for (long i = 0; i < times; i++) {
+            timeList.add(it.next());
+        }
+        return timeList;
+    }
+
+    public enum Trigger {
+        week,
+        month,
+        cron
+    }
+
+    public enum ExecuteMod {
+        period,
+        once
+    }
+
+    @AllArgsConstructor
+    public enum PeriodUnit {
+        seconds(ChronoUnit.SECONDS),
+        minutes(ChronoUnit.MINUTES),
+        hours(ChronoUnit.HOURS);
+        private final TemporalUnit temporal;
+
+    }
+}

+ 1 - 1
jetlinks-components/common-component/src/main/java/org/jetlinks/community/Version.java

@@ -8,6 +8,6 @@ public class Version {
 
     private final String edition = "community";
 
-    private final String version = "1.12.0-SNAPSHOT";
+    private final String version = "2.0.0-SNAPSHOT";
 
 }

+ 56 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigManager.java

@@ -0,0 +1,56 @@
+package org.jetlinks.community.config;
+
+import org.jetlinks.community.ValueObject;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+
+/**
+ * 配置管理器,统一管理系统相关配置信息
+ *
+ * @author zhouhao
+ * @since 2.0
+ */
+public interface ConfigManager {
+
+    /**
+     * 获取全部已经定义的配置作用域
+     *
+     * @return 配置作用域
+     */
+    Flux<ConfigScope> getScopes();
+
+    /**
+     * 获取根据作用域ID获取已经定义的配置作用域
+     *
+     * @return 配置作用域
+     */
+    Mono<ConfigScope> getScope(String scope);
+
+    /**
+     * 获取指定作用域下的属性定义信息
+     *
+     * @param scope 配置作用域
+     * @return 属性定义信息
+     */
+    Flux<ConfigPropertyDef> getPropertyDef(String scope);
+
+    /**
+     * 获取作用于下的全部配置
+     *
+     * @param scope 配置作用域
+     * @return 配置信息
+     */
+    Mono<ValueObject> getProperties(String scope);
+
+    /**
+     * 设置作用域下的配置
+     *
+     * @param scope  作用域
+     * @param values 配置信息
+     * @return void
+     */
+    Mono<Void> setProperties(String scope, Map<String, Object> values);
+
+}

+ 28 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigPropertyDef.java

@@ -0,0 +1,28 @@
+package org.jetlinks.community.config;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import org.jetlinks.core.metadata.types.StringType;
+
+@Getter
+@Setter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+@EqualsAndHashCode(of = "key")
+public class ConfigPropertyDef {
+
+    @Schema(description = "配置key")
+    private String key;
+
+    @Schema(description = "配置名称")
+    private String name;
+
+    @Schema(description = "是否只读")
+    private boolean readonly;
+
+    @Schema(description = "配置类型")
+    private String type = StringType.ID;
+
+    @Schema(description = "默认值")
+    private String defaultValue;
+}

+ 23 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScope.java

@@ -0,0 +1,23 @@
+package org.jetlinks.community.config;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+@Getter
+@Setter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+@EqualsAndHashCode(of = "id")
+public class ConfigScope {
+
+    @Schema(description = "ID")
+    private String id;
+
+    @Schema(description = "名称")
+    private String name;
+
+    @Schema(description = "是否公开访问(不需要登录)")
+    private boolean publicAccess;
+
+
+}

+ 18 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScopeCustomizer.java

@@ -0,0 +1,18 @@
+package org.jetlinks.community.config;
+
+/**
+ * 实现此接口,自定义配置域以及配置定义
+ *
+ * @author zhouhao
+ * @since 2.0
+ */
+public interface ConfigScopeCustomizer {
+
+    /**
+     * 执行自定义,通过manager来添加自定义作用域
+     *
+     * @param manager manager
+     */
+    void custom(ConfigScopeManager manager);
+
+}

+ 9 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScopeManager.java

@@ -0,0 +1,9 @@
+package org.jetlinks.community.config;
+
+import java.util.List;
+
+public interface ConfigScopeManager {
+
+    void addScope(ConfigScope scope, List<ConfigPropertyDef> properties);
+
+}

+ 31 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScopeProperties.java

@@ -0,0 +1,31 @@
+package org.jetlinks.community.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@ConfigurationProperties(prefix = "system.config")
+public class ConfigScopeProperties implements ConfigScopeCustomizer{
+
+    @Getter
+    @Setter
+    private List<Scope> scopes = new ArrayList<>();
+
+    @Override
+    public void custom(ConfigScopeManager manager) {
+        for (Scope scope : scopes) {
+            manager.addScope(FastBeanCopier.copy(scope,new ConfigScope()), scope.properties);
+        }
+    }
+
+    @Getter
+    @Setter
+    public static class Scope extends ConfigScope {
+        private List<ConfigPropertyDef> properties = new ArrayList<>();
+    }
+
+}

+ 89 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/SimpleConfigManager.java

@@ -0,0 +1,89 @@
+package org.jetlinks.community.config;
+
+import lombok.AllArgsConstructor;
+import org.apache.commons.collections4.MapUtils;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
+import org.jetlinks.community.ValueObject;
+import org.jetlinks.community.config.entity.ConfigEntity;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+@AllArgsConstructor
+public class SimpleConfigManager implements ConfigManager, ConfigScopeManager {
+
+    private final Map<ConfigScope, Set<ConfigPropertyDef>> scopes = new ConcurrentHashMap<>();
+
+    private final ReactiveRepository<ConfigEntity, String> repository;
+
+    @Override
+    public void addScope(ConfigScope scope,
+                         List<ConfigPropertyDef> properties) {
+        scopes.computeIfAbsent(scope, ignore -> new LinkedHashSet<>())
+              .addAll(properties);
+    }
+
+    @Override
+    public Flux<ConfigScope> getScopes() {
+        return Flux.fromIterable(scopes.keySet());
+    }
+
+    @Override
+    public Mono<ConfigScope> getScope(String scope) {
+        return this
+            .getScopes()
+            .filter(configScope -> Objects.equals(configScope.getId(), scope))
+            .take(1)
+            .singleOrEmpty();
+    }
+
+    @Override
+    public Flux<ConfigPropertyDef> getPropertyDef(String scope) {
+        return Flux.fromIterable(scopes.getOrDefault(
+            ConfigScope.of(scope, scope, false),
+            Collections.emptySet()));
+    }
+
+    @Override
+    public Mono<ValueObject> getProperties(String scope) {
+        return Mono
+            .zip(
+                //默认值
+                getPropertyDef(scope)
+                    .filter(def -> null != def.getDefaultValue())
+                    .collectMap(ConfigPropertyDef::getKey, ConfigPropertyDef::getDefaultValue),
+                //数据库配置的值
+                repository
+                    .createQuery()
+                    .where(ConfigEntity::getScope, scope)
+                    .fetch()
+                    .filter(val -> MapUtils.isNotEmpty(val.getProperties()))
+                    .<Map<String, Object>>reduce(new LinkedHashMap<>(), (l, r) -> {
+                        l.putAll(r.getProperties());
+                        return l;
+                    }),
+                (defaults, values) -> {
+                    defaults.forEach(values::putIfAbsent);
+                    return values;
+                }
+            ).map(ValueObject::of);
+    }
+
+    @Override
+    public Mono<Void> setProperties(String scope, Map<String, Object> values) {
+        return Flux
+            .fromIterable(values.entrySet())
+            .map(e -> {
+                ConfigEntity entity = new ConfigEntity();
+                entity.setProperties(values);
+                entity.setScope(scope);
+                entity.getId();
+                return entity;
+            })
+            .as(repository::save)
+            .then();
+    }
+
+}

+ 46 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/entity/ConfigEntity.java

@@ -0,0 +1,46 @@
+package org.jetlinks.community.config.entity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType;
+import org.hswebframework.ezorm.rdb.mapping.annotation.JsonCodec;
+import org.hswebframework.web.api.crud.entity.GenericEntity;
+import org.hswebframework.web.utils.DigestUtils;
+import org.springframework.util.StringUtils;
+
+import javax.persistence.Column;
+import javax.persistence.Index;
+import javax.persistence.Table;
+import java.sql.JDBCType;
+import java.util.Map;
+
+@Table(name = "s_config", indexes = {
+    @Index(name = "idx_conf_scope", columnList = "scope")
+})
+@Getter
+@Setter
+public class ConfigEntity extends GenericEntity<String> {
+
+    @Column(length = 64, nullable = false, updatable = false)
+    @Schema(description = "作用域")
+    private String scope;
+
+    @Column(nullable = false)
+    @Schema
+    @JsonCodec
+    @ColumnType(jdbcType = JDBCType.LONGVARCHAR)
+    private Map<String,Object> properties;
+
+    @Override
+    public String getId() {
+        if (!StringUtils.hasText(super.getId())) {
+            setId(generateId(scope));
+        }
+        return super.getId();
+    }
+
+    public static String generateId(String scope) {
+        return DigestUtils.md5Hex(String.join("|", scope));
+    }
+}

+ 130 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/web/SystemConfigManagerController.java

@@ -0,0 +1,130 @@
+package org.jetlinks.community.config.web;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.authorization.annotation.Authorize;
+import org.hswebframework.web.authorization.annotation.QueryAction;
+import org.hswebframework.web.authorization.annotation.Resource;
+import org.hswebframework.web.authorization.annotation.SaveAction;
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.jetlinks.community.ValueObject;
+import org.jetlinks.community.config.ConfigManager;
+import org.jetlinks.community.config.ConfigPropertyDef;
+import org.jetlinks.community.config.ConfigScope;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+
+@RestController
+@RequestMapping("/system/config")
+@Resource(id = "system_config", name = "系统配置管理")
+@AllArgsConstructor
+@Tag(name = "系统配置管理")
+public class SystemConfigManagerController {
+
+    private final ConfigManager configManager;
+
+    @GetMapping("/scopes")
+    @QueryAction
+    @Operation(description = "获取配置作用域")
+    public Flux<ConfigScope> getConfigScopes() {
+        return configManager.getScopes();
+    }
+
+    @GetMapping("/{scope}")
+    @Authorize(ignore = true)
+    @Operation(description = "获取作用域下的全部配置信息")
+    public Mono<Map<String, Object>> getConfigs(@PathVariable String scope) {
+        return Authentication
+            .currentReactive()
+            .hasElement()
+            .flatMap(hasAuth -> configManager
+                .getScope(scope)
+                //公共访问配置或者用户已登录
+                .map(conf -> conf.isPublicAccess() || hasAuth)
+                //没有定义配置,则用户登录即可访问
+                .defaultIfEmpty(hasAuth)
+                .filter(Boolean::booleanValue)
+                .flatMap(ignore -> configManager.getProperties(scope))
+                .map(ValueObject::values))
+            .defaultIfEmpty(Collections.emptyMap());
+    }
+
+    @GetMapping("/{scope}/_detail")
+    @QueryAction
+    @Operation(description = "获取作用域下的配置信息")
+    public Flux<ConfigPropertyValue> getConfigDetail(@PathVariable String scope) {
+        return configManager
+            .getProperties(scope)
+            .flatMapMany(values -> configManager
+                .getPropertyDef(scope)
+                .map(def -> ConfigPropertyValue.of(def, values.get(def.getKey()).orElse(null))));
+
+    }
+
+    @PostMapping("/scopes")
+    @QueryAction
+    @Operation(description = "获取作用域下的配置详情")
+    public Flux<Scope> getConfigDetail(@RequestBody Mono<List<String>> scopeMono) {
+        return scopeMono
+            .flatMapMany(scopes -> Flux
+                .fromIterable(scopes)
+                .flatMap(scope -> getConfigs(scope)
+                    .map(properties -> new Scope(scope, properties))));
+    }
+
+    @PostMapping("/{scope}")
+    @SaveAction
+    @Operation(description = "保存配置")
+    public Mono<Void> saveConfig(@PathVariable String scope,
+                                 @RequestBody Mono<Map<String, Object>> properties) {
+        return properties.flatMap(props -> configManager.setProperties(scope, props));
+
+    }
+
+    @PostMapping("/scope/_save")
+    @SaveAction
+    @Operation(description = "批量保存配置")
+    @Transactional
+    public Mono<Void> saveConfig(@RequestBody Flux<Scope> scope) {
+        return scope
+            .flatMap(scopeConfig -> configManager.setProperties(scopeConfig.getScope(), scopeConfig.getProperties()))
+            .then();
+    }
+
+    @Getter
+    @Setter
+    public static class ConfigPropertyValue extends ConfigPropertyDef {
+        private Object value;
+
+        public static ConfigPropertyValue of(ConfigPropertyDef def, Object value) {
+            ConfigPropertyValue val = FastBeanCopier.copy(def, new ConfigPropertyValue());
+            val.setValue(value);
+            return val;
+        }
+    }
+
+
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class Scope {
+
+        private String scope;
+
+        private Map<String, Object> properties;
+    }
+
+}

+ 48 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/configuration/CommonConfiguration.java

@@ -5,14 +5,30 @@ import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
 import org.apache.commons.beanutils.BeanUtilsBean;
 import org.apache.commons.beanutils.Converter;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
 import org.jetlinks.community.Interval;
+import org.jetlinks.community.config.ConfigManager;
+import org.jetlinks.community.config.ConfigScopeCustomizer;
+import org.jetlinks.community.config.ConfigScopeProperties;
+import org.jetlinks.community.config.SimpleConfigManager;
+import org.jetlinks.community.config.entity.ConfigEntity;
+import org.jetlinks.community.reference.DataReferenceManager;
+import org.jetlinks.community.reference.DataReferenceProvider;
+import org.jetlinks.community.reference.DefaultDataReferenceManager;
+import org.jetlinks.community.resource.DefaultResourceManager;
+import org.jetlinks.community.resource.ResourceManager;
+import org.jetlinks.community.resource.ResourceProvider;
+import org.jetlinks.community.resource.initialize.PermissionResourceProvider;
 import org.jetlinks.community.utils.TimeUtils;
+import org.jetlinks.core.rpc.RpcManager;
 import org.jetlinks.reactor.ql.feature.Feature;
 import org.jetlinks.reactor.ql.supports.DefaultReactorQLMetadata;
 import org.jetlinks.reactor.ql.utils.CastUtils;
 import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.MediaType;
@@ -25,6 +41,7 @@ import java.util.Date;
 
 @Configuration
 @SuppressWarnings("all")
+@EnableConfigurationProperties({ConfigScopeProperties.class})
 public class CommonConfiguration {
 
     static {
@@ -116,4 +133,35 @@ public class CommonConfiguration {
         };
     }
 
+    @Bean
+    public ConfigManager configManager(ObjectProvider<ConfigScopeCustomizer> configScopeCustomizers,
+                                       ReactiveRepository<ConfigEntity, String> repository) {
+
+        SimpleConfigManager configManager = new SimpleConfigManager(repository);
+        for (ConfigScopeCustomizer customizer : configScopeCustomizers) {
+            customizer.custom(configManager);
+        }
+        return configManager;
+    }
+
+    @Bean
+    public PermissionResourceProvider permissionResourceProvider(){
+        return new PermissionResourceProvider();
+    }
+
+    @Bean
+    public ResourceManager resourceManager(ObjectProvider<ResourceProvider> providers) {
+        DefaultResourceManager manager = new DefaultResourceManager();
+        providers.forEach(manager::addProvider);
+        return manager;
+    }
+
+    @Bean
+    public DataReferenceManager dataReferenceManager(ObjectProvider<DataReferenceProvider> provider) {
+        DefaultDataReferenceManager referenceManager = new DefaultDataReferenceManager();
+
+        provider.forEach(referenceManager::addStrategy);
+
+        return referenceManager;
+    }
 }

+ 27 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/doc/QueryConditionOnly.java

@@ -0,0 +1,27 @@
+package org.jetlinks.community.doc;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.ezorm.core.param.Term;
+
+import java.util.List;
+
+/**
+ * 文档专用,描述仅有查询功能的动态查询参数
+ *
+ * @author zhouhao
+ * @since 1.5
+ * @see org.hswebframework.web.api.crud.entity.QueryParamEntity
+ */
+@Getter
+@Setter
+public class QueryConditionOnly {
+
+    @Schema(description = "where条件表达式,与terms参数不能共存.语法: name = 张三 and age > 16")
+    private String where;
+
+    @Schema(description = "查询条件集合")
+    private List<Term> terms;
+
+}

+ 19 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistryCustomizer.java

@@ -0,0 +1,19 @@
+package org.jetlinks.community.micrometer;
+
+/**
+ * 监控指标自定义注册接口,用于对指标进行自定义,如添加指标标签等操作
+ *
+ * @author zhouhao
+ * @since 1.11
+ */
+public interface MeterRegistryCustomizer {
+
+    /**
+     * 在指标首次初始化时调用,可以通过判断metric进行自定义标签
+     *
+     * @param metric   指标
+     * @param settings 自定义设置
+     */
+    void custom(String metric, MeterRegistrySettings settings);
+
+}

+ 23 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistrySettings.java

@@ -0,0 +1,23 @@
+package org.jetlinks.community.micrometer;
+
+import org.jetlinks.core.metadata.DataType;
+
+import javax.annotation.Nonnull;
+
+/**
+ * 指标注册配置信息
+ *
+ * @author zhouhao
+ * @since 1.11
+ */
+public interface MeterRegistrySettings {
+
+    /**
+     * 给指标添加标签,用于自定义标签类型.在相应指标实现中会根据类型对数据进行存储
+     *
+     * @param tag  标签key
+     * @param type 类型
+     */
+    void addTag(@Nonnull String tag, @Nonnull DataType type);
+
+}

+ 26 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/EnableReactorQL.java

@@ -0,0 +1,26 @@
+package org.jetlinks.community.reactorql;
+
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * 在配置类上加上此注解,并指定{@link EnableReactorQL#value()},将扫描指定包下注解了{@link ReactorQLOperation}的接口类,
+ * 并生成代理对象注入到spring中.
+ *
+ * @author zhouhao
+ * @since  1.6
+ * @see ReactorQL
+ * @see ReactorQLOperation
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@Import(ReactorQLBeanDefinitionRegistrar.class)
+public @interface EnableReactorQL {
+    /**
+     * @return 扫描的包名
+     */
+    String[] value() default {};
+}

+ 37 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/ReactorQL.java

@@ -0,0 +1,37 @@
+package org.jetlinks.community.reactorql;
+
+import java.lang.annotation.*;
+
+/**
+ * 在接口的方法上注解,使用sql语句来处理参数
+ *
+ * @author zhouhao
+ * @see org.jetlinks.reactor.ql.ReactorQL
+ * @see ReactorQLOperation
+ * @since 1.6
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface ReactorQL {
+
+    /**
+     * 使用SQL语句来处理{@link reactor.core.publisher.Flux}操作.例如分组聚合.
+     * <a href="https://doc.jetlinks.cn/dev-guide/reactor-ql.html">查看文档说明</a>
+     *
+     * <pre>
+     *  select count(1) total,name from "arg0" group by name
+     * </pre>
+     * <p>
+     * <p>
+     * 当方法有参数时,可通过arg{index}来获取参数,如:
+     * <pre>
+     *     select name newName from "arg0" where id = :arg1
+     * </pre>
+     *
+     * @return SQL语句
+     */
+    String[] value();
+
+}

+ 59 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/ReactorQLBeanDefinitionRegistrar.java

@@ -0,0 +1,59 @@
+package org.jetlinks.community.reactorql;
+
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.context.index.CandidateComponentsIndex;
+import org.springframework.context.index.CandidateComponentsIndexLoader;
+import org.springframework.core.type.AnnotationMetadata;
+
+import javax.annotation.Nonnull;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+public class ReactorQLBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
+
+    @Override
+    @SneakyThrows
+    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, @Nonnull BeanDefinitionRegistry registry) {
+        Map<String, Object> attr = importingClassMetadata.getAnnotationAttributes(EnableReactorQL.class.getName());
+        if (attr == null) {
+            return;
+        }
+        String[] packages = (String[]) attr.get("value");
+
+        CandidateComponentsIndex index = CandidateComponentsIndexLoader.loadIndex(org.springframework.util.ClassUtils.getDefaultClassLoader());
+        if (null == index) {
+            return;
+        }
+        Set<String> path = Arrays.stream(packages)
+                                 .flatMap(str -> index
+                                     .getCandidateTypes(str, ReactorQLOperation.class.getName())
+                                     .stream())
+                                 .collect(Collectors.toSet());
+
+        for (String className : path) {
+            Class<?> type = org.springframework.util.ClassUtils.forName(className, null);
+            if (!type.isInterface() || type.getAnnotation(ReactorQLOperation.class) == null) {
+                continue;
+            }
+            RootBeanDefinition definition = new RootBeanDefinition();
+            definition.setTargetType(type);
+            definition.setBeanClass(ReactorQLFactoryBean.class);
+            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
+            definition.getPropertyValues().add("target", type);
+            if (!registry.containsBeanDefinition(type.getName())) {
+                log.debug("register ReactorQL Operator {}", type);
+                registry.registerBeanDefinition(type.getName(), definition);
+            }
+        }
+
+    }
+
+}

+ 164 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/ReactorQLFactoryBean.java

@@ -0,0 +1,164 @@
+package org.jetlinks.community.reactorql;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.SneakyThrows;
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.jetlinks.reactor.ql.ReactorQLContext;
+import org.reactivestreams.Publisher;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.core.Ordered;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.ResolvableType;
+import org.springframework.util.ClassUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+public class ReactorQLFactoryBean implements FactoryBean<Object>, InitializingBean, Ordered {
+
+    @Getter
+    @Setter
+    private Class<?> target;
+
+    private Object proxy;
+
+    private static final ParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
+
+    public ReactorQLFactoryBean() {
+
+    }
+
+    @Override
+    public Object getObject() {
+        return proxy;
+    }
+
+    @Override
+    public Class<?> getObjectType() {
+        return target;
+    }
+
+    @Override
+    public void afterPropertiesSet() {
+        Map<Method, Function<Object[], Object>> cache = new ConcurrentHashMap<>();
+
+        this.proxy = Proxy
+            .newProxyInstance(ClassUtils.getDefaultClassLoader(),
+                              new Class[]{target},
+                              (proxy, method, args) ->
+                                  cache
+                                      .computeIfAbsent(method, mtd -> createInvoker(target, mtd, mtd.getAnnotation(ReactorQL.class)))
+                                      .apply(args));
+    }
+
+    @SneakyThrows
+    private Function<Object[], Object> createInvoker(Class<?> type, Method method, ReactorQL ql) {
+        if (method.isDefault() || ql == null) {
+            Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
+                .getDeclaredConstructor(Class.class);
+            constructor.setAccessible(true);
+            MethodHandles.Lookup lookup = constructor.newInstance(type);
+            MethodHandle handle = lookup
+                .in(type)
+                .unreflectSpecial(method, type)
+                .bindTo(proxy);
+            return args -> {
+                try {
+                    return handle.invokeWithArguments(args);
+                } catch (Throwable e) {
+                    return Mono.error(e);
+                }
+            };
+        }
+
+        ResolvableType returnType = ResolvableType.forMethodReturnType(method);
+        if (returnType.toClass() != Mono.class && returnType.toClass() != Flux.class) {
+            throw new UnsupportedOperationException("方法返回值必须为Mono或者Flux");
+        }
+        Class<?> genericType = returnType.getGeneric(0).toClass();
+        Function<Map<String, Object>, ?> mapper;
+
+        if (genericType == Map.class || genericType == Object.class) {
+            mapper = Function.identity();
+        } else {
+            mapper = map -> FastBeanCopier.copy(map, genericType);
+        }
+
+        Function<Flux<?>, Publisher<?>> resultMapper =
+            returnType.resolve() == Mono.class
+                ? flux -> flux.take(1).singleOrEmpty()
+                : flux -> flux;
+
+        String[] names = nameDiscoverer.getParameterNames(method);
+
+        try {
+            org.jetlinks.reactor.ql.ReactorQL reactorQL =
+                org.jetlinks.reactor.ql.ReactorQL
+                    .builder()
+                    .sql(ql.value())
+                    .build();
+
+            return args -> {
+                Map<String, Object> argsMap = new HashMap<>();
+                ReactorQLContext context = ReactorQLContext.ofDatasource(name -> {
+                    if (args.length == 0) {
+                        return Flux.just(1);
+                    }
+                    if (args.length == 1) {
+                        return convertToFlux(args[0]);
+                    }
+                    return convertToFlux(argsMap.get(name));
+                });
+                for (int i = 0; i < args.length; i++) {
+                    String indexName = "arg" + i;
+
+                    String name = names == null ? indexName : names[i];
+                    context.bind(i, args[i]);
+                    context.bind(name, args[i]);
+                    context.bind(indexName, args[i]);
+                    argsMap.put(names == null ? indexName : names[i], args[i]);
+                    argsMap.put(indexName, args[i]);
+                }
+                return reactorQL.start(context)
+                                .map(record -> mapper.apply(record.asMap()))
+                                .as(resultMapper);
+            };
+        } catch (Throwable e) {
+            throw new IllegalArgumentException(
+                "create ReactorQL method [" + method + "] error,sql:\n" + (String.join(" ", ql.value())), e);
+        }
+    }
+
+    protected Flux<Object> convertToFlux(Object arg) {
+        if (arg == null) {
+            return Flux.empty();
+        }
+        if (arg instanceof Publisher) {
+            return Flux.from((Publisher<?>) arg);
+        }
+        if (arg instanceof Iterable) {
+            return Flux.fromIterable(((Iterable<?>) arg));
+        }
+        if (arg instanceof Object[]) {
+            return Flux.fromArray(((Object[]) arg));
+        }
+        return Flux.just(arg);
+    }
+
+    @Override
+    public int getOrder() {
+        return Ordered.LOWEST_PRECEDENCE;
+    }
+}

+ 22 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/ReactorQLOperation.java

@@ -0,0 +1,22 @@
+package org.jetlinks.community.reactorql;
+
+import org.springframework.stereotype.Indexed;
+
+import java.lang.annotation.*;
+
+/**
+ * 在接口上添加此注解,开启使用sql来处理reactor数据
+ *
+ * @author zhouhao
+ * @see ReactorQL
+ * @see EnableReactorQL
+ * @since 1.6
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@Indexed
+public @interface ReactorQLOperation {
+
+}

+ 170 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/term/FixedTermTypeSupport.java

@@ -0,0 +1,170 @@
+package org.jetlinks.community.reactorql.term;
+
+import lombok.Getter;
+import org.hswebframework.ezorm.core.param.Term;
+import org.hswebframework.ezorm.rdb.operator.builder.fragments.NativeSql;
+import org.hswebframework.ezorm.rdb.operator.builder.fragments.PrepareSqlFragments;
+import org.hswebframework.ezorm.rdb.operator.builder.fragments.SqlFragments;
+import org.hswebframework.web.i18n.LocaleUtils;
+import org.jetlinks.core.metadata.DataType;
+import org.jetlinks.core.metadata.types.*;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.jetlinks.community.reactorql.term.TermType.OPTIONS_NATIVE_SQL;
+
+@Getter
+public enum FixedTermTypeSupport implements TermTypeSupport {
+
+    eq("等于", "eq"),
+    neq("不等于", "neq"),
+
+    gt("大于", "gt", DateTimeType.ID, IntType.ID, LongType.ID, FloatType.ID, DoubleType.ID),
+    gte("大于等于", "gte", DateTimeType.ID, IntType.ID, LongType.ID, FloatType.ID, DoubleType.ID),
+    lt("小于", "lt", DateTimeType.ID, IntType.ID, LongType.ID, FloatType.ID, DoubleType.ID),
+    lte("小于等于", "lte", DateTimeType.ID, IntType.ID, LongType.ID, FloatType.ID, DoubleType.ID),
+
+    btw("在...之间", "btw", DateTimeType.ID, IntType.ID, LongType.ID, FloatType.ID, DoubleType.ID) {
+        @Override
+        protected Object convertValue(Object val, Term term) {
+            return val;
+        }
+    },
+    nbtw("不在...之间", "nbtw", DateTimeType.ID, IntType.ID, LongType.ID, FloatType.ID, DoubleType.ID) {
+        @Override
+        protected Object convertValue(Object val, Term term) {
+            return val;
+        }
+    },
+    in("在...之中", "in", StringType.ID, IntType.ID, LongType.ID, FloatType.ID, DoubleType.ID, EnumType.ID) {
+        @Override
+        protected Object convertValue(Object val, Term term) {
+            return val;
+        }
+    },
+    nin("不在...之中", "nin", StringType.ID, IntType.ID, LongType.ID, FloatType.ID, DoubleType.ID, EnumType.ID) {
+        @Override
+        protected Object convertValue(Object val, Term term) {
+            return val;
+        }
+    },
+    contains_all("全部包含在...之中", "contains_all", ArrayType.ID) {
+        @Override
+        protected Object convertValue(Object val, Term term) {
+            return val;
+        }
+    },
+    contains_any("任意包含在...之中", "contains_any", ArrayType.ID) {
+        @Override
+        protected Object convertValue(Object val, Term term) {
+            return val;
+        }
+    },
+    not_contains("不包含在...之中", "not_contains", ArrayType.ID) {
+        @Override
+        protected Object convertValue(Object val, Term term) {
+            return val;
+        }
+    },
+
+    like("包含字符", "str_like", StringType.ID) {
+        @Override
+        protected Object convertValue(Object val, Term term) {
+            if (val instanceof NativeSql) {
+                NativeSql sql = ((NativeSql) val);
+                return NativeSql.of("concat('%'," + sql.getSql() + ",'%')");
+            }
+            return super.convertValue(val, term);
+        }
+    },
+    nlike("不包含字符", "str_nlike", StringType.ID){
+        @Override
+        protected Object convertValue(Object val, Term term) {
+            if (val instanceof NativeSql) {
+                NativeSql sql = ((NativeSql) val);
+                return NativeSql.of("concat('%'," + sql.getSql() + ",'%')");
+            }
+            return super.convertValue(val, term);
+        }
+    },
+
+    // gt(math.sub(column,now()),?)
+    time_gt_now("距离当前时间大于...秒", "time_gt_now", DateTimeType.ID) {
+        @Override
+        protected void appendFunction(String column, PrepareSqlFragments fragments) {
+            fragments.addSql("gt(math.divi(math.sub(now(),", column, "),1000),");
+        }
+    },
+    time_lt_now("距离当前时间小于...秒", "time_lt_now", DateTimeType.ID) {
+        @Override
+        protected void appendFunction(String column, PrepareSqlFragments fragments) {
+            fragments.addSql("lt(math.divi(math.sub(now(),", column, "),1000),");
+        }
+    };
+
+    private final String text;
+    private final Set<String> supportTypes;
+
+    private final String function;
+
+    private FixedTermTypeSupport(String text, String function, String... supportTypes) {
+        this.text = text;
+        this.function = function;
+        this.supportTypes = new HashSet<>(Arrays.asList(supportTypes));
+    }
+
+    @Override
+    public boolean isSupported(DataType type) {
+        return supportTypes.isEmpty() || supportTypes.contains(type.getType());
+    }
+
+    protected Object convertValue(Object val, Term term) {
+        if (val instanceof Collection) {
+            //值为数组,则尝试获取第一个值
+            if (((Collection<?>) val).size() == 1) {
+                return ((Collection<?>) val).iterator().next();
+            }
+        }
+        return val;
+    }
+
+    protected void appendFunction(String column, PrepareSqlFragments fragments) {
+        fragments.addSql(function + "(", column, ",");
+    }
+
+    @Override
+    public SqlFragments createSql(String column, Object value, Term term) {
+        PrepareSqlFragments fragments = PrepareSqlFragments.of();
+        appendFunction(column, fragments);
+
+        if (term.getOptions().contains(OPTIONS_NATIVE_SQL)) {
+            value = NativeSql.of(String.valueOf(value));
+        }
+
+        value = convertValue(value, term);
+
+        if (value instanceof NativeSql) {
+            fragments
+                .addSql(((NativeSql) value).getSql())
+                .addParameter(((NativeSql) value).getParameters());
+        } else {
+            fragments.addSql("?")
+                .addParameter(value);
+        }
+        fragments.addSql(")");
+        return fragments;
+    }
+
+    @Override
+    public String getType() {
+        return name();
+    }
+
+    @Override
+    public String getName() {
+        return LocaleUtils.resolveMessage("message.term_type_" + name(), text);
+    }
+}

+ 19 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/term/TermType.java

@@ -0,0 +1,19 @@
+package org.jetlinks.community.reactorql.term;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+public class TermType {
+
+    public static final String OPTIONS_NATIVE_SQL = "native";
+
+    private String id;
+
+    private String name;
+}

+ 20 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/term/TermTypeSupport.java

@@ -0,0 +1,20 @@
+package org.jetlinks.community.reactorql.term;
+
+import org.hswebframework.ezorm.core.param.Term;
+import org.hswebframework.ezorm.rdb.operator.builder.fragments.SqlFragments;
+import org.jetlinks.core.metadata.DataType;
+
+public interface TermTypeSupport {
+
+    String getType();
+
+    String getName();
+
+    boolean isSupported(DataType type);
+
+    SqlFragments createSql(String column, Object value, Term term);
+
+    default TermType type() {
+        return TermType.of(getType(), getName());
+    }
+}

+ 40 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reactorql/term/TermTypes.java

@@ -0,0 +1,40 @@
+package org.jetlinks.community.reactorql.term;
+
+import org.jetlinks.core.metadata.DataType;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @see org.jetlinks.community.utils.ReactorUtils#createFilter(List)
+ */
+public class TermTypes {
+    private static final Map<String, TermTypeSupport> supports = new LinkedHashMap<>();
+
+    static {
+        for (FixedTermTypeSupport value : FixedTermTypeSupport.values()) {
+            register(value);
+        }
+    }
+
+    public static void register(TermTypeSupport support){
+        supports.put(support.getType(),support);
+    }
+
+    public static List<TermType> lookup(DataType dataType) {
+
+        return supports
+            .values()
+            .stream()
+            .filter(support -> support.isSupported(dataType))
+            .map(TermTypeSupport::type)
+            .collect(Collectors.toList());
+    }
+
+    public static Optional<TermTypeSupport> lookupSupport(String type) {
+        return Optional.ofNullable(supports.get(type));
+    }
+}

+ 26 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reference/DataReferenceInfo.java

@@ -0,0 +1,26 @@
+package org.jetlinks.community.reference;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+@Getter
+@Setter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+public class DataReferenceInfo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String dataId;
+
+    private String referenceType;
+
+    private String referenceId;
+
+    private String referenceName;
+
+}

+ 72 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reference/DataReferenceManager.java

@@ -0,0 +1,72 @@
+package org.jetlinks.community.reference;
+
+import org.apache.commons.collections4.CollectionUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * 数据引用管理器,用于获取、判断指定的数据是否被其他地方所引用.
+ * <p>
+ * 可以通过实现接口: {@link DataReferenceProvider}来定义数据引用者.
+ * <p>
+ * 使用场景: 在对一些数据进行删除操作时,可以通过判断引用关系来阻止删除.
+ * 比如: 删除网络组件时,如果网络组件已经被其他地方使用,则不能删除.
+ *
+ * @author zhouhao
+ * @see DataReferenceProvider
+ * @since 2.0
+ */
+public interface DataReferenceManager {
+
+    //数据类型: 设备接入网关
+    String TYPE_DEVICE_GATEWAY = "device-gateway";
+    //数据类型: 网络组件
+    String TYPE_NETWORK = "network";
+    //数据类型:关系配置
+    String TYPE_RELATION = "relation";
+    //数据类型:消息协议
+    String TYPE_PROTOCOL = "protocol";
+
+    /**
+     * 判断指定数据类型的数据是否已经被其他地方所引用
+     *
+     * @param dataType 数据类型
+     * @param dataId   数据ID
+     * @return 是否已经被引用
+     */
+    Mono<Boolean> isReferenced(String dataType, String dataId);
+
+    /**
+     * 获取指定类型数据的引用关系
+     *
+     * @param dataType 数据类型
+     * @param dataId   数据ID
+     * @return 引用信息
+     */
+    Flux<DataReferenceInfo> getReferences(String dataType, String dataId);
+
+    /**
+     * 获取指定类型数据的全部引用关系
+     *
+     * @param dataType 数据类型
+     * @return 引用信息
+     */
+    Flux<DataReferenceInfo> getReferences(String dataType);
+
+
+    /**
+     * 断言数据没有被引用,如果存在引用,则抛出异常: {@link DataReferencedException}
+     *
+     * @param dataType 数据类型
+     * @param dataId   数据ID
+     * @return void
+     */
+    default Mono<Void> assertNotReferenced(String dataType, String dataId) {
+        return this
+            .getReferences(dataType, dataId)
+            .collectList()
+            .filter(CollectionUtils::isNotEmpty)
+            .flatMap(list -> Mono.error(new DataReferencedException(dataType, dataId, list)));
+    }
+
+}

+ 36 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reference/DataReferenceProvider.java

@@ -0,0 +1,36 @@
+package org.jetlinks.community.reference;
+
+import org.jetlinks.community.strategy.Strategy;
+import reactor.core.publisher.Flux;
+
+/**
+ * 数据引用提供商,用于提供获取对应类型的引用信息
+ *
+ * @author zhouhao
+ * @since 2.0
+ */
+public interface DataReferenceProvider extends Strategy {
+
+    /**
+     * 引用的数据类型
+     *
+     * @see DataReferenceManager
+     */
+    @Override
+    String getId();
+
+    /**
+     * 获取数据引用信息
+     *
+     * @param dataId 数据ID
+     * @return 引用信息
+     */
+    Flux<DataReferenceInfo> getReference(String dataId);
+
+    /**
+     * 获取全部数据引用信息
+     *
+     * @return 引用信息
+     */
+    Flux<DataReferenceInfo> getReferences();
+}

+ 30 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reference/DataReferencedException.java

@@ -0,0 +1,30 @@
+package org.jetlinks.community.reference;
+
+import lombok.Getter;
+import org.hswebframework.web.exception.I18nSupportException;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import java.util.List;
+
+@Getter
+@ResponseStatus(HttpStatus.BAD_REQUEST)
+public class DataReferencedException extends I18nSupportException {
+
+    private final String dataType;
+    private final String dataId;
+
+    private final List<DataReferenceInfo> referenceList;
+
+
+    public DataReferencedException(String dataType,
+                                   String dataId,
+                                   List<DataReferenceInfo> referenceList) {
+        this.dataType = dataType;
+        this.dataId = dataId;
+        this.referenceList = referenceList;
+
+        super.setI18nCode("error.data.referenced");
+    }
+
+}

+ 26 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reference/DefaultDataReferenceManager.java

@@ -0,0 +1,26 @@
+package org.jetlinks.community.reference;
+
+import org.jetlinks.community.strategy.StaticStrategyManager;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class DefaultDataReferenceManager extends StaticStrategyManager<DataReferenceProvider> implements DataReferenceManager {
+
+    @Override
+    public Mono<Boolean> isReferenced(String dataType, String dataId) {
+        return this
+            .getReferences(dataType, dataId)
+            .hasElements();
+    }
+
+    @Override
+    public Flux<DataReferenceInfo> getReferences(String dataType, String dataId) {
+        return doWithFlux(dataType, provider -> provider.getReference(dataId));
+    }
+
+    @Override
+    public Flux<DataReferenceInfo> getReferences(String dataType) {
+        return doWithFlux(dataType, DataReferenceProvider::getReferences);
+    }
+}

+ 75 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/ClassPathJsonResourceProvider.java

@@ -0,0 +1,75 @@
+package org.jetlinks.community.resource;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.util.StreamUtils;
+import org.springframework.util.StringUtils;
+import reactor.core.publisher.Flux;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+@Slf4j
+public abstract class ClassPathJsonResourceProvider implements ResourceProvider {
+
+    @Getter
+    private final String type;
+    private final String filePath;
+    private static final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+    private List<Resource> cache;
+
+    public ClassPathJsonResourceProvider(String type, String filePath) {
+        this.type = type;
+        this.filePath = filePath;
+    }
+
+    @Override
+    public final Flux<Resource> getResources() {
+        return Flux.fromIterable(cache == null ? cache = read() : cache);
+    }
+
+    @Override
+    public final Flux<Resource> getResources(Collection<String> id) {
+        Set<String> filter = new HashSet<>(id);
+        return getResources()
+            .filter(res -> filter.contains(res.getId()));
+    }
+
+
+    private List<Resource> read() {
+        List<Resource> resources = new ArrayList<>();
+        try {
+            log.debug("start load {} resource [{}]", type, filePath);
+            for (org.springframework.core.io.Resource resource : resourcePatternResolver.getResources(filePath)) {
+                log.debug("loading {} resource {}", type, resource);
+                try (InputStream inputStream = resource.getInputStream()) {
+                    int index = 0;
+                    for (JSONObject json : JSON.parseArray(StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8), JSONObject.class)) {
+                        index++;
+                        String id = getResourceId(json);
+                        if (StringUtils.hasText(id)) {
+                            resources.add(SimpleResource.of(id, type, json.toJSONString()));
+                        } else {
+                            log.warn("{} resource [{}] id (index:{}) is empty : {}", type, resource, index, json);
+                        }
+                    }
+                } catch (Throwable err) {
+                    log.debug("load {} resource {} error", type, resource, err);
+                }
+            }
+        } catch (Throwable e) {
+            log.warn("load {} resource [{}] error", type, filePath, e);
+            return Collections.emptyList();
+        }
+        return resources;
+    }
+
+    protected String getResourceId(JSONObject data) {
+        return data.getString("id");
+    }
+}

+ 37 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/DefaultResourceManager.java

@@ -0,0 +1,37 @@
+package org.jetlinks.community.resource;
+
+import org.springframework.util.CollectionUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class DefaultResourceManager implements ResourceManager {
+    private final Map<String, ResourceProvider> providers = new ConcurrentHashMap<>();
+
+    public DefaultResourceManager() {
+    }
+
+    public void addProvider(ResourceProvider provider) {
+        providers.put(provider.getType(), provider);
+    }
+
+    @Override
+    public Flux<Resource> getResources(String type) {
+        return getResources(type, null);
+    }
+
+    @Override
+    public Flux<Resource> getResources(String type, Collection<String> id) {
+        return this
+            .getProvider(type)
+            .flatMapMany(provider -> CollectionUtils.isEmpty(id) ? provider.getResources() : provider.getResources(id));
+    }
+
+    private Mono<ResourceProvider> getProvider(String type) {
+        return Mono.justOrEmpty(providers.get(type));
+    }
+
+}

+ 16 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/Resource.java

@@ -0,0 +1,16 @@
+package org.jetlinks.community.resource;
+
+import java.lang.reflect.Type;
+
+public interface Resource {
+
+    String getId();
+
+    String getType();
+
+    <T> T as(Class<T> type);
+
+    <T> T as(Type type);
+
+    String asString();
+}

+ 13 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/ResourceManager.java

@@ -0,0 +1,13 @@
+package org.jetlinks.community.resource;
+
+import reactor.core.publisher.Flux;
+
+import java.util.Collection;
+
+public interface ResourceManager {
+
+    Flux<Resource> getResources(String type);
+
+    Flux<Resource> getResources(String type, Collection<String> id);
+
+}

+ 15 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/ResourceProvider.java

@@ -0,0 +1,15 @@
+package org.jetlinks.community.resource;
+
+import reactor.core.publisher.Flux;
+
+import java.util.Collection;
+
+public interface ResourceProvider {
+
+    String getType();
+
+    Flux<Resource> getResources();
+
+    Flux<Resource> getResources(Collection<String> id);
+
+}

+ 34 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/SimpleResource.java

@@ -0,0 +1,34 @@
+package org.jetlinks.community.resource;
+
+import com.alibaba.fastjson.JSON;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.lang.reflect.Type;
+
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+@Getter
+@Setter
+public class SimpleResource implements Resource {
+    private String id;
+    private String type;
+    private String resource;
+
+    @Override
+    public <T> T as(Class<T> type) {
+        return JSON.parseObject(resource, type);
+    }
+
+    @Override
+    public <T> T as(Type type) {
+        return JSON.parseObject(resource, type);
+    }
+
+    @Override
+    public String asString() {
+        return resource;
+    }
+}

+ 42 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/resource/initialize/PermissionResourceProvider.java

@@ -0,0 +1,42 @@
+package org.jetlinks.community.resource.initialize;
+
+import com.alibaba.fastjson.JSON;
+import org.hswebframework.web.authorization.define.AuthorizeDefinitionInitializedEvent;
+import org.hswebframework.web.authorization.define.MergedAuthorizeDefinition;
+import org.jetlinks.community.resource.Resource;
+import org.jetlinks.community.resource.ResourceProvider;
+import org.jetlinks.community.resource.SimpleResource;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+
+import java.util.Collection;
+
+public class PermissionResourceProvider implements ResourceProvider {
+    public static final String type = "permission";
+
+    private final MergedAuthorizeDefinition definition = new MergedAuthorizeDefinition();
+
+    @Override
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public Flux<Resource> getResources() {
+        return Flux
+            .fromIterable(definition.getResources())
+            .map(def -> SimpleResource.of(def.getId(), getType(), JSON.toJSONString(def)));
+    }
+
+    @Override
+    public Flux<Resource> getResources(Collection<String> id) {
+        return getResources()
+            .filter(resource -> id == null || id.contains(resource.getId()));
+    }
+
+    @EventListener
+    public void handleInitializedEvent(AuthorizeDefinitionInitializedEvent event) {
+        definition.merge(event.getAllDefinition());
+    }
+}

+ 51 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/strategy/StaticStrategyManager.java

@@ -0,0 +1,51 @@
+package org.jetlinks.community.strategy;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+public class StaticStrategyManager<S
+    extends Strategy>
+    implements StrategyManager<S> {
+
+    private final Map<String, Mono<S>> strategies = new ConcurrentHashMap<>();
+
+    public void addStrategy(S strategy) {
+        this.addStrategy(strategy.getId(), Mono.just(strategy));
+    }
+
+    public void addStrategy(String strategyId, Mono<S> providerMono) {
+        strategies.put(strategyId, providerMono);
+    }
+
+    @Override
+    public final Mono<S> getStrategy(String strategyId) {
+        return strategies.getOrDefault(strategyId, Mono.empty());
+    }
+
+    @Override
+    public final Flux<S> getStrategies() {
+        return Flux.concat(strategies.values());
+    }
+
+    protected final <T> Mono<T> doWithMono(String strategy, Function<S, Mono<T>> executor) {
+        return this
+            .getStrategy(strategy)
+            .switchIfEmpty(onStrategyNotFound(strategy))
+            .flatMap(executor);
+    }
+
+    protected final <T> Flux<T> doWithFlux(String strategy, Function<S, Flux<T>> executor) {
+        return this
+            .getStrategy(strategy)
+            .switchIfEmpty(onStrategyNotFound(strategy))
+            .flatMapMany(executor);
+    }
+
+    protected <T> Mono<T> onStrategyNotFound(String strategy){
+        return Mono.empty();
+    }
+}

+ 17 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/strategy/Strategy.java

@@ -0,0 +1,17 @@
+package org.jetlinks.community.strategy;
+
+/**
+ * 策略接口定义
+ *
+ * @author zhouhao
+ * @since 2.0
+ */
+public interface Strategy {
+
+    /**
+     * @return 策略ID
+     */
+    String getId();
+
+
+}

+ 12 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/strategy/StrategyManager.java

@@ -0,0 +1,12 @@
+package org.jetlinks.community.strategy;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public interface StrategyManager<S extends Strategy> {
+
+    Mono<S> getStrategy(String provider);
+
+    Flux<S> getStrategies();
+
+}

+ 123 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/topic/Topics.java

@@ -0,0 +1,123 @@
+package org.jetlinks.community.topic;
+
+import lombok.Generated;
+import org.jetlinks.core.utils.StringBuilderUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public interface Topics {
+
+    static String creator(String creatorId, String topic) {
+        return StringBuilderUtils.buildString(creatorId, topic, Topics::creator);
+    }
+
+    static void creator(String creatorId, String topic, StringBuilder builder) {
+        builder.append("/user/").append(creatorId);
+        if (topic.charAt(0) != '/') {
+            builder.append('/');
+        }
+        builder.append(topic);
+    }
+
+    /**
+     * 根据绑定信息构造topic
+     *
+     * @param bindings 绑定信息
+     * @param topic    topic
+     * @return topic
+     */
+    static List<String> bindings(List<Map<String, Object>> bindings, String topic) {
+        List<String> topics = new ArrayList<>(bindings.size());
+        for (Map<String, Object> binding : bindings) {
+            topics.add(binding(String.valueOf(binding.get("type")), String.valueOf(binding.get("id")), topic));
+        }
+        return topics;
+    }
+
+    static void binding(String type, String id, String topic, StringBuilder builder) {
+        builder.append('/')
+               .append(type)
+               .append('/')
+               .append(id);
+        if (topic.charAt(0) != '/') {
+            builder.append('/');
+        }
+        builder.append(topic);
+    }
+
+    static String binding(String type, String id, String topic) {
+        return StringBuilderUtils.buildString(type, id, topic, Topics::binding);
+    }
+
+    @Deprecated
+    static String tenantMember(String memberId, String topic) {
+        if (!topic.startsWith("/")) {
+            topic = "/" + topic;
+        }
+        return String.join("", "/member/", memberId, topic);
+    }
+
+    @Deprecated
+    static List<String> tenantMembers(List<String> members, String topic) {
+        return members
+            .stream()
+            .map(id -> tenantMember(id, topic))
+            .collect(Collectors.toList());
+    }
+
+    String allDeviceRegisterEvent = "/_sys/registry-device/*/register";
+    String allDeviceUnRegisterEvent = "/_sys/registry-device/*/unregister";
+    String allDeviceMetadataChangedEvent = "/_sys/registry-device/*/metadata";
+
+    @Generated
+    static String deviceRegisterEvent(String deviceId) {
+        return registryDeviceEvent(deviceId, "register");
+    }
+
+    @Generated
+    static String deviceUnRegisterEvent(String deviceId) {
+        return registryDeviceEvent(deviceId, "unregister");
+    }
+
+    @Generated
+    static String deviceMetadataChangedEvent(String deviceId) {
+        return registryDeviceEvent(deviceId, "metadata");
+    }
+
+    String allProductRegisterEvent = "/_sys/registry-product/*/register";
+    String allProductUnRegisterEvent = "/_sys/registry-product/*/unregister";
+    String allProductMetadataChangedEvent = "/_sys/registry-product/*/metadata";
+
+    @Generated
+    static String productRegisterEvent(String deviceId) {
+        return registryProductEvent(deviceId, "register");
+    }
+
+    @Generated
+    static String productUnRegisterEvent(String deviceId) {
+        return registryProductEvent(deviceId, "unregister");
+    }
+
+    @Generated
+    static String productMetadataChangedEvent(String deviceId) {
+        return registryProductEvent(deviceId, "metadata");
+    }
+
+
+    static String registryDeviceEvent(String deviceId, String event) {
+        return "/_sys/registry-device/" + deviceId + "/" + event;
+    }
+
+    static String registryProductEvent(String deviceId, String event) {
+        return "/_sys/registry-product/" + deviceId + "/" + event;
+    }
+
+    static String alarm(String targetType, String targetId, String alarmId) {
+        //  /alarm/{targetType}/{targetId}/{alarmId}/record
+        return String.join("", "/alarm/", targetType, "/", targetId, "/", alarmId, "/record");
+    }
+}

+ 209 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/utils/ReactorUtils.java

@@ -0,0 +1,209 @@
+package org.jetlinks.community.utils;
+
+import org.hswebframework.ezorm.core.param.Term;
+import org.hswebframework.ezorm.rdb.executor.SqlRequest;
+import org.hswebframework.ezorm.rdb.operator.builder.fragments.AbstractTermsFragmentBuilder;
+import org.hswebframework.ezorm.rdb.operator.builder.fragments.SqlFragments;
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.jetlinks.community.reactorql.term.FixedTermTypeSupport;
+import org.jetlinks.community.reactorql.term.TermTypeSupport;
+import org.jetlinks.community.reactorql.term.TermTypes;
+import org.jetlinks.core.metadata.Jsonable;
+import org.jetlinks.core.utils.FluxUtils;
+import org.jetlinks.core.utils.Reactors;
+import org.jetlinks.reactor.ql.ReactorQL;
+import org.jetlinks.reactor.ql.ReactorQLContext;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * 响应式相关工具类
+ *
+ * @author zhouhao
+ * @since 1.12
+ */
+public class ReactorUtils {
+
+    public static <T> Function<Flux<T>, Flux<T>> limit(Long pageIndex, Long pageSize) {
+        if (pageIndex == null || pageSize == null) {
+            return Function.identity();
+        }
+        return flux -> flux.skip(pageIndex & pageSize).take(pageSize);
+    }
+
+    /**
+     * 构造有效期内去重的Flux
+     *
+     * <pre>
+     *    flux.as(ReactorUtils.distinct(MyData::getId,Duration.ofSeconds(30)))
+     * </pre>
+     *
+     * @param keySelector 去重的key
+     * @param duration    有效期
+     * @param <T>         泛型
+     * @return 去重构造器
+     */
+    public static <T> Function<Flux<T>, Flux<T>> distinct(Function<T, ?> keySelector, Duration duration) {
+        return FluxUtils.distinct(keySelector, duration);
+    }
+
+
+    public static final Function<Object, Mono<Boolean>> alwaysTrue = ignore -> Reactors.ALWAYS_TRUE;
+
+
+    /**
+     * 使用 {@link Term}来构造一个异步过滤器,请缓存过滤器函数使用,不要每次构建.
+     * <p>
+     * 在判断时会尝试把对象转为Map,
+     *
+     * <pre>{@code
+     *
+     * flux
+     *  .filter(createFilter(TermExpressionParser.parse("age gt 1 and name like '%张%'")))
+     *  .flatMap(this::handleData)
+     *  ...
+     *
+     * }</pre>
+     *
+     * @param terms 条件对象
+     * @param <T>   对象泛型
+     * @return 过滤器函数
+     */
+    @SuppressWarnings("all")
+    public static <T> Function<T, Mono<Boolean>> createFilter(List<Term> filter) {
+        return createFilter(filter, t -> {
+            if (t instanceof Map) {
+                return ((Map<String, Object>) t);
+            }
+            if (t instanceof Jsonable) {
+                return ((Jsonable) t).toJson();
+            }
+            return FastBeanCopier.copy(t, new HashMap<>());
+        });
+    }
+
+    /**
+     * 使用 {@link Term}来构造一个异步过滤器,请缓存过滤器函数使用,不要每次构建
+     *
+     * <pre>{@code
+     *
+     * flux
+     *  .filter(createFilter(TermExpressionParser.parse("age gt 1 and name like '%张%'"),Data::toMap))
+     *  .flatMap(this::handleData)
+     *  ...
+     *
+     * }</pre>
+     *
+     * @param terms     条件对象
+     * @param converter 转换器,用于将对象转为map,更有利于进行条件判断
+     * @param <T>       对象泛型
+     * @return 过滤器函数
+     */
+    @SuppressWarnings("all")
+    public static <T> Function<T, Mono<Boolean>> createFilter(List<Term> terms,
+                                                              Function<T, Map<String, Object>> converter) {
+
+        return createFilter(terms, converter, (arg, data) -> arg);
+    }
+
+    @SuppressWarnings("all")
+    public static <T> Function<T, Mono<Boolean>> createFilter(List<Term> terms,
+                                                              Function<T, Map<String, Object>> converter,
+                                                              BiFunction<Object, Map<String, Object>, Object> bindConverter) {
+        if (CollectionUtils.isEmpty(terms)) {
+            return (Function<T, Mono<Boolean>>) alwaysTrue;
+        }
+
+        SqlFragments fragments = termBuilder.createTermFragments(null, terms);
+        if (fragments.isEmpty()) {
+            return (Function<T, Mono<Boolean>>) alwaysTrue;
+        }
+
+        SqlRequest request = fragments.toRequest();
+
+        String sql = "select 1 from t where " + request.getSql();
+        String nativeSql = request.toNativeSql();
+        try {
+            ReactorQL ql = ReactorQL.builder().sql(sql).build();
+            Object[] parameters = request.getParameters();
+            return new Function<T, Mono<Boolean>>() {
+                @Override
+                public String toString() {
+                    return nativeSql;
+                }
+
+                @Override
+                public Mono<Boolean> apply(T data) {
+                    Map<String, Object> mapValue = converter.apply(data);
+                    ReactorQLContext context = ReactorQLContext.ofDatasource(ignore -> Flux.just(mapValue));
+                    for (Object parameter : parameters) {
+                        context.bind(bindConverter.apply(parameter, mapValue));
+                    }
+                    return ql
+                        .start(context)
+                        .hasElements();
+                }
+            };
+        } catch (Throwable e) {
+            throw new IllegalArgumentException("error.create_connector_filter_error", e);
+        }
+    }
+
+    static final TermBuilder termBuilder = new TermBuilder();
+
+    static class TermBuilder extends AbstractTermsFragmentBuilder<Object> {
+
+        @Override
+        public SqlFragments createTermFragments(Object parameter, List<Term> terms) {
+            return super.createTermFragments(parameter, terms);
+        }
+
+        @Override
+        protected SqlFragments createTermFragments(Object trigger, Term term) {
+            String termType = StringUtils.hasText(term.getTermType()) ? term.getTermType() : "eq";
+            switch (termType) {
+                case "is":
+                case "=":
+                    termType = FixedTermTypeSupport.eq.name();
+                    break;
+                case ">":
+                    termType = FixedTermTypeSupport.gt.name();
+                    break;
+                case ">=":
+                    termType = FixedTermTypeSupport.gte.name();
+                    break;
+                case "<":
+                    termType = FixedTermTypeSupport.lt.getName();
+                    break;
+                case "<=":
+                    termType = FixedTermTypeSupport.lte.getName();
+                    break;
+                case "!=":
+                case "<>":
+                    termType = FixedTermTypeSupport.neq.getName();
+                    break;
+            }
+
+            TermTypeSupport support = TermTypes.lookupSupport(termType).orElse(null);
+            if (support == null) {
+                throw new UnsupportedOperationException("unsupported termType " + term.getTermType());
+            }
+            String column = term.getColumn();
+            if (!column.contains("[") && !column.contains("'")) {
+                column = "this['" + column + "']";
+            }
+            return support.createSql(column, term.getValue(), term);
+
+        }
+    }
+
+}

+ 12 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/web/ErrorControllerAdvice.java

@@ -6,6 +6,8 @@ import org.hswebframework.ezorm.rdb.exception.DuplicateKeyException;
 import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
 import org.hswebframework.web.crud.web.ResponseMessage;
 import org.hswebframework.web.i18n.LocaleUtils;
+import org.jetlinks.community.reference.DataReferenceInfo;
+import org.jetlinks.community.reference.DataReferencedException;
 import org.jetlinks.core.enums.ErrorCode;
 import org.jetlinks.core.exception.DeviceOperationException;
 import org.springframework.core.Ordered;
@@ -102,5 +104,15 @@ public class ErrorControllerAdvice {
                 .body(ResponseMessage.error(status.value(), e.getCode().name().toLowerCase(), msg))
             );
     }
+    @ExceptionHandler
+    public Mono<ResponseMessage<List<DataReferenceInfo>>> handleException(DataReferencedException e) {
+        return e
+            .getLocalizedMessageReactive()
+            .map(msg -> {
+                return ResponseMessage
+                    .<List<DataReferenceInfo>>error(400,"error.data.referenced", msg)
+                    .result(e.getReferenceList());
+            });
+    }
 
 }

+ 37 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/web/SystemResourcesController.java

@@ -0,0 +1,37 @@
+package org.jetlinks.community.web;
+
+import io.swagger.v3.oas.annotations.Hidden;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.authorization.exception.UnAuthorizedException;
+import org.jetlinks.community.resource.Resource;
+import org.jetlinks.community.resource.ResourceManager;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+@RestController
+@RequestMapping("/system/resources")
+@Hidden
+@AllArgsConstructor
+public class SystemResourcesController {
+
+    private final ResourceManager resourceManager;
+
+    @GetMapping("/{type}")
+    @SneakyThrows
+    public Flux<String> getResources(@PathVariable String type) {
+        return Authentication
+            .currentReactive()
+            .filter(auth -> "admin".equals(auth.getUser().getUsername()))
+            .switchIfEmpty(Mono.error(UnAuthorizedException::new))
+            .flatMapMany(auth -> resourceManager.getResources(type))
+            .map(Resource::asString);
+    }
+
+
+}

+ 36 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/web/response/ValidationResult.java

@@ -0,0 +1,36 @@
+package org.jetlinks.community.web.response;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author bestfeng
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+public class ValidationResult {
+
+    private boolean passed;
+
+    private String reason;
+
+
+    public static ValidationResult error(String reason) {
+        return new ValidationResult(false, reason);
+    }
+
+    public static ValidationResult success(String reason) {
+        return new ValidationResult(true, reason);
+    }
+
+    public static ValidationResult success() {
+        return ValidationResult.success("");
+    }
+
+    public static ValidationResult of(boolean passed, String reason) {
+        return new ValidationResult(passed, reason);
+    }
+
+}

+ 2 - 1
jetlinks-components/common-component/src/main/resources/i18n/common-component/messages_en.properties

@@ -1,3 +1,4 @@
 message.device_message_handing=Message sent to device, processing...
 
-error.duplicate_key_detail=Duplicate Data:{0}
+error.duplicate_key_detail=Duplicate Data:{0}
+error.data.referenced=The data has been used elsewhere

+ 1 - 1
jetlinks-components/common-component/src/main/resources/i18n/common-component/messages_zh.properties

@@ -1,3 +1,3 @@
 message.device_message_handing=消息已发往设备,处理中...
-
+error.data.referenced=数据已经被其他地方使用
 error.duplicate_key_detail=重复的数据:{0}

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

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

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

@@ -2,9 +2,6 @@ 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;
@@ -15,11 +12,8 @@ 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;
@@ -75,11 +69,6 @@ public class ClusterConfiguration {
         return impl;
     }
 
-    @Bean
-    public EventBroker eventBroker(ExtendedCluster cluster) {
-        return new ScalecubeEventBusBroker(cluster);
-    }
-
     @Bean
     public BrokerEventBus eventBus(ObjectProvider<EventBroker> provider,
                                    ObjectProvider<Scheduler> scheduler) {

+ 67 - 0
jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/redis/ObjectRedisSerializer.java

@@ -0,0 +1,67 @@
+package org.jetlinks.community.configure.redis;
+
+import io.netty.util.concurrent.FastThreadLocal;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.core.utils.SerializeUtils;
+import org.jetlinks.community.codec.Serializers;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+@Slf4j
+@AllArgsConstructor
+public class ObjectRedisSerializer implements RedisSerializer<Object> {
+
+    static final FastThreadLocal<ByteArrayOutputStream> STREAM_LOCAL = new FastThreadLocal<ByteArrayOutputStream>() {
+        @Override
+        protected ByteArrayOutputStream initialValue() {
+            return new ByteArrayOutputStream(1024) {
+                @Override
+                public void close() {
+                    reset();
+                }
+            };
+        }
+    };
+
+    @Override
+    @SneakyThrows
+    public byte[] serialize(Object o) throws SerializationException {
+        if (o == null) {
+            return null;
+        }
+        ByteArrayOutputStream arr = STREAM_LOCAL.get();
+        try (ObjectOutput output = Serializers.getDefault().createOutput(arr)) {
+
+            SerializeUtils.writeObject(o, output);
+            output.flush();
+
+            return arr.toByteArray();
+        } catch (Throwable e) {
+            log.error(e.getMessage(), e);
+            throw e;
+        }
+    }
+
+    @Override
+    @SneakyThrows
+    public Object deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null) {
+            return null;
+        }
+        try (ObjectInput input = Serializers
+            .getDefault()
+            .createInput(new ByteArrayInputStream(bytes))) {
+            return SerializeUtils.readObject(input);
+        } catch (Throwable e) {
+            log.error(e.getMessage(), e);
+            throw e;
+        }
+    }
+}

+ 8 - 16
jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/JetLinksRedisConfiguration.java

@@ -1,11 +1,9 @@
-package org.jetlinks.community.standalone.configuration;
+package org.jetlinks.community.configure.redis;
 
-import org.jetlinks.community.standalone.configuration.fst.FstSerializationRedisSerializer;
-import org.nustaq.serialization.FSTConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ResourceLoader;
+import org.springframework.context.annotation.Primary;
 import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
 import org.springframework.data.redis.core.ReactiveRedisTemplate;
 import org.springframework.data.redis.serializer.RedisSerializationContext;
@@ -13,23 +11,17 @@ import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
 @Configuration
-@ConditionalOnProperty(prefix = "spring.redis",name = "serializer",havingValue = "fst")
-public class JetLinksRedisConfiguration {
+@ConditionalOnProperty(prefix = "spring.redis",name = "serializer",havingValue = "obj",matchIfMissing = true)
+public class RedisSerializationConfiguration {
 
     @Bean
-    public ReactiveRedisTemplate<Object, Object> reactiveRedisTemplate(
-        ReactiveRedisConnectionFactory reactiveRedisConnectionFactory, ResourceLoader resourceLoader) {
-
-        FstSerializationRedisSerializer serializer = new FstSerializationRedisSerializer(() -> {
-            FSTConfiguration configuration = FSTConfiguration.createDefaultConfiguration()
-                .setForceSerializable(true);
-            configuration.setClassLoader(resourceLoader.getClassLoader());
-            return configuration;
-        });
+    @Primary
+    public ReactiveRedisTemplate<Object, Object> reactiveRedisTemplate(ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
+        ObjectRedisSerializer serializer = new ObjectRedisSerializer();
         @SuppressWarnings("all")
         RedisSerializationContext<Object, Object> serializationContext = RedisSerializationContext
             .newSerializationContext()
-            .key((RedisSerializer)new StringRedisSerializer())
+            .key((RedisSerializer) StringRedisSerializer.UTF_8)
             .value(serializer)
             .hashKey(StringRedisSerializer.UTF_8)
             .hashValue(serializer)

+ 0 - 5
jetlinks-components/configure-component/src/main/resources/META-INF/spring.factories

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

+ 5 - 0
jetlinks-components/configure-component/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,5 @@
+org.jetlinks.community.configure.cluster.ClusterConfiguration
+org.jetlinks.community.configure.doc.SpringDocCustomizerConfiguration
+org.jetlinks.community.configure.trace.TraceConfiguration
+org.jetlinks.community.configure.device.DeviceClusterConfiguration
+org.jetlinks.community.configure.redis.RedisSerializationConfiguration

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.20.0-SNAPSHOT</version>
+        <version>2.0.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -29,6 +29,11 @@
             <version>${project.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>com.github.oshi</groupId>
+            <artifactId>oshi-core</artifactId>
+            <version>6.2.2</version>
+        </dependency>
 
     </dependencies>
 

+ 0 - 81
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/JvmCpuMeasurementProvider.java

@@ -1,81 +0,0 @@
-package org.jetlinks.community.dashboard.measurements;
-
-import org.hswebframework.utils.time.DateFormatter;
-import org.jetlinks.community.dashboard.*;
-import org.jetlinks.community.dashboard.supports.StaticMeasurement;
-import org.jetlinks.community.dashboard.supports.StaticMeasurementProvider;
-import org.jetlinks.core.metadata.ConfigMetadata;
-import org.jetlinks.core.metadata.DataType;
-import org.jetlinks.core.metadata.types.DoubleType;
-import org.jetlinks.core.metadata.unit.UnifyUnit;
-import org.springframework.stereotype.Component;
-import reactor.core.publisher.Flux;
-
-import java.math.BigDecimal;
-import java.time.Duration;
-import java.util.Date;
-
-import static java.math.BigDecimal.ROUND_HALF_UP;
-
-/**
- * 实时CPU 使用率监控
- * <pre>
- *     /dashboard/systemMonitor/cpu/usage/realTime
- * </pre>
- *
- * @author zhouhao
- */
-@Component
-public class JvmCpuMeasurementProvider
-    extends StaticMeasurementProvider {
-
-    public JvmCpuMeasurementProvider() {
-        super(DefaultDashboardDefinition.jvmMonitor, MonitorObjectDefinition.cpu);
-        addMeasurement(cpuUseAgeMeasurement);
-    }
-
-    static DataType type = new DoubleType().scale(1).min(0).max(100).unit(UnifyUnit.percent);
-
-    static StaticMeasurement cpuUseAgeMeasurement = new StaticMeasurement(CommonMeasurementDefinition.usage)
-        .addDimension(new CpuRealTimeMeasurementDimension());
-
-
-    static class CpuRealTimeMeasurementDimension implements MeasurementDimension {
-
-        @Override
-        public DimensionDefinition getDefinition() {
-            return CommonDimensionDefinition.realTime;
-        }
-
-        @Override
-        public DataType getValueType() {
-            return type;
-        }
-
-        @Override
-        public ConfigMetadata getParams() {
-            return null;
-        }
-
-        @Override
-        public boolean isRealTime() {
-            return true;
-        }
-
-        @Override
-        public Flux<MeasurementValue> getValue(MeasurementParameter parameter) {
-            //每秒获取系统CPU使用率
-            return Flux.concat(
-                Flux.just(1),
-                Flux.interval(Duration.ofSeconds(1)))
-                .map(t -> SimpleMeasurementValue.of(BigDecimal
-                        .valueOf(SystemMonitor.jvmCpuUsage.getValue())
-                        .setScale(1, ROUND_HALF_UP),
-                    DateFormatter.toString(new Date(), "HH:mm:ss"),
-                    System.currentTimeMillis()))
-                .cast(MeasurementValue.class);
-        }
-
-    }
-
-}

+ 0 - 132
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/JvmMemoryMeasurementProvider.java

@@ -1,132 +0,0 @@
-package org.jetlinks.community.dashboard.measurements;
-
-import lombok.Getter;
-import lombok.Setter;
-import org.hswebframework.utils.time.DateFormatter;
-import org.jetlinks.community.dashboard.*;
-import org.jetlinks.community.dashboard.supports.StaticMeasurement;
-import org.jetlinks.community.dashboard.supports.StaticMeasurementProvider;
-import org.jetlinks.core.metadata.ConfigMetadata;
-import org.jetlinks.core.metadata.DataType;
-import org.jetlinks.core.metadata.SimplePropertyMetadata;
-import org.jetlinks.core.metadata.types.DoubleType;
-import org.jetlinks.core.metadata.types.LongType;
-import org.jetlinks.core.metadata.types.ObjectType;
-import org.springframework.stereotype.Component;
-import reactor.core.publisher.Flux;
-
-import java.lang.management.ManagementFactory;
-import java.lang.management.MemoryMXBean;
-import java.lang.management.MemoryUsage;
-import java.math.BigDecimal;
-import java.time.Duration;
-import java.util.Date;
-
-import static java.math.BigDecimal.ROUND_HALF_UP;
-
-/**
- * 实时内存使用率监控
- * <pre>
- *     /dashboard/jvmMonitor/memory/info/realTime
- * </pre>
- *
- * @author zhouhao
- */
-@Component
-public class JvmMemoryMeasurementProvider extends StaticMeasurementProvider {
-    public JvmMemoryMeasurementProvider() {
-        super(DefaultDashboardDefinition.jvmMonitor, MonitorObjectDefinition.memory);
-        addMeasurement(jvmMemoryInfo);
-    }
-
-    static ObjectType type = new ObjectType();
-
-    static {
-        {
-            SimplePropertyMetadata metadata = new SimplePropertyMetadata();
-            metadata.setId("max");
-            metadata.setName("最大值");
-            metadata.setValueType(new LongType());
-            type.addPropertyMetadata(metadata);
-        }
-
-        {
-            SimplePropertyMetadata metadata = new SimplePropertyMetadata();
-            metadata.setId("used");
-            metadata.setName("已使用");
-            metadata.setValueType(new LongType());
-            type.addPropertyMetadata(metadata);
-        }
-
-        {
-            SimplePropertyMetadata metadata = new SimplePropertyMetadata();
-            metadata.setId("usage");
-            metadata.setName("使用率");
-            metadata.setValueType(new DoubleType());
-            type.addPropertyMetadata(metadata);
-        }
-
-    }
-
-    static StaticMeasurement jvmMemoryInfo = new StaticMeasurement(CommonMeasurementDefinition.info)
-        .addDimension(new JvmMemoryInfoDimension());
-
-    static class JvmMemoryInfoDimension implements MeasurementDimension {
-
-        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
-
-        @Override
-        public DimensionDefinition getDefinition() {
-            return CommonDimensionDefinition.realTime;
-        }
-
-        @Override
-        public DataType getValueType() {
-            return type;
-        }
-
-        @Override
-        public ConfigMetadata getParams() {
-            return null;
-        }
-
-        @Override
-        public boolean isRealTime() {
-            return true;
-        }
-
-        @Override
-        public Flux<MeasurementValue> getValue(MeasurementParameter parameter) {
-            return Flux.concat(
-                Flux.just(MemoryInfo.of(memoryMXBean.getHeapMemoryUsage())),
-                Flux.interval(Duration.ofSeconds(1))
-                    .map(t -> MemoryInfo.of(memoryMXBean.getHeapMemoryUsage()))
-                    .windowUntilChanged(MemoryInfo::getUsage)
-                    .flatMap(Flux::last))
-                .map(val -> SimpleMeasurementValue.of(val,
-                    DateFormatter.toString(new Date(), "HH:mm:ss"),
-                    System.currentTimeMillis()))
-                .cast(MeasurementValue.class);
-        }
-
-    }
-
-    @Getter
-    @Setter
-    public static class MemoryInfo {
-        private long max;
-
-        private long used;
-
-        private double usage;
-
-        public static MemoryInfo of(MemoryUsage usage) {
-            MemoryInfo info = new MemoryInfo();
-            info.max = (usage.getMax()) / 1000 / 1000;
-            info.used = usage.getUsed() / 1000 / 1000;
-            info.usage = BigDecimal.valueOf(((double) usage.getUsed() / usage.getMax()) * 100D).setScale(2, ROUND_HALF_UP)
-                .doubleValue();
-            return info;
-        }
-    }
-}

+ 3 - 2
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/MonitorObjectDefinition.java

@@ -9,9 +9,10 @@ import org.jetlinks.community.dashboard.ObjectDefinition;
 public enum MonitorObjectDefinition implements ObjectDefinition {
 
     cpu("CPU"),
-    memory("内存");
+    memory("内存"),
+    stats("运行状态");
 
-    private String name;
+    private final String name;
 
     @Override
     public String getId() {

+ 0 - 79
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/SystemCpuMeasurementProvider.java

@@ -1,79 +0,0 @@
-package org.jetlinks.community.dashboard.measurements;
-
-import org.hswebframework.utils.time.DateFormatter;
-import org.jetlinks.community.dashboard.*;
-import org.jetlinks.community.dashboard.supports.StaticMeasurement;
-import org.jetlinks.community.dashboard.supports.StaticMeasurementProvider;
-import org.jetlinks.core.metadata.ConfigMetadata;
-import org.jetlinks.core.metadata.DataType;
-import org.jetlinks.core.metadata.types.DoubleType;
-import org.jetlinks.core.metadata.unit.UnifyUnit;
-import org.springframework.stereotype.Component;
-import reactor.core.publisher.Flux;
-
-import java.math.BigDecimal;
-import java.time.Duration;
-import java.util.Date;
-
-import static java.math.BigDecimal.ROUND_HALF_UP;
-
-/**
- * 实时CPU 使用率监控
- * <pre>
- *     /dashboard/systemMonitor/cpu/usage/realTime
- * </pre>
- *
- * @author zhouhao
- */
-@Component
-public class SystemCpuMeasurementProvider
-    extends StaticMeasurementProvider {
-
-    public SystemCpuMeasurementProvider() {
-        super(DefaultDashboardDefinition.systemMonitor, MonitorObjectDefinition.cpu);
-        addMeasurement(cpuUseAgeMeasurement);
-    }
-
-    static DataType type = new DoubleType().scale(1).min(0).max(100).unit(UnifyUnit.percent);
-
-    static StaticMeasurement cpuUseAgeMeasurement = new StaticMeasurement(CommonMeasurementDefinition.usage)
-        .addDimension(new CpuRealTimeMeasurementDimension());
-
-
-    static class CpuRealTimeMeasurementDimension implements MeasurementDimension {
-
-        @Override
-        public DimensionDefinition getDefinition() {
-            return CommonDimensionDefinition.realTime;
-        }
-
-        @Override
-        public DataType getValueType() {
-            return type;
-        }
-
-        @Override
-        public ConfigMetadata getParams() {
-            return null;
-        }
-
-        @Override
-        public boolean isRealTime() {
-            return true;
-        }
-
-        @Override
-        public Flux<MeasurementValue> getValue(MeasurementParameter parameter) {
-            //每秒获取系统CPU使用率
-            return Flux.interval(Duration.ofSeconds(1))
-                .map(t -> SimpleMeasurementValue.of(BigDecimal
-                        .valueOf(SystemMonitor.systemCpuUsage.getValue())
-                        .setScale(1, ROUND_HALF_UP),
-                    DateFormatter.toString(new Date(), "HH:mm:ss"),
-                    System.currentTimeMillis()))
-                .cast(MeasurementValue.class);
-        }
-
-    }
-
-}

+ 0 - 128
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/SystemMemoryMeasurementProvider.java

@@ -1,128 +0,0 @@
-package org.jetlinks.community.dashboard.measurements;
-
-import lombok.Getter;
-import lombok.Setter;
-import org.hswebframework.utils.time.DateFormatter;
-import org.jetlinks.community.dashboard.*;
-import org.jetlinks.community.dashboard.supports.StaticMeasurement;
-import org.jetlinks.community.dashboard.supports.StaticMeasurementProvider;
-import org.jetlinks.core.metadata.ConfigMetadata;
-import org.jetlinks.core.metadata.DataType;
-import org.jetlinks.core.metadata.SimplePropertyMetadata;
-import org.jetlinks.core.metadata.types.DoubleType;
-import org.jetlinks.core.metadata.types.LongType;
-import org.jetlinks.core.metadata.types.ObjectType;
-import org.springframework.stereotype.Component;
-import reactor.core.publisher.Flux;
-
-import java.math.BigDecimal;
-import java.time.Duration;
-import java.util.Date;
-
-import static java.math.BigDecimal.ROUND_HALF_UP;
-
-/**
- * 实时内存使用率监控
- * <pre>
- *     /dashboard/systemMonitor/memory/info/realTime
- * </pre>
- *
- * @author zhouhao
- */
-@Component
-public class SystemMemoryMeasurementProvider extends StaticMeasurementProvider {
-    public SystemMemoryMeasurementProvider() {
-        super(DefaultDashboardDefinition.systemMonitor, MonitorObjectDefinition.memory);
-        addMeasurement(systemMemoryInfo);
-    }
-
-    static ObjectType type = new ObjectType();
-
-    static {
-        {
-            SimplePropertyMetadata metadata = new SimplePropertyMetadata();
-            metadata.setId("max");
-            metadata.setName("最大值");
-            metadata.setValueType(new LongType());
-            type.addPropertyMetadata(metadata);
-        }
-
-        {
-            SimplePropertyMetadata metadata = new SimplePropertyMetadata();
-            metadata.setId("used");
-            metadata.setName("已使用");
-            metadata.setValueType(new LongType());
-            type.addPropertyMetadata(metadata);
-        }
-
-        {
-            SimplePropertyMetadata metadata = new SimplePropertyMetadata();
-            metadata.setId("usage");
-            metadata.setName("使用率");
-            metadata.setValueType(new DoubleType());
-            type.addPropertyMetadata(metadata);
-        }
-
-    }
-
-    static StaticMeasurement systemMemoryInfo = new StaticMeasurement(CommonMeasurementDefinition.info)
-        .addDimension(new JvmMemoryInfoDimension());
-
-    static class JvmMemoryInfoDimension implements MeasurementDimension {
-
-        @Override
-        public DimensionDefinition getDefinition() {
-            return CommonDimensionDefinition.realTime;
-        }
-
-        @Override
-        public DataType getValueType() {
-            return type;
-        }
-
-        @Override
-        public ConfigMetadata getParams() {
-            return null;
-        }
-
-        @Override
-        public boolean isRealTime() {
-            return true;
-        }
-
-        @Override
-        public Flux<MeasurementValue> getValue(MeasurementParameter parameter) {
-            return Flux.concat(
-                Flux.just(MemoryInfo.of()),
-                Flux.interval(Duration.ofSeconds(1))
-                    .map(t -> MemoryInfo.of())
-                    .windowUntilChanged(MemoryInfo::getUsage)
-                    .flatMap(Flux::last))
-                .map(val -> SimpleMeasurementValue.of(val,
-                    DateFormatter.toString(new Date(), "HH:mm:ss"),
-                    System.currentTimeMillis()))
-                .cast(MeasurementValue.class);
-        }
-
-    }
-
-    @Getter
-    @Setter
-    public static class MemoryInfo {
-        private long max;
-
-        private long used;
-
-        private double usage;
-
-        public static MemoryInfo of() {
-            MemoryInfo info = new MemoryInfo();
-            long total = (long) SystemMonitor.totalSystemMemory.getValue();
-
-            info.max = total;
-            info.used = (long) (total-  SystemMonitor.freeSystemMemory.getValue());
-            info.usage = BigDecimal.valueOf(((double) info.getUsed() / info.getMax()) * 100D).setScale(2, ROUND_HALF_UP).doubleValue();
-            return info;
-        }
-    }
-}

+ 18 - 7
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/SystemMonitor.java

@@ -3,6 +3,8 @@ package org.jetlinks.community.dashboard.measurements;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.SneakyThrows;
+import oshi.SystemInfo;
+import oshi.hardware.GlobalMemory;
 
 import java.lang.management.ManagementFactory;
 import java.lang.management.OperatingSystemMXBean;
@@ -25,21 +27,22 @@ public enum SystemMonitor {
     maxOpenFileCount("最大打开文件数"),
     ;
 
-    private String text;
+    private final String text;
 
     public double getValue() {
         return getValue(name());
     }
 
-    static OperatingSystemMXBean osMxBean = ManagementFactory.getOperatingSystemMXBean();
-    private static Map<String, Callable<Double>> items = new HashMap<>();
+    static final OperatingSystemMXBean osMxBean = ManagementFactory.getOperatingSystemMXBean();
+
+    private final static Map<String, Callable<Double>> items = new HashMap<>();
 
     private static final List<String> OPERATING_SYSTEM_BEAN_CLASS_NAMES = Arrays.asList(
         "com.sun.management.OperatingSystemMXBean", // HotSpot
         "com.ibm.lang.management.OperatingSystemMXBean" // J9
     );
 
-    private static Callable<Double> zero = () -> 0D;
+    private static final Callable<Double> zero = () -> 0D;
 
     private static Class<?> mxBeanClass;
 
@@ -47,7 +50,7 @@ public enum SystemMonitor {
         try {
             Method method = mxBeanClass.getMethod(methodName);
             items.put(item, () -> mapping.apply(((Number) method.invoke(osMxBean)).doubleValue()));
-        } catch (Exception e) {
+        } catch (Exception ignore) {
 
         }
     }
@@ -59,12 +62,17 @@ public enum SystemMonitor {
             } catch (Exception ignore) {
             }
         }
+        GlobalMemory memory = new oshi.SystemInfo()
+            .getHardware()
+            .getMemory();
+        {
+            items.put(freeSystemMemory.name(),()->memory.getAvailable()/ 1024 / 1024D);
+            items.put(totalSystemMemory.name(),()->memory.getTotal()/ 1024 / 1024D);
+        }
         try {
             if (mxBeanClass != null) {
                 register(systemCpuUsage.name(), "getSystemCpuLoad", usage -> usage * 100D);
                 register(jvmCpuUsage.name(), "getProcessCpuLoad", usage -> usage * 100D);
-                register(freeSystemMemory.name(), "getFreePhysicalMemorySize", val -> val / 1024 / 1024);
-                register(totalSystemMemory.name(), "getTotalPhysicalMemorySize", val -> val / 1024 / 1024);
                 register("virtualMemory", "getCommittedVirtualMemorySize", val -> val / 1024 / 1024);
                 register(openFileCount.name(), "getOpenFileDescriptorCount", Function.identity());
                 register(maxOpenFileCount.name(), "getMaxFileDescriptorCount", Function.identity());
@@ -74,6 +82,9 @@ public enum SystemMonitor {
         }
     }
 
+    public double value() {
+        return SystemMonitor.getValue(this.name());
+    }
 
     @SneakyThrows
     public static double getValue(String id) {

+ 36 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/CpuInfo.java

@@ -0,0 +1,36 @@
+package org.jetlinks.community.dashboard.measurements.sys;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+@Getter
+@Setter
+@ToString
+@AllArgsConstructor
+@NoArgsConstructor
+public class CpuInfo implements MonitorInfo<CpuInfo> {
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "JVM进程CPU使用率,0-100")
+    private float jvmUsage;
+
+    @Schema(description = "系统CPU使用率,0-100")
+    private float systemUsage;
+
+    @Override
+    public CpuInfo add(CpuInfo info) {
+        return new CpuInfo(
+            MonitorUtils.round(info.jvmUsage + this.jvmUsage),
+            MonitorUtils.round(info.systemUsage + this.systemUsage)
+        );
+    }
+
+    @Override
+    public CpuInfo division(int num) {
+        return new CpuInfo(
+            MonitorUtils.round(this.jvmUsage / num),
+            MonitorUtils.round(this.systemUsage / num)
+        );
+    }
+
+}

+ 41 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/DiskInfo.java

@@ -0,0 +1,41 @@
+package org.jetlinks.community.dashboard.measurements.sys;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+@Getter
+@Setter
+@ToString
+@AllArgsConstructor
+@NoArgsConstructor
+public class DiskInfo implements MonitorInfo<DiskInfo> {
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "磁盘总容量,单位MB")
+    private long total;
+
+    @Schema(description = "磁盘可用容量,单位MB")
+    private long free;
+
+    public float getUsage() {
+        return MonitorUtils.round(
+            ((total - free) / (float) total) * 100
+        );
+    }
+
+    @Override
+    public DiskInfo add(DiskInfo info) {
+        return new DiskInfo(
+            info.total + this.total,
+            info.free + this.free
+        );
+    }
+
+    @Override
+    public DiskInfo division(int num) {
+        return new DiskInfo(
+            this.total / num,
+            this.free / num
+        );
+    }
+}

+ 73 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/MemoryInfo.java

@@ -0,0 +1,73 @@
+package org.jetlinks.community.dashboard.measurements.sys;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+@Getter
+@Setter
+@ToString
+@AllArgsConstructor
+@NoArgsConstructor
+public class MemoryInfo implements MonitorInfo<MemoryInfo> {
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "JVM堆总内存,单位MB")
+    private long jvmHeapTotal;
+
+    @Schema(description = "JVM堆可用内存,单位MB")
+    private long jvmHeapFree;
+
+    @Schema(description = "JVM堆外总内存,单位MB")
+    private long jvmNonHeapTotal;
+
+    @Schema(description = "JVM堆外可用内存,单位MB")
+    private long jvmNonHeapFree;
+
+    @Schema(description = "系统总内存,单位MB")
+    private long systemTotal;
+
+    @Schema(description = "系统可用内存,单位MB")
+    private long systemFree;
+
+    public float getJvmHeapUsage() {
+        return MonitorUtils.round(
+            ((jvmHeapTotal - jvmHeapFree) / (float) jvmHeapTotal)*100
+        );
+    }
+
+    public float getJvmNonHeapUsage() {
+        return MonitorUtils.round(
+            ((jvmNonHeapTotal - jvmNonHeapFree) / (float) jvmNonHeapTotal)*100
+        );
+    }
+
+    public float getSystemUsage() {
+        return MonitorUtils.round(
+            ((systemTotal - systemFree) / (float) systemTotal)*100
+        );
+    }
+
+    @Override
+    public MemoryInfo add(MemoryInfo info) {
+        return new MemoryInfo(
+            info.jvmHeapTotal + this.jvmHeapTotal,
+            info.jvmHeapFree + this.jvmHeapFree,
+            info.jvmNonHeapTotal + this.jvmNonHeapTotal,
+            info.jvmNonHeapFree + this.jvmNonHeapFree,
+            info.systemTotal + this.systemTotal,
+            info.systemFree + this.systemFree
+        );
+    }
+
+    @Override
+    public MemoryInfo division(int num) {
+        return new MemoryInfo(
+            this.jvmHeapTotal / num,
+            this.jvmHeapFree / num,
+            this.jvmNonHeapTotal / num,
+            this.jvmNonHeapFree / num,
+            this.systemTotal / num,
+            this.systemFree / num
+        );
+    }
+}

+ 13 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/MonitorInfo.java

@@ -0,0 +1,13 @@
+package org.jetlinks.community.dashboard.measurements.sys;
+
+import java.io.Serializable;
+
+public interface MonitorInfo<T extends MonitorInfo<T>> extends Serializable {
+
+
+    T add(T info);
+
+
+    T division(int num);
+
+}

+ 15 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/MonitorUtils.java

@@ -0,0 +1,15 @@
+package org.jetlinks.community.dashboard.measurements.sys;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+class MonitorUtils {
+
+     static float round(float val) {
+        return BigDecimal
+            .valueOf(val)
+            .setScale(1, RoundingMode.HALF_UP)
+            .floatValue();
+    }
+
+}

+ 34 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/SystemInfo.java

@@ -0,0 +1,34 @@
+package org.jetlinks.community.dashboard.measurements.sys;
+
+import lombok.*;
+
+@Getter
+@Setter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+@ToString
+public class SystemInfo implements MonitorInfo<SystemInfo> {
+
+    private CpuInfo cpu;
+    private MemoryInfo memory;
+    private DiskInfo disk;
+
+    @Override
+    public SystemInfo add(SystemInfo info) {
+        return new SystemInfo(
+            this.cpu.add(info.cpu),
+            this.memory.add(info.memory),
+            this.disk.add(info.disk)
+        );
+    }
+
+    @Override
+    public SystemInfo division(int num) {
+
+        return new SystemInfo(
+            this.cpu.division(num),
+            this.memory.division(num),
+            this.disk.division(num)
+        );
+    }
+}

+ 148 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/SystemMonitorMeasurementProvider.java

@@ -0,0 +1,148 @@
+package org.jetlinks.community.dashboard.measurements.sys;
+
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.jetlinks.community.dashboard.*;
+import org.jetlinks.community.dashboard.measurements.MonitorObjectDefinition;
+import org.jetlinks.community.dashboard.supports.StaticMeasurement;
+import org.jetlinks.community.dashboard.supports.StaticMeasurementProvider;
+import org.jetlinks.core.metadata.ConfigMetadata;
+import org.jetlinks.core.metadata.DataType;
+import org.jetlinks.core.metadata.DefaultConfigMetadata;
+import org.jetlinks.core.metadata.types.EnumType;
+import org.jetlinks.core.metadata.types.ObjectType;
+import org.jetlinks.core.metadata.types.StringType;
+import org.reactivestreams.Publisher;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <h1>系统监控支持</h1>
+ * <p>
+ * 支持获取cpu,内存,磁盘信息.支持实时监控
+ * <p>
+ * <h2>实时数据</h2>
+ * 通过websocket(topic)或者sse(url)请求:
+ * <p>
+ * /dashboard/systemMonitor/stats/info/realTime
+ *
+ * <p>
+ * <h3>参数:</h3>
+ * <ul>
+ * <li>type: memory,cpu,disk,其他则为全部信息</li>
+ * <li>clusterNodeId: 指定获取集群节点的信息,不支持则返回所有节点的监控信息</li>
+ * <li>agg: 在没有指定clusterNodeId时有效,设置为avg表示所有集群节点的平均值,sum为总和.</li>
+ * </ul>
+ *
+ * <h3>响应结构:</h3>
+ * <p>
+ * 类型不同结构不同,memory: {@link MemoryInfo},cpu:{@link CpuInfo},disk:{@link DiskInfo},all:{@link SystemInfo}
+ * <p>
+ *  🌟: 企业版支持集群监控以及历史记录
+ *
+ * @author zhouhao
+ * @since 2.0
+ */
+@Component
+public class SystemMonitorMeasurementProvider extends StaticMeasurementProvider {
+
+    private final SystemMonitorService monitorService = new SystemMonitorServiceImpl();
+
+
+    public SystemMonitorMeasurementProvider() {
+        super(DefaultDashboardDefinition.systemMonitor, MonitorObjectDefinition.stats);
+
+        addMeasurement(new StaticMeasurement(CommonMeasurementDefinition.info)
+                           .addDimension(new RealTimeDimension())
+        );
+
+
+    }
+
+
+    private void putTo(String prefix, MonitorInfo<?> source, Map<String, Object> target) {
+        Map<String, Object> data = FastBeanCopier.copy(source, new HashMap<>());
+        data.forEach((key, value) -> {
+            char[] keyChars = key.toCharArray();
+            keyChars[0] = Character.toUpperCase(keyChars[0]);
+            target.put(prefix + new String(keyChars), value);
+        });
+    }
+
+
+    //实时监控
+    class RealTimeDimension implements MeasurementDimension {
+
+        @Override
+        public DimensionDefinition getDefinition() {
+            return CommonDimensionDefinition.realTime;
+        }
+
+        @Override
+        public DataType getValueType() {
+            return new ObjectType();
+        }
+
+        @Override
+        public ConfigMetadata getParams() {
+
+            return new DefaultConfigMetadata()
+                .add("serverNodeId", "服务节点ID", StringType.GLOBAL)
+                .add("interval", "更新频率", StringType.GLOBAL)
+                .add("type", "指标类型", new EnumType()
+                    .addElement(EnumType.Element.of("all", "全部"))
+                    .addElement(EnumType.Element.of("cpu", "CPU"))
+                    .addElement(EnumType.Element.of("memory", "内存"))
+                    .addElement(EnumType.Element.of("disk", "硬盘"))
+                );
+        }
+
+        @Override
+        public boolean isRealTime() {
+            return true;
+        }
+
+        @Override
+        @SuppressWarnings("all")
+        public Publisher<? extends MeasurementValue> getValue(MeasurementParameter parameter) {
+            Duration interval = parameter.getDuration("interval", Duration.ofSeconds(1));
+            String type = parameter.getString("type", "all");
+
+            return Flux
+                .concat(
+                    info(monitorService, type),
+                    Flux
+                        .interval(interval)
+                        .flatMap(ignore -> info(monitorService, type))
+                )
+                .map(info -> SimpleMeasurementValue.of(info, System.currentTimeMillis()));
+        }
+
+        private Mono<? extends MonitorInfo<?>> info(SystemMonitorService service, String type) {
+            Mono<? extends MonitorInfo<?>> data;
+            switch (type) {
+                case "cpu":
+                    data = service.cpu();
+                    break;
+                case "memory":
+                    data = service.memory();
+                    break;
+                case "disk":
+                    data = service.disk();
+                    break;
+                default:
+                    data = service.system();
+                    break;
+            }
+            return data
+                .onErrorResume(err -> Mono.empty());
+        }
+
+
+    }
+
+}

+ 14 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/SystemMonitorService.java

@@ -0,0 +1,14 @@
+package org.jetlinks.community.dashboard.measurements.sys;
+
+import reactor.core.publisher.Mono;
+
+public interface SystemMonitorService {
+
+    Mono<SystemInfo> system();
+
+    Mono<MemoryInfo> memory();
+
+    Mono<CpuInfo> cpu();
+
+    Mono<DiskInfo> disk();
+}

+ 66 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/sys/SystemMonitorServiceImpl.java

@@ -0,0 +1,66 @@
+package org.jetlinks.community.dashboard.measurements.sys;
+
+import org.jetlinks.community.dashboard.measurements.SystemMonitor;
+import reactor.core.publisher.Mono;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.MemoryUsage;
+
+public class SystemMonitorServiceImpl implements SystemMonitorService {
+
+    private final static MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
+
+    private final static int MB = 1024 * 1024;
+
+    @Override
+    public Mono<SystemInfo> system() {
+        return Mono
+            .zip(
+                cpu(),
+                memory(),
+                disk()
+            )
+            .map(tp3 -> SystemInfo.of(tp3.getT1(), tp3.getT2(), tp3.getT3()));
+    }
+
+    @Override
+    public Mono<DiskInfo> disk() {
+        long total = 0, usable = 0;
+        for (File file : File.listRoots()) {
+            total += file.getTotalSpace();
+            usable += file.getUsableSpace();
+        }
+        DiskInfo diskInfo = new DiskInfo();
+        diskInfo.setTotal(total / MB);
+        diskInfo.setFree(usable / MB);
+        return Mono.just(diskInfo);
+    }
+
+    public Mono<MemoryInfo> memory() {
+        MemoryInfo info = new MemoryInfo();
+        info.setSystemTotal((long) SystemMonitor.totalSystemMemory.value());
+        info.setSystemFree((long) SystemMonitor.freeSystemMemory.value());
+
+        MemoryUsage heap = memoryMXBean.getHeapMemoryUsage();
+        MemoryUsage nonHeap = memoryMXBean.getNonHeapMemoryUsage();
+        long nonHeapMax = (nonHeap.getMax() > 0 ? nonHeap.getMax() / MB : info.getSystemTotal());
+
+        info.setJvmHeapFree((heap.getMax() - heap.getUsed()) / MB);
+        info.setJvmHeapTotal(heap.getMax() / MB);
+
+        info.setJvmNonHeapFree(nonHeapMax - nonHeap.getUsed() / MB);
+        info.setJvmNonHeapTotal(nonHeapMax);
+        return Mono.just(info);
+    }
+
+    public Mono<CpuInfo> cpu() {
+        CpuInfo info = new CpuInfo();
+
+        info.setSystemUsage(MonitorUtils.round((float) (SystemMonitor.systemCpuUsage.value())));
+        info.setJvmUsage(MonitorUtils.round((float) (SystemMonitor.jvmCpuUsage.value())));
+
+        return Mono.just(info);
+    }
+}

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
-        <version>1.20.0-SNAPSHOT</version>
+        <version>2.0.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -72,6 +72,11 @@
             <artifactId>reactor-netty</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>things-component</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 0 - 20
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/ElasticRestClient.java

@@ -1,20 +0,0 @@
-package org.jetlinks.community.elastic.search;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import org.elasticsearch.client.RestHighLevelClient;
-
-/**
- * @author bsetfeng
- * @since 1.0
- **/
-@AllArgsConstructor
-@NoArgsConstructor
-@Getter
-public class ElasticRestClient {
-
-    private RestHighLevelClient queryClient;
-
-    private RestHighLevelClient writeClient;
-}

+ 0 - 268
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/DefaultAggregationService.java

@@ -1,268 +0,0 @@
-package org.jetlinks.community.elastic.search.aggreation;
-
-import lombok.extern.slf4j.Slf4j;
-import org.elasticsearch.ElasticsearchException;
-import org.elasticsearch.ElasticsearchParseException;
-import org.elasticsearch.action.ActionListener;
-import org.elasticsearch.action.search.SearchRequest;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.client.RequestOptions;
-import org.elasticsearch.search.aggregations.bucket.histogram.LongBounds;
-import org.elasticsearch.search.builder.SearchSourceBuilder;
-import org.hswebframework.ezorm.core.param.QueryParam;
-import org.hswebframework.ezorm.core.param.TermType;
-import org.jetlinks.community.elastic.search.ElasticRestClient;
-import org.jetlinks.community.elastic.search.aggreation.bucket.Bucket;
-import org.jetlinks.community.elastic.search.aggreation.bucket.BucketAggregationsStructure;
-import org.jetlinks.community.elastic.search.aggreation.bucket.BucketResponse;
-import org.jetlinks.community.elastic.search.aggreation.bucket.Sort;
-import org.jetlinks.community.elastic.search.aggreation.enums.BucketType;
-import org.jetlinks.community.elastic.search.aggreation.enums.MetricsType;
-import org.jetlinks.community.elastic.search.aggreation.enums.OrderType;
-import org.jetlinks.community.elastic.search.aggreation.metrics.MetricsAggregationStructure;
-import org.jetlinks.community.elastic.search.index.ElasticSearchIndexManager;
-import org.jetlinks.community.elastic.search.service.AggregationService;
-import org.jetlinks.community.elastic.search.service.DefaultElasticSearchService;
-import org.jetlinks.community.elastic.search.utils.ElasticSearchConverter;
-import org.jetlinks.community.elastic.search.utils.ReactorActionListener;
-import org.jetlinks.community.timeseries.query.AggregationQueryParam;
-import org.springframework.beans.factory.annotation.Autowired;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-import reactor.core.publisher.MonoSink;
-
-import java.util.*;
-import java.util.stream.Collectors;
-
-/**
- * @author bsetfeng
- * @since 1.0
- **/
-//@Service
-@Slf4j
-public class DefaultAggregationService implements AggregationService {
-
-    private final ElasticRestClient restClient;
-
-    private final ElasticSearchIndexManager indexManager;
-
-    @Autowired
-    public DefaultAggregationService(ElasticSearchIndexManager indexManager,
-                                     ElasticRestClient restClient) {
-        this.restClient = restClient;
-        this.indexManager = indexManager;
-    }
-
-//    @Override
-//    public Mono<MetricsResponse> metricsAggregation(String index, QueryParam queryParam,
-//                                                    MetricsAggregationStructure structure) {
-//        return createSearchSourceBuilder(queryParam, index)
-//            .map(builder -> new SearchRequest(index)
-//                .source(builder.aggregation(structure.getType().aggregationBuilder(structure.getName(), structure.getField()))))
-//            .flatMap(request -> Mono.<SearchResponse>create(monoSink ->
-//                restClient.getQueryClient().searchAsync(request, RequestOptions.DEFAULT, translatorActionListener(monoSink))))
-//            .map(searchResponse -> structure.getType().getResponse(structure.getName(), searchResponse));
-//    }
-//
-//    @Override
-//    public Mono<BucketResponse> bucketAggregation(String index, QueryParam queryParam, BucketAggregationsStructure structure) {
-//        return createSearchSourceBuilder(queryParam, index)
-//            .map(builder -> new SearchRequest(index)
-//                .source(builder
-//                    .aggregation(structure.getType().aggregationBuilder(structure))
-//                    //.aggregation(AggregationBuilders.topHits("last_val").from(1))
-//                ))
-////            .doOnNext(searchRequest -> {
-////                if (log.isDebugEnabled()) {
-////                    log.debug("聚合查询ElasticSearch:{},参数:{}", index, JSON.toJSON(searchRequest.source().toString()));
-////                }
-////            })
-//            .flatMap(request -> Mono.<SearchResponse>create(monoSink ->
-//                restClient
-//                    .getQueryClient()
-//                    .searchAsync(request, RequestOptions.DEFAULT, translatorActionListener(monoSink))))
-//            .map(response -> BucketResponse.builder()
-//                .name(structure.getName())
-//                .buckets(structure.getType().convert(response.getAggregations().get(structure.getName())))
-//                .build())
-//            ;
-//
-//    }
-
-    private Mono<SearchSourceBuilder> createSearchSourceBuilder(QueryParam queryParam, String index) {
-
-        return indexManager
-            .getIndexMetadata(index)
-            .map(metadata -> ElasticSearchConverter.convertSearchSourceBuilder(queryParam, metadata));
-    }
-
-    private <T> ActionListener<T> translatorActionListener(MonoSink<T> sink) {
-        return new ActionListener<T>() {
-            @Override
-            public void onResponse(T response) {
-                sink.success(response);
-            }
-
-            @Override
-            public void onFailure(Exception e) {
-                if (e instanceof ElasticsearchException) {
-                    if (((ElasticsearchException) e).status().getStatus() == 404) {
-                        sink.success();
-                        return;
-                    } else if (((ElasticsearchException) e).status().getStatus() == 400) {
-                        sink.error(new ElasticsearchParseException("查询参数格式错误", e));
-                    }
-                }
-                sink.error(e);
-            }
-        };
-    }
-
-    @Override
-    public Flux<Map<String, Object>> aggregation(String[] index, AggregationQueryParam aggregationQueryParam) {
-        QueryParam queryParam = prepareQueryParam(aggregationQueryParam);
-        BucketAggregationsStructure structure = createAggParameter(aggregationQueryParam);
-        return Flux.fromArray(index)
-            .flatMap(idx -> Mono.zip(indexManager.getIndexStrategy(idx), Mono.just(idx)))
-            .collectList()
-            .flatMap(strategy ->
-                createSearchSourceBuilder(queryParam, index[0])
-                    .map(builder ->
-                        new SearchRequest(strategy
-                            .stream()
-                            .map(tp2 -> tp2.getT1().getIndexForSearch(tp2.getT2()))
-                            .toArray(String[]::new))
-                            .indicesOptions(DefaultElasticSearchService.indexOptions)
-                            .source(builder.size(0).aggregation(structure.getType().aggregationBuilder(structure))
-                            )
-                    )
-            )
-            .flatMap(searchRequest ->
-                ReactorActionListener
-                    .<SearchResponse>mono(listener ->
-                        restClient.getQueryClient()
-                            .searchAsync(searchRequest, RequestOptions.DEFAULT, listener)
-                    ))
-            .filter(response -> response.getAggregations() != null)
-            .map(response -> BucketResponse.builder()
-                .name(structure.getName())
-                .buckets(structure.getType().convert(response.getAggregations().get(structure.getName())))
-                .build())
-            .flatMapIterable(BucketsParser::convert)
-            .take(aggregationQueryParam.getLimit())
-            ;
-    }
-
-    static class BucketsParser {
-
-        private final List<Map<String, Object>> result = new ArrayList<>();
-
-        public static List<Map<String, Object>> convert(BucketResponse response) {
-            return new BucketsParser(response).result;
-        }
-
-        public BucketsParser(BucketResponse response) {
-            this(response.getBuckets());
-        }
-
-        public BucketsParser(List<Bucket> buckets) {
-            buckets.forEach(bucket -> parser(bucket, new HashMap<>()));
-        }
-
-        public void parser(Bucket bucket, Map<String, Object> fMap) {
-            addBucketProperty(bucket, fMap);
-            if (bucket.getBuckets() != null && !bucket.getBuckets().isEmpty()) {
-                bucket.getBuckets().forEach(b -> {
-                    Map<String, Object> map = new HashMap<>(fMap);
-                    addBucketProperty(b, map);
-                    parser(b, map);
-                });
-            } else {
-                result.add(fMap);
-            }
-        }
-
-        private void addBucketProperty(Bucket bucket, Map<String, Object> fMap) {
-            fMap.put(bucket.getName(), bucket.getKey());
-            fMap.putAll(bucket.toMap());
-        }
-    }
-
-    protected static QueryParam prepareQueryParam(AggregationQueryParam param) {
-        QueryParam queryParam = param.getQueryParam().clone();
-        queryParam.setPaging(false);
-        queryParam.and(param.getTimeProperty(), TermType.btw, Arrays.asList(calculateStartWithTime(param), param.getEndWithTime()));
-        if (queryParam.getSorts().isEmpty()) {
-            queryParam.orderBy(param.getTimeProperty()).desc();
-        }
-        return queryParam;
-    }
-
-    protected BucketAggregationsStructure createAggParameter(AggregationQueryParam param) {
-        List<BucketAggregationsStructure> structures = new ArrayList<>();
-        if (param.getGroupByTime() != null) {
-            structures.add(convertAggGroupTimeStructure(param));
-        }
-        if (param.getGroupBy() != null && !param.getGroupBy().isEmpty()) {
-            structures.addAll(getTermTypeStructures(param));
-        }
-        for (int i = 0, size = structures.size(); i < size; i++) {
-            if (i < size - 1) {
-                structures.get(i).setSubBucketAggregation(Collections.singletonList(structures.get(i + 1)));
-            }
-            if (i == size - 1) {
-                structures.get(i)
-                    .setSubMetricsAggregation(param
-                        .getAggColumns()
-                        .stream()
-                        .map(agg -> {
-                            MetricsAggregationStructure metricsAggregationStructure = new MetricsAggregationStructure();
-                            metricsAggregationStructure.setField(agg.getProperty());
-                            metricsAggregationStructure.setName(agg.getAlias());
-                            metricsAggregationStructure.setType(MetricsType.of(agg.getAggregation().name()));
-                            return metricsAggregationStructure;
-                        }).collect(Collectors.toList()));
-            }
-        }
-        return structures.get(0);
-    }
-
-    protected BucketAggregationsStructure convertAggGroupTimeStructure(AggregationQueryParam param) {
-        BucketAggregationsStructure structure = new BucketAggregationsStructure();
-        structure.setInterval(param.getGroupByTime().getInterval().toString());
-        structure.setType(BucketType.DATE_HISTOGRAM);
-        structure.setFormat(param.getGroupByTime().getFormat());
-        structure.setName(param.getGroupByTime().getAlias());
-        structure.setField(param.getGroupByTime().getProperty());
-        structure.setSort(Sort.desc(OrderType.KEY));
-        structure.setExtendedBounds(getExtendedBounds(param));
-        return structure;
-    }
-
-    protected static LongBounds getExtendedBounds(AggregationQueryParam param) {
-        return new LongBounds(calculateStartWithTime(param), param.getEndWithTime());
-    }
-
-    private static long calculateStartWithTime(AggregationQueryParam param) {
-        long startWithParam = param.getStartWithTime();
-//        if (param.getGroupByTime() != null && param.getGroupByTime().getInterval() != null) {
-//            long timeInterval = param.getGroupByTime().getInterval().toMillis() * param.getLimit();
-//            long tempStartWithParam = param.getEndWithTime() - timeInterval;
-//            startWithParam = Math.max(tempStartWithParam, startWithParam);
-//        }
-        return startWithParam;
-    }
-
-    protected List<BucketAggregationsStructure> getTermTypeStructures(AggregationQueryParam param) {
-        return param.getGroupBy()
-            .stream()
-            .map(group -> {
-                BucketAggregationsStructure structure = new BucketAggregationsStructure();
-                structure.setType(BucketType.TERMS);
-                structure.setSize(param.getLimit());
-                structure.setField(group.getProperty());
-                structure.setName(group.getAlias());
-                return structure;
-            }).collect(Collectors.toList());
-    }
-}

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

@@ -1,64 +1,58 @@
 package org.jetlinks.community.elastic.search.configuration;
 
-import io.netty.channel.ChannelOption;
-import io.netty.handler.ssl.ApplicationProtocolConfig;
-import io.netty.handler.ssl.ClientAuth;
-import io.netty.handler.ssl.IdentityCipherSuiteFilter;
-import io.netty.handler.ssl.JdkSslContext;
-import io.netty.handler.timeout.ReadTimeoutHandler;
-import io.netty.handler.timeout.WriteTimeoutHandler;
+import lombok.Generated;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
-import org.elasticsearch.client.RestClient;
-import org.elasticsearch.client.RestHighLevelClient;
-import org.jetlinks.community.elastic.search.ElasticRestClient;
 import org.jetlinks.community.elastic.search.embedded.EmbeddedElasticSearch;
 import org.jetlinks.community.elastic.search.embedded.EmbeddedElasticSearchProperties;
+import org.jetlinks.community.elastic.search.index.DefaultElasticSearchIndexManager;
+import org.jetlinks.community.elastic.search.index.ElasticSearchIndexManager;
 import org.jetlinks.community.elastic.search.index.ElasticSearchIndexProperties;
-import org.jetlinks.community.elastic.search.service.reactive.DefaultReactiveElasticsearchClient;
+import org.jetlinks.community.elastic.search.index.ElasticSearchIndexStrategy;
+import org.jetlinks.community.elastic.search.index.strategies.DirectElasticSearchIndexStrategy;
+import org.jetlinks.community.elastic.search.index.strategies.TimeByMonthElasticSearchIndexStrategy;
+import org.jetlinks.community.elastic.search.service.AggregationService;
+import org.jetlinks.community.elastic.search.service.ElasticSearchService;
+import org.jetlinks.community.elastic.search.service.reactive.*;
+import org.jetlinks.community.elastic.search.timeseries.ElasticSearchTimeSeriesManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
 import org.springframework.data.elasticsearch.client.ClientConfiguration;
 import org.springframework.data.elasticsearch.client.reactive.HostProvider;
 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.TcpClient;
-import reactor.netty.transport.ProxyProvider;
 
-import javax.net.ssl.SSLContext;
 import java.net.InetSocketAddress;
-import java.time.Duration;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
+import java.util.List;
 
 /**
  * @author bsetfeng
  * @author zhouhao
  * @since 1.0
  **/
-@Configuration
+@Configuration(proxyBeanMethods = false)
 @Slf4j
 @EnableConfigurationProperties({
-    ElasticSearchProperties.class,
     EmbeddedElasticSearchProperties.class,
-    ElasticSearchIndexProperties.class})
+    ElasticSearchIndexProperties.class,
+    ElasticSearchBufferProperties.class})
+@AutoConfigureAfter(ReactiveElasticsearchRestClientAutoConfiguration.class)
+@ConditionalOnBean(ClientConfiguration.class)
+@Generated
 public class ElasticSearchConfiguration {
 
-    private final ElasticSearchProperties properties;
-
-    private final EmbeddedElasticSearchProperties embeddedProperties;
-
-    public ElasticSearchConfiguration(ElasticSearchProperties properties, EmbeddedElasticSearchProperties embeddedProperties) {
-        this.properties = properties;
-        this.embeddedProperties = embeddedProperties;
-    }
-
     @Bean
     @SneakyThrows
-    public DefaultReactiveElasticsearchClient reactiveElasticsearchClient(ClientConfiguration clientConfiguration) {
+    @Primary
+    public DefaultReactiveElasticsearchClient defaultReactiveElasticsearchClient(EmbeddedElasticSearchProperties embeddedProperties,
+                                                                                 ClientConfiguration clientConfiguration) {
         if (embeddedProperties.isEnabled()) {
             log.debug("starting embedded elasticsearch on {}:{}",
                       embeddedProperties.getHost(),
@@ -66,10 +60,10 @@ public class ElasticSearchConfiguration {
 
             new EmbeddedElasticSearch(embeddedProperties).start();
         }
-
         WebClientProvider provider = getWebClientProvider(clientConfiguration);
 
-        HostProvider hostProvider = HostProvider.provider(provider, clientConfiguration.getHeadersSupplier(),
+        HostProvider<?> hostProvider = HostProvider.provider(provider,
+                                                          clientConfiguration.getHeadersSupplier(),
                                                           clientConfiguration
                                                               .getEndpoints()
                                                               .toArray(new InetSocketAddress[0]));
@@ -85,77 +79,45 @@ public class ElasticSearchConfiguration {
 
     private static WebClientProvider getWebClientProvider(ClientConfiguration clientConfiguration) {
 
-        Duration connectTimeout = clientConfiguration.getConnectTimeout();
-        Duration soTimeout = clientConfiguration.getSocketTimeout();
-
-        TcpClient tcpClient = TcpClient.create();
-
-        if (!connectTimeout.isNegative()) {
-            tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis()));
-        }
-
-        if (!soTimeout.isNegative()) {
-            tcpClient = tcpClient.doOnConnected(connection -> connection //
-                                                                         .addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))
-                                                                         .addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)));
-        }
-
-        if (clientConfiguration.getProxy().isPresent()) {
-            String proxy = clientConfiguration.getProxy().get();
-            String[] hostPort = proxy.split(":");
-
-            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])
-                .port(Integer.parseInt(hostPort[1])));
-        }
-
-        String scheme = "http";
-        HttpClient httpClient = HttpClient.from(tcpClient);
-
-        if (clientConfiguration.useSsl()) {
-
-            Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
-
-            if (sslContext.isPresent()) {
-                httpClient = httpClient.secure(sslContextSpec -> {
-                    sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null, IdentityCipherSuiteFilter.INSTANCE,
-                                                                ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false));
-                });
-            } else {
-                httpClient = httpClient.secure();
-            }
-
-            scheme = "https";
-        }
+        return WebClientProvider.getWebClientProvider(clientConfiguration);
+    }
 
-        ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
-        WebClientProvider provider = WebClientProvider.create(scheme, connector);
+    @Bean
+    public DirectElasticSearchIndexStrategy directElasticSearchIndexStrategy(ReactiveElasticsearchClient elasticsearchClient,
+                                                                             ElasticSearchIndexProperties indexProperties) {
+        return new DirectElasticSearchIndexStrategy(elasticsearchClient, indexProperties);
+    }
 
-        if (clientConfiguration.getPathPrefix() != null) {
-            provider = provider.withPathPrefix(clientConfiguration.getPathPrefix());
-        }
+    @Bean
+    public TimeByMonthElasticSearchIndexStrategy timeByMonthElasticSearchIndexStrategy(ReactiveElasticsearchClient elasticsearchClient,
+                                                                                       ElasticSearchIndexProperties indexProperties) {
+        return new TimeByMonthElasticSearchIndexStrategy(elasticsearchClient, indexProperties);
+    }
 
-        provider = provider.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
-                           .withWebClientConfigurer(clientConfiguration.getWebClientConfigurer());
-        return provider;
+    @Bean
+    public DefaultElasticSearchIndexManager elasticSearchIndexManager(@Autowired(required = false) List<ElasticSearchIndexStrategy> strategies) {
+        return new DefaultElasticSearchIndexManager(strategies);
     }
 
     @Bean
-    @SneakyThrows
-    public ElasticRestClient elasticRestClient() {
+    @ConditionalOnMissingBean(ElasticSearchService.class)
+    public ReactiveElasticSearchService reactiveElasticSearchService(ReactiveElasticsearchClient elasticsearchClient,
+                                                                     ElasticSearchIndexManager indexManager,
+                                                                     ElasticSearchBufferProperties properties) {
+        return new ReactiveElasticSearchService(elasticsearchClient, indexManager, properties);
+    }
 
-        RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(properties.createHosts())
-                                                                       .setRequestConfigCallback(properties::applyRequestConfigBuilder)
-                                                                       .setHttpClientConfigCallback(properties::applyHttpAsyncClientBuilder));
-        return new ElasticRestClient(client, client);
+    @Bean
+    public ReactiveAggregationService reactiveAggregationService(ElasticSearchIndexManager indexManager,
+                                                                 ReactiveElasticsearchClient restClient) {
+        return new ReactiveAggregationService(indexManager, restClient);
     }
 
-    @Bean(destroyMethod = "close")
-    public RestHighLevelClient restHighLevelClient(ElasticRestClient client) {
-        return client.getWriteClient();
+    @Bean
+    public ElasticSearchTimeSeriesManager elasticSearchTimeSeriesManager(ElasticSearchIndexManager indexManager,
+                                                                         ElasticSearchService elasticSearchService,
+                                                                         AggregationService aggregationService) {
+        return new ElasticSearchTimeSeriesManager(indexManager, elasticSearchService, aggregationService);
     }
 
 }

+ 37 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/configuration/ElasticSearchThingDataConfiguration.java

@@ -0,0 +1,37 @@
+package org.jetlinks.community.elastic.search.configuration;
+
+import org.jetlinks.core.things.ThingsRegistry;
+import org.jetlinks.community.elastic.search.index.ElasticSearchIndexManager;
+import org.jetlinks.community.elastic.search.service.AggregationService;
+import org.jetlinks.community.elastic.search.service.ElasticSearchService;
+import org.jetlinks.community.elastic.search.things.ElasticSearchColumnModeStrategy;
+import org.jetlinks.community.elastic.search.things.ElasticSearchRowModeStrategy;
+import org.jetlinks.community.things.data.ThingsDataRepositoryStrategy;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnClass(ThingsDataRepositoryStrategy.class)
+public class ElasticSearchThingDataConfiguration {
+
+    @Bean
+    public ElasticSearchColumnModeStrategy elasticSearchColumnModThingDataPolicy(
+        ThingsRegistry registry,
+        ElasticSearchService searchService,
+        AggregationService aggregationService,
+        ElasticSearchIndexManager indexManager) {
+
+        return new ElasticSearchColumnModeStrategy(registry, searchService, aggregationService, indexManager);
+    }
+
+    @Bean
+    public ElasticSearchRowModeStrategy elasticSearchRowModThingDataPolicy(
+        ThingsRegistry registry,
+        ElasticSearchService searchService,
+        AggregationService aggregationService,
+        ElasticSearchIndexManager indexManager) {
+
+        return new ElasticSearchRowModeStrategy(registry, searchService, aggregationService, indexManager);
+    }
+}

+ 0 - 1
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/DefaultElasticSearchIndexManager.java

@@ -12,7 +12,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentSkipListMap;
 
-@Component
 @ConfigurationProperties(prefix = "elasticsearch.index")
 public class DefaultElasticSearchIndexManager implements ElasticSearchIndexManager {
 

+ 5 - 8
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/strategies/TimeByMonthElasticSearchIndexStrategy.java

@@ -1,11 +1,9 @@
 package org.jetlinks.community.elastic.search.index.strategies;
 
-import org.hswebframework.utils.time.DateFormatter;
 import org.jetlinks.community.elastic.search.index.ElasticSearchIndexProperties;
 import org.jetlinks.community.elastic.search.service.reactive.ReactiveElasticsearchClient;
-import org.springframework.stereotype.Component;
 
-import java.util.Date;
+import java.time.LocalDate;
 
 /**
  * 按月对来划分索引策略
@@ -13,17 +11,16 @@ import java.util.Date;
  * @author zhouhao
  * @since 1.0
  */
-@Component
 public class TimeByMonthElasticSearchIndexStrategy extends TemplateElasticSearchIndexStrategy {
 
-    private final String format = "yyyy-MM";
-
     public TimeByMonthElasticSearchIndexStrategy(ReactiveElasticsearchClient client, ElasticSearchIndexProperties properties) {
-        super("time-by-month", client,properties);
+        super("time-by-month", client, properties);
     }
 
     @Override
     public String getIndexForSave(String index) {
-        return wrapIndex(index).concat("_").concat(DateFormatter.toString(new Date(), format));
+        LocalDate now = LocalDate.now();
+        String idx = wrapIndex(index);
+        return idx + "_" + now.getYear() + "-" + now.getMonthValue();
     }
 }

+ 6 - 2
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/parser/DefaultLinkTypeParser.java

@@ -4,6 +4,7 @@ import org.elasticsearch.index.query.BoolQueryBuilder;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.hswebframework.ezorm.core.param.Term;
 import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
 
 import java.util.List;
 import java.util.function.Consumer;
@@ -15,10 +16,13 @@ import java.util.function.Consumer;
 @Component
 public class DefaultLinkTypeParser implements LinkTypeParser {
 
-    private TermTypeParser parser = new DefaultTermTypeParser();
+    private final TermTypeParser parser = new DefaultTermTypeParser();
 
     @Override
     public BoolQueryBuilder process(Term term, Consumer<Term> consumer, BoolQueryBuilder queryBuilders) {
+        if (term.getValue() == null && CollectionUtils.isEmpty(term.getTerms())) {
+            return queryBuilders;
+        }
         if (term.getType() == Term.Type.or) {
             handleOr(queryBuilders, term, consumer);
         } else {
@@ -52,4 +56,4 @@ public class DefaultLinkTypeParser implements LinkTypeParser {
     }
 
 
-}
+}

+ 9 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/AggregationService.java

@@ -13,4 +13,13 @@ public interface AggregationService {
 
     Flux<Map<String, Object>> aggregation(String[] index, AggregationQueryParam queryParam);
 
+    /**
+     * @param index      索引
+     * @param queryParam 聚合查询参数
+     * @return 查询结果
+     * @see AggregationService#aggregation(String[], AggregationQueryParam)
+     */
+    default Flux<Map<String, Object>> aggregation(String index, AggregationQueryParam queryParam) {
+        return aggregation(new String[]{index}, queryParam);
+    }
 }

+ 0 - 444
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/DefaultElasticSearchService.java

@@ -1,444 +0,0 @@
-package org.jetlinks.community.elastic.search.service;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.collections4.CollectionUtils;
-import org.elasticsearch.action.ActionListener;
-import org.elasticsearch.action.bulk.BulkItemResponse;
-import org.elasticsearch.action.bulk.BulkRequest;
-import org.elasticsearch.action.bulk.BulkResponse;
-import org.elasticsearch.action.index.IndexRequest;
-import org.elasticsearch.action.search.MultiSearchRequest;
-import org.elasticsearch.action.search.MultiSearchResponse;
-import org.elasticsearch.action.search.SearchRequest;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.action.support.IndicesOptions;
-import org.elasticsearch.client.RequestOptions;
-import org.elasticsearch.client.core.CountRequest;
-import org.elasticsearch.client.core.CountResponse;
-import org.elasticsearch.index.query.QueryBuilder;
-import org.elasticsearch.index.reindex.BulkByScrollResponse;
-import org.elasticsearch.index.reindex.DeleteByQueryRequest;
-import org.elasticsearch.search.SearchHit;
-import org.elasticsearch.search.builder.SearchSourceBuilder;
-import org.hswebframework.ezorm.core.param.QueryParam;
-import org.hswebframework.utils.time.DateFormatter;
-import org.hswebframework.utils.time.DefaultDateFormatter;
-import org.hswebframework.web.api.crud.entity.PagerResult;
-import org.hswebframework.web.bean.FastBeanCopier;
-import org.jetlinks.community.elastic.search.ElasticRestClient;
-import org.jetlinks.community.elastic.search.index.ElasticSearchIndexManager;
-import org.jetlinks.community.elastic.search.index.ElasticSearchIndexMetadata;
-import org.jetlinks.community.elastic.search.utils.ElasticSearchConverter;
-import org.jetlinks.community.elastic.search.utils.QueryParamTranslator;
-import org.jetlinks.community.elastic.search.utils.ReactorActionListener;
-import org.jetlinks.core.utils.FluxUtils;
-import org.reactivestreams.Publisher;
-import org.springframework.context.annotation.DependsOn;
-import org.springframework.util.StringUtils;
-import reactor.core.publisher.BufferOverflowStrategy;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.FluxSink;
-import reactor.core.publisher.Mono;
-import reactor.core.scheduler.Schedulers;
-import reactor.function.Consumer3;
-import reactor.util.function.Tuple2;
-import reactor.util.function.Tuples;
-
-import javax.annotation.PreDestroy;
-import java.time.Duration;
-import java.util.*;
-import java.util.function.Function;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-/**
- * @author zhouhao
- * @since 1.0
- **/
-//@Service
-@Slf4j
-@DependsOn("restHighLevelClient")
-@Deprecated
-public class DefaultElasticSearchService implements ElasticSearchService {
-
-    private final ElasticRestClient restClient;
-
-    private final ElasticSearchIndexManager indexManager;
-
-    FluxSink<Buffer> sink;
-
-    public static final IndicesOptions indexOptions = IndicesOptions.fromOptions(
-        true, true, false, false
-    );
-
-    static {
-        DateFormatter.supportFormatter.add(new DefaultDateFormatter(Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.+"), "yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
-    }
-
-    public DefaultElasticSearchService(ElasticRestClient restClient,
-                                       ElasticSearchIndexManager indexManager) {
-        this.restClient = restClient;
-        init();
-        this.indexManager = indexManager;
-    }
-
-    @Override
-    public <T> Flux<T> multiQuery(String[] index, Collection<QueryParam> queryParam, Function<Map<String, Object>, T> mapper) {
-        return indexManager
-            .getIndexesMetadata(index)
-            .flatMap(idx -> Mono.zip(
-                Mono.just(idx), getIndexForSearch(idx.getIndex())
-            ))
-            .take(1)
-            .singleOrEmpty()
-            .flatMapMany(indexMetadata -> {
-                MultiSearchRequest request = new MultiSearchRequest();
-                return Flux
-                    .fromIterable(queryParam)
-                    .flatMap(entry -> createSearchRequest(entry, index))
-                    .doOnNext(request::add)
-                    .then(Mono.just(request))
-                    .flatMapMany(searchRequest -> ReactorActionListener
-                        .<MultiSearchResponse>mono(actionListener -> {
-                            restClient.getQueryClient()
-                                .msearchAsync(searchRequest, RequestOptions.DEFAULT, actionListener);
-                        })
-                        .flatMapMany(response -> Flux.fromArray(response.getResponses()))
-                        .flatMap(item -> {
-                            if (item.isFailure()) {
-                                log.warn(item.getFailureMessage(), item.getFailure());
-                                return Mono.empty();
-                            }
-                            return Flux.fromIterable(translate((map) -> mapper.apply(indexMetadata.getT1().convertFromElastic(map)), item.getResponse()));
-                        }))
-                    ;
-            });
-    }
-
-    @Override
-    public <T> Flux<T> query(String index, QueryParam queryParam, Function<Map<String, Object>, T> mapper) {
-        return this
-            .doQuery(new String[]{index}, queryParam)
-            .flatMapMany(tp2 -> convertQueryResult(tp2.getT1(), tp2.getT2(), mapper));
-    }
-
-    @Override
-    public <T> Flux<T> query(String[] index, QueryParam queryParam, Function<Map<String, Object>, T> mapper) {
-        return this
-            .doQuery(index, queryParam)
-            .flatMapMany(tp2 -> convertQueryResult(tp2.getT1(), tp2.getT2(), mapper));
-    }
-
-    @Override
-    public <T> Mono<PagerResult<T>> queryPager(String[] index, QueryParam queryParam, Function<Map<String, Object>, T> mapper) {
-        return this
-            .doQuery(index, queryParam)
-            .flatMap(tp2 ->
-                convertQueryResult(tp2.getT1(), tp2.getT2(), mapper)
-                    .collectList()
-                    .filter(CollectionUtils::isNotEmpty)
-                    .map(list -> PagerResult.of((int) tp2.getT2().getHits().getTotalHits().value, list, queryParam))
-            )
-            .switchIfEmpty(Mono.fromSupplier(PagerResult::empty));
-    }
-
-    private <T> Flux<T> convertQueryResult(List<ElasticSearchIndexMetadata> indexList,
-                                           SearchResponse response,
-                                           Function<Map<String, Object>, T> mapper) {
-        return Flux
-            .create(sink -> {
-                Map<String, ElasticSearchIndexMetadata> metadata = indexList
-                    .stream()
-                    .collect(Collectors.toMap(ElasticSearchIndexMetadata::getIndex, Function.identity()));
-                SearchHit[] hits = response.getHits().getHits();
-                for (SearchHit hit : hits) {
-                    Map<String, Object> hitMap = hit.getSourceAsMap();
-                    if (StringUtils.isEmpty(hitMap.get("id"))) {
-                        hitMap.put("id", hit.getId());
-                    }
-
-                    sink.next(mapper
-                        .apply(Optional
-                            .ofNullable(metadata.get(hit.getIndex())).orElse(indexList.get(0))
-                            .convertFromElastic(hitMap)));
-                }
-                sink.complete();
-            });
-
-    }
-
-    private Mono<Tuple2<List<ElasticSearchIndexMetadata>, SearchResponse>> doQuery(String[] index,
-                                                                                   QueryParam queryParam) {
-        return indexManager
-            .getIndexesMetadata(index)
-            .collectList()
-            .filter(CollectionUtils::isNotEmpty)
-            .flatMap(metadataList -> this
-                .createSearchRequest(queryParam, metadataList)
-                .flatMap(this::doSearch)
-                .map(response -> Tuples.of(metadataList, response))
-            ).onErrorResume(err -> {
-                log.error(err.getMessage(), err);
-                return Mono.empty();
-            });
-    }
-
-
-    @Override
-    public Mono<Long> count(String[] index, QueryParam queryParam) {
-        QueryParam param = queryParam.clone();
-        param.setPaging(false);
-        return createCountRequest(param, index)
-            .flatMap(this::doCount)
-            .map(CountResponse::getCount)
-            .defaultIfEmpty(0L)
-            .onErrorReturn(err -> {
-                log.error("query elastic error", err);
-                return true;
-            }, 0L);
-    }
-
-    @Override
-    public Mono<Long> delete(String index, QueryParam queryParam) {
-
-        return createQueryBuilder(queryParam, index)
-            .flatMap(request -> ReactorActionListener
-                .<BulkByScrollResponse>mono(listener ->
-                    restClient
-                        .getWriteClient()
-                        .deleteByQueryAsync(new DeleteByQueryRequest(index)
-                                .setQuery(request),
-                            RequestOptions.DEFAULT, listener)))
-            .map(BulkByScrollResponse::getDeleted);
-    }
-
-    @Override
-    public <T> Mono<Void> commit(String index, T payload) {
-        sink.next(new Buffer(index, payload));
-        return Mono.empty();
-    }
-
-    @Override
-    public <T> Mono<Void> commit(String index, Collection<T> payload) {
-        for (T t : payload) {
-            sink.next(new Buffer(index, t));
-        }
-        return Mono.empty();
-    }
-
-    @Override
-    public <T> Mono<Void> commit(String index, Publisher<T> data) {
-        return Flux.from(data)
-            .flatMap(d -> commit(index, d))
-            .then();
-    }
-
-    @Override
-    public <T> Mono<Void> save(String index, T payload) {
-        return save(index, Mono.just(payload));
-    }
-
-    @Override
-    public <T> Mono<Void> save(String index, Publisher<T> data) {
-        return Flux.from(data)
-            .map(v -> new Buffer(index, v))
-            .collectList()
-            .flatMap(this::doSave)
-            .then();
-    }
-
-    @Override
-    public <T> Mono<Void> save(String index, Collection<T> payload) {
-        return save(index, Flux.fromIterable(payload));
-    }
-
-    @PreDestroy
-    public void shutdown() {
-        sink.complete();
-    }
-
-    //@PostConstruct
-    public void init() {
-        //最小间隔
-        int flushRate = Integer.getInteger("elasticsearch.buffer.rate", 1000);
-        //缓冲最大数量
-        int bufferSize = Integer.getInteger("elasticsearch.buffer.size", 3000);
-        //缓冲超时时间
-        Duration bufferTimeout = Duration.ofSeconds(Integer.getInteger("elasticsearch.buffer.timeout", 3));
-        //缓冲背压
-        int bufferBackpressure = Integer.getInteger("elasticsearch.buffer.backpressure", 64);
-
-        FluxUtils.bufferRate(
-            Flux.<Buffer>create(sink -> this.sink = sink),
-            flushRate,
-            bufferSize,
-            bufferTimeout)
-            .onBackpressureBuffer(bufferBackpressure,
-                drop -> System.err.println("无法处理更多索引请求!"),
-                BufferOverflowStrategy.DROP_OLDEST)
-            .parallel()
-            .runOn(Schedulers.newParallel("elasticsearch-writer"))
-            .flatMap(buffers -> {
-                long time = System.currentTimeMillis();
-                return this
-                    .doSave(buffers)
-                    .doOnNext((len) -> log.trace("保存ElasticSearch数据成功,数量:{},耗时:{}ms", len, (System.currentTimeMillis() - time)))
-                    .onErrorContinue((err, obj) -> {
-                        //这里的错误都输出到控制台,输入到slf4j可能会造成日志递归.
-                        System.err.println("保存ElasticSearch数据失败:\n" + org.hswebframework.utils.StringUtils.throwable2String(err));
-                    });
-            })
-            .subscribe();
-    }
-
-    @AllArgsConstructor
-    @Getter
-    static class Buffer {
-        String index;
-        Object payload;
-    }
-
-
-    private Mono<String> getIndexForSave(String index) {
-        return indexManager
-            .getIndexStrategy(index)
-            .map(strategy -> strategy.getIndexForSave(index));
-
-    }
-
-    private Mono<String> getIndexForSearch(String index) {
-        return indexManager
-            .getIndexStrategy(index)
-            .map(strategy -> strategy.getIndexForSearch(index));
-
-    }
-
-    protected Mono<Integer> doSave(Collection<Buffer> buffers) {
-        return Flux.fromIterable(buffers)
-            .groupBy(Buffer::getIndex)
-            .flatMap(group -> {
-                String index = group.key();
-                return this.getIndexForSave(index)
-                    .zipWith(indexManager.getIndexMetadata(index))
-                    .flatMapMany(tp2 ->
-                        group.map(buffer -> {
-                            Map<String, Object> data = FastBeanCopier.copy(buffer.getPayload(), HashMap::new);
-
-                            IndexRequest request;
-                            if (data.get("id") != null) {
-                                request = new IndexRequest(tp2.getT1(), "_doc", String.valueOf(data.get("id")));
-                            } else {
-                                request = new IndexRequest(tp2.getT1(), "_doc");
-                            }
-                            request.source(tp2.getT2().convertToElastic(data));
-                            return request;
-                        }));
-            })
-            .collectList()
-            .filter(CollectionUtils::isNotEmpty)
-            .flatMap(lst -> {
-                BulkRequest request = new BulkRequest();
-                lst.forEach(request::add);
-                return ReactorActionListener.<BulkResponse>mono(listener ->
-                    restClient.getWriteClient().bulkAsync(request, RequestOptions.DEFAULT, listener))
-                    .doOnNext(this::checkResponse);
-            })
-            .thenReturn(buffers.size());
-    }
-
-    @SneakyThrows
-    protected void checkResponse(BulkResponse response) {
-        if (response.hasFailures()) {
-            for (BulkItemResponse item : response.getItems()) {
-                if (item.isFailed()) {
-                    throw item.getFailure().getCause();
-                }
-            }
-        }
-    }
-
-    private <T> List<T> translate(Function<Map<String, Object>, T> mapper, SearchResponse response) {
-        return Arrays.stream(response.getHits().getHits())
-            .map(hit -> {
-                Map<String, Object> hitMap = hit.getSourceAsMap();
-                if (StringUtils.isEmpty(hitMap.get("id"))) {
-                    hitMap.put("id", hit.getId());
-                }
-                return mapper.apply(hitMap);
-            })
-            .collect(Collectors.toList());
-    }
-
-    private Mono<SearchResponse> doSearch(SearchRequest request) {
-        return this
-            .execute(request, restClient.getQueryClient()::searchAsync)
-            .onErrorResume(err -> {
-                log.error("query elastic error", err);
-                return Mono.empty();
-            });
-    }
-
-    private <REQ, RES> Mono<RES> execute(REQ request, Consumer3<REQ, RequestOptions, ActionListener<RES>> function4) {
-        return ReactorActionListener.mono(actionListener -> function4.accept(request, RequestOptions.DEFAULT, actionListener));
-    }
-
-    private Mono<CountResponse> doCount(CountRequest request) {
-        return this
-            .execute(request, restClient.getQueryClient()::countAsync)
-            .onErrorResume(err -> {
-                log.error("query elastic error", err);
-                return Mono.empty();
-            });
-    }
-
-    protected Mono<SearchRequest> createSearchRequest(QueryParam queryParam, String... indexes) {
-        return indexManager
-            .getIndexesMetadata(indexes)
-            .collectList()
-            .filter(CollectionUtils::isNotEmpty)
-            .flatMap(list -> createSearchRequest(queryParam, list));
-    }
-
-    protected Mono<SearchRequest> createSearchRequest(QueryParam queryParam, List<ElasticSearchIndexMetadata> indexes) {
-
-        SearchSourceBuilder builder = ElasticSearchConverter.convertSearchSourceBuilder(queryParam, indexes.get(0));
-        return Flux.fromIterable(indexes)
-            .flatMap(index -> getIndexForSearch(index.getIndex()))
-            .collectList()
-            .map(indexList ->
-                new SearchRequest(indexList.toArray(new String[0]))
-                    .source(builder)
-                    .indicesOptions(indexOptions)
-                    .types("_doc"));
-    }
-
-    protected Mono<QueryBuilder> createQueryBuilder(QueryParam queryParam, String index) {
-        return indexManager
-            .getIndexMetadata(index)
-            .map(metadata -> QueryParamTranslator.createQueryBuilder(queryParam, metadata))
-            .switchIfEmpty(Mono.fromSupplier(() -> QueryParamTranslator.createQueryBuilder(queryParam, null)));
-    }
-
-    protected Mono<CountRequest> createCountRequest(QueryParam queryParam, List<ElasticSearchIndexMetadata> indexes) {
-        QueryParam tempQueryParam = queryParam.clone();
-        tempQueryParam.setPaging(false);
-        tempQueryParam.setSorts(Collections.emptyList());
-
-        SearchSourceBuilder builder = ElasticSearchConverter.convertSearchSourceBuilder(queryParam, indexes.get(0));
-        return Flux.fromIterable(indexes)
-            .flatMap(index -> getIndexForSearch(index.getIndex()))
-            .collectList()
-            .map(indexList -> new CountRequest(indexList.toArray(new String[0])).source(builder));
-    }
-
-    private Mono<CountRequest> createCountRequest(QueryParam queryParam, String... index) {
-        return indexManager
-            .getIndexesMetadata(index)
-            .collectList()
-            .filter(CollectionUtils::isNotEmpty)
-            .flatMap(list -> createCountRequest(queryParam, list));
-    }
-}

+ 19 - 9
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/AggType.java

@@ -20,23 +20,28 @@ public enum AggType {
         public AggregationBuilder aggregationBuilder(String name, String filed) {
             return AggregationBuilders.max(name).field(filed).missing(0);
         }
-
-
     },
-    COUNT("非空值计数") {
+    MEDIAN("中间值") {
         @Override
         public AggregationBuilder aggregationBuilder(String name, String filed) {
-            return AggregationBuilders.count(name).field(filed).missing(0);
+            return AggregationBuilders.medianAbsoluteDeviation(name).field(filed).missing(0);
         }
-
     },
-    MIN("最小") {
+    STDDEV("标准差") {
         @Override
         public AggregationBuilder aggregationBuilder(String name, String filed) {
-            return AggregationBuilders.min(name).field(filed).missing(0);
+            return AggregationBuilders.extendedStats(name)
+                                      .field(filed)
+                                      .missing(0);
         }
     },
+    COUNT("非空值计数") {
+        @Override
+        public AggregationBuilder aggregationBuilder(String name, String filed) {
+            return AggregationBuilders.count(name).field(filed).missing(0);
+        }
 
+    },
     DISTINCT_COUNT("去重计数") {
         @Override
         public AggregationBuilder aggregationBuilder(String name, String filed) {
@@ -47,7 +52,12 @@ public enum AggType {
         }
 
     },
-
+    MIN("最小") {
+        @Override
+        public AggregationBuilder aggregationBuilder(String name, String filed) {
+            return AggregationBuilders.min(name).field(filed).missing(0);
+        }
+    },
     FIRST("第一条数据") {
         @Override
         public AggregationBuilder aggregationBuilder(String name, String filed) {
@@ -85,7 +95,7 @@ public enum AggType {
                 return type;
             }
         }
-        throw new UnsupportedOperationException("不支持的聚合度量类型:" + name);
+        throw new UnsupportedOperationException("不支持的聚合类型:" + name);
     }
 
 }

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

@@ -60,17 +60,19 @@ 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.client.tasks.TaskSubmissionResponse;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.common.Priority;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.lucene.uid.Versions;
-import org.elasticsearch.common.unit.TimeValue;
-import org.elasticsearch.common.xcontent.*;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.core.TimeValue;
 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.ReindexRequest;
 import org.elasticsearch.index.reindex.UpdateByQueryRequest;
 import org.elasticsearch.index.seqno.SequenceNumbers;
 import org.elasticsearch.rest.BytesRestResponse;
@@ -84,16 +86,17 @@ 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.elasticsearch.xcontent.*;
 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.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.ResponseConverter;
 import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
 import org.springframework.data.util.Lazy;
 import org.springframework.http.HttpHeaders;
@@ -107,12 +110,11 @@ import org.springframework.web.reactive.function.BodyExtractors;
 import org.springframework.web.reactive.function.client.ClientRequest;
 import org.springframework.web.reactive.function.client.ClientResponse;
 import org.springframework.web.reactive.function.client.WebClient;
-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 javax.annotation.Nonnull;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.lang.reflect.Method;
@@ -130,7 +132,7 @@ import static org.springframework.data.elasticsearch.client.util.RequestConverte
 
 @Slf4j
 @Generated
-public class DefaultReactiveElasticsearchClient implements org.jetlinks.community.elastic.search.service.reactive.ReactiveElasticsearchClient,
+public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient,
     org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Cluster {
     private final HostProvider<?> hostProvider;
     private final RequestCreator requestCreator;
@@ -342,12 +344,8 @@ public class DefaultReactiveElasticsearchClient implements org.jetlinks.communit
                                                                                                    .flatMap(Flux::fromIterable);
     }
 
-    /*
-     * (non-Javadoc)
-     * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#scroll(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
-     */
-    @Override
-    public Flux<SearchHit> scroll(HttpHeaders headers, SearchRequest searchRequest) {
+    @Override@Nonnull
+    public Flux<SearchHit> scroll(@Nonnull HttpHeaders headers, SearchRequest searchRequest) {
 
         TimeValue scrollTimeout = searchRequest.scroll() != null ? searchRequest.scroll().keepAlive()
             : TimeValue.timeValueMinutes(1);
@@ -356,60 +354,31 @@ public class DefaultReactiveElasticsearchClient implements org.jetlinks.communit
             searchRequest.scroll(scrollTimeout);
         }
 
-        EmitterProcessor<ActionRequest> outbound = EmitterProcessor.create(false);
-        FluxSink<ActionRequest> request = outbound.sink();
-
-        EmitterProcessor<SearchResponse> inbound = EmitterProcessor.create(false);
-
-        Flux<SearchResponse> exchange = outbound.startWith(searchRequest).flatMap(it -> {
-
-            if (it instanceof SearchRequest) {
-                return sendRequest((SearchRequest) it, requestCreator.search(), SearchResponse.class, headers);
-            } else if (it instanceof SearchScrollRequest) {
-                return sendRequest((SearchScrollRequest) it, requestCreator.scroll(), SearchResponse.class, headers);
-            } else if (it instanceof ClearScrollRequest) {
-                return sendRequest((ClearScrollRequest) it, requestCreator.clearScroll(), ClearScrollResponse.class, headers)
-                    .flatMap(discard -> Flux.empty());
-            }
-
-            throw new IllegalArgumentException(
-                String.format("Cannot handle '%s'. Please make sure to use a 'SearchRequest' or 'SearchScrollRequest'.", it));
-        });
-
-        return Flux.usingWhen(Mono.fromSupplier(ScrollState::new),
-
-                              scrollState -> {
-
-                                  Flux<SearchHit> searchHits = inbound
-                                      .<SearchResponse>handle((searchResponse, sink) -> {
-
-                                          scrollState.updateScrollId(searchResponse.getScrollId());
-                                          if (isEmpty(searchResponse.getHits())) {
-
-                                              inbound.onComplete();
-                                              outbound.onComplete();
-
-                                          } else {
-
-                                              sink.next(searchResponse);
-
-                                              SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollState.getScrollId())
-                                                  .scroll(scrollTimeout);
-                                              request.next(searchScrollRequest);
-                                          }
-
-                                      })
-                                      .map(SearchResponse::getHits) //
-                                      .flatMap(Flux::fromIterable);
-
-                                  return searchHits.doOnSubscribe(ignore -> exchange.subscribe(inbound));
-
-                              },
-                              state -> cleanupScroll(headers, state), //
-                              (state, error) -> cleanupScroll(headers, state), //
-                              state -> cleanupScroll(headers, state)); //
+        return Flux
+            .usingWhen(Mono.fromSupplier(ScrollState::new),
+                       state -> this
+                           .sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers)
+                           .expand(searchResponse -> {
+
+                               state.updateScrollId(searchResponse.getScrollId());
+                               if (isEmpty(searchResponse.getHits())) {
+                                   return Mono.empty();
+                               }
+
+                               return this
+                                   .sendRequest(new SearchScrollRequest(searchResponse.getScrollId()).scroll(scrollTimeout),
+                                                requestCreator.scroll(),
+                                                SearchResponse.class,
+                                                headers);
+
+                           }),
+                       state -> cleanupScroll(headers, state),
+                       (state, ex) -> cleanupScroll(headers, state),
+                       state -> cleanupScroll(headers, state))
+            .filter(it -> !isEmpty(it.getHits()))
+            .map(SearchResponse::getHits)
+            .flatMapIterable(Function.identity());
     }
-
     private static boolean isEmpty(@Nullable SearchHits hits) {
         return hits != null && hits.getHits() != null && hits.getHits().length == 0;
     }
@@ -442,7 +411,7 @@ public class DefaultReactiveElasticsearchClient implements org.jetlinks.communit
     public Mono<ByQueryResponse> updateBy(HttpHeaders headers, UpdateByQueryRequest updateRequest) {
         return sendRequest(updateRequest, requestCreator.updateByQuery(), BulkByScrollResponse.class, headers)
             .next()
-            .map(ByQueryResponse::of);
+            .map(ResponseConverter::byQueryResponseOf);
     }
 
     static XContentType enforceSameContentType(IndexRequest indexRequest, @Nullable XContentType xContentType) {
@@ -605,6 +574,17 @@ public class DefaultReactiveElasticsearchClient implements org.jetlinks.communit
                                                                                         .publishNext();
     }
 
+    @Override
+    public Mono<BulkByScrollResponse> reindex(HttpHeaders headers, ReindexRequest reindexRequest) {
+        return sendRequest(reindexRequest, requestCreator.reindex(), BulkByScrollResponse.class, headers).next();
+    }
+
+    @Override
+    public Mono<String> submitReindex(HttpHeaders headers, ReindexRequest reindexRequest) {
+        return sendRequest(reindexRequest, requestCreator.submitReindex(), TaskSubmissionResponse.class, headers).next()
+                                                                                                                 .map(TaskSubmissionResponse::getTask);
+    }
+
     // --> INDICES
 
     /*
@@ -712,11 +692,25 @@ public class DefaultReactiveElasticsearchClient implements org.jetlinks.communit
     @Override
     public Mono<Boolean> putMapping(HttpHeaders headers, org.elasticsearch.client.indices.PutMappingRequest putMappingRequest) {
 
-        return sendRequest(putMappingRequest, requestCreator.putMappingRequest(), AcknowledgedResponse.class, headers)
+        return sendRequest(putMappingRequest, this::createPutMapping, AcknowledgedResponse.class, headers)
             .map(AcknowledgedResponse::isAcknowledged)
             .next();
     }
 
+    private Request createPutMapping(org.elasticsearch.client.indices.PutMappingRequest putMappingRequest) {
+        Request request = requestCreator.putMappingRequest().apply(putMappingRequest);
+        Request newReq = new Request(request.getMethod(), request.getEndpoint());
+
+        Params params = new Params(newReq)
+            .withTimeout(putMappingRequest.timeout())
+            .withMasterTimeout(putMappingRequest.masterNodeTimeout());
+        if (serverVersion().before(Version.V_7_0_0)) {
+            params.putParam("include_type_name", "false");
+        }
+        newReq.setEntity(request.getEntity());
+        return newReq;
+    }
+
     /*
      * (non-Javadoc)
      * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#flushIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.flush.FlushRequest)
@@ -1167,7 +1161,7 @@ public class DefaultReactiveElasticsearchClient implements org.jetlinks.communit
     @Override
     public Mono<GetMappingsResponse> getMapping(HttpHeaders headers, GetMappingsRequest getMappingsRequest) {
         return sendRequest(getMappingsRequest, requestCreator.getMapping(),
-                           org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse.class, headers).next();
+                           GetMappingsResponse.class, headers).next();
     }
 
     @Override

+ 20 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/ElasticSearchBufferProperties.java

@@ -0,0 +1,20 @@
+package org.jetlinks.community.elastic.search.service.reactive;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jetlinks.community.buffer.BufferProperties;
+import org.jetlinks.community.buffer.BufferProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "elasticsearch.buffer")
+public class ElasticSearchBufferProperties extends BufferProperties {
+    public ElasticSearchBufferProperties() {
+        //固定缓冲文件目录
+        setFilePath("./data/elasticsearch-buffer");
+        setSize(3000);
+    }
+
+    private boolean refreshWhenWrite = false;
+}

+ 45 - 42
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/ReactiveAggregationService.java

@@ -21,16 +21,14 @@ import org.elasticsearch.search.sort.SortOrder;
 import org.hswebframework.ezorm.core.param.QueryParam;
 import org.hswebframework.ezorm.core.param.Term;
 import org.hswebframework.ezorm.core.param.TermType;
+import org.jetlinks.core.metadata.types.DateTimeType;
 import org.jetlinks.community.Interval;
 import org.jetlinks.community.elastic.search.index.ElasticSearchIndexManager;
 import org.jetlinks.community.elastic.search.service.AggregationService;
-import org.jetlinks.community.elastic.search.service.DefaultElasticSearchService;
 import org.jetlinks.community.elastic.search.utils.ElasticSearchConverter;
 import org.jetlinks.community.timeseries.query.*;
-import org.jetlinks.core.metadata.types.DateTimeType;
 import org.jetlinks.reactor.ql.utils.CastUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 import reactor.core.publisher.Flux;
@@ -45,11 +43,9 @@ import java.util.stream.Collectors;
  * @author zhouhao
  * @since 1.5
  **/
-@Service
 @Slf4j
 public class ReactiveAggregationService implements AggregationService {
 
-
     private final ReactiveElasticsearchClient restClient;
 
     private final ElasticSearchIndexManager indexManager;
@@ -108,7 +104,9 @@ public class ReactiveAggregationService implements AggregationService {
                 .terms(group.getAlias())
                 .field(group.getProperty());
             if (group instanceof LimitGroup) {
-                builder.size(((LimitGroup) group).getLimit());
+                if (((LimitGroup) group).getLimit() > 0) {
+                    builder.size(((LimitGroup) group).getLimit());
+                }
             } else {
                 builder.size(100);
             }
@@ -142,8 +140,10 @@ public class ReactiveAggregationService implements AggregationService {
 
         boolean group = aggregationBuilder != null;
         for (AggregationColumn aggColumn : aggregationQueryParam.getAggColumns()) {
-            AggregationBuilder builder = AggType.of(aggColumn.getAggregation().name())
-                                                .aggregationBuilder(aggColumn.getAlias(), aggColumn.getProperty());
+            AggregationBuilder builder = AggType
+                .of(aggColumn.getAggregation().name())
+                .aggregationBuilder(aggColumn.getAlias(), aggColumn.getProperty());
+
             if (builder instanceof TopHitsAggregationBuilder) {
                 TopHitsAggregationBuilder topHitsBuilder = ((TopHitsAggregationBuilder) builder);
                 if (CollectionUtils.isEmpty(queryParam.getSorts())) {
@@ -154,7 +154,9 @@ public class ReactiveAggregationService implements AggregationService {
                                              .stream()
                                              .map(sort -> SortBuilders
                                                  .fieldSort(sort.getName())
-                                                 .order("desc".equalsIgnoreCase(sort.getOrder()) ? SortOrder.DESC : SortOrder.ASC))
+                                                 .order("desc".equalsIgnoreCase(sort.getOrder())
+                                                            ? SortOrder.DESC
+                                                            : SortOrder.ASC))
                                              .collect(Collectors.toList()));
                 }
                 if (aggColumn instanceof LimitAggregationColumn) {
@@ -170,36 +172,37 @@ public class ReactiveAggregationService implements AggregationService {
             }
         }
 
-        return Flux.fromArray(index)
-                   .flatMap(idx -> Mono.zip(indexManager.getIndexStrategy(idx), Mono.just(idx)))
-                   .collectList()
-                   .flatMap(strategy -> this
-                       .createSearchSourceBuilder(queryParam, index[0])
-                       .map(builder -> {
-                                aggs.forEach(builder.size(0)::aggregation);
-                                return new SearchRequest(strategy
-                                                             .stream()
-                                                             .map(tp2 -> tp2
-                                                                 .getT1()
-                                                                 .getIndexForSearch(tp2.getT2()))
-                                                             .toArray(String[]::new))
-                                    .indicesOptions(ReactiveElasticSearchService.indexOptions)
-                                    .source(builder);
-                            }
-                       )
-                   )
-                   .flatMap(restClient::searchForPage)
-                   .flatMapMany(this::parseResult)
-                   .as(flux -> {
-                       if (!group) {
-                           return flux
-                               .map(Map::entrySet)
-                               .flatMap(Flux::fromIterable)
-                               .collectMap(Map.Entry::getKey, Map.Entry::getValue)
-                               .flux();
-                       }
-                       return flux;
-                   })
+        return Flux
+            .fromArray(index)
+            .flatMap(idx -> Mono.zip(indexManager.getIndexStrategy(idx), Mono.just(idx)))
+            .collectList()
+            .flatMap(strategy -> this
+                .createSearchSourceBuilder(queryParam, index[0])
+                .map(builder -> {
+                         aggs.forEach(builder.size(0)::aggregation);
+                         return new SearchRequest(strategy
+                                                      .stream()
+                                                      .map(tp2 -> tp2
+                                                          .getT1()
+                                                          .getIndexForSearch(tp2.getT2()))
+                                                      .toArray(String[]::new))
+                             .indicesOptions(ReactiveElasticSearchService.indexOptions)
+                             .source(builder);
+                     }
+                )
+            )
+            .flatMap(restClient::searchForPage)
+            .flatMapMany(this::parseResult)
+            .as(flux -> {
+                if (!group) {
+                    return flux
+                        .map(Map::entrySet)
+                        .flatMap(Flux::fromIterable)
+                        .collectMap(Map.Entry::getKey, Map.Entry::getValue)
+                        .flux();
+                }
+                return flux;
+            })
             ;
     }
 
@@ -209,7 +212,8 @@ public class ReactiveAggregationService implements AggregationService {
                    .flatMap(agg -> parseAggregation(agg.getName(), agg), Integer.MAX_VALUE);
     }
 
-    private Flux<Map<String, Object>> parseAggregation(String name, org.elasticsearch.search.aggregations.Aggregation aggregation) {
+    private Flux<Map<String, Object>> parseAggregation(String name,
+                                                       org.elasticsearch.search.aggregations.Aggregation aggregation) {
         if (aggregation instanceof Terms) {
             return parseAggregation(((Terms) aggregation));
         }
@@ -310,7 +314,7 @@ public class ReactiveAggregationService implements AggregationService {
         .ofDays(Integer.getInteger("elasticsearch.agg.default-range-day", 90))
         .toMillis();
 
-    private static long calculateStartWithTime(AggregationQueryParam param) {
+    static long calculateStartWithTime(AggregationQueryParam param) {
         long startWithParam = param.getStartWithTime();
         if (startWithParam == 0) {
             //从查询条件中提取时间参数来获取时间区间
@@ -336,5 +340,4 @@ public class ReactiveAggregationService implements AggregationService {
         return startWithParam;
     }
 
-
 }

+ 20 - 10
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/ReactiveElasticSearchService.java

@@ -14,18 +14,19 @@ import org.elasticsearch.action.search.SearchRequest;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.action.support.WriteRequest;
-import org.elasticsearch.common.unit.TimeValue;
-import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.core.TimeValue;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.reindex.BulkByScrollResponse;
 import org.elasticsearch.rest.RestStatus;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.elasticsearch.xcontent.XContentType;
 import org.hswebframework.ezorm.core.param.QueryParam;
 import org.hswebframework.utils.time.DateFormatter;
 import org.hswebframework.utils.time.DefaultDateFormatter;
 import org.hswebframework.web.api.crud.entity.PagerResult;
 import org.hswebframework.web.bean.FastBeanCopier;
+import org.jetlinks.core.utils.SerializeUtils;
 import org.jetlinks.community.buffer.BufferProperties;
 import org.jetlinks.community.buffer.BufferSettings;
 import org.jetlinks.community.buffer.MemoryUsage;
@@ -38,11 +39,9 @@ import org.jetlinks.community.elastic.search.utils.QueryParamTranslator;
 import org.jetlinks.community.utils.ErrorUtils;
 import org.jetlinks.community.utils.ObjectMappers;
 import org.jetlinks.community.utils.SystemUtils;
-import org.jetlinks.core.utils.SerializeUtils;
 import org.reactivestreams.Publisher;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.DependsOn;
-import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 import org.springframework.web.reactive.function.client.WebClientException;
@@ -73,13 +72,8 @@ import java.util.stream.Collectors;
 @Slf4j
 @DependsOn("reactiveElasticsearchClient")
 @ConfigurationProperties(prefix = "elasticsearch")
-@Service
 public class ReactiveElasticSearchService implements ElasticSearchService {
 
-    @Getter
-    @Setter
-    private BufferConfig buffer = new BufferConfig();
-
     @Getter
     private final ReactiveElasticsearchClient restClient;
     @Getter
@@ -96,10 +90,21 @@ public class ReactiveElasticSearchService implements ElasticSearchService {
 
     private PersistenceBuffer<Buffer> writer;
 
+    @Getter
+    @Setter
+    private ElasticSearchBufferProperties buffer;
+
     public ReactiveElasticSearchService(ReactiveElasticsearchClient restClient,
                                         ElasticSearchIndexManager indexManager) {
+        this(restClient, indexManager, new ElasticSearchBufferProperties());
+    }
+
+    public ReactiveElasticSearchService(ReactiveElasticsearchClient restClient,
+                                        ElasticSearchIndexManager indexManager,
+                                        ElasticSearchBufferProperties buffer) {
         this.restClient = restClient;
         this.indexManager = indexManager;
+        this.buffer = buffer;
         init();
     }
 
@@ -269,6 +274,10 @@ public class ReactiveElasticSearchService implements ElasticSearchService {
     }
 
     private boolean checkWritable(String index) {
+//        if (SystemUtils.memoryIsOutOfWatermark()) {
+//            SystemUtils.printError("JVM内存不足,elasticsearch无法处理更多索引[%s]请求!", index);
+//            return false;
+//        }
         return true;
     }
 
@@ -324,6 +333,7 @@ public class ReactiveElasticSearchService implements ElasticSearchService {
         writer.dispose();
     }
 
+
     @Getter
     @Setter
     public static class BufferConfig extends BufferProperties {
@@ -468,7 +478,7 @@ public class ReactiveElasticSearchService implements ElasticSearchService {
             .flatMap(lst -> {
                 BulkRequest request = new BulkRequest();
                 request.timeout(TimeValue.timeValueSeconds(9));
-                if (buffer.refreshWhenWrite) {
+                if (buffer.isRefreshWhenWrite()) {
                     request.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
                 }
                 lst.forEach(request::add);

+ 0 - 1
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/service/reactive/ReactiveElasticsearchClient.java

@@ -28,5 +28,4 @@ public interface ReactiveElasticsearchClient extends
     Mono<AcknowledgedResponse> updateTemplate(PutIndexTemplateRequest request);
 
     Version serverVersion();
-
 }

+ 38 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/things/ElasticSearchColumnModeDDLOperations.java

@@ -0,0 +1,38 @@
+package org.jetlinks.community.elastic.search.things;
+
+import org.jetlinks.core.metadata.PropertyMetadata;
+import org.jetlinks.community.elastic.search.index.DefaultElasticSearchIndexMetadata;
+import org.jetlinks.community.elastic.search.index.ElasticSearchIndexManager;
+import org.jetlinks.community.things.data.operations.ColumnModeDDLOperationsBase;
+import org.jetlinks.community.things.data.operations.DataSettings;
+import org.jetlinks.community.things.data.operations.MetricBuilder;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+class ElasticSearchColumnModeDDLOperations extends ColumnModeDDLOperationsBase {
+
+    private final ElasticSearchIndexManager indexManager;
+
+    public ElasticSearchColumnModeDDLOperations(String thingType,
+                                                String templateId,
+                                                String thingId,
+                                                DataSettings settings,
+                                                MetricBuilder metricBuilder,
+                                                ElasticSearchIndexManager indexManager) {
+        super(thingType, templateId, thingId, settings, metricBuilder);
+        this.indexManager = indexManager;
+    }
+
+    @Override
+    protected Mono<Void> register(MetricType metricType,String metric, List<PropertyMetadata> properties) {
+        return indexManager
+            .putIndex(new DefaultElasticSearchIndexMetadata(metric, properties));
+    }
+
+    @Override
+    protected Mono<Void> reload(MetricType metricType,String metric, List<PropertyMetadata> properties) {
+        return indexManager
+            .putIndex(new DefaultElasticSearchIndexMetadata(metric, properties));
+    }
+}

+ 130 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/things/ElasticSearchColumnModeQueryOperations.java

@@ -0,0 +1,130 @@
+package org.jetlinks.community.elastic.search.things;
+
+import org.hswebframework.ezorm.core.dsl.Query;
+import org.hswebframework.web.api.crud.entity.PagerResult;
+import org.hswebframework.web.api.crud.entity.QueryParamEntity;
+import org.jetlinks.core.things.ThingsRegistry;
+import org.jetlinks.community.elastic.search.service.AggregationService;
+import org.jetlinks.community.elastic.search.service.ElasticSearchService;
+import org.jetlinks.community.things.data.AggregationRequest;
+import org.jetlinks.community.things.data.PropertyAggregation;
+import org.jetlinks.community.things.data.operations.ColumnModeQueryOperationsBase;
+import org.jetlinks.community.things.data.operations.DataSettings;
+import org.jetlinks.community.things.data.operations.MetricBuilder;
+import org.jetlinks.community.timeseries.TimeSeriesData;
+import org.jetlinks.community.timeseries.query.*;
+import org.jetlinks.reactor.ql.utils.CastUtils;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+class ElasticSearchColumnModeQueryOperations extends ColumnModeQueryOperationsBase {
+
+    private final ElasticSearchService searchService;
+    private final AggregationService aggregationService;
+
+    public ElasticSearchColumnModeQueryOperations(String thingType,
+                                                  String thingTemplateId,
+                                                  String thingId,
+                                                  MetricBuilder metricBuilder,
+                                                  DataSettings settings,
+                                                  ThingsRegistry registry,
+                                                  ElasticSearchService service,
+                                                  AggregationService aggregationService) {
+        super(thingType, thingTemplateId, thingId, metricBuilder, settings, registry);
+        this.searchService = service;
+        this.aggregationService = aggregationService;
+    }
+
+    @Override
+    protected Flux<TimeSeriesData> doQuery(String metric, Query<?, QueryParamEntity> query) {
+
+        return searchService
+            .query(metric,
+                   query.getParam(),
+                   data -> {
+                       long ts = CastUtils.castNumber(data.getOrDefault("timestamp", 0L)).longValue();
+                       data.put("timestamp",ts);
+                       return TimeSeriesData.of(ts, data);
+                   });
+    }
+
+    @Override
+    protected <T> Mono<PagerResult<T>> doQueryPage(String metric,
+                                                   Query<?, QueryParamEntity> query,
+                                                   Function<TimeSeriesData, T> mapper) {
+        return searchService
+            .queryPager(metric,
+                        query.getParam(),
+                        data -> {
+                            long ts = CastUtils.castNumber(data.getOrDefault("timestamp", 0L)).longValue();
+                            data.put("timestamp",ts);
+                            return mapper.apply(TimeSeriesData.of(ts, data));
+                        });
+    }
+
+    @Override
+    protected Flux<AggregationData> doAggregation(String metric, AggregationRequest request, AggregationContext context) {
+        org.joda.time.format.DateTimeFormatter formatter = DateTimeFormat.forPattern(request.getFormat());
+        PropertyAggregation[] properties = context.getProperties();
+        return AggregationQueryParam
+            .of()
+            .as(param -> {
+                for (PropertyAggregation property : properties) {
+                    param.agg(property.getProperty(), property.getAlias(), property.getAgg());
+                }
+                return param;
+            })
+            .as(param -> {
+                if (request.getInterval() == null) {
+                    return param;
+                }
+                return param.groupBy((Group) new TimeGroup(request.getInterval(), "time", request.getFormat()));
+            })
+            .limit(request.getLimit() * properties.length)
+            .from(request.getFrom())
+            .to(request.getTo())
+            .filter(request.getFilter())
+            .execute(param -> aggregationService.aggregation(metric, param))
+            .map(AggregationData::of)
+            .groupBy(agg -> agg.getString("time", ""), Integer.MAX_VALUE)
+            .flatMap(group -> group
+                .map(data -> {
+                    Map<String, Object> newMap = new HashMap<>();
+                    newMap.put("time", data.get("time").orElse(null));
+                    for (PropertyAggregation property : properties) {
+                        Object val;
+                        if(property.getAgg() ==Aggregation.FIRST || property.getAgg()==Aggregation.TOP){
+                            val = data
+                                .get(property.getProperty())
+                                .orElse(null);
+                        }else {
+                            val = data
+                                .get(property.getAlias())
+                                .orElse(null);
+                        }
+                        if (null != val) {
+                            newMap.put(property.getAlias(), val);
+                        }
+                    }
+                    return newMap;
+                })
+                .reduce((a, b) -> {
+                    a.putAll(b);
+                    return a;
+                })
+                .map(AggregationData::of))
+            .sort(Comparator.<AggregationData, Date>comparing(agg -> DateTime
+                .parse(agg.getString("time", ""), formatter)
+                .toDate()).reversed())
+            .take(request.getLimit())
+            ;
+    }
+}

+ 33 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/things/ElasticSearchColumnModeSaveOperations.java

@@ -0,0 +1,33 @@
+package org.jetlinks.community.elastic.search.things;
+
+import org.jetlinks.core.things.ThingsRegistry;
+import org.jetlinks.community.elastic.search.service.ElasticSearchService;
+import org.jetlinks.community.things.data.operations.ColumnModeSaveOperationsBase;
+import org.jetlinks.community.things.data.operations.DataSettings;
+import org.jetlinks.community.things.data.operations.MetricBuilder;
+import org.jetlinks.community.timeseries.TimeSeriesData;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+class ElasticSearchColumnModeSaveOperations extends ColumnModeSaveOperationsBase {
+
+    private final ElasticSearchService searchService;
+
+    public ElasticSearchColumnModeSaveOperations(ThingsRegistry registry,
+                                                 MetricBuilder metricBuilder,
+                                                 DataSettings settings,
+                                                 ElasticSearchService searchService) {
+        super(registry, metricBuilder, settings);
+        this.searchService = searchService;
+    }
+
+    @Override
+    protected Mono<Void> doSave(String metric, TimeSeriesData data) {
+        return searchService.commit(metric, data.getData());
+    }
+
+    @Override
+    protected Mono<Void> doSave(String metric, Flux<TimeSeriesData> data) {
+        return searchService.save(metric, data.map(TimeSeriesData::getData));
+    }
+}

+ 66 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/things/ElasticSearchColumnModeStrategy.java

@@ -0,0 +1,66 @@
+package org.jetlinks.community.elastic.search.things;
+
+import lombok.AllArgsConstructor;
+import org.jetlinks.core.things.ThingsRegistry;
+import org.jetlinks.community.elastic.search.index.ElasticSearchIndexManager;
+import org.jetlinks.community.elastic.search.service.AggregationService;
+import org.jetlinks.community.elastic.search.service.ElasticSearchService;
+import org.jetlinks.community.things.data.AbstractThingDataRepositoryStrategy;
+import org.jetlinks.community.things.data.operations.*;
+
+@AllArgsConstructor
+public class ElasticSearchColumnModeStrategy extends AbstractThingDataRepositoryStrategy {
+
+    private final ThingsRegistry registry;
+    private final ElasticSearchService searchService;
+    private final AggregationService aggregationService;
+    private final ElasticSearchIndexManager indexManager;
+
+    @Override
+    public String getId() {
+        return "default-column";
+    }
+
+    @Override
+    public String getName() {
+        return "ElasticSearch-列式存储";
+    }
+
+    @Override
+    public SaveOperations createOpsForSave(OperationsContext context) {
+        return new ElasticSearchColumnModeSaveOperations(
+            registry,
+            context.getMetricBuilder(),
+            context.getSettings(),
+            searchService);
+    }
+
+    @Override
+    protected QueryOperations createForQuery(String thingType, String templateId, String thingId, OperationsContext context) {
+        return new ElasticSearchColumnModeQueryOperations(
+            thingType,
+            templateId,
+            thingId,
+            context.getMetricBuilder(),
+            context.getSettings(),
+            registry,
+            searchService,
+            aggregationService);
+    }
+
+    @Override
+    protected DDLOperations createForDDL(String thingType, String templateId, String thingId, OperationsContext context) {
+        return new ElasticSearchColumnModeDDLOperations(
+            thingType,
+            templateId,
+            thingId,
+            context.getSettings(),
+            context.getMetricBuilder(),
+            indexManager);
+    }
+
+    @Override
+    public int getOrder() {
+        return 10001;
+    }
+}

+ 0 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/things/ElasticSearchRowModeDDLOperations.java


Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů