From d96c9a54e27d579b2dcbbb34a2b8eb1fab776e73 Mon Sep 17 00:00:00 2001 From: ayan Date: Wed, 2 Nov 2022 17:46:12 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=A8=A1=E5=9D=97=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/run-all/entrypoint.sh | 45 ++ .../micrometer/MeterRegistryManager.java | 36 +- .../device/DeviceClusterConfiguration.java | 104 +-- .../ElasticSearchThingDataConfiguration.java | 3 + .../TimeByDayElasticSearchIndexStrategy.java | 2 + ...ebSocketMessagingHandlerConfiguration.java | 9 +- .../network/DefaultNetworkManager.java | 2 + .../RuleEngineLogIndexInitialize.java | 2 + .../engine/event/handler/RuleLogHandler.java | 3 + .../timeseries/NoneTimeSeriesManager.java | 76 ++ .../TimeSeriesManagerConfiguration.java | 18 + jetlinks-manager/device-manager/pom.xml | 7 + .../device/DeviceTestConfiguration.java | 73 ++ .../web/DeviceInstanceControllerTest.java | 695 ++++++++++++++++++ jetlinks-standalone/Dockerfile | 2 + 15 files changed, 1012 insertions(+), 65 deletions(-) create mode 100644 docker/run-all/entrypoint.sh create mode 100644 jetlinks-components/timeseries-component/src/main/java/org/jetlinks/community/timeseries/NoneTimeSeriesManager.java create mode 100644 jetlinks-components/timeseries-component/src/main/java/org/jetlinks/community/timeseries/TimeSeriesManagerConfiguration.java create mode 100644 jetlinks-manager/device-manager/src/test/java/org/jetlinks/community/device/DeviceTestConfiguration.java create mode 100644 jetlinks-manager/device-manager/src/test/java/org/jetlinks/community/device/web/DeviceInstanceControllerTest.java diff --git a/docker/run-all/entrypoint.sh b/docker/run-all/entrypoint.sh new file mode 100644 index 00000000..5b0f7d9a --- /dev/null +++ b/docker/run-all/entrypoint.sh @@ -0,0 +1,45 @@ +#!/bin/bash +#set -x +#****************************************************************************** +# @file : entrypoint.sh +# @author : wangyubin +# @date : 2018-08- 1 10:18:43 +# +# @brief : entry point for manage service start order +# history : init +#****************************************************************************** + +: ${SLEEP_SECOND:=2} + +wait_for() { + echo Waiting for $1 to listen on $2... + while ! nc -z $1 $2; do echo waiting...; sleep $SLEEP_SECOND; done +} + +declare DEPENDS +declare CMD + +while getopts "d:c:" arg +do + case $arg in + d) + DEPENDS=$OPTARG + ;; + c) + CMD=$OPTARG + ;; + ?) + echo "unkonw argument" + exit 1 + ;; + esac +done + +for var in ${DEPENDS//,/ } +do + host=${var%:*} + port=${var#*:} + wait_for $host $port +done + +eval $CMD \ No newline at end of file diff --git a/jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistryManager.java b/jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistryManager.java index 5ddb9a11..8b19ea75 100644 --- a/jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistryManager.java +++ b/jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistryManager.java @@ -4,12 +4,14 @@ import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.composite.CompositeMeterRegistry; import lombok.Setter; +import org.jetlinks.core.metadata.DataType; +import org.jetlinks.core.metadata.types.StringType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -23,17 +25,33 @@ public class MeterRegistryManager { private Map meterRegistryMap = new ConcurrentHashMap<>(); - private List suppliers; + private final List 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 MeterRegistryManager(@Autowired(required = false) List suppliers) { + this.suppliers = suppliers == null ? new ArrayList<>() : suppliers; + } + + + private MeterRegistry createMeterRegistry(String metric, Map tagDefine) { + Map tags = new HashMap<>(tagDefine); + MeterRegistrySettings settings = tags::put; + return new CompositeMeterRegistry(Clock.SYSTEM, suppliers + .stream() + .map(supplier -> supplier.getMeterRegistry(metric)) + .collect(Collectors.toList())); } public MeterRegistry getMeterRegister(String metric, String... tagKeys) { - return meterRegistryMap.computeIfAbsent(metric, _metric -> createMeterRegistry(_metric, tagKeys)); + + return meterRegistryMap.computeIfAbsent(metric, _metric -> { + if (tagKeys.length == 0) { + return createMeterRegistry(metric, Collections.emptyMap()); + } + return createMeterRegistry(metric, Arrays + .stream(tagKeys) + .collect(Collectors.toMap(Function.identity(), key -> StringType.GLOBAL))); + }); } } diff --git a/jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/DeviceClusterConfiguration.java b/jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/DeviceClusterConfiguration.java index 1d6f644f..246d8c34 100644 --- a/jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/DeviceClusterConfiguration.java +++ b/jetlinks-components/configure-component/src/main/java/org/jetlinks/community/configure/device/DeviceClusterConfiguration.java @@ -29,58 +29,58 @@ import org.springframework.context.annotation.Configuration; @ConditionalOnBean(ProtocolSupports.class) public class DeviceClusterConfiguration { - @Bean - public ClusterDeviceRegistry deviceRegistry(ProtocolSupports supports, - ClusterManager manager, - ConfigStorageManager storageManager, - DeviceOperationBroker handler) { - - return new ClusterDeviceRegistry(supports, - storageManager, - manager, - handler, - CaffeinatedGuava.build(Caffeine.newBuilder())); - } - - - @Bean - @ConditionalOnBean(ClusterDeviceRegistry.class) - public BeanPostProcessor interceptorRegister(ClusterDeviceRegistry registry) { - return new BeanPostProcessor() { - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof DeviceMessageSenderInterceptor) { - registry.addInterceptor(((DeviceMessageSenderInterceptor) bean)); - } - if (bean instanceof DeviceStateChecker) { - registry.addStateChecker(((DeviceStateChecker) bean)); - } - return bean; - } - }; - } - - @Bean(initMethod = "init", destroyMethod = "shutdown") - @ConditionalOnBean(RpcManager.class) - public PersistenceDeviceSessionManager deviceSessionManager(RpcManager rpcManager) { - - return new PersistenceDeviceSessionManager(rpcManager); - } - - @ConditionalOnBean(DecodedClientMessageHandler.class) - @Bean - public ClusterSendToDeviceMessageHandler defaultSendToDeviceMessageHandler(DeviceSessionManager sessionManager, - DeviceRegistry registry, - MessageHandler messageHandler, - DecodedClientMessageHandler clientMessageHandler) { - return new ClusterSendToDeviceMessageHandler(sessionManager, messageHandler, registry, clientMessageHandler); - } - - @Bean - public RpcDeviceOperationBroker rpcDeviceOperationBroker(RpcManager rpcManager, - DeviceSessionManager sessionManager) { - return new RpcDeviceOperationBroker(rpcManager, sessionManager); - } +// @Bean +// public ClusterDeviceRegistry deviceRegistry(ProtocolSupports supports, +// ClusterManager manager, +// ConfigStorageManager storageManager, +// DeviceOperationBroker handler) { +// +// return new ClusterDeviceRegistry(supports, +// storageManager, +// manager, +// handler, +// CaffeinatedGuava.build(Caffeine.newBuilder())); +// } +// +// +// @Bean +// @ConditionalOnBean(ClusterDeviceRegistry.class) +// public BeanPostProcessor interceptorRegister(ClusterDeviceRegistry registry) { +// return new BeanPostProcessor() { +// @Override +// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { +// if (bean instanceof DeviceMessageSenderInterceptor) { +// registry.addInterceptor(((DeviceMessageSenderInterceptor) bean)); +// } +// if (bean instanceof DeviceStateChecker) { +// registry.addStateChecker(((DeviceStateChecker) bean)); +// } +// return bean; +// } +// }; +// } +// +// @Bean(initMethod = "init", destroyMethod = "shutdown") +// @ConditionalOnBean(RpcManager.class) +// public PersistenceDeviceSessionManager deviceSessionManager(RpcManager rpcManager) { +// +// return new PersistenceDeviceSessionManager(rpcManager); +// } +// +// @ConditionalOnBean(DecodedClientMessageHandler.class) +// @Bean +// public ClusterSendToDeviceMessageHandler defaultSendToDeviceMessageHandler(DeviceSessionManager sessionManager, +// DeviceRegistry registry, +// MessageHandler messageHandler, +// DecodedClientMessageHandler clientMessageHandler) { +// return new ClusterSendToDeviceMessageHandler(sessionManager, messageHandler, registry, clientMessageHandler); +// } +// +// @Bean +// public RpcDeviceOperationBroker rpcDeviceOperationBroker(RpcManager rpcManager, +// DeviceSessionManager sessionManager) { +// return new RpcDeviceOperationBroker(rpcManager, sessionManager); +// } } diff --git a/jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/configuration/ElasticSearchThingDataConfiguration.java b/jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/configuration/ElasticSearchThingDataConfiguration.java index 12b77574..4807c0b1 100644 --- a/jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/configuration/ElasticSearchThingDataConfiguration.java +++ b/jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/configuration/ElasticSearchThingDataConfiguration.java @@ -7,6 +7,7 @@ import org.jetlinks.community.elastic.search.service.ElasticSearchService; import org.jetlinks.community.elastic.search.things.ElasticSearchColumnModeStrategy; import org.jetlinks.community.elastic.search.things.ElasticSearchRowModeStrategy; import org.jetlinks.community.things.data.ThingsDataRepositoryStrategy; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -16,6 +17,7 @@ import org.springframework.context.annotation.Configuration; public class ElasticSearchThingDataConfiguration { @Bean + @ConditionalOnBean(ElasticSearchService.class) public ElasticSearchColumnModeStrategy elasticSearchColumnModThingDataPolicy( ThingsRegistry registry, ElasticSearchService searchService, @@ -26,6 +28,7 @@ public class ElasticSearchThingDataConfiguration { } @Bean + @ConditionalOnBean(ElasticSearchService.class) public ElasticSearchRowModeStrategy elasticSearchRowModThingDataPolicy( ThingsRegistry registry, ElasticSearchService searchService, diff --git a/jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/strategies/TimeByDayElasticSearchIndexStrategy.java b/jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/strategies/TimeByDayElasticSearchIndexStrategy.java index 3ef3e5f1..8dbf937b 100644 --- a/jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/strategies/TimeByDayElasticSearchIndexStrategy.java +++ b/jetlinks-components/elasticsearch-component/src/main/java/org/jetlinks/community/elastic/search/index/strategies/TimeByDayElasticSearchIndexStrategy.java @@ -3,6 +3,7 @@ package org.jetlinks.community.elastic.search.index.strategies; import org.hswebframework.utils.time.DateFormatter; import org.jetlinks.community.elastic.search.index.ElasticSearchIndexProperties; import org.jetlinks.community.elastic.search.service.reactive.ReactiveElasticsearchClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.stereotype.Component; import java.time.LocalDate; @@ -15,6 +16,7 @@ import java.util.Date; * @since 1.0 */ @Component +@ConditionalOnBean(ReactiveElasticsearchClient.class) public class TimeByDayElasticSearchIndexStrategy extends TemplateElasticSearchIndexStrategy { public TimeByDayElasticSearchIndexStrategy(ReactiveElasticsearchClient client, ElasticSearchIndexProperties properties) { diff --git a/jetlinks-components/gateway-component/src/main/java/org/jetlinks/community/gateway/external/socket/WebSocketMessagingHandlerConfiguration.java b/jetlinks-components/gateway-component/src/main/java/org/jetlinks/community/gateway/external/socket/WebSocketMessagingHandlerConfiguration.java index 584a27c5..198dd351 100644 --- a/jetlinks-components/gateway-component/src/main/java/org/jetlinks/community/gateway/external/socket/WebSocketMessagingHandlerConfiguration.java +++ b/jetlinks-components/gateway-component/src/main/java/org/jetlinks/community/gateway/external/socket/WebSocketMessagingHandlerConfiguration.java @@ -3,6 +3,7 @@ package org.jetlinks.community.gateway.external.socket; import org.hswebframework.web.authorization.ReactiveAuthenticationManager; import org.hswebframework.web.authorization.token.UserTokenManager; import org.jetlinks.community.gateway.external.MessagingManager; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -16,10 +17,10 @@ import java.util.HashMap; import java.util.Map; @Configuration -//@ConditionalOnBean({ -// ReactiveAuthenticationManager.class, -// UserTokenManager.class -//}) +@ConditionalOnBean({ + ReactiveAuthenticationManager.class, + UserTokenManager.class +}) public class WebSocketMessagingHandlerConfiguration { diff --git a/jetlinks-components/network-component/network-core/src/main/java/org/jetlinks/community/network/DefaultNetworkManager.java b/jetlinks-components/network-component/network-core/src/main/java/org/jetlinks/community/network/DefaultNetworkManager.java index 0a9b38e7..fe703fcc 100644 --- a/jetlinks-components/network-component/network-core/src/main/java/org/jetlinks/community/network/DefaultNetworkManager.java +++ b/jetlinks-components/network-component/network-core/src/main/java/org/jetlinks/community/network/DefaultNetworkManager.java @@ -8,6 +8,7 @@ import org.jetlinks.core.event.Subscription; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -30,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap; */ @Component @Slf4j +@ConditionalOnBean(NetworkConfigManager.class) public class DefaultNetworkManager implements NetworkManager, BeanPostProcessor, CommandLineRunner { private final NetworkConfigManager configManager; diff --git a/jetlinks-components/rule-engine-component/src/main/java/org/jetlinks/community/rule/engine/configuration/RuleEngineLogIndexInitialize.java b/jetlinks-components/rule-engine-component/src/main/java/org/jetlinks/community/rule/engine/configuration/RuleEngineLogIndexInitialize.java index bdb6796a..fedff3b7 100644 --- a/jetlinks-components/rule-engine-component/src/main/java/org/jetlinks/community/rule/engine/configuration/RuleEngineLogIndexInitialize.java +++ b/jetlinks-components/rule-engine-component/src/main/java/org/jetlinks/community/rule/engine/configuration/RuleEngineLogIndexInitialize.java @@ -6,6 +6,7 @@ import org.jetlinks.community.elastic.search.index.ElasticSearchIndexManager; import org.jetlinks.community.rule.engine.event.handler.RuleEngineLoggerIndexProvider; import org.jetlinks.core.metadata.types.DateTimeType; import org.jetlinks.core.metadata.types.StringType; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @@ -16,6 +17,7 @@ import org.springframework.stereotype.Component; @Component @Order(1) @Slf4j +@ConditionalOnBean(DefaultElasticSearchIndexMetadata.class) public class RuleEngineLogIndexInitialize { public RuleEngineLogIndexInitialize(ElasticSearchIndexManager indexManager) { diff --git a/jetlinks-components/rule-engine-component/src/main/java/org/jetlinks/community/rule/engine/event/handler/RuleLogHandler.java b/jetlinks-components/rule-engine-component/src/main/java/org/jetlinks/community/rule/engine/event/handler/RuleLogHandler.java index 5c511599..508988a9 100644 --- a/jetlinks-components/rule-engine-component/src/main/java/org/jetlinks/community/rule/engine/event/handler/RuleLogHandler.java +++ b/jetlinks-components/rule-engine-component/src/main/java/org/jetlinks/community/rule/engine/event/handler/RuleLogHandler.java @@ -2,11 +2,13 @@ package org.jetlinks.community.rule.engine.event.handler; import lombok.extern.slf4j.Slf4j; import org.jetlinks.community.elastic.search.service.ElasticSearchService; +import org.jetlinks.community.elastic.search.service.reactive.ReactiveElasticsearchClient; import org.jetlinks.community.gateway.annotation.Subscribe; import org.jetlinks.community.rule.engine.entity.RuleEngineExecuteEventInfo; import org.jetlinks.core.event.TopicPayload; import org.jetlinks.rule.engine.defaults.LogEvent; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; @@ -14,6 +16,7 @@ import reactor.core.publisher.Mono; @Component @Slf4j @Order(3) +@ConditionalOnBean(ElasticSearchService.class) public class RuleLogHandler { @Autowired diff --git a/jetlinks-components/timeseries-component/src/main/java/org/jetlinks/community/timeseries/NoneTimeSeriesManager.java b/jetlinks-components/timeseries-component/src/main/java/org/jetlinks/community/timeseries/NoneTimeSeriesManager.java new file mode 100644 index 00000000..45f38936 --- /dev/null +++ b/jetlinks-components/timeseries-component/src/main/java/org/jetlinks/community/timeseries/NoneTimeSeriesManager.java @@ -0,0 +1,76 @@ +package org.jetlinks.community.timeseries; + +import org.hswebframework.ezorm.core.param.QueryParam; +import org.jetlinks.community.timeseries.query.AggregationData; +import org.jetlinks.community.timeseries.query.AggregationQueryParam; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +public class NoneTimeSeriesManager implements TimeSeriesManager { + public static final NoneTimeSeriesService NONE = new NoneTimeSeriesService(); + + @Override + public TimeSeriesService getService(TimeSeriesMetric metric) { + return NONE; + } + + @Override + public TimeSeriesService getServices(TimeSeriesMetric... metric) { + return NONE; + } + + @Override + public TimeSeriesService getServices(String... metric) { + return NONE; + } + + @Override + public TimeSeriesService getService(String metric) { + return NONE; + } + + @Override + public Mono registerMetadata(TimeSeriesMetadata metadata) { + return Mono.empty(); + } + + static class NoneTimeSeriesService implements TimeSeriesService { + @Override + public Flux query(QueryParam queryParam) { + return Flux.empty(); + } + + @Override + public Flux multiQuery(Collection query) { + return Flux.empty(); + } + + @Override + public Mono count(QueryParam queryParam) { + return Mono.empty(); + } + + @Override + public Flux aggregation(AggregationQueryParam queryParam) { + return Flux.empty(); + } + + @Override + public Mono commit(Publisher data) { + return Mono.empty(); + } + + @Override + public Mono commit(TimeSeriesData data) { + return Mono.empty(); + } + + @Override + public Mono save(Publisher data) { + return Mono.empty(); + } + } +} diff --git a/jetlinks-components/timeseries-component/src/main/java/org/jetlinks/community/timeseries/TimeSeriesManagerConfiguration.java b/jetlinks-components/timeseries-component/src/main/java/org/jetlinks/community/timeseries/TimeSeriesManagerConfiguration.java new file mode 100644 index 00000000..d26bbd3b --- /dev/null +++ b/jetlinks-components/timeseries-component/src/main/java/org/jetlinks/community/timeseries/TimeSeriesManagerConfiguration.java @@ -0,0 +1,18 @@ +package org.jetlinks.community.timeseries; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import reactor.core.publisher.Flux; + +import java.time.Duration; + +@Configuration(proxyBeanMethods = false) +public class TimeSeriesManagerConfiguration { + + @ConditionalOnMissingBean(TimeSeriesManager.class) + @Bean + public NoneTimeSeriesManager timeSeriesManager() { + return new NoneTimeSeriesManager(); + } +} diff --git a/jetlinks-manager/device-manager/pom.xml b/jetlinks-manager/device-manager/pom.xml index 5e3b64a3..fefe7a4f 100644 --- a/jetlinks-manager/device-manager/pom.xml +++ b/jetlinks-manager/device-manager/pom.xml @@ -99,6 +99,13 @@ compile + + org.jetlinks.community + test-component + ${project.version} + test + + diff --git a/jetlinks-manager/device-manager/src/test/java/org/jetlinks/community/device/DeviceTestConfiguration.java b/jetlinks-manager/device-manager/src/test/java/org/jetlinks/community/device/DeviceTestConfiguration.java new file mode 100644 index 00000000..d68e3996 --- /dev/null +++ b/jetlinks-manager/device-manager/src/test/java/org/jetlinks/community/device/DeviceTestConfiguration.java @@ -0,0 +1,73 @@ +package org.jetlinks.community.device; + +import org.hswebframework.web.authorization.token.DefaultUserTokenManager; +import org.hswebframework.web.authorization.token.UserTokenManager; +import org.hswebframework.web.starter.jackson.CustomCodecsAutoConfiguration; +import org.jetlinks.community.configure.cluster.ClusterConfiguration; +import org.jetlinks.community.configure.device.DeviceClusterConfiguration; +import org.jetlinks.community.elastic.search.configuration.ElasticSearchConfiguration; +import org.jetlinks.core.ProtocolSupports; +import org.jetlinks.core.device.DeviceRegistry; +import org.jetlinks.core.device.StandaloneDeviceMessageBroker; +import org.jetlinks.core.device.session.DeviceSessionManager; +import org.jetlinks.core.server.MessageHandler; +import org.jetlinks.supports.device.session.LocalDeviceSessionManager; +import org.jetlinks.supports.official.JetLinksDeviceMetadataCodec; +import org.jetlinks.supports.test.InMemoryDeviceRegistry; +import org.jetlinks.supports.test.MockProtocolSupport; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ImportAutoConfiguration({ + CodecsAutoConfiguration.class, + JacksonAutoConfiguration.class, + CustomCodecsAutoConfiguration.class, + ClusterConfiguration.class, + DeviceClusterConfiguration.class +}) +public class DeviceTestConfiguration { + + @Bean + public ProtocolSupports mockProtocolSupport(){ + return new MockProtocolSupport(); + } + + @Bean + public UserTokenManager userTokenManager(){ + return new DefaultUserTokenManager(); + } + + @Bean + public DeviceRegistry deviceRegistry() { + + return new InMemoryDeviceRegistry(); + } + + @Bean + public MessageHandler messageHandler() { + + return new StandaloneDeviceMessageBroker(); + } + + @Bean + public DeviceSessionManager deviceSessionManager() { + + return LocalDeviceSessionManager.create(); + } + + @Bean + public ElasticSearchConfiguration searchConfiguration() { + + return new ElasticSearchConfiguration(); + } + + @Bean + public JetLinksDeviceMetadataCodec jetLinksDeviceMetadataCodec(){ + return new JetLinksDeviceMetadataCodec(); + } + +} diff --git a/jetlinks-manager/device-manager/src/test/java/org/jetlinks/community/device/web/DeviceInstanceControllerTest.java b/jetlinks-manager/device-manager/src/test/java/org/jetlinks/community/device/web/DeviceInstanceControllerTest.java new file mode 100644 index 00000000..eee6bfe6 --- /dev/null +++ b/jetlinks-manager/device-manager/src/test/java/org/jetlinks/community/device/web/DeviceInstanceControllerTest.java @@ -0,0 +1,695 @@ +package org.jetlinks.community.device.web; + +import com.alibaba.fastjson.JSON; +import lombok.SneakyThrows; +import org.jetlinks.community.test.spring.TestJetLinksController; +import org.jetlinks.core.metadata.SimplePropertyMetadata; +import org.jetlinks.core.metadata.types.FloatType; +import org.jetlinks.community.PropertyMetadataConstants; +import org.jetlinks.community.PropertyMetric; +import org.jetlinks.community.device.entity.DeviceInstanceEntity; +import org.jetlinks.community.device.entity.DeviceProductEntity; +import org.jetlinks.community.device.entity.DeviceTagEntity; +import org.jetlinks.community.device.service.LocalDeviceInstanceService; +import org.jetlinks.community.device.service.LocalDeviceProductService; +import org.jetlinks.community.device.service.data.DeviceDataService; +import org.jetlinks.community.device.web.request.AggRequest; +import org.jetlinks.community.relation.entity.RelationEntity; +import org.jetlinks.community.relation.service.RelatedObjectInfo; +import org.jetlinks.community.relation.service.RelationService; +import org.jetlinks.community.relation.service.request.SaveRelationRequest; +import org.jetlinks.community.timeseries.query.Aggregation; +import org.jetlinks.supports.official.JetLinksDeviceMetadata; +import org.jetlinks.supports.official.JetLinksDeviceMetadataCodec; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; +import reactor.test.StepVerifier; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@WebFluxTest(value = DeviceInstanceController.class, properties = { + "spring.reactor.debug-agent.enabled=true" +}) +class DeviceInstanceControllerTest extends TestJetLinksController { + + @Autowired + @SuppressWarnings("all") + private LocalDeviceInstanceService deviceService; + + @Autowired + @SuppressWarnings("all") + private LocalDeviceProductService productService; + + private String deviceId; + private String productId; + + private String metadata; + @BeforeEach + void setup() { + DeviceProductEntity product = new DeviceProductEntity(); + product.setMetadata("{}"); + product.setTransportProtocol("MQTT"); + product.setMessageProtocol("test"); + product.setId(productId = "deviceinstancecontrollertest_product"); + product.setName("DeviceInstanceControllerTest"); + + JetLinksDeviceMetadata metadata = new JetLinksDeviceMetadata("Test", "Test"); + { + SimplePropertyMetadata metric = SimplePropertyMetadata.of( + "metric", "Metric", FloatType.GLOBAL + ); + metric.setExpands( + PropertyMetadataConstants.Metrics + .metricsToExpands(Arrays.asList( + PropertyMetric.of("max", "最大值", 100), + PropertyMetric.of("min", "最小值", -100) + )) + ); + metadata.addProperty(metric); + } + + product.setMetadata(this.metadata=JetLinksDeviceMetadataCodec.getInstance().doEncode(metadata)); + + productService + .save(product) + .then(productService.deploy(productId)) + .then() + .as(StepVerifier::create) + .expectComplete() + .verify(); + + DeviceInstanceEntity device = new DeviceInstanceEntity(); + device.setId(deviceId = "deviceinstancecontrollertest_device"); + device.setName("DeviceInstanceControllerTest"); + device.setProductId(product.getId()); + device.setProductName(device.getName()); + + client + .patch() + .uri("/device/instance") + .bodyValue(device) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .put() + .uri("/device/instance/batch/_deploy") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(Arrays.asList(deviceId)) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + + } + + @AfterEach + void shutdown() { + client + .put() + .uri("/device/instance/batch/_unDeploy") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(Arrays.asList(deviceId)) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .put() + .uri("/device/instance/batch/_delete") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(Arrays.asList(deviceId)) + .exchange() + .expectStatus() + .is2xxSuccessful(); + } + + @Test + @SneakyThrows + void testCommon() { +// { +// DeviceInstanceEntity device = new DeviceInstanceEntity(); +// device.setId(deviceId); +// device.setName("DeviceInstanceControllerTest"); +// device.setProductId(productId); +// device.setProductName(device.getName()); +// //重复创建 +// client +// .post() +// .uri("/device/instance") +// .bodyValue(device) +// .exchange() +// .expectStatus() +// .is4xxClientError(); +// } + client + .get() + .uri("/device/instance/{id:.+}/detail", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .get() + .uri("/device/instance/bind-providers") + .exchange() + .expectStatus() + .is2xxSuccessful(); + + + client + .get() + .uri("/device/instance/{deviceId}/state", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful(); + client + .get() + .uri("/device/instance/state/_sync") + .accept(MediaType.TEXT_EVENT_STREAM) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + + client + .post() + .uri("/device/instance/{deviceId}/deploy", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + + client + .post() + .uri("/device/instance/{deviceId}/undeploy", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .post() + .uri("/device/instance/{deviceId}/disconnect", deviceId) + .exchange(); + } + + @Test + void testProperties() { + + client + .get() + .uri("/device/instance/{deviceId:.+}/properties/_query?where=property is test", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .get() + .uri("/device/instance/{deviceId:.+}/properties/_query", deviceId) + .exchange() + .expectStatus() + .is4xxClientError(); + + client + .get() + .uri("/device/instance/{deviceId:.+}/properties/latest", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .get() + .uri("/device/instance/{deviceId:.+}/properties", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .post() + .uri("/device/instance/{deviceId:.+}/properties/_top/{numberOfTop}", deviceId, 1) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{}") + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .get() + .uri("/device/instance/{deviceId:.+}/property/{property}/_query", deviceId, "test") + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .post() + .uri("/device/instance/{deviceId:.+}/property/{property}/_query", deviceId, "test") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{}") + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .post() + .uri("/device/instance/{deviceId:.+}/properties/_query", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{}") + .exchange(); + + + client + .get() + .uri("/device/instance/{deviceId:.+}/property/{property:.+}", deviceId, "test") + .exchange() + .expectStatus() + .is2xxSuccessful(); + + AggRequest request = new AggRequest(); + + request.setColumns(Arrays.asList( + new DeviceDataService.DevicePropertyAggregation("test", "alias", Aggregation.AVG) + )); + request.setQuery(DeviceDataService.AggregationRequest + .builder() + .build()); + + client + .post() + .uri("/device/instance/{deviceId:.+}/agg/_query", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange(); + + + } + + @Test + void testEvent() { + client + .get() + .uri("/device/instance/{deviceId:.+}/event/{eventId}", deviceId, "test") + .exchange() + .expectStatus() + .is2xxSuccessful(); + client + .post() + .uri("/device/instance/{deviceId:.+}/event/{eventId}", deviceId, "test") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{}") + .exchange() + .expectStatus() + .is2xxSuccessful(); + } + + @Test + void testLog() { + client + .get() + .uri("/device/instance/{deviceId:.+}/logs", deviceId, "test") + .exchange() + .expectStatus() + .is2xxSuccessful(); + client + .post() + .uri("/device/instance/{deviceId:.+}/logs", deviceId, "test") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{}") + .exchange() + .expectStatus() + .is2xxSuccessful(); + } + + @Test + void testTag() { + DeviceTagEntity tag = new DeviceTagEntity(); + tag.setKey("test"); + tag.setValue("value"); + + client + .patch() + .uri("/device/instance/{deviceId}/tag", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(tag) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + List tags = client + .get() + .uri("/device/instance/{deviceId}/tags", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBodyList(DeviceTagEntity.class) + .returnResult() + .getResponseBody(); + + assertNotNull(tags); + assertFalse(tags.isEmpty()); + + client + .delete() + .uri("/device/instance/{deviceId}/tag/{tagId}", deviceId, tags.get(0).getId()) + .exchange() + .expectStatus() + .is2xxSuccessful(); + } + + @Test + @SneakyThrows + void testMetadata() { + client + .get() + .uri("/device/instance/{id:.+}/config-metadata", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .get() + .uri("/device/instance/{id:.+}/config-metadata/{metadataType}/{metadataId}/{typeId}", + deviceId, + "property", + "temp", + "test") + .exchange() + .expectStatus() + .is2xxSuccessful(); + + + String metadata = client + .post() + .uri("/device/instance/{deviceId}/property-metadata/import?fileUrl=" + new ClassPathResource("property-metadata.csv") + .getFile() + .getAbsolutePath(), deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody(String.class) + .returnResult() + .getResponseBody(); + assertNotNull(metadata); + client + .put() + .uri("/device/instance/{id:.+}/metadata", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(metadata) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + deviceService.findById(deviceId) + .as(StepVerifier::create) + .expectNextMatches(device -> Objects.equals( + device.getDeriveMetadata(), + metadata + )) + .expectComplete() + .verify(); + client + .put() + .uri("/device/instance/{id}/metadata/merge-product", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .delete() + .uri("/device/instance/{id}/metadata", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + deviceService + .findById(deviceId) + .as(StepVerifier::create) + .expectNextMatches(device -> StringUtils.isEmpty(device.getDeriveMetadata())) + .expectComplete() + .verify(); + } + + @Test + void testConfiguration() { + + deviceService.deploy(deviceId) + .then() + .as(StepVerifier::create) + .expectComplete() + .verify(); + client + .post() + .uri("/device/instance/{deviceId:.+}/configuration/_write", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{\"test\":\"123\"}") + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .post() + .uri("/device/instance/{deviceId:.+}/configuration/_read", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("[\"test\"]") + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody(Map.class) + .isEqualTo(Collections.singletonMap("test", "123")); + + client + .put() + .uri("/device/instance/{deviceId:.+}/configuration/_reset", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .put() + .uri("/device/instance/{deviceId:.+}/shadow", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{\"test\":\"123\"}") + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .get() + .uri("/device/instance/{deviceId:.+}/shadow", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody(String.class) + .isEqualTo("{\"test\":\"123\"}"); + + } + + @Test + void testCommand() { + client + .put() + .uri("/device/instance/{deviceId:.+}/property", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(Collections.singletonMap("test", "value")) + .exchange(); + + client + .post() + .uri("/device/instance/{deviceId:.+}/property/_read", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(Collections.singleton("test")) + .exchange(); + + + client + .post() + .uri("/device/instance/{deviceId:.+}/function/test", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(Collections.singletonMap("test", "value")) + .exchange(); + + client + .post() + .uri("/device/instance/{deviceId:.+}/message", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(Collections.singletonMap("properties", Collections.singletonList("test"))) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + Map data = new HashMap<>(); + data.put("deviceId", "test"); + data.put("properties", Collections.singletonList("test")); + + client + .post() + .uri("/device/instance/messages", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(data) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + } + + @Test + void testAutoChangeProductInfo() { + client.post() + .uri("/device/instance") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{\"id\":\"testAutoChangeProductInfo\",\"name\":\"Test\",\"productId\":\"" + productId + "\"}") + .exchange() + .expectStatus() + .is2xxSuccessful(); + + } + + @Autowired + private RelationService relationService; + + @Test + void testRelation() { + RelationEntity entity = new RelationEntity(); + entity.setRelation("manager"); + entity.setObjectType("device"); + entity.setObjectTypeName("设备"); + entity.setTargetType("user"); + entity.setTargetTypeName("用户"); + entity.setName("管理员"); + relationService + .save(entity).block(); + + + client.get() + .uri("/device/instance/{deviceId}/detail", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody() + .jsonPath("$.relations[0].relation").isEqualTo("manager") + .jsonPath("$.relations[0].related").isEmpty(); + + + client.patch() + .uri("/device/instance/{deviceId}/relations", deviceId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(SaveRelationRequest.of( + "user", + "manager", + Arrays.asList(RelatedObjectInfo.of("admin", "管理员")), + null + )) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client.get() + .uri("/device/instance/{deviceId}/detail", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody() + .jsonPath("$.relations[0].relation").isEqualTo("manager") + .jsonPath("$.relations[0].related[0].id").isEqualTo("admin") + .jsonPath("$.relations[0].related[0].name").isEqualTo("管理员"); + + + } + + @Test + void testMetric() { + client.get() + .uri("/device/instance/{deviceId}/metric/property/{property}", deviceId, "metric") + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody() + .jsonPath("[0].id").isEqualTo("max") + .jsonPath("[0].value").isEqualTo(100) + .jsonPath("[1].id").isEqualTo("min") + .jsonPath("[1].value").isEqualTo(-100); + + + client.patch() + .uri("/device/instance/{deviceId}/metric/property/{property}", deviceId, "metric") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(PropertyMetric.of("max", "最大值", 110)) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client.get() + .uri("/device/instance/{deviceId}/metric/property/{property}", deviceId, "metric") + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody() + .jsonPath("[0].id").isEqualTo("max") + .jsonPath("[0].value").isEqualTo(110) + .jsonPath("[1].id").isEqualTo("min") + .jsonPath("[1].value").isEqualTo(-100); + + + } + + @Test + void testDetail(){ + client + .get() + .uri("/device/instance/{deviceId}/detail", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody() + .jsonPath("$.metadata").isEqualTo(metadata); + + String newMetadata="{\"properties\":[]}"; + + client + .put() + .uri("/device/product/{productId}", productId) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{\"metadata\":"+ JSON.toJSONString(newMetadata)+"}") + .exchange() + .expectStatus() + .is2xxSuccessful(); + + //仅保存 未发布 + client + .get() + .uri("/device/instance/{deviceId}/detail", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody() + .jsonPath("$.metadata").isEqualTo(metadata); + + client + .post() + .uri("/device/product/{productId}/deploy", productId) + .exchange() + .expectStatus() + .is2xxSuccessful(); + + client + .get() + .uri("/device/instance/{deviceId}/detail", deviceId) + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody() + .jsonPath("$.metadata").isEqualTo(newMetadata); + + + } +} \ No newline at end of file diff --git a/jetlinks-standalone/Dockerfile b/jetlinks-standalone/Dockerfile index a925ffa1..22eb4e83 100644 --- a/jetlinks-standalone/Dockerfile +++ b/jetlinks-standalone/Dockerfile @@ -11,5 +11,7 @@ COPY --from=builder application/snapshot-dependencies/ ./ COPY --from=builder application/spring-boot-loader/ ./ COPY --from=builder application/application/ ./ COPY docker-entrypoint.sh ./ +COPY entrypoint.sh ./ RUN chmod +x docker-entrypoint.sh +RUN chmod +x entrypoint.sh ENTRYPOINT ["./docker-entrypoint.sh"] \ No newline at end of file