Просмотр исходного кода

Merge remote-tracking branch 'origin/master'

zhouhao 2 лет назад
Родитель
Сommit
a7306ca921

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

@@ -6,6 +6,7 @@ import lombok.Setter;
 import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType;
 import org.hswebframework.ezorm.rdb.mapping.annotation.JsonCodec;
 import org.hswebframework.web.api.crud.entity.GenericEntity;
+import org.hswebframework.web.crud.annotation.EnableEntityEvent;
 import org.hswebframework.web.utils.DigestUtils;
 import org.springframework.util.StringUtils;
 
@@ -20,6 +21,7 @@ import java.util.Map;
 })
 @Getter
 @Setter
+@EnableEntityEvent
 public class ConfigEntity extends GenericEntity<String> {
 
     @Column(length = 64, nullable = false, updatable = false)

+ 85 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/verification/ConfigVerificationService.java

@@ -0,0 +1,85 @@
+package org.jetlinks.community.config.verification;
+
+import io.swagger.v3.oas.annotations.Operation;
+import org.hswebframework.web.crud.events.EntitySavedEvent;
+import org.hswebframework.web.exception.BusinessException;
+import org.jetlinks.community.config.entity.ConfigEntity;
+import org.jetlinks.reactor.ql.utils.CastUtils;
+import org.springframework.context.event.EventListener;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.net.UnknownHostException;
+import java.time.Duration;
+import java.util.Objects;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * @author bestfeng
+ */
+@RestController
+public class ConfigVerificationService {
+
+
+    private final WebClient webClient;
+
+    private static final String PATH_VERIFICATION_URI = "/system/config/base-path/verification";
+
+    public ConfigVerificationService() {
+        this.webClient = WebClient
+            .builder()
+            .build();
+    }
+
+    @GetMapping(value = PATH_VERIFICATION_URI)
+    @Operation(description = "basePath配置验证接口")
+    public Mono<Void> basePathValidate(ServerWebExchange response) {
+        response.getResponse().getHeaders().set("auth", PATH_VERIFICATION_URI);
+        return Mono.empty();
+    }
+
+
+    @EventListener
+    public void handleConfigSavedEvent(EntitySavedEvent<ConfigEntity> event){
+        //base-path校验
+        event.async(
+            Flux.fromIterable(event.getEntity())
+                .filter(config -> Objects.equals(config.getScope(), "paths"))
+                .flatMap(config-> doBasePathValidate(config.getProperties().get("base-path")))
+        );
+    }
+
+
+    public Mono<Void> doBasePathValidate(Object basePath) {
+        if (basePath == null) {
+            return Mono.empty();
+        }
+        return webClient
+            .get()
+            .uri(CastUtils.castString(basePath).concat(PATH_VERIFICATION_URI))
+            .exchangeToMono(cr -> {
+                if (cr.statusCode().is2xxSuccessful()
+                    && Objects.equals(cr.headers().asHttpHeaders().getFirst("auth"), PATH_VERIFICATION_URI)) {
+                    return Mono.empty();
+                }
+                return Mono.defer(() -> Mono.error(new BusinessException("error.base_path_error")));
+            })
+            .timeout(Duration.ofSeconds(3), Mono.error(TimeoutException::new))
+            .onErrorResume(err -> {
+                while (err != null) {
+                    if (err instanceof TimeoutException) {
+                        return Mono.error(() -> new BusinessException("error.base_path_validate_request_timeout"));
+                    } else if (err instanceof UnknownHostException) {
+                        return Mono.error(() -> new BusinessException("error.base_path_DNS_resolution_failed"));
+                    }
+                    err = err.getCause();
+                }
+                return Mono.error(() -> new BusinessException("error.base_path_error"));
+            })
+            .then();
+    }
+}

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

@@ -98,6 +98,7 @@ public class SystemConfigManagerController {
     @Operation(description = "批量保存配置")
     @Transactional
     public Mono<Void> saveConfig(@RequestBody Flux<Scope> scope) {
+
         return scope
             .flatMap(scopeConfig -> configManager.setProperties(scopeConfig.getScope(), scopeConfig.getProperties()))
             .then();

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

@@ -1,4 +1,10 @@
 message.device_message_handing=Message sent to device, processing...
 
 error.duplicate_key_detail=Duplicate Data:{0}
-error.data.referenced=The data has been used elsewhere
+error.data.referenced=The data has been used elsewhere
+error.base_path_error=base-path error. \
+  format\uFF1A{http/https}: //{IP address of the server where the front-end is located}:{Front end exposed service port}/api
+error.base_path_DNS_resolution_failed=base-path DNS resolution failed\u3002\
+  format\uFF1A{http/https}: //{IP address of the server where the front-end is located}:{Front end exposed service port}/api
+error.base_path_validate_request_timeout=base-path validate request timeout\u3002\
+  format\uFF1A{http/https}: //{IP address of the server where the front-end is located}:{Front end exposed service port}/api

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

@@ -1,3 +1,6 @@
-message.device_message_handing=消息已发往设备,处理中...
-error.data.referenced=数据已经被其他地方使用
-error.duplicate_key_detail=重复的数据:{0}
+message.device_message_handing=\u6D88\u606F\u5DF2\u53D1\u5F80\u8BBE\u5907,\u5904\u7406\u4E2D...
+error.data.referenced=\u6570\u636E\u5DF2\u7ECF\u88AB\u5176\u4ED6\u5730\u65B9\u4F7F\u7528
+error.duplicate_key_detail=\u91CD\u590D\u7684\u6570\u636E:{0}
+error.base_path_error=base-path\u9519\u8BEF\u3002 \u6B63\u786E\u683C\u5F0F\uFF1A{http/https}: //{\u524D\u7AEF\u6240\u5728\u670D\u52A1\u5668IP\u5730\u5740}:{\u524D\u7AEF\u66B4\u9732\u7684\u670D\u52A1\u7AEF\u53E3}/api
+error.base_path_DNS_resolution_failed=base-path DNS\u89E3\u6790\u5931\u8D25\u3002\u6B63\u786E\u683C\u5F0F\uFF1A{http/https}: //{\u524D\u7AEF\u6240\u5728\u670D\u52A1\u5668IP\u5730\u5740}:{\u524D\u7AEF\u66B4\u9732\u7684\u670D\u52A1\u7AEF\u53E3}/api
+error.base_path_validate_request_timeout=base-path \u8BF7\u6C42\u9A8C\u8BC1\u8D85\u65F6\u3002\u6B63\u786E\u683C\u5F0F\uFF1A{http/https}: //{\u524D\u7AEF\u6240\u5728\u670D\u52A1\u5668IP\u5730\u5740}:{\u524D\u7AEF\u66B4\u9732\u7684\u670D\u52A1\u7AEF\u53E3}/api

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

@@ -30,6 +30,12 @@
         </dependency>
 
         <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>timeseries-component</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
             <groupId>com.github.oshi</groupId>
             <artifactId>oshi-core</artifactId>
             <version>6.2.2</version>

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

@@ -1,10 +1,17 @@
 package org.jetlinks.community.dashboard.measurements.sys;
 
+import com.google.common.collect.Maps;
+import org.hswebframework.web.api.crud.entity.QueryParamEntity;
 import org.hswebframework.web.bean.FastBeanCopier;
 import org.jetlinks.community.dashboard.*;
 import org.jetlinks.community.dashboard.measurements.MonitorObjectDefinition;
 import org.jetlinks.community.dashboard.supports.StaticMeasurement;
 import org.jetlinks.community.dashboard.supports.StaticMeasurementProvider;
+import org.jetlinks.community.timeseries.TimeSeriesData;
+import org.jetlinks.community.timeseries.TimeSeriesManager;
+import org.jetlinks.community.timeseries.TimeSeriesMetadata;
+import org.jetlinks.community.timeseries.TimeSeriesMetric;
+import org.jetlinks.community.utils.TimeUtils;
 import org.jetlinks.core.metadata.ConfigMetadata;
 import org.jetlinks.core.metadata.DataType;
 import org.jetlinks.core.metadata.DefaultConfigMetadata;
@@ -13,10 +20,17 @@ import org.jetlinks.core.metadata.types.ObjectType;
 import org.jetlinks.core.metadata.types.StringType;
 import org.reactivestreams.Publisher;
 import org.springframework.stereotype.Component;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Scheduler;
+import reactor.core.scheduler.Schedulers;
 
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 import java.time.Duration;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -42,7 +56,55 @@ import java.util.Map;
  * <p>
  * 类型不同结构不同,memory: {@link MemoryInfo},cpu:{@link CpuInfo},disk:{@link DiskInfo},all:{@link SystemInfo}
  * <p>
- *  🌟: 企业版支持集群监控以及历史记录
+ *
+ * <h2>历史数据</h2>
+ *
+ * <pre>{@code
+ * POST /dashboard/_multi
+ *
+ *  [
+ *     {
+ *         "dashboard": "systemMonitor",
+ *         "object": "stats",
+ *         "measurement": "info",
+ *         "dimension": "history",
+ *         "group": "system-monitor",
+ *         "params": {
+ *              "from":"now-10m",
+ *              "to":"now"
+ *         }
+ *     }
+ * ]
+ *
+ * 返回:
+ *
+ *  [
+ *    {
+ *    "group":"system-monitor",
+ *    "data": {
+ *          "value": {
+ *              "memorySystemFree": 344, //系统可用内存
+ *              "memoryJvmHeapFree": 3038, //jvm可用内存
+ *              "memorySystemTotal": 49152, //系统总内存
+ *              "memoryJvmNonHeapTotal": 49152, //jvm堆外总内存
+ *              "diskTotal": 1907529, //磁盘总空间
+ *              "cpuSystemUsage": 11.8, //系统cpu使用率
+ *              "diskFree": 1621550, //磁盘可用空间
+ *              "clusterNodeId": "jetlinks-platform:8820", //集群节点ID
+ *              "memoryJvmHeapTotal": 4001, //jvm总内存
+ *              "cpuJvmUsage": 0.1, //jvm cpu使用率
+ *              "memoryJvmNonHeapFree": 48964, //jvm堆外可用内存
+ *              "id": "eSEeBYEBN57nz4ZBo0WI", // ID
+ *          },
+ *          "timeString": "2023-05-16 18:32:27",//时间
+ *          "timestamp": 1684233147193 //时间
+ *       }
+ *    }
+ *  ]
+ *
+ * }</pre>
+ *
+ *  🌟: 企业版支持集群监控
  *
  * @author zhouhao
  * @since 2.0
@@ -52,17 +114,56 @@ public class SystemMonitorMeasurementProvider extends StaticMeasurementProvider
 
     private final SystemMonitorService monitorService = new SystemMonitorServiceImpl();
 
+    private final Duration collectInterval = TimeUtils.parse(System.getProperty("monitor.system.collector.interval", "1m"));
+
+    private final Scheduler scheduler;
+
+    private final TimeSeriesManager timeSeriesManager;
+
+    static final TimeSeriesMetric metric = TimeSeriesMetric.of(System.getProperty("monitor.system.collector.metric", "system_monitor"));
+
+    private final Disposable.Composite disposable = Disposables.composite();
 
-    public SystemMonitorMeasurementProvider() {
+
+    public SystemMonitorMeasurementProvider(TimeSeriesManager timeSeriesManager) {
         super(DefaultDashboardDefinition.systemMonitor, MonitorObjectDefinition.stats);
+        this.timeSeriesManager = timeSeriesManager;
 
         addMeasurement(new StaticMeasurement(CommonMeasurementDefinition.info)
-                           .addDimension(new RealTimeDimension())
+            .addDimension(new RealTimeDimension())
+            .addDimension(new HistoryDimension())
         );
 
+        this.scheduler = Schedulers.newSingle("system-monitor-collector");
+
+        disposable.add(this.scheduler);
+    }
 
+    @PreDestroy
+    public void destroy() {
+        disposable.dispose();
     }
 
+    @PostConstruct
+    public void init() {
+        //注册监控信息
+        timeSeriesManager
+            .registerMetadata(
+                TimeSeriesMetadata.of(metric)
+            )
+            .block(Duration.ofSeconds(10));
+
+        //定时收集监控信息
+        disposable.add(Flux
+            .interval(collectInterval, scheduler)
+            .flatMap(ignore -> monitorService
+                .system()
+                .map(this::systemInfoToMap)
+                .flatMap(data -> timeSeriesManager.getService(metric).commit(data))
+                .onErrorResume(err -> Mono.empty()))
+            .subscribe()
+        );
+    }
 
     private void putTo(String prefix, MonitorInfo<?> source, Map<String, Object> target) {
         Map<String, Object> data = FastBeanCopier.copy(source, new HashMap<>());
@@ -73,6 +174,50 @@ public class SystemMonitorMeasurementProvider extends StaticMeasurementProvider
         });
     }
 
+    public TimeSeriesData systemInfoToMap(SystemInfo info) {
+        Map<String, Object> map = Maps.newLinkedHashMapWithExpectedSize(12);
+        putTo("cpu", info.getCpu(), map);
+        putTo("disk", info.getDisk(), map);
+        putTo("memory", info.getMemory(), map);
+        return TimeSeriesData.of(System.currentTimeMillis(), map);
+    }
+
+    //历史记录
+    class HistoryDimension implements MeasurementDimension {
+
+        @Override
+        public DimensionDefinition getDefinition() {
+            return CommonDimensionDefinition.history;
+        }
+
+        @Override
+        public DataType getValueType() {
+            return new ObjectType();
+        }
+
+        @Override
+        public ConfigMetadata getParams() {
+            return new DefaultConfigMetadata();
+        }
+
+        @Override
+        public boolean isRealTime() {
+            return false;
+        }
+
+        @Override
+        public Flux<? extends MeasurementValue> getValue(MeasurementParameter parameter) {
+            Date from = parameter.getDate("from", TimeUtils.parseDate("now-1h"));
+            Date to = parameter.getDate("to", TimeUtils.parseDate("now"));
+
+            return QueryParamEntity
+                .newQuery()
+                .noPaging()
+                .between("timestamp", from, to)
+                .execute(timeSeriesManager.getService(metric)::query)
+                .map(tsData -> SimpleMeasurementValue.of(tsData.getData(), tsData.getTimestamp()));
+        }
+    }
 
     //实时监控
     class RealTimeDimension implements MeasurementDimension {
@@ -91,7 +236,6 @@ public class SystemMonitorMeasurementProvider extends StaticMeasurementProvider
         public ConfigMetadata getParams() {
 
             return new DefaultConfigMetadata()
-                .add("serverNodeId", "服务节点ID", StringType.GLOBAL)
                 .add("interval", "更新频率", StringType.GLOBAL)
                 .add("type", "指标类型", new EnumType()
                     .addElement(EnumType.Element.of("all", "全部"))