18339543638 před 4 roky
revize
5c623a441a
100 změnil soubory, kde provedl 5557 přidání a 0 odebrání
  1. 12 0
      .editorconfig
  2. 35 0
      .gitignore
  3. binární
      .mvn/wrapper/maven-wrapper.jar
  4. 1 0
      .mvn/wrapper/maven-wrapper.properties
  5. 204 0
      LICENSE
  6. 69 0
      README.md
  7. 10 0
      build-and-push-docker.sh
  8. 50 0
      docker/dev-env/docker-compose.yml
  9. 51 0
      docker/run-all/docker-compose-embedded.yml
  10. 101 0
      docker/run-all/docker-compose.yml
  11. 0 0
      flow.svg
  12. binární
      idea/settings.zip
  13. 42 0
      jetlinks-components/common-component/pom.xml
  14. 19 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/ConfigMetadataConstants.java
  15. 126 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/Interval.java
  16. 48 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/PropertyConstants.java
  17. 85 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/PropertyMetadataConstants.java
  18. 105 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/ValueObject.java
  19. 13 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/Version.java
  20. 119 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/configuration/CommonConfiguration.java
  21. 29 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/configuration/SmartDateDeserializer.java
  22. 40 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistryManager.java
  23. 9 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistrySupplier.java
  24. 171 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/utils/DateMathParser.java
  25. 21 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/utils/ErrorUtils.java
  26. 91 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/utils/MessageTypeMatcher.java
  27. 83 0
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/utils/TimeUtils.java
  28. 95 0
      jetlinks-components/dashboard-component/README.md
  29. 35 0
      jetlinks-components/dashboard-component/pom.xml
  30. 25 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/CommonDimensionDefinition.java
  31. 27 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/CommonMeasurementDefinition.java
  32. 14 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/Dashboard.java
  33. 5 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/DashboardDefinition.java
  34. 12 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/DashboardManager.java
  35. 17 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/DashboardObject.java
  36. 32 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/DefaultDashboardDefinition.java
  37. 10 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/Definition.java
  38. 19 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/DimensionDefinition.java
  39. 31 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/Measurement.java
  40. 19 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/MeasurementDefinition.java
  41. 24 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/MeasurementDimension.java
  42. 29 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/MeasurementParameter.java
  43. 38 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/MeasurementValue.java
  44. 5 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/ObjectDefinition.java
  45. 28 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/SimpleMeasurementValue.java
  46. 81 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/JvmCpuMeasurementProvider.java
  47. 132 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/JvmMemoryMeasurementProvider.java
  48. 20 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/MonitorObjectDefinition.java
  49. 79 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/SystemCpuMeasurementProvider.java
  50. 128 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/SystemMemoryMeasurementProvider.java
  51. 85 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/SystemMonitor.java
  52. 60 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/CompositeDashboard.java
  53. 45 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/CompositeDashboardObject.java
  54. 42 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/CompositeMeasurement.java
  55. 58 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/DefaultDashboardManager.java
  56. 33 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/MeasurementProvider.java
  57. 42 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/StaticMeasurement.java
  58. 41 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/StaticMeasurementProvider.java
  59. 107 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/DashboardController.java
  60. 58 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/request/DashboardMeasurementRequest.java
  61. 34 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/response/DashboardInfo.java
  62. 20 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/response/DashboardMeasurementResponse.java
  63. 31 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/response/DimensionInfo.java
  64. 35 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/response/MeasurementInfo.java
  65. 24 0
      jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/response/ObjectInfo.java
  66. 82 0
      jetlinks-components/elasticsearch-component/pom.xml
  67. 148 0
      jetlinks-components/elasticsearch-component/src/main/java/org/elasticsearch/common/logging/Loggers.java
  68. 20 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/ElasticRestClient.java
  69. 268 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/DefaultAggregationService.java
  70. 171 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/AggregationResponseHandle.java
  71. 70 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/Bucket.java
  72. 70 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/BucketAggregationsStructure.java
  73. 21 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/BucketResponse.java
  74. 38 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/DateHistogramInterval.java
  75. 19 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/Ranges.java
  76. 54 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/Sort.java
  77. 28 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/enums/AggregationType.java
  78. 199 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/enums/BucketType.java
  79. 136 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/enums/MetricsType.java
  80. 18 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/enums/OrderType.java
  81. 37 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/metrics/MetricsAggregationStructure.java
  82. 33 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/metrics/MetricsResponse.java
  83. 47 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/metrics/MetricsResponseSingleValue.java
  84. 157 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/configuration/ElasticSearchConfiguration.java
  85. 51 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/configuration/ElasticSearchProperties.java
  86. 33 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/embedded/EmbeddedElasticSearch.java
  87. 30 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/embedded/EmbeddedElasticSearchProperties.java
  88. 54 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/enums/ElasticDateFormat.java
  89. 66 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/enums/ElasticPropertyType.java
  90. 69 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/enums/LinkTypeEnum.java
  91. 114 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/enums/TermTypeEnum.java
  92. 68 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/DefaultElasticSearchIndexManager.java
  93. 53 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/DefaultElasticSearchIndexMetadata.java
  94. 9 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/ElasticIndex.java
  95. 65 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/ElasticSearchIndexManager.java
  96. 28 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/ElasticSearchIndexMetadata.java
  97. 26 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/ElasticSearchIndexProperties.java
  98. 45 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/ElasticSearchIndexStrategy.java
  99. 12 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/IndexTemplateProvider.java
  100. 64 0
      jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/mapping/IndexMappingMetadata.java

+ 12 - 0
.editorconfig

@@ -0,0 +1,12 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+
+[*.java]
+indent_style = space
+indent_size = tab
+tab_width = 4
+trim_trailing_whitespace = true
+insert_final_newline = false

+ 35 - 0
.gitignore

@@ -0,0 +1,35 @@
+
+
+
+
+
+**/pom.xml.versionsBackup
+**/target/
+**/out/
+*.class
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+.idea/
+/nbproject
+*.ipr
+*.iws
+*.iml
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.log
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+**/transaction-logs/
+!/.mvn/wrapper/maven-wrapper.jar
+*.db
+./data/
+/static/
+/upload
+/ui/upload/
+docker/data
+!device-simulator.jar
+!demo-protocol-1.0.jar
+application-local.yml

binární
.mvn/wrapper/maven-wrapper.jar


+ 1 - 0
.mvn/wrapper/maven-wrapper.properties

@@ -0,0 +1 @@
+distributionUrl=http://mirrors.hust.edu.cn/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.zip

+ 204 - 0
LICENSE

@@ -0,0 +1,204 @@
+
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 69 - 0
README.md

@@ -0,0 +1,69 @@
+# linked-everything
+
+驼人物联网平台
+基于Jetlinks 二次开发
+拟支持mqtt coap、 tcp udp 等协议
+![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.9--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)
+[![QQ①群2021514](https://img.shields.io/badge/QQ①群-2021514-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=LGf0OPQqvLGdJIZST3VTcypdVWhdfAOG&jump_from=webapi)
+[![QQ②群324606263](https://img.shields.io/badge/QQ②群-324606263-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=IMas2cH-TNsYxUcY8lRbsXqPnA2sGHYQ&jump_from=webapi)
+![jetlinks](https://visitor-badge.glitch.me/badge?page_id=jetlinks)
+
+JetLinks 基于Java8,Spring Boot 2.x,WebFlux,Netty,Vert.x,Reactor等开发, 
+是一个开箱即用,可二次开发的企业级物联网基础平台。平台实现了物联网相关的众多基础功能,
+能帮助你快速建立物联网相关业务系统。
+ 
+
+## 核心特性
+
+支持统一物模型管理,多种设备,多种厂家,统一管理。
+
+统一设备连接管理,多协议适配(TCP,MQTT,UDP,CoAP,HTTP等),屏蔽网络编程复杂性,灵活接入不同厂家不同协议的设备。
+
+灵活的规则引擎,设备告警,消息通知,数据转发.
+
+强大的ReactorQL引擎,使用SQL来处理实时数据.
+
+地理位置:统一管理地理位置信息,支持区域搜索. 
+
+官方QQ: ①群 [2021514](https://qm.qq.com/cgi-bin/qm/qr?k=LGf0OPQqvLGdJIZST3VTcypdVWhdfAOG&jump_from=webapi)
+, ②群 [324606263](https://qm.qq.com/cgi-bin/qm/qr?k=IMas2cH-TNsYxUcY8lRbsXqPnA2sGHYQ&jump_from=webapi)
+
+## 技术栈
+
+1. [Spring Boot 2.3.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/) 高性能网络编程框架
+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)
+
+## 设备接入流程
+
+![flow](./flow.svg)
+
+## 模块
+
+```bash
+--jetlinks-community
+------|----docker
+------|------|----dev-env       # 启动开发环境
+------|------|----run-all       # 启动全部,通过http://localhost:9000 访问系统.
+------|----jetlinks-components  # 公共组件模块
+------|----jetlinks-manager     # 业务管理模块
+------|----jetlinks-standalone  # 服务启动模块
+------|----simulator            # 设备模拟器
+```
+
+## 文档
+
+[快速开始](http://doc.jetlinks.cn/basics-guide/quick-start.html) 
+[开发文档](http://doc.jetlinks.cn/dev-guide/start.html) 
+[常见问题](http://doc.jetlinks.cn/common-problems/network-components.html) 

+ 10 - 0
build-and-push-docker.sh

@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+dockerImage=registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
+./mvnw clean package -Dmaven.test.skip=true -Dmaven.build.timestamp="$(date "+%Y-%m-%d %H:%M:%S")"
+if [ $? -ne 0 ];then
+    echo "构建失败!"
+else
+  cd ./jetlinks-standalone || exit
+  docker build -t "$dockerImage" . && docker push "$dockerImage"
+fi

+ 50 - 0
docker/dev-env/docker-compose.yml

@@ -0,0 +1,50 @@
+version: '2'
+services:
+  redis:
+    image: redis:5.0.4
+    container_name: jetlinks-ce-redis
+    ports:
+      - "6379:6379"
+    volumes:
+      - "redis-volume:/data"
+    command: redis-server --appendonly yes
+    environment:
+      - TZ=Asia/Shanghai
+  elasticsearch:
+    image: elasticsearch:6.8.11
+    container_name: jetlinks-ce-elasticsearch
+    environment:
+      ES_JAVA_OPTS: -Djava.net.preferIPv4Stack=true -Xms1g -Xmx1g
+      transport.host: 0.0.0.0
+      discovery.type: single-node
+      bootstrap.memory_lock: "true"
+      discovery.zen.minimum_master_nodes: 1
+      discovery.zen.ping.unicast.hosts: elasticsearch
+    ports:
+      - "9200:9200"
+      - "9300:9300"
+  kibana:
+    image: kibana:6.8.11
+    container_name: jetlinks-ce-kibana
+    environment:
+      ELASTICSEARCH_URL: http://elasticsearch:9200
+    links:
+      - elasticsearch:elasticsearch
+    ports:
+      - "5601:5601"
+    depends_on:
+      - elasticsearch
+  postgres:
+    image: postgres:11-alpine
+    container_name: jetlinks-ce-postgres
+    ports:
+      - "5432:5432"
+    volumes:
+      - "postgres-volume:/var/lib/postgresql/data"
+    environment:
+      POSTGRES_PASSWORD: jetlinks
+      POSTGRES_DB: jetlinks
+      TZ: Asia/Shanghai
+volumes:
+  postgres-volume:
+  redis-volume:

+ 51 - 0
docker/run-all/docker-compose-embedded.yml

@@ -0,0 +1,51 @@
+version: '2'
+services:
+  redis:
+    image: redis:5.0.4
+    container_name: jetlinks-ce-redis
+    #    ports:
+    #      - "6379:6379"
+    volumes:
+      - "redis-volume:/data"
+    command: redis-server --appendonly yes
+    environment:
+      - TZ=Asia/Shanghai
+  ui:
+    image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-antd:1.4.0
+    container_name: jetlinks-ce-ui
+    ports:
+      - 9000:80
+    environment:
+      - "API_BASE_PATH=http://jetlinks:8848/" #API根路径
+    volumes:
+      - "jetlinks-upload-volume:/usr/share/nginx/html/upload"
+    links:
+      - jetlinks:jetlinks
+  jetlinks:
+    image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:1.4.0
+    container_name: jetlinks-ce
+    ports:
+      - 8848:8848 # API端口
+      - 1883:1883 # MQTT端口
+      - 8000:8000 # 预留
+      - 8001:8001 # 预留
+      - 8002:8002 # 预留
+      - 9000:9000 # elasticsearch
+      - 6379:6379 # redis
+    volumes:
+      - "jetlinks-upload-volume:/static/upload"  # 持久化上传的文件
+      - "jetlinks-data-volume:/data"
+    environment:
+     # - "JAVA_OPTS=-Xms4g -Xmx18g -XX:+UseG1GC"
+      - "spring.profiles.active=dev,embedded" #使用dev和embedded环境.
+      - "hsweb.file.upload.static-location=http://127.0.0.1:8848/upload"  #上传的静态文件访问根地址,为ui的地址.
+      - "logging.level.io.r2dbc=warn"
+      - "spring.redis.host=redis"
+      - "logging.level.org.springframework.data=warn"
+      - "logging.level.org.springframework=warn"
+      - "logging.level.org.jetlinks=warn"
+      - "logging.level.org.hswebframework=warn"
+      - "logging.level.org.springframework.data.r2dbc.connectionfactory=warn"
+volumes:
+  jetlinks-upload-volume:
+  jetlinks-data-volume:

+ 101 - 0
docker/run-all/docker-compose.yml

@@ -0,0 +1,101 @@
+version: '2'
+services:
+  redis:
+    image: redis:5.0.4
+    container_name: jetlinks-ce-redis
+    #    ports:
+    #      - "6379:6379"
+    volumes:
+      - "redis-volume:/data"
+    command: redis-server --appendonly yes
+    environment:
+      - TZ=Asia/Shanghai
+  elasticsearch:
+    image: elasticsearch:6.8.11
+    container_name: jetlinks-ce-elasticsearch
+    environment:
+      ES_JAVA_OPTS: -Djava.net.preferIPv4Stack=true -Xms1g -Xmx1g
+      transport.host: 0.0.0.0
+      discovery.type: single-node
+      bootstrap.memory_lock: "true"
+      discovery.zen.minimum_master_nodes: 1
+      discovery.zen.ping.unicast.hosts: elasticsearch
+    volumes:
+      - elasticsearch-volume:/usr/share/elasticsearch/data
+  #    ports:
+  #      - "9200:9200"
+  #      - "9300:9300"
+  kibana:
+    image: kibana:6.8.11
+    container_name: jetlinks-ce-kibana
+    environment:
+      ELASTICSEARCH_URL: http://elasticsearch:9200
+    links:
+      - elasticsearch:elasticsearch
+    ports:
+      - "5602:5601"
+    depends_on:
+      - elasticsearch
+  postgres:
+    image: postgres:11-alpine
+    container_name: jetlinks-ce-postgres
+    volumes:
+      - "postgres-volume:/var/lib/postgresql/data"
+    ports:
+      - "5432:5432"
+    environment:
+      POSTGRES_PASSWORD: jetlinks
+      POSTGRES_DB: jetlinks
+      TZ: Asia/Shanghai
+  ui:
+    image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-antd:1.10.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"
+    links:
+      - jetlinks:jetlinks
+  jetlinks:
+    image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:1.10.0-SNAPSHOT
+    container_name: jetlinks-ce
+    ports:
+      - 8848:8848 # API端口
+      - 1883-1890:1883-1890 # 预留
+      - 8000-8010:8000-8010 # 预留
+    volumes:
+      - "jetlinks-volume:/application/static/upload"  # 持久化上传的文件
+      - "jetlinks-protocol-volume:/application/data/protocols"
+    environment:
+     # - "JAVA_OPTS=-Xms4g -Xmx18g -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"
+      - "elasticsearch.client.host=elasticsearch"
+      - "elasticsearch.client.post=9200"
+      - "spring.redis.host=redis"
+      - "spring.redis.port=6379"
+      - "logging.level.io.r2dbc=warn"
+      - "logging.level.org.springframework.data=warn"
+      - "logging.level.org.springframework=warn"
+      - "logging.level.org.jetlinks=warn"
+      - "logging.level.org.hswebframework=warn"
+      - "logging.level.org.springframework.data.r2dbc.connectionfactory=warn"
+    links:
+      - redis:redis
+      - postgres:postgres
+      - elasticsearch:elasticsearch
+    depends_on:
+      - postgres
+      - redis
+      - elasticsearch
+volumes:
+  postgres-volume:
+  redis-volume:
+  elasticsearch-volume:
+  jetlinks-volume:
+  jetlinks-protocol-volume:

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


binární
idea/settings.zip


+ 42 - 0
jetlinks-components/common-component/pom.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>jetlinks-components</artifactId>
+        <groupId>org.jetlinks.community</groupId>
+        <version>1.10.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>common-component</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jetlinks</groupId>
+            <artifactId>jetlinks-core</artifactId>
+            <version>${jetlinks.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-authorization-api</artifactId>
+            <version>${hsweb.framework.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-starter</artifactId>
+            <version>${hsweb.framework.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jetlinks</groupId>
+            <artifactId>reactor-ql</artifactId>
+        </dependency>
+    </dependencies>
+</project>

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

@@ -0,0 +1,19 @@
+package org.jetlinks.community;
+
+import org.jetlinks.core.config.ConfigKey;
+
+/**
+ * @see ConfigKey
+ */
+public interface ConfigMetadataConstants {
+
+    //字符串相关配置
+    ConfigKey<Long> maxLength = ConfigKey.of("maxLength", "字符串最大长度", Long.TYPE);
+    ConfigKey<Boolean> isRichText = ConfigKey.of("isRichText", "是否为富文本", Boolean.TYPE);
+    ConfigKey<Boolean> isScript = ConfigKey.of("isScript", "是否为脚本", Boolean.TYPE);
+
+    ConfigKey<Boolean> allowInput = ConfigKey.of("allowInput", "允许输入", Boolean.TYPE);
+    ConfigKey<Boolean> required = ConfigKey.of("required", "是否必填", Boolean.TYPE);
+
+
+}

+ 126 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/Interval.java

@@ -0,0 +1,126 @@
+package org.jetlinks.community;
+
+import com.alibaba.fastjson.annotation.JSONType;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.SneakyThrows;
+
+import java.math.BigDecimal;
+
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonDeserialize(using = Interval.IntervalJSONDeserializer.class)
+@JSONType(deserializer = Interval.IntervalJSONDeserializer.class)
+public class Interval {
+
+    public static final String year = "y";
+    public static final String quarter = "q";
+    public static final String month = "M";
+    public static final String weeks = "w";
+    public static final String days = "d";
+    public static final String hours = "h";
+    public static final String minutes = "m";
+    public static final String seconds = "s";
+
+    private BigDecimal number;
+
+    private String expression;
+
+    public boolean isFixed() {
+        return expression.equalsIgnoreCase(hours) ||
+            expression.equals(minutes) ||
+            expression.equals(seconds);
+    }
+
+    public boolean isCalendar() {
+        return expression.equals(days) ||
+            expression.equals(month) ||
+            expression.equals(year);
+    }
+
+    @Override
+    public String toString() {
+        return (number) + expression;
+    }
+
+    public static Interval ofSeconds(int seconds) {
+        return of(seconds, Interval.seconds);
+    }
+
+    public static Interval ofDays(int days) {
+        return of(days, Interval.days);
+    }
+
+    public static Interval ofHours(int hours) {
+        return of(hours, Interval.hours);
+    }
+
+    public static Interval ofMonth(int month) {
+        return of(month, Interval.month);
+    }
+
+    public static Interval of(int month, String expression) {
+        return new Interval(new BigDecimal(month), expression);
+    }
+
+    public static Interval of(String expr) {
+
+        char[] chars = expr.toCharArray();
+        int numIndex = 0;
+        for (char c : expr.toCharArray()) {
+            if (c == '-' || c == '.' || (c >= '0' && c <= '9')) {
+                numIndex++;
+            } else {
+                BigDecimal val = new BigDecimal(chars, 0, numIndex);
+                return new Interval(val, expr.substring(numIndex));
+            }
+
+        }
+
+        throw new IllegalArgumentException("can not parse interval expression:" + expr);
+    }
+
+    public String getDefaultFormat() {
+        switch (getExpression()) {
+            case year:
+                return "yyyy";
+            case quarter:
+            case month:
+                return "yyyy-MM";
+            case days:
+                return "yyyy-MM-dd";
+            case hours:
+                return "MM-dd HH";
+            case minutes:
+                return "MM-dd HH:mm";
+            case seconds:
+                return "HH:mm:ss";
+            default:
+                return "yyyy-MM-dd HH:mm:ss";
+        }
+    }
+
+    public static class IntervalJSONDeserializer extends JsonDeserializer<Interval> {
+
+        @Override
+        @SneakyThrows
+        public Interval deserialize(JsonParser jp, DeserializationContext ctxt) {
+            JsonNode node = jp.getCodec().readTree(jp);
+
+            String currentName = jp.currentName();
+            Object currentValue = jp.getCurrentValue();
+            if (currentName == null || currentValue == null) {
+                return null;
+            }
+            return of(node.textValue());
+        }
+    }
+
+}

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

@@ -0,0 +1,48 @@
+package org.jetlinks.community;
+
+import org.jetlinks.core.config.ConfigKey;
+import org.jetlinks.core.message.HeaderKey;
+
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author wangzheng
+ * @since 1.0
+ */
+public interface PropertyConstants {
+    Key<String> orgId = Key.of("orgId");
+
+    Key<String> deviceName = Key.of("deviceName");
+
+    Key<String> productId = Key.of("productId");
+
+
+    @SuppressWarnings("all")
+    static <T> Optional<T> getFromMap(ConfigKey<T> key, Map<String, Object> map) {
+        return Optional.ofNullable((T) map.get(key.getKey()));
+    }
+
+    interface Key<V> extends ConfigKey<V>, HeaderKey<V> {
+
+        @Override
+        default Class<V> getType() {
+            return ConfigKey.super.getType();
+        }
+
+        static <T> Key<T> of(String key) {
+            return new Key<T>() {
+                @Override
+                public String getKey() {
+                    return key;
+                }
+
+                @Override
+                public T getDefaultValue() {
+                    return null;
+                }
+            };
+        }
+
+    }
+}

+ 85 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/PropertyMetadataConstants.java

@@ -0,0 +1,85 @@
+package org.jetlinks.community;
+
+import org.jetlinks.core.metadata.PropertyMetadata;
+import org.jetlinks.reactor.ql.utils.CastUtils;
+
+public interface PropertyMetadataConstants {
+
+    /**
+     * 属性来源
+     */
+    interface Source {
+        //数据来源
+        String id = "source";
+
+        //手动写值
+        String manual = "manual";
+
+        //规则,虚拟属性
+        String rule = "rule";
+
+        /**
+         * 判断属性是否手动赋值
+         *
+         * @param metadata 属性物模型
+         * @return 是否手动赋值
+         */
+        static boolean isManual(PropertyMetadata metadata) {
+            return metadata.getExpand(id)
+                           .map(manual::equals)
+                           .orElse(false);
+        }
+
+        /**
+         * 判断属性是否为规则
+         *
+         * @param metadata 物模型
+         * @return 是否规则
+         */
+        static boolean isRule(PropertyMetadata metadata) {
+            return  metadata
+                .getExpand(id)
+                .map(rule::equals)
+                .orElse(false);
+        }
+    }
+
+    /**
+     * 属性读写模式
+     */
+    interface AccessMode {
+        String id = "accessMode";
+
+        //读
+        String read = "r";
+        //写
+        String write = "w";
+        //上报
+        String report = "u";
+
+        static boolean isRead(PropertyMetadata property) {
+            return property
+                .getExpand(id)
+                .map(val -> val.toString().contains(read))
+                .orElse(true);
+        }
+
+        static boolean isWrite(PropertyMetadata property) {
+            return property
+                .getExpand(id)
+                .map(val -> val.toString().contains(write))
+                .orElseGet(() -> property
+                    .getExpand("readOnly")
+                    .map(readOnly -> !CastUtils.castBoolean(readOnly))
+                    .orElse(true)
+                );
+        }
+
+        static boolean isReport(PropertyMetadata property) {
+            return property
+                .getExpand(id)
+                .map(val -> val.toString().contains(report))
+                .orElse(true);
+        }
+    }
+}

+ 105 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/ValueObject.java

@@ -0,0 +1,105 @@
+package org.jetlinks.community;
+
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.jetlinks.community.utils.TimeUtils;
+import org.springframework.util.StringUtils;
+
+import java.time.Duration;
+import java.util.Date;
+import java.util.Map;
+import java.util.Optional;
+
+public interface ValueObject {
+
+    Map<String, Object> values();
+
+    default Optional<Object> get(String name) {
+        return Optional.ofNullable(values())
+            .map(map -> map.get(name));
+    }
+
+    default Optional<Integer> getInt(String name) {
+        return get(name, Integer.class);
+    }
+
+    default int getInt(String name, int defaultValue) {
+        return getInt(name).orElse(defaultValue);
+    }
+
+    default Optional<Long> getLong(String name) {
+        return get(name, Long.class);
+    }
+
+    default long getLong(String name, long defaultValue) {
+        return getLong(name).orElse(defaultValue);
+    }
+
+    default Optional<Duration> getDuration(String name) {
+        return getString(name)
+            .map(TimeUtils::parse);
+    }
+
+    default Optional<Interval> getInterval(String name) {
+        return getString(name)
+            .map(Interval::of);
+    }
+
+    default Interval getInterval(String name,Interval defaultValue) {
+        return getString(name)
+            .map(Interval::of)
+            .orElse(defaultValue);
+    }
+
+    default Duration getDuration(String name, Duration defaultValue) {
+        return getDuration(name)
+            .orElse(defaultValue);
+    }
+
+    default Optional<Date> getDate(String name) {
+        return get(name)
+            .map(String::valueOf)
+            .map(TimeUtils::parseDate);
+    }
+
+    default Date getDate(String name, Date defaultValue) {
+        return getDate(name).orElse(defaultValue);
+    }
+
+    default Optional<Double> getDouble(String name) {
+        return get(name, Double.class);
+    }
+
+    default double getDouble(String name, double defaultValue) {
+        return getDouble(name).orElse(defaultValue);
+    }
+
+    default Optional<String> getString(String name) {
+        return get(name, String.class)
+            .filter(StringUtils::hasText);
+    }
+
+    default String getString(String name, String defaultValue) {
+        return getString(name).orElse(defaultValue);
+    }
+
+    default Optional<Boolean> getBoolean(String name) {
+        return get(name, Boolean.class);
+    }
+
+    default boolean getBoolean(String name, boolean defaultValue) {
+        return getBoolean(name).orElse(defaultValue);
+    }
+
+    default <T> Optional<T> get(String name, Class<T> type) {
+        return get(name)
+            .map(obj -> FastBeanCopier.DEFAULT_CONVERT.convert(obj, type, FastBeanCopier.EMPTY_CLASS_ARRAY));
+    }
+
+    static ValueObject of(Map<String, Object> mapVal) {
+        return () -> mapVal;
+    }
+
+    default <T> T as(Class<T> type) {
+        return FastBeanCopier.copy(values(), type);
+    }
+}

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

@@ -0,0 +1,13 @@
+package org.jetlinks.community;
+
+import lombok.Getter;
+
+@Getter
+public class Version {
+    public static Version current = new Version();
+
+    private final String edition = "community";
+
+    private final String version = "1.9.0";
+
+}

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

@@ -0,0 +1,119 @@
+package org.jetlinks.community.configuration;
+
+import com.alibaba.fastjson.JSON;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.apache.commons.beanutils.BeanUtilsBean;
+import org.apache.commons.beanutils.Converter;
+import org.jetlinks.community.Interval;
+import org.jetlinks.community.utils.TimeUtils;
+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.config.BeanPostProcessor;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.util.unit.DataSize;
+
+import javax.annotation.Nonnull;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+
+@Configuration
+@SuppressWarnings("all")
+public class CommonConfiguration {
+
+    static {
+        BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
+            @Override
+            public <T> T convert(Class<T> aClass, Object o) {
+                if (o instanceof String) {
+                    o = ((String) o).getBytes();
+                }
+                if (o instanceof byte[]) {
+                    o = Unpooled.wrappedBuffer(((byte[]) o));
+                }
+                if (o instanceof ByteBuf) {
+                    return (T) o;
+                }
+                return convert(aClass, JSON.toJSONBytes(o));
+            }
+        }, ByteBuf.class);
+
+        BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
+            @Override
+            public <T> T convert(Class<T> aClass, Object o) {
+                return (T) MediaType.valueOf(String.valueOf(o));
+            }
+        }, MediaType.class);
+
+        BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
+            @Override
+            public <T> T convert(Class<T> type, Object value) {
+                return (T)DataSize.parse(String.valueOf(value));
+            }
+        }, DataSize.class);
+
+        BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
+            @Override
+            public <T> T convert(Class<T> type, Object value) {
+                return (T) TimeUtils.parse(String.valueOf(value));
+            }
+        }, Duration.class);
+
+        BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
+            @Override
+            public <T> T convert(Class<T> type, Object value) {
+                return (T) Interval.of(String.valueOf(value));
+            }
+        }, Interval.class);
+
+        BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
+            @Override
+            public <T> T convert(Class<T> type, Object value) {
+                return (T) TimeUtils.parseUnit(String.valueOf(value));
+            }
+        }, ChronoUnit.class);
+
+        BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
+            @Override
+            public <T> T convert(Class<T> type, Object value) {
+
+                return (T)((Long)CastUtils.castNumber(value).longValue());
+            }
+        }, long.class);
+
+        BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
+            @Override
+            public <T> T convert(Class<T> type, Object value) {
+
+                return (T)((Long) CastUtils.castNumber(value).longValue());
+            }
+        }, Long.class);
+    }
+
+    @Bean
+    public BeanPostProcessor globalReactorQlFeatureRegister() {
+        return new BeanPostProcessor() {
+            @Override
+            public Object postProcessAfterInitialization(@Nonnull Object bean, @Nonnull String beanName) throws BeansException {
+                if (bean instanceof Feature) {
+                    DefaultReactorQLMetadata.addGlobal(((Feature) bean));
+                }
+                return bean;
+            }
+        };
+    }
+
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
+        return builder->{
+            builder.deserializerByType(Date.class,new SmartDateDeserializer());
+        };
+    }
+
+}

+ 29 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/configuration/SmartDateDeserializer.java

@@ -0,0 +1,29 @@
+package org.jetlinks.community.configuration;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import lombok.SneakyThrows;
+import org.jetlinks.community.utils.TimeUtils;
+
+import java.util.Date;
+
+public class SmartDateDeserializer extends JsonDeserializer<Date> {
+    @Override
+    @SneakyThrows
+    public Date deserialize(JsonParser p, DeserializationContext ctxt) {
+        if (p.hasToken(JsonToken.VALUE_STRING)) {
+            String str = p.getText().trim();
+            if (str.length() == 0) {
+                return (Date) getEmptyValue(ctxt);
+            }
+            return TimeUtils.parseDate(str);
+        }
+        if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+            long ts = p.getLongValue();
+            return new Date(ts);
+        }
+        return null;
+    }
+}

+ 40 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistryManager.java

@@ -0,0 +1,40 @@
+package org.jetlinks.community.micrometer;
+
+import io.micrometer.core.instrument.Clock;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+import lombok.Setter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * @author bsetfeng
+ * @author wanghzeng
+ * @since 1.0
+ **/
+@Component
+@Setter
+public class MeterRegistryManager {
+
+    private Map<String, MeterRegistry> meterRegistryMap = new ConcurrentHashMap<>();
+
+    @Autowired
+    private List<MeterRegistrySupplier> suppliers;
+
+    private MeterRegistry createMeterRegistry(String metric, String... tagKeys) {
+        return new CompositeMeterRegistry(Clock.SYSTEM,
+            suppliers.stream()
+                .map(supplier -> supplier.getMeterRegistry(metric, tagKeys))
+                .collect(Collectors.toList()));
+    }
+
+    public MeterRegistry getMeterRegister(String metric, String... tagKeys) {
+        return meterRegistryMap.computeIfAbsent(metric, _metric -> createMeterRegistry(_metric, tagKeys));
+    }
+
+}

+ 9 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistrySupplier.java

@@ -0,0 +1,9 @@
+package org.jetlinks.community.micrometer;
+
+import io.micrometer.core.instrument.MeterRegistry;
+
+public interface MeterRegistrySupplier {
+
+    MeterRegistry getMeterRegistry(String metric, String... tagKeys);
+
+}

+ 171 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/utils/DateMathParser.java

@@ -0,0 +1,171 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jetlinks.community.utils;
+
+
+import lombok.SneakyThrows;
+import org.jetlinks.core.metadata.types.DateTimeType;
+import org.springframework.util.StringUtils;
+
+import java.time.*;
+import java.time.temporal.TemporalAdjusters;
+import java.util.function.LongSupplier;
+
+
+public class DateMathParser {
+
+
+    @SneakyThrows
+    public static long parse(String text, LongSupplier now) {
+        long time;
+        String mathString;
+        if (text.startsWith("now()")) {
+            time = now.getAsLong();
+            mathString = text.substring("now()".length());
+        } else if (text.startsWith("now")) {
+            time = now.getAsLong();
+            mathString = text.substring("now".length());
+        } else {
+            int index = text.indexOf("||");
+            if (index == -1) {
+                return parseDateTime(text);
+            }
+            time = parseDateTime(text.substring(0, index));
+            mathString = text.substring(index + 2);
+        }
+
+        return parseMath(mathString, time);
+    }
+
+    private static long parseMath(final String mathString, final long time) {
+        ZonedDateTime dateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault());
+        for (int i = 0; i < mathString.length(); ) {
+            char c = mathString.charAt(i++);
+            final boolean round;
+            final int sign;
+            if (c == '/') {
+                round = true;
+                sign = 1;
+            } else {
+                round = false;
+                if (c == '+') {
+                    sign = 1;
+                } else if (c == '-') {
+                    sign = -1;
+                } else {
+                    throw new IllegalArgumentException("不支持的表达式:" + mathString);
+                }
+            }
+
+            if (i >= mathString.length()) {
+                throw new IllegalArgumentException("不支持的表达式:" + mathString);
+            }
+
+            final int num;
+            if (!Character.isDigit(mathString.charAt(i))) {
+                num = 1;
+            } else {
+                int numFrom = i;
+                while (i < mathString.length() && Character.isDigit(mathString.charAt(i))) {
+                    i++;
+                }
+                if (i >= mathString.length()) {
+                    throw new IllegalArgumentException("不支持的表达式:" + mathString);
+                }
+                num = Integer.parseInt(mathString.substring(numFrom, i));
+            }
+            if (round) {
+                if (num != 1) {
+                    throw new IllegalArgumentException("不支持的表达式:" + mathString);
+                }
+            }
+            char unit = mathString.charAt(i++);
+            switch (unit) {
+                case 'y':
+                    if (round) {
+                        dateTime = dateTime.withDayOfYear(1).with(LocalTime.MIN);
+                    } else {
+                        dateTime = dateTime.plusYears(sign * num);
+                    }
+
+                    break;
+                case 'M':
+                    if (round) {
+                        dateTime = dateTime.withDayOfMonth(1).with(LocalTime.MIN);
+                    } else {
+                        dateTime = dateTime.plusMonths(sign * num);
+                    }
+
+                    break;
+                case 'w':
+                    if (round) {
+                        dateTime = dateTime.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).with(LocalTime.MIN);
+                    } else {
+                        dateTime = dateTime.plusWeeks(sign * num);
+                    }
+
+                    break;
+                case 'd':
+                    if (round) {
+                        dateTime = dateTime.with(LocalTime.MIN);
+                    } else {
+                        dateTime = dateTime.plusDays(sign * num);
+                    }
+
+                    break;
+                case 'h':
+                case 'H':
+                    if (round) {
+                        dateTime = dateTime.withMinute(0).withSecond(0).withNano(0);
+                    } else {
+                        dateTime = dateTime.plusHours(sign * num);
+                    }
+
+                    break;
+                case 'm':
+                    if (round) {
+                        dateTime = dateTime.withSecond(0).withNano(0);
+                    } else {
+                        dateTime = dateTime.plusMinutes(sign * num);
+                    }
+
+                    break;
+                case 's':
+                    if (round) {
+                        dateTime = dateTime.withNano(0);
+                    } else {
+                        dateTime = dateTime.plusSeconds(sign * num);
+                    }
+
+                    break;
+                default:
+                    throw new IllegalArgumentException("不支持的表达式:" + mathString);
+            }
+        }
+        return dateTime.toInstant().toEpochMilli();
+    }
+
+    private static long parseDateTime(String value) {
+        if (StringUtils.isEmpty(value)) {
+            throw new IllegalArgumentException("cannot parse empty date");
+        }
+        return DateTimeType.GLOBAL.convert(value).getTime();
+    }
+}

+ 21 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/utils/ErrorUtils.java

@@ -0,0 +1,21 @@
+package org.jetlinks.community.utils;
+
+import org.hswebframework.web.authorization.exception.AccessDenyException;
+import org.hswebframework.web.exception.NotFoundException;
+import reactor.core.publisher.Mono;
+
+/**
+ * @author wangzheng
+ * @see
+ * @since 1.0
+ */
+public class ErrorUtils {
+
+    public static <T> Mono<T> notFound(String message){
+        return Mono.error(()->new NotFoundException(message));
+    }
+
+    public static <T> Mono<T> accessDeny(String message){
+        return Mono.error(()->new AccessDenyException(message));
+    }
+}

+ 91 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/utils/MessageTypeMatcher.java

@@ -0,0 +1,91 @@
+package org.jetlinks.community.utils;
+
+import lombok.*;
+import org.apache.commons.collections4.CollectionUtils;
+import org.jetlinks.core.message.MessageType;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class MessageTypeMatcher {
+
+    @Getter
+    private Set<String> excludes;
+
+    @Getter
+    private Set<String> includes = new HashSet<>(Collections.singleton("*"));
+
+    /**
+     * 设置为true时, 优选判断excludes
+     */
+    @Setter
+    @Getter
+    private boolean excludeFirst = true;
+
+    private long excludesMask;
+
+    private long includesMask;
+
+    public void setExcludes(Set<String> excludes) {
+        this.excludes = excludes;
+        init();
+    }
+
+    public void setIncludes(Set<String> includes) {
+        this.includes = includes;
+        init();
+    }
+
+    private long createMask(Collection<MessageType> messageTypes) {
+        long mask = 0;
+
+        for (MessageType messageType : messageTypes) {
+            mask |= 1L << messageType.ordinal();
+        }
+        return mask;
+    }
+
+    protected void init() {
+        if (!CollectionUtils.isEmpty(excludes)) {
+            if (excludes.contains("*")) {
+                excludesMask = createMask(Arrays.asList(MessageType.values()));
+            } else {
+                excludesMask = createMask(excludes.stream()
+                    .map(String::toUpperCase)
+                    .map(MessageType::valueOf)
+                    .collect(Collectors.toList()));
+            }
+        }
+        if (!CollectionUtils.isEmpty(includes)) {
+            if (includes.contains("*")) {
+                includesMask = createMask(Arrays.asList(MessageType.values()));
+            } else {
+                includesMask = createMask(includes.stream()
+                    .map(String::toUpperCase)
+                    .map(MessageType::valueOf)
+                    .collect(Collectors.toList()));
+            }
+        }
+    }
+
+    public boolean match(MessageType type) {
+        long mask = 1L << type.ordinal();
+        if (includesMask != 0) {
+            boolean include = (includesMask & mask) != 0;
+
+            if (excludeFirst && excludesMask != 0) {
+                return include && (excludesMask & mask) == 0;
+            }
+
+            return include;
+
+        }
+        if (excludesMask != 0) {
+            return (excludesMask & mask) == 0;
+        }
+        return true;
+    }
+}

+ 83 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/utils/TimeUtils.java

@@ -0,0 +1,83 @@
+package org.jetlinks.community.utils;
+
+import java.math.BigDecimal;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+
+public class TimeUtils {
+
+
+    /**
+     * 将时间字符解析为{@link Duration}.如: 1d, 15m, 1h15m.
+     * 支持天(D,d),时(H,h),分(M,m),秒(s),毫秒(S)
+     *
+     * @param timeString 时间字符串
+     * @return Duration
+     */
+    public static Duration parse(String timeString) {
+
+        char[] all = timeString.toCharArray();
+        if ((all[0] == 'P') || (all[0] == '-' && all[1] == 'P')) {
+            return Duration.parse(timeString);
+        }
+        Duration duration = Duration.ofSeconds(0);
+        char[] tmp = new char[32];
+        int numIndex = 0;
+        for (char c : all) {
+            if (c == '-' || (c >= '0' && c <= '9')) {
+                tmp[numIndex++] = c;
+                continue;
+            }
+            long val = new BigDecimal(tmp, 0, numIndex).longValue();
+            numIndex = 0;
+            Duration plus = null;
+            if (c == 'D' || c == 'd') {
+                plus = Duration.ofDays(val);
+            } else if (c == 'H' || c == 'h') {
+                plus = Duration.ofHours(val);
+            } else if (c == 'M' || c == 'm') {
+                plus = Duration.ofMinutes(val);
+            } else if (c == 's') {
+                plus = Duration.ofSeconds(val);
+            } else if (c == 'S') {
+                plus = Duration.ofMillis(val);
+            } else if (c == 'W' || c == 'w') {
+                plus = Duration.ofDays(val * 7);
+            }
+            if (plus != null) {
+                duration = duration.plus(plus);
+            }
+        }
+        return duration;
+    }
+
+    public static ChronoUnit parseUnit(String expr) {
+
+        expr = expr.toUpperCase();
+
+        if (!expr.endsWith("S")) {
+            expr = expr + "S";
+        }
+
+        return ChronoUnit.valueOf(expr);
+
+    }
+
+    /**
+     * 将字符串格式化为时间,支持简单的数学运算
+     *
+     * <pre>
+     *     now+1d
+     *
+     *
+     * </pre>
+     *
+     * @param expr 时间表达式
+     * @return 时间
+     */
+    public static Date parseDate(String expr) {
+        return new Date(DateMathParser.parse(expr, System::currentTimeMillis));
+    }
+
+}

+ 95 - 0
jetlinks-components/dashboard-component/README.md

@@ -0,0 +1,95 @@
+# 仪表盘组件
+
+获取所有类型
+
+/dashboard/defs
+
+    [
+      {
+          "id":"system",
+          "name":"系统",
+          "objects":[
+            {
+              "id":"memory",
+              "name":"内存""
+            },
+            {
+              "id":"cpu",
+              "name":"CPU""
+            }
+          ]
+      }
+    ]
+
+
+获取类型支持的指标和维度
+
+/dashboard/类型/对象/指标
+
+/dashboard/system/memory/measurements
+
+    [
+        {
+         "id":"max",
+         "name":"最大值",
+         "dimensions":[ //指标支持的维度
+           {"id":"time-interval","name":"历史数据","params":{"properties":[ ... ]}},
+           {"id":"real-time","name":"实时数据","params":{"properties":[ ... ]}}
+         ]
+        },
+        {
+         "id":"usage",
+         "name":"已使用"
+        }
+    ]
+
+
+
+POST /dashboard/system/memory/_batch
+
+    [
+        {
+           "measurement":"usage",
+           "dimension":"time-interval",
+           "params":{"interval":"1s"}
+        },
+        {
+           "measurement":"max",
+           "dimension":"time-interval",
+           "params":{"interval":"1s"}
+        }
+    ]
+
+
+    {
+     "usage":[
+        {
+           "time":"15:00",
+           "value":1.2
+        },
+        {
+           "time":"16:00",
+           "value":1.3
+        }
+     ]
+     ,
+     "max":[
+         {
+            "time":"15:00",
+            "value":2
+         },
+         {
+            "time":"16:00",
+            "value":2
+         }
+      ]
+    }
+
+/dashboard/类型/对象/维度/指标?参数
+
+GET /dashboard/device/property/temp/realTime?history=10
+ 
+     {
+        "timestamp":1578914267417,
+        "value":5.6
+     }

+ 35 - 0
jetlinks-components/dashboard-component/pom.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>jetlinks-components</artifactId>
+        <groupId>org.jetlinks.community</groupId>
+        <version>1.10.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>dashboard-component</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jetlinks</groupId>
+            <artifactId>jetlinks-core</artifactId>
+            <version>${jetlinks.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework</groupId>
+            <artifactId>hsweb-easy-orm-rdb</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>common-component</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+
+    </dependencies>
+
+</project>

+ 25 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/CommonDimensionDefinition.java

@@ -0,0 +1,25 @@
+package org.jetlinks.community.dashboard;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 通用维度定义
+ *
+ * @author zhouhao
+ */
+@AllArgsConstructor
+@Getter
+public enum CommonDimensionDefinition implements DimensionDefinition {
+    realTime("实时数据"),
+    history("历史数据"),
+    current("当前数据"),
+    agg("聚合数据");
+
+    private String name;
+
+    @Override
+    public String getId() {
+        return name();
+    }
+}

+ 27 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/CommonMeasurementDefinition.java

@@ -0,0 +1,27 @@
+package org.jetlinks.community.dashboard;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 通用指标定义
+ *
+ * @author zhouhao
+ */
+@AllArgsConstructor
+@Getter
+public enum CommonMeasurementDefinition implements MeasurementDefinition {
+    usage("使用率"),
+    used("已使用"),
+    info("明细"),
+    max("最大值"),
+    min("最小值"),
+    avg("平均值");
+
+    private String name;
+
+    @Override
+    public String getId() {
+        return name();
+    }
+}

+ 14 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/Dashboard.java

@@ -0,0 +1,14 @@
+package org.jetlinks.community.dashboard;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public interface Dashboard {
+
+    DashboardDefinition getDefinition();
+
+    Flux<DashboardObject> getObjects();
+
+    Mono<DashboardObject> getObject(String id);
+
+}

+ 5 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/DashboardDefinition.java

@@ -0,0 +1,5 @@
+package org.jetlinks.community.dashboard;
+
+public interface DashboardDefinition extends Definition {
+
+}

+ 12 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/DashboardManager.java

@@ -0,0 +1,12 @@
+package org.jetlinks.community.dashboard;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public interface DashboardManager {
+
+    Flux<Dashboard> getDashboards();
+
+    Mono<Dashboard> getDashboard(String id);
+
+}

+ 17 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/DashboardObject.java

@@ -0,0 +1,17 @@
+package org.jetlinks.community.dashboard;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * 仪表对象: CPU,内存
+ */
+public interface DashboardObject  {
+
+    ObjectDefinition getDefinition();
+
+    Flux<Measurement> getMeasurements();
+
+    Mono<Measurement> getMeasurement(String id);
+
+}

+ 32 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/DefaultDashboardDefinition.java

@@ -0,0 +1,32 @@
+package org.jetlinks.community.dashboard;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.hswebframework.web.dict.EnumDict;
+
+@AllArgsConstructor
+@Getter
+public enum DefaultDashboardDefinition implements DashboardDefinition, EnumDict<String> {
+
+    systemMonitor("系统监控"),
+    jvmMonitor("jvm监控")
+
+    ;
+
+    private String name;
+
+    @Override
+    public String getId() {
+        return name();
+    }
+
+    @Override
+    public String getValue() {
+        return getId();
+    }
+
+    @Override
+    public String getText() {
+        return name;
+    }
+}

+ 10 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/Definition.java

@@ -0,0 +1,10 @@
+package org.jetlinks.community.dashboard;
+
+public interface Definition {
+    String getId();
+
+    String getName();
+
+
+
+}

+ 19 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/DimensionDefinition.java

@@ -0,0 +1,19 @@
+package org.jetlinks.community.dashboard;
+
+public interface DimensionDefinition extends Definition {
+    static DimensionDefinition of(String id, String name) {
+        return new DimensionDefinition() {
+
+            @Override
+            public String getId() {
+                return id;
+            }
+
+            @Override
+            public String getName() {
+                return name;
+            }
+        };
+    }
+
+}

+ 31 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/Measurement.java

@@ -0,0 +1,31 @@
+package org.jetlinks.community.dashboard;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * 度量,指标. 如: 使用率
+ *
+ * @author zhouhao
+ * @since 1.0
+ */
+public interface Measurement {
+
+    MeasurementDefinition getDefinition();
+
+    /**
+     * 获取所有指标维度
+     *
+     * @return 维度
+     */
+    Flux<MeasurementDimension> getDimensions();
+
+    /**
+     * 获取指定ID的维度
+     *
+     * @param id 维度定义ID
+     * @return 指定的维度, 不存在则返回 {@link Mono#empty()}
+     */
+    Mono<MeasurementDimension> getDimension(String id);
+
+}

+ 19 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/MeasurementDefinition.java

@@ -0,0 +1,19 @@
+package org.jetlinks.community.dashboard;
+
+public interface MeasurementDefinition extends Definition {
+    static MeasurementDefinition of(String id, String name) {
+        return new MeasurementDefinition() {
+
+            @Override
+            public String getId() {
+                return id;
+            }
+
+            @Override
+            public String getName() {
+                return name;
+            }
+        };
+    }
+
+}

+ 24 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/MeasurementDimension.java

@@ -0,0 +1,24 @@
+package org.jetlinks.community.dashboard;
+
+import org.jetlinks.core.metadata.ConfigMetadata;
+import org.jetlinks.core.metadata.DataType;
+import org.reactivestreams.Publisher;
+
+/**
+ * 指标维度,如: 每小时,服务器1
+ *
+ * @author zhouhao
+ */
+public interface MeasurementDimension {
+
+    DimensionDefinition getDefinition();
+
+    DataType getValueType();
+
+    ConfigMetadata getParams();
+
+    boolean isRealTime();
+
+    Publisher<? extends MeasurementValue> getValue(MeasurementParameter parameter);
+
+}

+ 29 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/MeasurementParameter.java

@@ -0,0 +1,29 @@
+package org.jetlinks.community.dashboard;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.jetlinks.community.ValueObject;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+@Getter
+@Setter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+public class MeasurementParameter implements ValueObject {
+    private Map<String, Object> params = new HashMap<>();
+
+    @Override
+    public Optional<Object> get(String name) {
+        return Optional.ofNullable(params).map(p -> p.get(name));
+    }
+
+    @Override
+    public Map<String, Object> values() {
+        return params;
+    }
+}

+ 38 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/MeasurementValue.java

@@ -0,0 +1,38 @@
+package org.jetlinks.community.dashboard;
+
+import org.hswebframework.ezorm.rdb.operator.dml.query.SortOrder;
+
+import java.util.Comparator;
+
+public interface MeasurementValue  extends Comparable<MeasurementValue> {
+
+    Object getValue();
+
+    String getTimeString();
+
+    long getTimestamp();
+
+    //默认排序,时间大的在前.
+    @Override
+    default int compareTo(MeasurementValue o) {
+        return Long.compare(o.getTimestamp(), getTimestamp());
+    }
+
+    static Comparator<MeasurementValue> sort(SortOrder.Order order) {
+        return order == SortOrder.Order.asc ? sort() : sortDesc();
+    }
+
+    Comparator<MeasurementValue> asc = Comparator.comparing(MeasurementValue::getTimestamp);
+
+    Comparator<MeasurementValue> desc = asc.reversed();
+
+    //返回排序对比器,时间小的在前
+    static Comparator<MeasurementValue> sort() {
+        return asc;
+    }
+
+    static Comparator<MeasurementValue> sortDesc() {
+        return desc;
+    }
+
+}

+ 5 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/ObjectDefinition.java

@@ -0,0 +1,5 @@
+package org.jetlinks.community.dashboard;
+
+public interface ObjectDefinition extends Definition {
+
+}

+ 28 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/SimpleMeasurementValue.java

@@ -0,0 +1,28 @@
+package org.jetlinks.community.dashboard;
+
+import lombok.*;
+import org.hswebframework.utils.time.DateFormatter;
+
+import java.util.Date;
+
+@Getter
+@Setter
+@Builder
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+public class SimpleMeasurementValue implements MeasurementValue {
+
+    private Object value;
+
+    private String timeString;
+
+    private long timestamp;
+
+    public static SimpleMeasurementValue of(Object value, Date time) {
+        return of(value, DateFormatter.toString(time, "yyyy-MM-dd HH:mm:ss"), time.getTime());
+    }
+
+    public static SimpleMeasurementValue of(Object value, long time) {
+        return of(value, new Date(time));
+    }
+}

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

@@ -0,0 +1,81 @@
+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);
+        }
+
+    }
+
+}

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

@@ -0,0 +1,132 @@
+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;
+        }
+    }
+}

+ 20 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/MonitorObjectDefinition.java

@@ -0,0 +1,20 @@
+package org.jetlinks.community.dashboard.measurements;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.jetlinks.community.dashboard.ObjectDefinition;
+
+@Getter
+@AllArgsConstructor
+public enum MonitorObjectDefinition implements ObjectDefinition {
+
+    cpu("CPU"),
+    memory("内存");
+
+    private String name;
+
+    @Override
+    public String getId() {
+        return name();
+    }
+}

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

@@ -0,0 +1,79 @@
+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);
+        }
+
+    }
+
+}

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

@@ -0,0 +1,128 @@
+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;
+        }
+    }
+}

+ 85 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/measurements/SystemMonitor.java

@@ -0,0 +1,85 @@
+package org.jetlinks.community.dashboard.measurements;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.SneakyThrows;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.OperatingSystemMXBean;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.function.Function;
+
+@AllArgsConstructor
+@Getter
+public enum SystemMonitor {
+    systemCpuUsage("系统CPU使用率"),
+    jvmCpuUsage("JVM进程CPU使用率"),
+    freeSystemMemory("系统空闲内存"),
+    totalSystemMemory("系统总内存"),
+    openFileCount("已打开文件数"),
+    maxOpenFileCount("最大打开文件数"),
+    ;
+
+    private String text;
+
+    public double getValue() {
+        return getValue(name());
+    }
+
+    static OperatingSystemMXBean osMxBean = ManagementFactory.getOperatingSystemMXBean();
+    private 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 Class<?> mxBeanClass;
+
+    private static void register(String item, String methodName, Function<Double, Double> mapping) {
+        try {
+            Method method = mxBeanClass.getMethod(methodName);
+            items.put(item, () -> mapping.apply(((Number) method.invoke(osMxBean)).doubleValue()));
+        } catch (Exception e) {
+
+        }
+    }
+
+    static {
+        for (String s : OPERATING_SYSTEM_BEAN_CLASS_NAMES) {
+            try {
+                mxBeanClass = Class.forName(s);
+            } catch (Exception ignore) {
+            }
+        }
+        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());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @SneakyThrows
+    public static double getValue(String id) {
+        double val = items.getOrDefault(id, zero).call();
+
+        return Double.isNaN(val) ? 0 : val;
+    }
+
+}

+ 60 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/CompositeDashboard.java

@@ -0,0 +1,60 @@
+package org.jetlinks.community.dashboard.supports;
+
+import lombok.Getter;
+import org.jetlinks.community.dashboard.Dashboard;
+import org.jetlinks.community.dashboard.DashboardDefinition;
+import org.jetlinks.community.dashboard.DashboardObject;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+class CompositeDashboard implements Dashboard {
+
+    @Getter
+    private DashboardDefinition definition;
+
+    public CompositeDashboard(DashboardDefinition definition) {
+        this.definition = definition;
+    }
+
+    private Map<String, DashboardObject> staticObjects = new ConcurrentHashMap<>();
+
+    private List<Dashboard> staticDashboard = new CopyOnWriteArrayList<>();
+
+    public void addProvider(MeasurementProvider provider) {
+
+        DashboardObject object = staticObjects.computeIfAbsent(provider.getObjectDefinition().getId(), __ -> new CompositeDashboardObject());
+        if(object instanceof CompositeDashboardObject){
+            CompositeDashboardObject compose = ((CompositeDashboardObject) object);
+            compose.addProvider(provider);
+        }
+
+    }
+
+    public void addDashboard(Dashboard dashboard){
+        staticDashboard.add(dashboard);
+    }
+
+    public void addObject(DashboardObject object) {
+        staticObjects.put(object.getDefinition().getId(), object);
+    }
+
+    @Override
+    public Flux<DashboardObject> getObjects() {
+        return Flux.concat(
+            Flux.fromIterable(staticObjects.values()),
+            Flux.fromIterable(staticDashboard).flatMap(Dashboard::getObjects));
+    }
+
+    @Override
+    public Mono<DashboardObject> getObject(String id) {
+        return Mono.justOrEmpty(staticObjects.get(id))
+            .switchIfEmpty(Mono.defer(()-> Flux.fromIterable(staticDashboard)
+                .flatMap(dashboard -> dashboard.getObject(id))
+                .next()));
+    }
+}

+ 45 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/CompositeDashboardObject.java

@@ -0,0 +1,45 @@
+package org.jetlinks.community.dashboard.supports;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.jetlinks.community.dashboard.DashboardObject;
+import org.jetlinks.community.dashboard.Measurement;
+import org.jetlinks.community.dashboard.ObjectDefinition;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+class CompositeDashboardObject implements DashboardObject {
+
+    private ObjectDefinition definition;
+
+    private List<MeasurementProvider> providers = new CopyOnWriteArrayList<>();
+
+    public void addProvider(MeasurementProvider provider) {
+        if (definition == null) {
+            definition = provider.getObjectDefinition();
+        }
+        providers.add(provider);
+    }
+
+    @Override
+    public ObjectDefinition getDefinition() {
+        return definition;
+    }
+
+    @Override
+    public Flux<Measurement> getMeasurements() {
+        return Flux.fromIterable(providers)
+            .flatMap(MeasurementProvider::getMeasurements);
+    }
+
+    @Override
+    public Mono<Measurement> getMeasurement(String id) {
+        return Flux.fromIterable(providers)
+            .flatMap(provider -> provider.getMeasurement(id))
+            .collectList()
+            .filter(CollectionUtils::isNotEmpty)
+            .map(CompositeMeasurement::new);
+    }
+}

+ 42 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/CompositeMeasurement.java

@@ -0,0 +1,42 @@
+package org.jetlinks.community.dashboard.supports;
+
+import org.jetlinks.community.dashboard.Measurement;
+import org.jetlinks.community.dashboard.MeasurementDefinition;
+import org.jetlinks.community.dashboard.MeasurementDimension;
+import org.springframework.util.Assert;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+class CompositeMeasurement implements Measurement {
+
+    private List<Measurement> measurements;
+
+    private Measurement main;
+
+    public CompositeMeasurement(List<Measurement> measurements) {
+        Assert.notEmpty(measurements, "measurements can not be empty");
+        this.measurements = measurements;
+        this.main = measurements.get(0);
+    }
+
+    @Override
+    public MeasurementDefinition getDefinition() {
+        return main.getDefinition();
+    }
+
+
+    @Override
+    public Flux<MeasurementDimension> getDimensions() {
+        return Flux.fromIterable(measurements)
+            .flatMap(Measurement::getDimensions);
+    }
+
+    @Override
+    public Mono<MeasurementDimension> getDimension(String id) {
+        return Flux.fromIterable(measurements)
+            .flatMap(measurement -> measurement.getDimension(id))
+            .next();
+    }
+}

+ 58 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/DefaultDashboardManager.java

@@ -0,0 +1,58 @@
+package org.jetlinks.community.dashboard.supports;
+
+import org.jetlinks.community.dashboard.Dashboard;
+import org.jetlinks.community.dashboard.DashboardDefinition;
+import org.jetlinks.community.dashboard.DashboardManager;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class DefaultDashboardManager implements DashboardManager, BeanPostProcessor {
+
+    private Map<String, CompositeDashboard> dashboards = new ConcurrentHashMap<>();
+
+    @Override
+    public Flux<Dashboard> getDashboards() {
+        return Flux.fromIterable(dashboards.values());
+    }
+
+    @Override
+    public Mono<Dashboard> getDashboard(String id) {
+        return Mono.justOrEmpty(dashboards.get(id));
+    }
+
+
+    private void addProvider(MeasurementProvider provider) {
+
+        DashboardDefinition definition = provider.getDashboardDefinition();
+
+        CompositeDashboard dashboard = dashboards.computeIfAbsent(definition.getId(), __ -> new CompositeDashboard(definition));
+
+        dashboard.addProvider(provider);
+    }
+
+    private void addDashboard(Dashboard dashboard) {
+
+        CompositeDashboard cached = dashboards.computeIfAbsent(dashboard.getDefinition().getId(), __ -> new CompositeDashboard(dashboard.getDefinition()));
+
+        cached.addDashboard(dashboard);
+
+    }
+
+    @Override
+    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+
+        if (bean instanceof MeasurementProvider) {
+            addProvider(((MeasurementProvider) bean));
+        } else if (bean instanceof Dashboard) {
+            addDashboard(((Dashboard) bean));
+        }
+        return bean;
+    }
+}

+ 33 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/MeasurementProvider.java

@@ -0,0 +1,33 @@
+package org.jetlinks.community.dashboard.supports;
+
+import org.jetlinks.community.dashboard.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public interface MeasurementProvider {
+
+    /**
+     * @return 仪表定义
+     * @see DefaultDashboardDefinition
+     */
+    DashboardDefinition getDashboardDefinition();
+
+    /**
+     * @return 对象定义
+     * @see org.jetlinks.community.dashboard.measurements.SystemObjectDefinition
+     */
+    ObjectDefinition getObjectDefinition();
+
+    /**
+     * @return 全部指标
+     */
+    Flux<Measurement> getMeasurements();
+
+    /**
+     * @param id 指标ID {@link Measurement#getDefinition()} {@link MeasurementDefinition#getId()}
+     * @return 对应等指标, 不存在则返回 {@link Mono#empty()}
+     * @see MeasurementDefinition
+     */
+    Mono<Measurement> getMeasurement(String id);
+
+}

+ 42 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/StaticMeasurement.java

@@ -0,0 +1,42 @@
+package org.jetlinks.community.dashboard.supports;
+
+import lombok.Getter;
+import org.jetlinks.community.dashboard.Measurement;
+import org.jetlinks.community.dashboard.MeasurementDefinition;
+import org.jetlinks.community.dashboard.MeasurementDimension;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class StaticMeasurement implements Measurement {
+
+    @Getter
+    private MeasurementDefinition definition;
+
+
+    public StaticMeasurement(MeasurementDefinition definition) {
+        this.definition = definition;
+    }
+
+    private Map<String, MeasurementDimension> dimensions = new ConcurrentHashMap<>();
+
+    public StaticMeasurement addDimension(MeasurementDimension dimension) {
+
+        dimensions.put(dimension.getDefinition().getId(), dimension);
+
+        return this;
+
+    }
+
+    @Override
+    public Flux<MeasurementDimension> getDimensions() {
+        return Flux.fromIterable(dimensions.values());
+    }
+
+    @Override
+    public Mono<MeasurementDimension> getDimension(String id) {
+        return Mono.justOrEmpty(dimensions.get(id));
+    }
+}

+ 41 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/supports/StaticMeasurementProvider.java

@@ -0,0 +1,41 @@
+package org.jetlinks.community.dashboard.supports;
+
+import lombok.Getter;
+import org.jetlinks.community.dashboard.DashboardDefinition;
+import org.jetlinks.community.dashboard.Measurement;
+import org.jetlinks.community.dashboard.ObjectDefinition;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public abstract class StaticMeasurementProvider implements MeasurementProvider {
+
+    private Map<String, Measurement> measurements = new ConcurrentHashMap<>();
+
+    @Getter
+    private DashboardDefinition dashboardDefinition;
+    @Getter
+    private ObjectDefinition objectDefinition;
+
+    public StaticMeasurementProvider(DashboardDefinition dashboardDefinition,
+                                     ObjectDefinition objectDefinition) {
+        this.dashboardDefinition = dashboardDefinition;
+        this.objectDefinition = objectDefinition;
+    }
+
+    protected void addMeasurement(Measurement measurement) {
+        measurements.put(measurement.getDefinition().getId(), measurement);
+    }
+
+    @Override
+    public Flux<Measurement> getMeasurements() {
+        return Flux.fromIterable(measurements.values());
+    }
+
+    @Override
+    public Mono<Measurement> getMeasurement(String id) {
+        return Mono.justOrEmpty(measurements.get(id));
+    }
+}

+ 107 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/DashboardController.java

@@ -0,0 +1,107 @@
+package org.jetlinks.community.dashboard.web;
+
+import com.alibaba.fastjson.JSON;
+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.exception.NotFoundException;
+import org.jetlinks.community.dashboard.DashboardManager;
+import org.jetlinks.community.dashboard.DashboardObject;
+import org.jetlinks.community.dashboard.MeasurementParameter;
+import org.jetlinks.community.dashboard.MeasurementValue;
+import org.jetlinks.community.dashboard.web.request.DashboardMeasurementRequest;
+import org.jetlinks.community.dashboard.web.response.DashboardInfo;
+import org.jetlinks.community.dashboard.web.response.DashboardMeasurementResponse;
+import org.jetlinks.community.dashboard.web.response.MeasurementInfo;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/dashboard")
+@Resource(id="dashboard",name = "仪表盘")
+@Authorize
+public class DashboardController {
+
+    private final DashboardManager dashboardManager;
+
+    public DashboardController(DashboardManager dashboardManager) {
+        this.dashboardManager = dashboardManager;
+    }
+
+    @GetMapping("/defs")
+    @QueryAction
+    public Flux<DashboardInfo> getDefinitions() {
+        return dashboardManager
+            .getDashboards()
+            .flatMap(DashboardInfo::of);
+    }
+
+    @GetMapping("/def/{dashboard}/{object}/measurements")
+    @QueryAction
+    public Flux<MeasurementInfo> getMeasurementDefinitions(@PathVariable String dashboard,
+                                                           @PathVariable String object) {
+        return dashboardManager
+            .getDashboard(dashboard)
+            .flatMap(dash -> dash.getObject(object))
+            .flatMapMany(DashboardObject::getMeasurements)
+            .flatMap(MeasurementInfo::of);
+    }
+
+    @GetMapping(value = "/{dashboard}/{object}/{measurement}/{dimension}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    @Authorize(merge = false)
+    public Flux<MeasurementValue> getMeasurementValue(@PathVariable String dashboard,
+                                                      @PathVariable String object,
+                                                      @PathVariable String dimension,
+                                                      @PathVariable String measurement,
+                                                      @RequestParam Map<String, Object> params) {
+        return dashboardManager
+            .getDashboard(dashboard)
+            .flatMap(dash -> dash.getObject(object))
+            .flatMap(obj -> obj.getMeasurement(measurement))
+            .flatMap(meas -> meas.getDimension(dimension))
+            .switchIfEmpty(Mono.error(() -> new NotFoundException("不支持的仪表盘")))
+            .flatMapMany(dim -> dim.getValue(MeasurementParameter.of(params)));
+    }
+
+    /**
+     * POST 方式批量获取仪表数据,不支持获取实时数据.
+     *
+     * @param requests 请求参数
+     * @return 仪表数据
+     */
+    @PostMapping(value = "/_multi")
+    @Authorize(merge = false)
+    public Flux<DashboardMeasurementResponse> getMultiMeasurementValue(@RequestBody Flux<DashboardMeasurementRequest> requests) {
+        return requests.flatMap(request -> dashboardManager
+            .getDashboard(request.getDashboard())
+            .flatMap(dash -> dash.getObject(request.getObject()))
+            .flatMap(obj -> obj.getMeasurement(request.getMeasurement()))
+            .flatMap(meas -> meas.getDimension(request.getDimension()))
+            .filter(dim -> !dim.isRealTime()) //实时数据请使用EventSource方式
+            .flatMapMany(dim -> dim.getValue(MeasurementParameter.of(request.getParams())))
+            .map(val -> DashboardMeasurementResponse.of(request.getGroup(), val)));
+    }
+
+    /**
+     * 使用EventSource方式批量获取仪表数据,支持获取实时数据.
+     *
+     * @param requestJson 请求集合json
+     * @return 仪表数据
+     */
+    @GetMapping(value = "/_multi", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    @Authorize(merge = false)
+    public Flux<DashboardMeasurementResponse> getMultiMeasurementValue(@RequestParam String requestJson) {
+        return Flux.fromIterable(JSON.parseArray(requestJson, DashboardMeasurementRequest.class))
+            .flatMap(request -> dashboardManager
+                .getDashboard(request.getDashboard())
+                .flatMap(dash -> dash.getObject(request.getObject()))
+                .flatMap(obj -> obj.getMeasurement(request.getMeasurement()))
+                .flatMap(meas -> meas.getDimension(request.getDimension()))
+                .flatMapMany(dim -> dim.getValue(MeasurementParameter.of(request.getParams())))
+                .map(val -> DashboardMeasurementResponse.of(request.getGroup(), val)));
+    }
+}

+ 58 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/request/DashboardMeasurementRequest.java

@@ -0,0 +1,58 @@
+package org.jetlinks.community.dashboard.web.request;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jetlinks.community.dashboard.Dashboard;
+import org.jetlinks.community.dashboard.DashboardDefinition;
+import org.jetlinks.community.dashboard.DimensionDefinition;
+import org.jetlinks.community.dashboard.MeasurementDefinition;
+import org.jetlinks.community.dashboard.web.response.DashboardMeasurementResponse;
+
+import java.util.Map;
+
+/**
+ * 仪表盘指标数据请求
+ *
+ * @author zhouhao
+ * @since 1.0
+ */
+@Getter
+@Setter
+public class DashboardMeasurementRequest {
+
+    /**
+     * 分组
+     * @see DashboardMeasurementResponse#getGroup()
+     */
+    private String group;
+
+    /**
+     * 仪表盘,如: device
+     * @see Dashboard#getDefinition()
+     */
+    private String dashboard;
+
+    /**
+     * 仪表对象,如: device1
+     * @see  DashboardDefinition#getId()
+     */
+    private String object;
+
+    /**
+     * 指标,如: 属性ID
+     * @see  MeasurementDefinition#getId()
+     */
+    private String measurement;
+
+    /**
+     * 维度
+     * @see DimensionDefinition#getId()
+     */
+    private String dimension;
+
+    /**
+     * 查询参数
+     */
+    private Map<String, Object> params;
+
+}

+ 34 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/response/DashboardInfo.java

@@ -0,0 +1,34 @@
+package org.jetlinks.community.dashboard.web.response;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jetlinks.community.dashboard.Dashboard;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+@Getter
+@Setter
+public class DashboardInfo {
+
+    private String id;
+
+    private String name;
+
+    private List<ObjectInfo> objects;
+
+    public static Mono<DashboardInfo> of(Dashboard dashboard) {
+        return dashboard.getObjects()
+            .map(ObjectInfo::of)
+            .collectList()
+            .map(list -> {
+                DashboardInfo dashboardInfo = new DashboardInfo();
+                dashboardInfo.setId(dashboard.getDefinition().getId());
+                dashboardInfo.setName(dashboard.getDefinition().getName());
+                dashboardInfo.setObjects(list);
+                return dashboardInfo;
+            });
+
+    }
+
+}

+ 20 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/response/DashboardMeasurementResponse.java

@@ -0,0 +1,20 @@
+package org.jetlinks.community.dashboard.web.response;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.jetlinks.community.dashboard.MeasurementValue;
+
+@Getter
+@Setter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+public class DashboardMeasurementResponse {
+
+    private String group;
+
+    private MeasurementValue data;
+
+
+}

+ 31 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/response/DimensionInfo.java

@@ -0,0 +1,31 @@
+package org.jetlinks.community.dashboard.web.response;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jetlinks.community.dashboard.MeasurementDimension;
+import org.jetlinks.core.metadata.ConfigMetadata;
+import org.jetlinks.core.metadata.DataType;
+
+@Getter
+@Setter
+public class DimensionInfo {
+    private String id;
+
+    private String name;
+
+    private DataType type;
+
+    private ConfigMetadata params;
+
+    private boolean realTime;
+
+    public static DimensionInfo of(MeasurementDimension dimension) {
+        DimensionInfo dimensionInfo = new DimensionInfo();
+        dimensionInfo.setId(dimension.getDefinition().getId());
+        dimensionInfo.setName(dimension.getDefinition().getName());
+        dimensionInfo.setParams(dimension.getParams());
+        dimensionInfo.setType(dimension.getValueType());
+        dimensionInfo.setRealTime(dimension.isRealTime());
+        return dimensionInfo;
+    }
+}

+ 35 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/response/MeasurementInfo.java

@@ -0,0 +1,35 @@
+package org.jetlinks.community.dashboard.web.response;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jetlinks.community.dashboard.Measurement;
+import org.jetlinks.core.metadata.DataType;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+@Getter
+@Setter
+public class MeasurementInfo {
+
+    private String id;
+
+    private String name;
+
+    private DataType type;
+
+    private List<DimensionInfo> dimensions;
+
+    public static Mono<MeasurementInfo> of(Measurement measurement){
+        return measurement.getDimensions()
+            .map(DimensionInfo::of)
+            .collectList()
+            .map(list->{
+                MeasurementInfo info=new MeasurementInfo();
+                info.setId(measurement.getDefinition().getId());
+                info.setName(measurement.getDefinition().getName());
+                info.setDimensions(list);
+                return info;
+            });
+    }
+}

+ 24 - 0
jetlinks-components/dashboard-component/src/main/java/org/jetlinks/community/dashboard/web/response/ObjectInfo.java

@@ -0,0 +1,24 @@
+package org.jetlinks.community.dashboard.web.response;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jetlinks.community.dashboard.DashboardObject;
+
+@Getter
+@Setter
+public class ObjectInfo {
+
+    private String id;
+
+    private String name;
+
+
+    public static ObjectInfo of(DashboardObject object){
+        ObjectInfo objectInfo=new ObjectInfo();
+        objectInfo.setName(object.getDefinition().getName());
+        objectInfo.setId(object.getDefinition().getId());
+
+        return objectInfo;
+    }
+
+}

+ 82 - 0
jetlinks-components/elasticsearch-component/pom.xml

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>jetlinks-components</artifactId>
+        <groupId>org.jetlinks.community</groupId>
+        <version>1.10.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>elasticsearch-component</artifactId>
+
+    <dependencies>
+
+        <dependency><!-- required by elasticsearch -->
+            <groupId>org.elasticsearch.plugin</groupId>
+            <artifactId>transport-netty4-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>log4j-over-slf4j</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework</groupId>
+            <artifactId>hsweb-easy-orm-elasticsearch</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.elasticsearch.client</groupId>
+            <artifactId>elasticsearch-rest-high-level-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-elasticsearch</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-commons-crud</artifactId>
+            <version>${hsweb.framework.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework</groupId>
+            <artifactId>hsweb-easy-orm-rdb</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jetlinks</groupId>
+            <artifactId>jetlinks-core</artifactId>
+            <version>${jetlinks.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>timeseries-component</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.projectreactor.netty</groupId>
+            <artifactId>reactor-netty</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 148 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/elasticsearch/common/logging/Loggers.java

@@ -0,0 +1,148 @@
+package org.elasticsearch.common.logging;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.Appender;
+import org.elasticsearch.common.settings.Setting;
+import org.elasticsearch.index.Index;
+import org.elasticsearch.index.shard.ShardId;
+
+import static org.elasticsearch.common.util.CollectionUtils.asArrayList;
+
+/**
+ * A set of utilities around Logging.
+ */
+public class Loggers {
+
+    public static final String SPACE = " ";
+
+    public static final Setting<Level> LOG_DEFAULT_LEVEL_SETTING =
+        new Setting<>("logger.level", Level.INFO.name(), Level::valueOf, Setting.Property.NodeScope);
+    public static final Setting.AffixSetting<Level> LOG_LEVEL_SETTING =
+        Setting.prefixKeySetting("logger.", (key) -> new Setting<>(key, Level.INFO.name(), Level::valueOf, Setting.Property.Dynamic,
+            Setting.Property.NodeScope));
+
+    public static Logger getLogger(Class<?> clazz, ShardId shardId, String... prefixes) {
+        return getLogger(clazz, shardId.getIndex(), asArrayList(Integer.toString(shardId.id()), prefixes).toArray(new String[0]));
+    }
+
+    /**
+     * Just like {@link #getLogger(Class, ShardId, String...)} but String loggerName instead of
+     * Class and no extra prefixes.
+     */
+    public static Logger getLogger(String loggerName, ShardId shardId) {
+        String prefix = formatPrefix(shardId.getIndexName(), Integer.toString(shardId.id()));
+        return new PrefixLogger(LogManager.getLogger(loggerName), prefix);
+    }
+
+    public static Logger getLogger(Class<?> clazz, Index index, String... prefixes) {
+        return getLogger(clazz, asArrayList(Loggers.SPACE, index.getName(), prefixes).toArray(new String[0]));
+    }
+
+    public static Logger getLogger(Class<?> clazz, String... prefixes) {
+        return new PrefixLogger(LogManager.getLogger(clazz), formatPrefix(prefixes));
+    }
+
+    public static Logger getLogger(Logger parentLogger, String s) {
+        Logger inner = LogManager.getLogger(parentLogger.getName() + s);
+        if (parentLogger instanceof PrefixLogger) {
+            return new PrefixLogger(inner, ((PrefixLogger)parentLogger).prefix());
+        }
+        return inner;
+    }
+
+    private static String formatPrefix(String... prefixes) {
+        String prefix = null;
+        if (prefixes != null && prefixes.length > 0) {
+            StringBuilder sb = new StringBuilder();
+            for (String prefixX : prefixes) {
+                if (prefixX != null) {
+                    if (prefixX.equals(SPACE)) {
+                        sb.append(" ");
+                    } else {
+                        sb.append("[").append(prefixX).append("]");
+                    }
+                }
+            }
+            if (sb.length() > 0) {
+                prefix = sb.toString();
+            }
+        }
+        return prefix;
+    }
+
+    /**
+     * Set the level of the logger. If the new level is null, the logger will inherit it's level from its nearest ancestor with a non-null
+     * level.
+     */
+    public static void setLevel(Logger logger, String level) {
+        final Level l;
+        if (level == null) {
+            l = null;
+        } else {
+            l = Level.valueOf(level);
+        }
+        setLevel(logger, l);
+    }
+
+    public static void setLevel(Logger logger, Level level) {
+//        if (!LogManager.ROOT_LOGGER_NAME.equals(logger.getName())) {
+//
+//            Configurator.setLevel(logger.getName(), level);
+//        } else {
+//
+//            final LoggerContext ctx = LoggerContext.getContext(false);
+//            final Configuration config = ctx.getConfiguration();
+//            final LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName());
+//            loggerConfig.setLevel(level);
+//            ctx.updateLoggers();
+//        }
+//
+//        // we have to descend the hierarchy
+//        final LoggerContext ctx = LoggerContext.getContext(false);
+//        for (final LoggerConfig loggerConfig : ctx.getConfiguration().getLoggers().values()) {
+//            if (LogManager.ROOT_LOGGER_NAME.equals(logger.getName()) || loggerConfig.getName().startsWith(logger.getName() + ".")) {
+//                Configurator.setLevel(loggerConfig.getName(), level);
+//            }
+//        }
+    }
+
+    public static void addAppender(final Logger logger, final Appender appender) {
+//        final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+//        final Configuration config = ctx.getConfiguration();
+//        config.addAppender(appender);
+//        LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName());
+//        if (!logger.getName().equals(loggerConfig.getName())) {
+//            loggerConfig = new LoggerConfig(logger.getName(), logger.getLevel(), true);
+//            config.addLogger(logger.getName(), loggerConfig);
+//        }
+//        loggerConfig.addAppender(appender, null, null);
+//        ctx.updateLoggers();
+    }
+
+    public static void removeAppender(final Logger logger, final Appender appender) {
+//        final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+//        final Configuration config = ctx.getConfiguration();
+//        LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName());
+//        if (!logger.getName().equals(loggerConfig.getName())) {
+//            loggerConfig = new LoggerConfig(logger.getName(), logger.getLevel(), true);
+//            config.addLogger(logger.getName(), loggerConfig);
+//        }
+//        loggerConfig.removeAppender(appender.getName());
+//        ctx.updateLoggers();
+    }
+
+    public static Appender findAppender(final Logger logger, final Class<? extends Appender> clazz) {
+//        final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+//        final Configuration config = ctx.getConfiguration();
+//        final LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName());
+//        for (final Map.Entry<String, Appender> entry : loggerConfig.getAppenders().entrySet()) {
+//            if (entry.getValue().getClass().equals(clazz)) {
+//                return entry.getValue();
+//            }
+//        }
+        return null;
+    }
+
+}

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

@@ -0,0 +1,20 @@
+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;
+}

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

@@ -0,0 +1,268 @@
+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());
+    }
+}

+ 171 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/AggregationResponseHandle.java

@@ -0,0 +1,171 @@
+package org.jetlinks.community.elastic.search.aggreation.bucket;
+
+import org.elasticsearch.search.aggregations.Aggregation;
+import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
+import org.elasticsearch.search.aggregations.bucket.range.Range;
+import org.elasticsearch.search.aggregations.bucket.terms.Terms;
+import org.elasticsearch.search.aggregations.metrics.*;
+import org.jetlinks.community.elastic.search.aggreation.metrics.MetricsResponseSingleValue;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+public class AggregationResponseHandle {
+
+
+    public static <A extends Aggregation> List<Bucket> terms(A a) {
+        Terms terms = (Terms) a;
+        return terms.getBuckets()
+            .stream()
+            .map(b -> {
+                Bucket bucket = Bucket.builder()
+                    .key(b.getKeyAsString())
+                    .count(b.getDocCount())
+                    .name(a.getName())
+                    .build();
+                b.getAggregations().asList()
+                    .forEach(subAggregation -> route(bucket, subAggregation));
+                return bucket;
+            }).collect(Collectors.toList())
+            ;
+    }
+
+    public static <A extends Aggregation> List<Bucket> range(A a) {
+        Range range = (Range) a;
+        return range.getBuckets()
+            .stream()
+            .map(b -> {
+                Bucket bucket = Bucket.builder()
+                    .key(b.getKeyAsString())
+                    .from(b.getFrom())
+                    .to(b.getTo())
+                    .fromAsString(b.getFromAsString())
+                    .toAsString(b.getToAsString())
+                    .count(b.getDocCount()).build();
+                b.getAggregations().asList()
+                    .forEach(subAggregation -> {
+                        route(bucket, subAggregation);
+                    });
+                return bucket;
+            }).collect(Collectors.toList())
+            ;
+    }
+
+    public static <A extends Aggregation> List<Bucket> dateHistogram(A a) {
+        Histogram histogram = (Histogram) a;
+        return bucketsHandle(histogram.getBuckets(), a.getName());
+    }
+
+    private static List<Bucket> bucketsHandle(List<? extends Histogram.Bucket> buckets, String name) {
+        return buckets
+            .stream()
+            .map(b -> {
+                Bucket bucket = Bucket.builder()
+                    .key(b.getKeyAsString())
+                    .count(b.getDocCount())
+                    .name(name)
+                    .build();
+                b.getAggregations().asList()
+                    .forEach(subAggregation -> route(bucket, subAggregation));
+                return bucket;
+            }).collect(Collectors.toList())
+            ;
+    }
+
+    private static <A extends Aggregation> void route(Bucket bucket, A a) {
+        if (a instanceof Terms) {
+            bucket.setBuckets(terms(a));
+        } else if (a instanceof Range) {
+            bucket.setBuckets(range(a));
+        } else if (a instanceof Histogram) {
+            bucket.setBuckets(range(a));
+        } else if (a instanceof Avg) {
+            bucket.setAvg(avg(a));
+        } else if (a instanceof Min) {
+            bucket.setMin(min(a));
+        } else if (a instanceof Max) {
+            bucket.setMax(max(a));
+        } else if (a instanceof Sum) {
+            bucket.setSum(sum(a));
+        } else if (a instanceof Stats) {
+            stats(bucket, a);
+        }  else if (a instanceof ValueCount) {
+            bucket.setValueCount(count(a));
+        } else {
+            throw new UnsupportedOperationException("不支持的聚合类型");
+        }
+    }
+
+    public static <A extends Aggregation> MetricsResponseSingleValue count(A a) {
+        ValueCount max = (ValueCount) a;
+        return MetricsResponseSingleValue.builder()
+            .value(max.getValue())
+            .name(a.getName())
+            .valueAsString(max.getValueAsString())
+            .build();
+    }
+
+    public static <A extends Aggregation> MetricsResponseSingleValue avg(A a) {
+        Avg avg = (Avg) a;
+        return MetricsResponseSingleValue.builder()
+            .value(avg.getValue())
+            .name(a.getName())
+            .valueAsString(avg.getValueAsString())
+            .build();
+    }
+
+    public static <A extends Aggregation> MetricsResponseSingleValue max(A a) {
+        Max max = (Max) a;
+        return MetricsResponseSingleValue.builder()
+            .value(max.getValue())
+            .name(a.getName())
+            .valueAsString(max.getValueAsString())
+            .build();
+    }
+
+    public static <A extends Aggregation> MetricsResponseSingleValue min(A a) {
+        Min min = (Min) a;
+        return MetricsResponseSingleValue.builder()
+            .value(min.getValue())
+            .name(a.getName())
+            .valueAsString(min.getValueAsString())
+            .build();
+    }
+
+    public static <A extends Aggregation> MetricsResponseSingleValue sum(A a) {
+        Sum sum = (Sum) a;
+        return MetricsResponseSingleValue.builder()
+            .value(sum.getValue())
+            .name(a.getName())
+            .valueAsString(sum.getValueAsString())
+            .build();
+    }
+
+    public static <A extends Aggregation> void stats(Bucket bucket, A a) {
+        Stats stats = (Stats) a;
+        bucket.setAvg(MetricsResponseSingleValue.builder()
+            .value(stats.getAvg())
+            .name(a.getName())
+            .valueAsString(stats.getAvgAsString())
+            .build());
+        bucket.setMax(MetricsResponseSingleValue.builder()
+            .value(stats.getMax())
+            .name(a.getName())
+            .valueAsString(stats.getMaxAsString())
+            .build());
+        bucket.setMin(MetricsResponseSingleValue.builder()
+            .value(stats.getMin())
+            .name(a.getName())
+            .valueAsString(stats.getMinAsString())
+            .build());
+        bucket.setSum(MetricsResponseSingleValue.builder()
+            .value(stats.getSum())
+            .name(a.getName())
+            .valueAsString(stats.getSumAsString())
+            .build());
+    }
+}

+ 70 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/Bucket.java

@@ -0,0 +1,70 @@
+package org.jetlinks.community.elastic.search.aggreation.bucket;
+
+import lombok.*;
+import org.jetlinks.community.elastic.search.aggreation.metrics.MetricsResponseSingleValue;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class Bucket {
+
+    private String key;
+
+    private String name;
+
+    private long count;
+
+    private String fromAsString;
+
+    private Object from;
+
+    private String toAsString;
+
+    private Object to;
+
+    private MetricsResponseSingleValue sum;
+
+    private MetricsResponseSingleValue valueCount;
+
+    private MetricsResponseSingleValue avg;
+
+    private MetricsResponseSingleValue min;
+
+    private MetricsResponseSingleValue max;
+
+    private List<Bucket> buckets;
+
+    private double toNumber(double number) {
+        return (Double.isInfinite(number) || Double.isNaN(number)) ? 0 : number;
+    }
+
+    public Map<String, Number> toMap() {
+        Map<String, Number> map = new HashMap<>();
+        if (this.sum != null) {
+            map.put(sum.getName(), toNumber(sum.getValue()));
+        }
+        if (this.valueCount != null) {
+            map.put(valueCount.getName(), toNumber(valueCount.getValue()));
+        }
+        if (this.avg != null) {
+            map.put(avg.getName(), toNumber(avg.getValue()));
+        }
+        if (this.min != null) {
+            map.put(min.getName(), toNumber(min.getValue()));
+        }
+        if (this.max != null) {
+            map.put(max.getName(), toNumber(max.getValue()));
+        }
+        return map;
+    }
+}

+ 70 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/BucketAggregationsStructure.java

@@ -0,0 +1,70 @@
+package org.jetlinks.community.elastic.search.aggreation.bucket;
+
+import lombok.*;
+import org.elasticsearch.search.aggregations.bucket.histogram.LongBounds;
+import org.hswebframework.utils.StringUtils;
+import org.jetlinks.community.elastic.search.aggreation.enums.BucketType;
+import org.jetlinks.community.elastic.search.aggreation.metrics.MetricsAggregationStructure;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@Setter
+@Getter
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class BucketAggregationsStructure {
+
+    @NonNull
+    private String field;
+
+    private String name;
+
+    @NonNull
+    private BucketType type = BucketType.TERMS;
+
+    /**
+     * 指定返回分组数量
+     */
+    private Integer size;
+
+    private Sort sort;
+
+    private List<Ranges> ranges;
+
+    private LongBounds extendedBounds;
+
+    /**
+     * 时间格式
+     */
+    private String format;
+
+
+    /**
+     * 单位时间间隔
+     *
+     * @see DateHistogramInterval
+     */
+    private String interval;
+
+    /**
+     * 缺失值
+     */
+    private Object missingValue;
+
+    private List<MetricsAggregationStructure> subMetricsAggregation = new LinkedList<>();
+
+    private List<BucketAggregationsStructure> subBucketAggregation = new LinkedList<>();
+
+    public String getName() {
+        if (StringUtils.isNullOrEmpty(name)) {
+            name = type.name().concat("_").concat(field);
+        }
+        return name;
+    }
+}

+ 21 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/BucketResponse.java

@@ -0,0 +1,21 @@
+package org.jetlinks.community.elastic.search.aggreation.bucket;
+
+import lombok.*;
+
+import java.util.List;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class BucketResponse {
+
+    private String name;
+
+    private List<Bucket> buckets;
+}

+ 38 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/DateHistogramInterval.java

@@ -0,0 +1,38 @@
+
+package org.jetlinks.community.elastic.search.aggreation.bucket;
+
+/**
+ * The interval the date histogram is based on.
+ */
+public class DateHistogramInterval {
+
+    public static final String SECOND = "1s";
+    public static final String MINUTE = "1m";
+    public static final String HOUR = "1h";
+    public static final String DAY = "1d";
+    public static final String WEEK = "1w";
+    public static final String MONTH = "1M";
+    public static final String QUARTER = "1q";
+    public static final String YEAR = "1y";
+
+    public static String seconds(int sec) {
+        return sec + "s";
+    }
+
+    public static String minutes(int min) {
+        return min + "m";
+    }
+
+    public static String hours(int hours) {
+        return hours + "h";
+    }
+
+    public static String days(int days) {
+        return days + "d";
+    }
+
+    public static String weeks(int weeks) {
+        return weeks + "w";
+    }
+
+}

+ 19 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/Ranges.java

@@ -0,0 +1,19 @@
+package org.jetlinks.community.elastic.search.aggreation.bucket;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@Getter
+@Setter
+public class Ranges {
+
+    private String key;
+
+    private Object form;
+
+    private Object to;
+}

+ 54 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/bucket/Sort.java

@@ -0,0 +1,54 @@
+package org.jetlinks.community.elastic.search.aggreation.bucket;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jetlinks.community.elastic.search.aggreation.enums.OrderType;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@Getter
+@Setter
+public class Sort {
+
+    private String order;
+
+    private OrderType type = OrderType.COUNT;
+
+    private Sort() {
+    }
+
+    private Sort(String order, OrderType type) {
+        this.type = type;
+        this.order = order;
+    }
+
+    private Sort(String order) {
+        this.order = order;
+    }
+
+    public String getOrder() {
+        if ("desc".equalsIgnoreCase(order)) {
+            return order;
+        } else {
+            return order = "asc";
+        }
+    }
+
+    public static Sort asc() {
+        return new Sort("asc");
+    }
+
+    public static Sort asc(OrderType type) {
+        return new Sort("asc", type);
+    }
+
+    public static Sort desc() {
+        return new Sort("desc");
+    }
+
+    public static Sort desc(OrderType type) {
+        return new Sort("desc", type);
+    }
+}

+ 28 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/enums/AggregationType.java

@@ -0,0 +1,28 @@
+package org.jetlinks.community.elastic.search.aggreation.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@Getter
+@AllArgsConstructor
+public enum AggregationType {
+    AVG("平均"),
+    MAX("最大"),
+    COUNT("计数"),
+    MIN("最小"),
+    SUM("总数"),
+    STATS("统计汇总"),
+    EXTENDED_STATS("扩展统计"),
+    CARDINALITY("基数"),//去重统计
+    VALUE_COUNT("非空值计数"),
+    TERMS("字段项"),
+    RANGE("范围"),
+    DATE_HISTOGRAM("直方图"),
+    DATE_RANGE("时间范围");
+
+    private String text;
+}

+ 199 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/enums/BucketType.java

@@ -0,0 +1,199 @@
+package org.jetlinks.community.elastic.search.aggreation.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import org.elasticsearch.search.aggregations.Aggregation;
+import org.elasticsearch.search.aggregations.AggregationBuilder;
+import org.elasticsearch.search.aggregations.AggregationBuilders;
+import org.elasticsearch.search.aggregations.BucketOrder;
+import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
+import org.elasticsearch.search.aggregations.bucket.range.DateRangeAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
+import org.jetlinks.community.elastic.search.aggreation.bucket.AggregationResponseHandle;
+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.Sort;
+import org.jetlinks.community.elastic.search.aggreation.metrics.MetricsAggregationStructure;
+import org.springframework.util.StringUtils;
+
+import java.time.ZoneId;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@Getter
+@AllArgsConstructor
+public enum BucketType {
+
+
+    TERMS("字段项") {
+        @Override
+        public AggregationBuilder aggregationBuilder(BucketAggregationsStructure structure) {
+            TermsAggregationBuilder builder = AggregationBuilders
+                .terms(structure.getName())
+                .field(structure.getField());
+            if (structure.getSize() != null) {
+                builder.size(structure.getSize());
+            }
+            Sort sort = structure.getSort();
+            if (sort != null) {
+                builder.order(mapping.get(OrderBuilder.of(sort.getOrder(), sort.getType())));
+            }
+            if (structure.getMissingValue() != null) {
+                builder.missing(structure.getMissingValue());
+            }
+            commonAggregationSetting(builder, structure);
+            return builder;
+        }
+
+        @Override
+        public <A extends Aggregation> List<Bucket> convert(A a) {
+            return AggregationResponseHandle.terms(a);
+        }
+    },
+    RANGE("范围") {
+        @Override
+        public AggregationBuilder aggregationBuilder(BucketAggregationsStructure structure) {
+            RangeAggregationBuilder builder = AggregationBuilders
+                .range(structure.getName())
+                .field(structure.getField());
+            if (StringUtils.hasText(structure.getFormat())) {
+                String format = structure.getFormat();
+                if (format.startsWith("yyyy")) {
+                    format = "8" + format;
+                }
+                builder.format(format);
+            }
+            structure.getRanges()
+                .forEach(ranges -> {
+                    builder.addRange(ranges.getKey(), (Double) ranges.getForm(), (Double) ranges.getTo());
+                });
+            commonAggregationSetting(builder, structure);
+            return builder;
+        }
+
+        @Override
+        public <A extends Aggregation> List<Bucket> convert(A a) {
+            return AggregationResponseHandle.range(a);
+        }
+    },
+    DATE_RANGE("时间范围") {
+        @Override
+        public AggregationBuilder aggregationBuilder(BucketAggregationsStructure structure) {
+            DateRangeAggregationBuilder builder = AggregationBuilders
+                .dateRange(structure.getName())
+                .field(structure.getField());
+            if (StringUtils.hasText(structure.getFormat())) {
+                String format = structure.getFormat();
+                if (format.startsWith("yyyy")) {
+                    format = "8" + format;
+                }
+                builder.format(format);
+            }
+            structure.getRanges()
+                .forEach(ranges -> {
+                    builder.addRange(ranges.getKey(), ranges.getForm().toString(), ranges.getTo().toString());
+                });
+            if (structure.getMissingValue() != null) {
+                builder.missing(structure.getMissingValue());
+            }
+            builder.timeZone(ZoneId.systemDefault());
+            commonAggregationSetting(builder, structure);
+            return builder;
+        }
+
+        @Override
+        public <A extends Aggregation> List<Bucket> convert(A a) {
+            return AggregationResponseHandle.range(a);
+        }
+    },
+    DATE_HISTOGRAM("时间范围") {
+        @Override
+        public AggregationBuilder aggregationBuilder(BucketAggregationsStructure structure) {
+            DateHistogramAggregationBuilder builder = AggregationBuilders
+                .dateHistogram(structure.getName())
+                .field(structure.getField());
+            if (StringUtils.hasText(structure.getFormat())) {
+                builder.format(structure.getFormat());
+            }
+            if (StringUtils.hasText(structure.getInterval())) {
+                builder.dateHistogramInterval(new DateHistogramInterval(structure.getInterval()));
+            }
+            if (structure.getExtendedBounds() != null) {
+                builder.extendedBounds(structure.getExtendedBounds());
+            }
+            if (structure.getMissingValue() != null) {
+                builder.missing(structure.getMissingValue());
+            }
+            Sort sort = structure.getSort();
+            if (sort != null) {
+                builder.order(mapping.get(OrderBuilder.of(sort.getOrder(), sort.getType())));
+            }
+            builder.timeZone(ZoneId.systemDefault());
+            commonAggregationSetting(builder, structure);
+            return builder;
+        }
+
+
+        @Override
+        public <A extends Aggregation> List<Bucket> convert(A a) {
+            return AggregationResponseHandle.dateHistogram(a);
+        }
+    };
+
+    private final String text;
+
+    public abstract AggregationBuilder aggregationBuilder(BucketAggregationsStructure structure);
+
+    public abstract <A extends Aggregation> List<Bucket> convert(A a);
+
+    private static void commonAggregationSetting(AggregationBuilder builder, BucketAggregationsStructure structure) {
+        if (structure.getSubMetricsAggregation() != null && structure.getSubMetricsAggregation().size() > 0) {
+            addMetricsSubAggregation(builder, structure.getSubMetricsAggregation());
+        }
+        if (structure.getSubBucketAggregation() != null && structure.getSubBucketAggregation().size() > 0) {
+            addBucketSubAggregation(builder, structure.getSubBucketAggregation());
+        }
+    }
+
+    private static void addMetricsSubAggregation(AggregationBuilder builder, List<MetricsAggregationStructure> subMetricsAggregation) {
+        subMetricsAggregation
+            .forEach(subStructure -> {
+                builder.subAggregation(subStructure.getType().aggregationBuilder(subStructure.getName(), subStructure.getField()));
+            });
+    }
+
+    private static void addBucketSubAggregation(AggregationBuilder builder, List<BucketAggregationsStructure> subBucketAggregation) {
+        subBucketAggregation
+            .forEach(subStructure -> {
+                builder.subAggregation(subStructure.getType().aggregationBuilder(subStructure));
+            });
+    }
+
+    @Getter
+    @AllArgsConstructor(staticName = "of")
+    @EqualsAndHashCode
+    static class OrderBuilder {
+
+        private String order;
+
+        private OrderType orderType;
+    }
+
+    static Map<OrderBuilder, BucketOrder> mapping = new HashMap<>();
+
+    static {
+        mapping.put(OrderBuilder.of("asc", OrderType.COUNT), BucketOrder.count(true));
+        mapping.put(OrderBuilder.of("desc", OrderType.COUNT), BucketOrder.count(false));
+        mapping.put(OrderBuilder.of("asc", OrderType.KEY), BucketOrder.key(true));
+        mapping.put(OrderBuilder.of("desc", OrderType.KEY), BucketOrder.key(false));
+    }
+
+}

+ 136 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/enums/MetricsType.java

@@ -0,0 +1,136 @@
+package org.jetlinks.community.elastic.search.aggreation.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.search.aggregations.AggregationBuilder;
+import org.elasticsearch.search.aggregations.AggregationBuilders;
+import org.elasticsearch.search.aggregations.metrics.*;
+import org.jetlinks.community.elastic.search.aggreation.metrics.MetricsResponse;
+import org.jetlinks.community.elastic.search.aggreation.metrics.MetricsResponseSingleValue;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@Getter
+@AllArgsConstructor
+public enum MetricsType {
+
+    AVG("平均") {
+        @Override
+        public AggregationBuilder aggregationBuilder(String name, String filed) {
+            return AggregationBuilders.avg(name).field(filed);
+        }
+
+        @Override
+        public MetricsResponse getResponse(String name, SearchResponse response) {
+            Avg avg = response.getAggregations().get(name);
+            return MetricsResponse.builder()
+                .results(Collections.singletonMap(AVG,
+                    new MetricsResponseSingleValue(avg.getValue(), avg.getName(), avg.getValueAsString())))
+                .build();
+        }
+    },
+    MAX("最大") {
+        @Override
+        public AggregationBuilder aggregationBuilder(String name, String filed) {
+            return AggregationBuilders.max(name).field(filed);
+        }
+
+        @Override
+        public MetricsResponse getResponse(String name, SearchResponse response) {
+            Max max = response.getAggregations().get(name);
+            return MetricsResponse.builder()
+                .results(Collections.singletonMap(MAX,
+                    new MetricsResponseSingleValue(max.getValue(), max.getName(), max.getValueAsString())))
+                .build();
+        }
+    },
+    COUNT("非空值计数") {
+        @Override
+        public AggregationBuilder aggregationBuilder(String name, String filed) {
+            return AggregationBuilders.count(name).field(filed);
+        }
+
+        @Override
+        public MetricsResponse getResponse(String name, SearchResponse response) {
+            ValueCount valueCount = response.getAggregations().get(name);
+            return MetricsResponse.builder()
+                .results(Collections.singletonMap(COUNT,
+                    new MetricsResponseSingleValue(valueCount.getValue(), valueCount.getName(), valueCount.getValueAsString())))
+                .build();
+        }
+    },
+    MIN("最小") {
+        @Override
+        public AggregationBuilder aggregationBuilder(String name, String filed) {
+            return AggregationBuilders.min(name).field(filed);
+        }
+
+        @Override
+        public MetricsResponse getResponse(String name, SearchResponse response) {
+            Min min = response.getAggregations().get(name);
+            return MetricsResponse.builder()
+                .results(Collections.singletonMap(MIN,
+                    new MetricsResponseSingleValue(min.getValue(), min.getName(), min.getValueAsString())))
+                .build();
+        }
+    },
+    SUM("总数") {
+        @Override
+        public AggregationBuilder aggregationBuilder(String name, String filed) {
+            return AggregationBuilders.sum(name).field(filed);
+        }
+
+        @Override
+        public MetricsResponse getResponse(String name, SearchResponse response) {
+            Sum sum = response.getAggregations().get(name);
+            return MetricsResponse.builder()
+                .results(Collections.singletonMap(SUM,
+                    new MetricsResponseSingleValue(sum.getValue(), sum.getName(), sum.getValueAsString())))
+                .build();
+        }
+    },
+    STATS("统计汇总") {
+        @Override
+        public AggregationBuilder aggregationBuilder(String name, String filed) {
+            return AggregationBuilders.stats(name).field(filed);
+        }
+
+        @Override
+        public MetricsResponse getResponse(String name, SearchResponse response) {
+            Stats stats = response.getAggregations().get(name);
+            Map<MetricsType, MetricsResponseSingleValue> results = new HashMap<>();
+            results.put(AVG, new MetricsResponseSingleValue(stats.getAvg(), stats.getName(), stats.getAvgAsString()));
+            results.put(MIN, new MetricsResponseSingleValue(stats.getMin(), stats.getName(), stats.getMinAsString()));
+            results.put(MAX, new MetricsResponseSingleValue(stats.getMax(), stats.getName(), stats.getMaxAsString()));
+            results.put(SUM, new MetricsResponseSingleValue(stats.getSum(), stats.getName(), stats.getMaxAsString()));
+            results.put(COUNT, new MetricsResponseSingleValue(stats.getCount(), stats.getName(), String.valueOf(stats.getCount())));
+            return MetricsResponse.builder()
+                .results(results)
+                .build();
+        }
+    };
+
+    private String text;
+
+
+    public abstract AggregationBuilder aggregationBuilder(String name, String filed);
+
+    public abstract MetricsResponse getResponse(String name, SearchResponse response);
+
+    public static MetricsType of(String name) {
+        for (MetricsType type : MetricsType.values()) {
+            if (type.name().equalsIgnoreCase(name)) {
+                return type;
+            }
+        }
+        throw new UnsupportedOperationException("不支持的聚合度量类型:" + name);
+    }
+
+}

+ 18 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/enums/OrderType.java

@@ -0,0 +1,18 @@
+package org.jetlinks.community.elastic.search.aggreation.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@AllArgsConstructor
+@Getter
+public enum OrderType {
+
+    COUNT("计数"),
+    KEY("分组值");
+
+    private String text;
+}

+ 37 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/metrics/MetricsAggregationStructure.java

@@ -0,0 +1,37 @@
+package org.jetlinks.community.elastic.search.aggreation.metrics;
+
+import lombok.*;
+import org.hswebframework.utils.StringUtils;
+import org.jetlinks.community.elastic.search.aggreation.enums.MetricsType;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@Setter
+@Getter
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class MetricsAggregationStructure {
+
+    @NonNull
+    private String field;
+
+    private String name;
+
+    @NonNull
+    private MetricsType type = MetricsType.COUNT;
+
+    /**
+     * 缺失值
+     */
+    private Object missingValue;
+
+    public String getName() {
+        if (StringUtils.isNullOrEmpty(name)) {
+            name = type.name().concat("_").concat(field);
+        }
+        return name;
+    }
+}

+ 33 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/metrics/MetricsResponse.java

@@ -0,0 +1,33 @@
+package org.jetlinks.community.elastic.search.aggreation.metrics;
+
+import lombok.*;
+import org.jetlinks.community.elastic.search.aggreation.enums.MetricsType;
+
+import java.util.Map;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class MetricsResponse {
+
+    private Map<MetricsType, MetricsResponseSingleValue> results;
+
+    private MetricsResponseSingleValue singleResult;
+
+    public MetricsResponseSingleValue getSingleResult() {
+        if (singleResult == null) {
+            this.singleResult = results.entrySet()
+                    .stream()
+                    .findFirst()
+                    .map(Map.Entry::getValue)
+                    .orElse(MetricsResponseSingleValue.empty());
+        }
+        return singleResult;
+    }
+}

+ 47 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/aggreation/metrics/MetricsResponseSingleValue.java

@@ -0,0 +1,47 @@
+package org.jetlinks.community.elastic.search.aggreation.metrics;
+
+import lombok.*;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+
+@Setter
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class MetricsResponseSingleValue {
+
+    private double value;
+
+    private String valueAsString;
+
+    private String name;
+
+    public static MetricsResponseSingleValue empty() {
+        return new MetricsResponseSingleValue();
+    }
+
+//    public double getValue() {
+//        if (isDoubleOrFloat(String.valueOf(value))) {
+//            BigDecimal b = BigDecimal.valueOf(value);
+//            return b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
+//        } else {
+//            return 0;
+//        }
+//    }
+//
+//    public static boolean isDoubleOrFloat(String str) {
+//        if (str.length() > 15) {
+//            str = str.substring(0, 15);
+//        }
+//        Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");
+//        return pattern.matcher(str).matches();
+//    }
+//
+//    public static void main(String[] args) {
+//        System.out.println(new BigDecimal("8.839927333333333E7"));
+//    }
+}

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

@@ -0,0 +1,157 @@
+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.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.ElasticSearchIndexProperties;
+import org.jetlinks.community.elastic.search.service.reactive.DefaultReactiveElasticsearchClient;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+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.ProxyProvider;
+import reactor.netty.tcp.TcpClient;
+
+import javax.net.ssl.SSLContext;
+import java.net.InetSocketAddress;
+import java.time.Duration;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author bsetfeng
+ * @author zhouhao
+ * @since 1.0
+ **/
+@Configuration
+@Slf4j
+@EnableConfigurationProperties({
+    ElasticSearchProperties.class,
+    EmbeddedElasticSearchProperties.class,
+    ElasticSearchIndexProperties.class})
+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) {
+        if (embeddedProperties.isEnabled()) {
+            log.debug("starting embedded elasticsearch on {}:{}",
+                embeddedProperties.getHost(),
+                embeddedProperties.getPort());
+
+            new EmbeddedElasticSearch(embeddedProperties).start();
+        }
+
+        WebClientProvider provider = getWebClientProvider(clientConfiguration);
+
+        HostProvider hostProvider = HostProvider.provider(provider, clientConfiguration.getHeadersSupplier(),
+            clientConfiguration.getEndpoints().toArray(new InetSocketAddress[0]));
+
+        DefaultReactiveElasticsearchClient client =
+            new DefaultReactiveElasticsearchClient(hostProvider, new RequestCreator() {
+            });
+
+        client.setHeadersSupplier(clientConfiguration.getHeadersSupplier());
+
+        return client;
+    }
+
+    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";
+        }
+
+        ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
+        WebClientProvider provider = WebClientProvider.create(scheme, connector);
+
+        if (clientConfiguration.getPathPrefix() != null) {
+            provider = provider.withPathPrefix(clientConfiguration.getPathPrefix());
+        }
+
+        provider = provider.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
+            .withWebClientConfigurer(clientConfiguration.getWebClientConfigurer());
+        return provider;
+    }
+
+    @Bean
+    @SneakyThrows
+    public ElasticRestClient elasticRestClient() {
+
+        RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(properties.createHosts())
+            .setRequestConfigCallback(properties::applyRequestConfigBuilder)
+            .setHttpClientConfigCallback(properties::applyHttpAsyncClientBuilder));
+        return new ElasticRestClient(client, client);
+    }
+
+    @Bean(destroyMethod = "close")
+    public RestHighLevelClient restHighLevelClient(ElasticRestClient client) {
+        return client.getWriteClient();
+    }
+
+}

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

@@ -0,0 +1,51 @@
+package org.jetlinks.community.elastic.search.configuration;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.http.HttpHost;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.List;
+
+@ConfigurationProperties(prefix = "elasticsearch.client")
+
+@Getter
+@Setter
+public class ElasticSearchProperties {
+
+    private String host = "localhost";
+    private int port = 9200;
+
+    private int connectionRequestTimeout = 5000;
+    private int connectTimeout = 2000;
+    private int socketTimeout = 2000;
+    private int maxConnTotal = 30;
+    private List<String> hosts;
+
+
+    public HttpHost[] createHosts() {
+        if (CollectionUtils.isEmpty(hosts)) {
+            return new HttpHost[]{new HttpHost(host, port, "http")};
+        }
+
+        return hosts.stream().map(HttpHost::create).toArray(HttpHost[]::new);
+    }
+
+    public RequestConfig.Builder applyRequestConfigBuilder(RequestConfig.Builder builder) {
+
+        builder.setConnectTimeout(connectTimeout);
+        builder.setConnectionRequestTimeout(connectionRequestTimeout);
+        builder.setSocketTimeout(socketTimeout);
+
+        return builder;
+    }
+
+    public HttpAsyncClientBuilder applyHttpAsyncClientBuilder(HttpAsyncClientBuilder builder) {
+        builder.setMaxConnTotal(maxConnTotal);
+
+        return builder;
+    }
+}

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

@@ -0,0 +1,33 @@
+package org.jetlinks.community.elastic.search.embedded;
+
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.node.InternalSettingsPreparer;
+import org.elasticsearch.node.Node;
+import org.elasticsearch.transport.Netty4Plugin;
+
+import java.util.Collections;
+
+@Slf4j
+public class EmbeddedElasticSearch extends Node {
+
+    static {
+        System.setProperty("es.set.netty.runtime.available.processors","false");
+    }
+    @SneakyThrows
+    public EmbeddedElasticSearch(EmbeddedElasticSearchProperties properties) {
+        super(InternalSettingsPreparer.prepareEnvironment(
+            properties.applySetting(
+                Settings.builder()
+                    .put("node.name", "test")
+                    .put("discovery.type", "single-node")
+                    .put("transport.type", Netty4Plugin.NETTY_TRANSPORT_NAME)
+                    .put("http.type", Netty4Plugin.NETTY_HTTP_TRANSPORT_NAME)
+                    .put("network.host", "0.0.0.0")
+                    .put("http.port", 9200)
+            ).build(), Collections.emptyMap(), null, () -> "default"),
+            Collections.singleton(Netty4Plugin.class), false);
+    }
+
+}

+ 30 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/embedded/EmbeddedElasticSearchProperties.java

@@ -0,0 +1,30 @@
+package org.jetlinks.community.elastic.search.embedded;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.elasticsearch.common.settings.Settings;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "elasticsearch.embedded")
+@Getter
+@Setter
+public class EmbeddedElasticSearchProperties {
+
+    private boolean enabled;
+
+    private String dataPath = "./data/elasticsearch";
+
+    private String homePath = "./";
+
+    private int port = 9200;
+
+    private String host = "0.0.0.0";
+
+
+    public Settings.Builder applySetting(Settings.Builder settings) {
+        return settings.put("network.host", host)
+            .put("http.port", port)
+            .put("path.data", dataPath)
+            .put("path.home", homePath);
+    }
+}

+ 54 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/enums/ElasticDateFormat.java

@@ -0,0 +1,54 @@
+package org.jetlinks.community.elastic.search.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.hswebframework.web.dict.EnumDict;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ * Values based on reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html
+ **/
+@Getter
+@AllArgsConstructor
+public enum ElasticDateFormat implements EnumDict<String> {
+
+    epoch_millis("epoch_millis", "毫秒"),
+    epoch_second("epoch_second", "秒"),
+    strict_date("strict_date", "yyyy-MM-dd"),
+    basic_date_time("basic_date_time", "yyyyMMdd'T'HHmmss.SSSZ"),
+    strict_date_time("strict_date_time", "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"),
+    strict_date_hour_minute_second("strict_date_hour_minute_second", "yyyy-MM-dd'T'HH:mm:ss"),
+    strict_hour_minute_second("strict_hour_minute_second", "HH:mm:ss"),
+    simple_date("yyyy-MM-dd HH:mm:ss", "通用格式");
+
+    private String value;
+
+    private final String text;
+
+    public static String getFormat(ElasticDateFormat... dateFormats) {
+        return getFormat(Arrays.asList(dateFormats));
+    }
+
+    public static String getFormat(List<ElasticDateFormat> dateFormats) {
+        return getFormatStr(dateFormats.stream()
+            .map(ElasticDateFormat::getValue)
+            .collect(Collectors.toList())
+        );
+    }
+
+    public static String getFormatStr(List<String> dateFormats) {
+        StringBuffer format = new StringBuffer();
+        for (int i = 0; i < dateFormats.size(); i++) {
+            format.append(dateFormats.get(i));
+            if (i != dateFormats.size() - 1) {
+                format.append("||");
+            }
+        }
+        return format.toString();
+    }
+}

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

@@ -0,0 +1,66 @@
+package org.jetlinks.community.elastic.search.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.hswebframework.web.dict.EnumDict;
+import org.hswebframework.web.exception.NotFoundException;
+import org.jetlinks.core.metadata.DataType;
+import org.jetlinks.core.metadata.types.*;
+import org.springframework.util.StringUtils;
+
+import java.util.function.Supplier;
+
+@AllArgsConstructor
+public enum ElasticPropertyType implements EnumDict<String> {
+
+    TEXT("text", "text", StringType::new),
+    BYTE("byte", "byte", () -> new IntType().min(Byte.MIN_VALUE).max(Byte.MAX_VALUE)),
+    SHORT("short", "short", () -> new IntType().min(Short.MIN_VALUE).max(Short.MAX_VALUE)),
+    INTEGER("int", "integer", IntType::new),
+    LONG("long", "long", LongType::new),
+    DATE("date", "date", DateTimeType::new),
+    HALF_FLOAT("half_float", "half_float", FloatType::new),
+    FLOAT("float", "float", FloatType::new),
+    DOUBLE("double", "double", DoubleType::new),
+    BOOLEAN("boolean", "boolean", BooleanType::new),
+    OBJECT("object", "object", ObjectType::new),
+    AUTO("auto", "auto", () -> null),
+    NESTED("nested", "nested", ObjectType::new),
+    IP("ip", "ip", LongType::new),
+    ATTACHMENT("attachment", "attachment", FileType::new),
+    KEYWORD("string", "keyword", StringType::new),
+    GEO_POINT("geo_point", "geo_point", GeoType::new);
+
+    @Getter
+    private String text;
+    @Getter
+    private String value;
+
+    private Supplier<DataType> typeBuilder;
+
+    public DataType getType() {
+        return typeBuilder.get();
+    }
+
+    public static ElasticPropertyType of(Object value) {
+        if (!StringUtils.isEmpty(value)) {
+            for (ElasticPropertyType elasticPropertyType : ElasticPropertyType.values()) {
+                if (elasticPropertyType.getValue().equals(value)) {
+                    return elasticPropertyType;
+                }
+            }
+        }
+        return null;
+    }
+
+    public static ElasticPropertyType ofJava(Object value) {
+        if (!StringUtils.isEmpty(value)) {
+            for (ElasticPropertyType elasticPropertyType : ElasticPropertyType.values()) {
+                if (elasticPropertyType.getText().equals(value)) {
+                    return elasticPropertyType;
+                }
+            }
+        }
+        throw new NotFoundException("未找到数据类型为:" + value + "的枚举");
+    }
+}

+ 69 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/enums/LinkTypeEnum.java

@@ -0,0 +1,69 @@
+package org.jetlinks.community.elastic.search.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.hswebframework.ezorm.core.param.Term;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.Optional;
+
+/**
+ * @author Jia_RG
+ */
+@Getter
+@AllArgsConstructor
+public enum LinkTypeEnum {
+    and("and") {
+        @Override
+        public void process(BoolQueryBuilder query, Term term) {
+            if (term.getTerms().isEmpty()) {
+                query.must(TermTypeEnum.of(term.getTermType().trim()).map(e -> e.process(term)).orElse(QueryBuilders.boolQuery()));
+            } else {
+                // 嵌套查询新建一个包起来
+                BoolQueryBuilder nextQuery = QueryBuilders.boolQuery();
+                LinkedList<Term> terms = ((LinkedList<Term>) term.getTerms());
+                // 同一层级取最后一个的type
+                LinkTypeEnum.of(getLast(terms).getType().name()).ifPresent(e -> terms.forEach(t -> e.process(nextQuery, t)));
+                // 处理完后包括进去
+                query.must(nextQuery);
+            }
+        }
+    },
+    or("or") {
+        @Override
+        public void process(BoolQueryBuilder query, Term term) {
+            // 跟上面代码相似
+            if (term.getTerms().isEmpty()) {
+                query.should(TermTypeEnum.of(term.getTermType().trim()).map(e -> e.process(term)).orElse(QueryBuilders.boolQuery()));
+            } else {
+                BoolQueryBuilder nextQuery = QueryBuilders.boolQuery();
+                LinkedList<Term> terms = ((LinkedList<Term>) term.getTerms());
+                LinkTypeEnum.of(getLast(terms).getType().name()).ifPresent(e -> terms.forEach(t -> e.process(nextQuery, t)));
+                query.should(nextQuery);
+            }
+        }
+    };
+
+    private final String type;
+
+    public abstract void process(BoolQueryBuilder query, Term term);
+
+
+    public static Optional<LinkTypeEnum> of(String type) {
+        return Arrays.stream(values())
+                .filter(e -> e.getType().equalsIgnoreCase(type))
+                .findAny();
+    }
+
+    private static Term getLast(LinkedList<Term> terms) {
+        int index = terms.indexOf(terms.getLast());
+        while (index >= 0) {
+            if (terms.get(index).getTerms().isEmpty()) break;
+            index--;
+        }
+        return terms.get(index);
+    }
+}

+ 114 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/enums/TermTypeEnum.java

@@ -0,0 +1,114 @@
+package org.jetlinks.community.elastic.search.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.hswebframework.ezorm.core.param.Term;
+import org.jetlinks.community.elastic.search.utils.TermCommonUtils;
+import org.jetlinks.reactor.ql.utils.CastUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author Jia_RG
+ * @author bestfeng
+ */
+@Getter
+@AllArgsConstructor
+public enum TermTypeEnum {
+    eq("eq") {
+        @Override
+        public QueryBuilder process(Term term) {
+            return QueryBuilders.termQuery(term.getColumn().trim(), term.getValue());
+        }
+    },
+    not("not") {
+        @Override
+        public QueryBuilder process(Term term) {
+            return QueryBuilders.boolQuery().mustNot(QueryBuilders.termQuery(term.getColumn().trim(), term.getValue()));
+        }
+    },
+    btw("btw") {
+        @Override
+        public QueryBuilder process(Term term) {
+            Object between = null;
+            Object and = null;
+            List<?> values = TermCommonUtils.convertToList(term.getValue());
+            if (values.size() > 0) {
+                between = CastUtils.castNumber(values.get(0));
+            }
+            if (values.size() > 1) {
+                and = CastUtils.castNumber(values.get(1));
+            }
+            return QueryBuilders.rangeQuery(term.getColumn().trim()).gte(between).lte(and);
+        }
+    },
+    gt("gt") {
+        @Override
+        public QueryBuilder process(Term term) {
+            Object value = CastUtils.castNumber(term.getValue());
+            return QueryBuilders.rangeQuery(term.getColumn().trim()).gt(value);
+        }
+    },
+    gte("gte") {
+        @Override
+        public QueryBuilder process(Term term) {
+            Object value = CastUtils.castNumber(term.getValue());
+            return QueryBuilders.rangeQuery(term.getColumn().trim()).gte(value);
+        }
+    },
+    lt("lt") {
+        @Override
+        public QueryBuilder process(Term term) {
+            Object value = CastUtils.castNumber(term.getValue());
+            return QueryBuilders.rangeQuery(term.getColumn().trim()).lt(value);
+        }
+    },
+    lte("lte") {
+        @Override
+        public QueryBuilder process(Term term) {
+            Object value = CastUtils.castNumber(term.getValue());
+            return QueryBuilders.rangeQuery(term.getColumn().trim()).lte(value);
+        }
+    },
+    in("in") {
+        @Override
+        public QueryBuilder process(Term term) {
+            return QueryBuilders.termsQuery(term.getColumn().trim(), TermCommonUtils.convertToList(term.getValue()));
+        }
+    },
+    like("like") {
+        @Override
+        public QueryBuilder process(Term term) {
+            //return QueryBuilders.matchPhraseQuery(term.getColumn().trim(), term.getValue());
+            return QueryBuilders.wildcardQuery(term.getColumn().trim(), likeQueryTermValueHandler(term.getValue()));
+        }
+    },
+    nlike("nlike") {
+        @Override
+        public QueryBuilder process(Term term) {
+            return QueryBuilders.boolQuery().mustNot(QueryBuilders.wildcardQuery(term.getColumn().trim(), likeQueryTermValueHandler(term.getValue())));
+        }
+    };
+
+    private final String type;
+
+    public abstract QueryBuilder process(Term term);
+
+    public static String likeQueryTermValueHandler(Object value) {
+        if (!StringUtils.isEmpty(value)) {
+            return value.toString().replace("%", "*");
+        }
+        return "**";
+    }
+
+    public static Optional<TermTypeEnum> of(String type) {
+        return Arrays.stream(values())
+                .filter(e -> e.getType().equalsIgnoreCase(type))
+                .findAny();
+    }
+}

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

@@ -0,0 +1,68 @@
+package org.jetlinks.community.elastic.search.index;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+@ConfigurationProperties(prefix = "elasticsearch.index")
+public class DefaultElasticSearchIndexManager implements ElasticSearchIndexManager {
+
+    @Getter
+    @Setter
+    private String defaultStrategy = "direct";
+
+    @Getter
+    @Setter
+    private Map<String, String> indexUseStrategy = new HashMap<>();
+
+    private final Map<String, ElasticSearchIndexStrategy> strategies = new ConcurrentHashMap<>();
+
+    private final Map<String, ElasticSearchIndexMetadata> indexMetadataStore = new ConcurrentHashMap<>();
+
+    public DefaultElasticSearchIndexManager(List<ElasticSearchIndexStrategy> strategies) {
+        strategies.forEach(this::registerStrategy);
+    }
+
+    @Override
+    public Mono<Void> putIndex(ElasticSearchIndexMetadata index) {
+        return this.getIndexStrategy(index.getIndex())
+            .flatMap(strategy -> strategy.putIndex(index))
+            .doOnSuccess(metadata -> indexMetadataStore.put(index.getIndex(), index));
+    }
+
+    @Override
+    public Mono<ElasticSearchIndexMetadata> getIndexMetadata(String index) {
+        return Mono.justOrEmpty(indexMetadataStore.get(index))
+            .switchIfEmpty(Mono.defer(() -> doLoadMetaData(index)
+                .doOnNext(metadata -> indexMetadataStore.put(metadata.getIndex(), metadata))));
+    }
+
+    protected Mono<ElasticSearchIndexMetadata> doLoadMetaData(String index) {
+        return getIndexStrategy(index)
+            .flatMap(strategy -> strategy.loadIndexMetadata(index));
+    }
+
+    @Override
+    public Mono<ElasticSearchIndexStrategy> getIndexStrategy(String index) {
+        return Mono.justOrEmpty(strategies.get(indexUseStrategy.getOrDefault(index.toLowerCase(), defaultStrategy)))
+            .switchIfEmpty(Mono.error(() -> new IllegalArgumentException("[" + index + "] 不支持任何索引策略")));
+    }
+
+    @Override
+    public void useStrategy(String index, String strategy) {
+        indexUseStrategy.put(index, strategy);
+    }
+
+    public void registerStrategy(ElasticSearchIndexStrategy strategy) {
+        strategies.put(strategy.getId(), strategy);
+    }
+
+}

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

@@ -0,0 +1,53 @@
+package org.jetlinks.community.elastic.search.index;
+
+import org.jetlinks.core.metadata.DataType;
+import org.jetlinks.core.metadata.PropertyMetadata;
+import org.jetlinks.core.metadata.SimplePropertyMetadata;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DefaultElasticSearchIndexMetadata implements ElasticSearchIndexMetadata {
+    private String index;
+
+    private Map<String, PropertyMetadata> properties = new HashMap<>();
+
+    public DefaultElasticSearchIndexMetadata(String index) {
+        this.index = index.toLowerCase().trim();
+    }
+
+    public DefaultElasticSearchIndexMetadata(String index, List<PropertyMetadata> properties) {
+        this(index);
+        properties.forEach(this::addProperty);
+    }
+
+    @Override
+    public PropertyMetadata getProperty(String property) {
+        return properties.get(property);
+    }
+
+    @Override
+    public String getIndex() {
+        return index;
+    }
+
+    @Override
+    public List<PropertyMetadata> getProperties() {
+        return new ArrayList<>(properties.values());
+    }
+
+    public DefaultElasticSearchIndexMetadata addProperty(PropertyMetadata property) {
+        properties.put(property.getId(), property);
+        return this;
+    }
+
+    public DefaultElasticSearchIndexMetadata addProperty(String property, DataType type) {
+        SimplePropertyMetadata metadata=new SimplePropertyMetadata();
+        metadata.setValueType(type);
+        metadata.setId(property);
+        properties.put(property, metadata);
+        return this;
+    }
+}

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

@@ -0,0 +1,9 @@
+package org.jetlinks.community.elastic.search.index;
+
+/**
+ * @version 1.0
+ **/
+public interface ElasticIndex {
+
+    String getIndex();
+}

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

@@ -0,0 +1,65 @@
+package org.jetlinks.community.elastic.search.index;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public interface ElasticSearchIndexManager {
+
+    /**
+     * 更新索引
+     *
+     * @param index 索引元数据
+     * @return 更新结果
+     */
+    Mono<Void> putIndex(ElasticSearchIndexMetadata index);
+
+    /**
+     * 获取索引元数据
+     *
+     * @param index 索引名称
+     * @return 索引元数据
+     */
+    Mono<ElasticSearchIndexMetadata> getIndexMetadata(String index);
+
+    /**
+     * 获取多个所有元数据
+     * @param index 索引名称
+     * @return 索引元数据
+     */
+    default Flux<ElasticSearchIndexMetadata> getIndexesMetadata(String... index) {
+        return Flux
+            .fromArray(index)
+            .flatMap(this::getIndexMetadata);
+    }
+
+    /**
+     * 获取索引策略
+     *
+     * @param index 索引名称
+     * @return 索引策略
+     * @see ElasticSearchIndexStrategy
+     */
+    Mono<ElasticSearchIndexStrategy> getIndexStrategy(String index);
+
+    default Flux<ElasticSearchIndexStrategy> getIndexesStrategy(String... index){
+        return Flux
+            .fromArray(index)
+            .flatMap(this::getIndexStrategy);
+    }
+
+    /**
+     * 设置索引策略
+     *
+     * @param index    索引策略
+     * @param strategy 策略标识
+     */
+    void useStrategy(String index, String strategy);
+
+    /**
+     * 注册索引策略
+     *
+     * @param strategy 策略
+     */
+    void registerStrategy(ElasticSearchIndexStrategy strategy);
+
+}

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

@@ -0,0 +1,28 @@
+package org.jetlinks.community.elastic.search.index;
+
+import org.jetlinks.community.elastic.search.utils.ElasticSearchConverter;
+import org.jetlinks.core.metadata.PropertyMetadata;
+
+import java.util.List;
+import java.util.Map;
+
+public interface ElasticSearchIndexMetadata {
+
+    String getIndex();
+
+    List<PropertyMetadata> getProperties();
+
+    PropertyMetadata getProperty(String property);
+
+    default Map<String, Object> convertToElastic(Map<String, Object> map) {
+        return ElasticSearchConverter.convertDataToElastic(map, getProperties());
+    }
+
+    default Map<String, Object> convertFromElastic(Map<String, Object> map) {
+        return ElasticSearchConverter.convertDataFromElastic(map, getProperties());
+    }
+
+    default ElasticSearchIndexMetadata newIndexName(String name) {
+        return new DefaultElasticSearchIndexMetadata(name, getProperties());
+    }
+}

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

@@ -0,0 +1,26 @@
+package org.jetlinks.community.elastic.search.index;
+
+import lombok.*;
+import org.elasticsearch.common.settings.Settings;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ConfigurationProperties(prefix = "elasticsearch.index.settings")
+public class ElasticSearchIndexProperties {
+
+    private int numberOfShards = 1;
+
+    private int numberOfReplicas = 0;
+
+    public Settings toSettings() {
+
+        return Settings.builder()
+            .put("number_of_shards", Math.max(1, numberOfShards))
+            .put("number_of_replicas", numberOfReplicas)
+            .build();
+    }
+}

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

@@ -0,0 +1,45 @@
+package org.jetlinks.community.elastic.search.index;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * es 索引策略
+ *
+ * @author zhouhao
+ * @version 1.0
+ */
+public interface ElasticSearchIndexStrategy {
+
+    /**
+     * 策略标识
+     *
+     * @return ID
+     */
+    String getId();
+
+    /**
+     * 获取用于获取保存数据的索引
+     *
+     * @param index 原始索引名
+     * @return 索引名
+     */
+    String getIndexForSave(String index);
+
+    /**
+     * 获取用于搜索的索引
+     *
+     * @param index 原始索引名
+     * @return 索引名
+     */
+    String getIndexForSearch(String index);
+
+    /**
+     * 更新索引
+     *
+     * @param metadata 索引元数据
+     * @return 更新结果
+     */
+    Mono<Void> putIndex(ElasticSearchIndexMetadata metadata);
+
+    Mono<ElasticSearchIndexMetadata> loadIndexMetadata(String index);
+}

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

@@ -0,0 +1,12 @@
+package org.jetlinks.community.elastic.search.index;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+public interface IndexTemplateProvider {
+
+    static String getIndexTemplate(String index) {
+        return index.concat("_template");
+    }
+}

+ 64 - 0
jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/mapping/IndexMappingMetadata.java

@@ -0,0 +1,64 @@
+package org.jetlinks.community.elastic.search.index.mapping;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jetlinks.community.elastic.search.enums.ElasticPropertyType;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * @author bsetfeng
+ * @since 1.0
+ **/
+@Getter
+@Setter
+public class IndexMappingMetadata {
+
+    private String index;
+
+    private Map<String, SingleMappingMetadata> metadata = new HashMap<>();
+
+    public List<SingleMappingMetadata> getAllMetaData() {
+        return metadata.entrySet()
+                .stream()
+                .map(Map.Entry::getValue)
+                .collect(Collectors.toList());
+    }
+
+    public SingleMappingMetadata getMetaData(String fieldName) {
+        return metadata.get(fieldName);
+    }
+
+    public List<SingleMappingMetadata> getMetaDataByType(ElasticPropertyType type) {
+        return getAllMetaData()
+                .stream()
+                .filter(singleMapping -> singleMapping.getType().equals(type))
+                .collect(Collectors.toList());
+    }
+
+    public Map<String, SingleMappingMetadata> getMetaDataByTypeToMap(ElasticPropertyType type) {
+        return getMetaDataByType(type)
+                .stream()
+                .collect(Collectors.toMap(SingleMappingMetadata::getName, Function.identity()));
+    }
+
+    public void setMetadata(SingleMappingMetadata singleMapping) {
+        metadata.put(singleMapping.getName(), singleMapping);
+    }
+
+
+    private IndexMappingMetadata(String index) {
+        this.index = index;
+    }
+
+    private IndexMappingMetadata() {
+    }
+
+    public static IndexMappingMetadata getInstance(String index) {
+        return new IndexMappingMetadata(index);
+    }
+}

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