Ver Fonte

fixed 固件升级

18339543638 há 4 anos atrás
pai
commit
545d2f5f46
100 ficheiros alterados com 7021 adições e 8 exclusões
  1. 2 0
      jetlinks-components/network-component/network-core/src/main/java/org/jetlinks/community/support/JetlinksExtendTopicMessageCodec.java
  2. 3 4
      jetlinks-components/notify-component/notify-webhook/src/main/java/org/jetlinks/community/notify/webhook/WebHookNotifier.java
  3. 0 3
      jetlinks-components/notify-component/notify-webhook/src/main/java/org/jetlinks/community/notify/webhook/WebHookNotifyProvider.java
  4. 1 1
      jetlinks-components/notify-component/notify-webhook/src/main/java/org/jetlinks/community/notify/webhook/WebHookProvider.java
  5. 11 0
      jetlinks-core/.travis.yml
  6. 86 0
      jetlinks-core/pom.xml
  7. 109 0
      jetlinks-core/src/main/java/org/jetlinks/core/ByteBufPayload.java
  8. 134 0
      jetlinks-core/src/main/java/org/jetlinks/core/Configurable.java
  9. 240 0
      jetlinks-core/src/main/java/org/jetlinks/core/NativePayload.java
  10. 191 0
      jetlinks-core/src/main/java/org/jetlinks/core/Payload.java
  11. 304 0
      jetlinks-core/src/main/java/org/jetlinks/core/ProtocolSupport.java
  12. 17 0
      jetlinks-core/src/main/java/org/jetlinks/core/ProtocolSupports.java
  13. 28 0
      jetlinks-core/src/main/java/org/jetlinks/core/SimpleValue.java
  14. 80 0
      jetlinks-core/src/main/java/org/jetlinks/core/SimpleValues.java
  15. 38 0
      jetlinks-core/src/main/java/org/jetlinks/core/Value.java
  16. 55 0
      jetlinks-core/src/main/java/org/jetlinks/core/Values.java
  17. 53 0
      jetlinks-core/src/main/java/org/jetlinks/core/cache/Caches.java
  18. 143 0
      jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterCache.java
  19. 75 0
      jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterCounter.java
  20. 75 0
      jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterManager.java
  21. 59 0
      jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterNotifier.java
  22. 75 0
      jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterQueue.java
  23. 20 0
      jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterSet.java
  24. 45 0
      jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterTopic.java
  25. 43 0
      jetlinks-core/src/main/java/org/jetlinks/core/cluster/HaManager.java
  26. 49 0
      jetlinks-core/src/main/java/org/jetlinks/core/cluster/ServerNode.java
  27. 6 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/Codec.java
  28. 57 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/Codecs.java
  29. 12 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/CodecsSupport.java
  30. 20 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/Decoder.java
  31. 9 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/Encoder.java
  32. 33 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/BooleanCodec.java
  33. 27 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/ByteBufCodec.java
  34. 36 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/ByteCodec.java
  35. 32 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/BytesCodec.java
  36. 112 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/DefaultCodecsSupport.java
  37. 37 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/DeviceMessageCodec.java
  38. 31 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/DirectCodec.java
  39. 33 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/DoubleCodec.java
  40. 37 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/EnumCodec.java
  41. 41 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/ErrorCodec.java
  42. 29 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/FastJsonArrayCodec.java
  43. 29 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/FastJsonCodec.java
  44. 33 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/FloatCodec.java
  45. 33 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/IntegerCodec.java
  46. 50 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/JsonArrayCodec.java
  47. 40 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/JsonCodec.java
  48. 33 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/LongCodec.java
  49. 44 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/StringCodec.java
  50. 70 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/SubscriptionCodec.java
  51. 89 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/TopicPayloadCodec.java
  52. 33 0
      jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/VoidCodec.java
  53. 50 0
      jetlinks-core/src/main/java/org/jetlinks/core/config/ConfigKey.java
  54. 14 0
      jetlinks-core/src/main/java/org/jetlinks/core/config/ConfigKeyValue.java
  55. 43 0
      jetlinks-core/src/main/java/org/jetlinks/core/config/ConfigStorage.java
  56. 9 0
      jetlinks-core/src/main/java/org/jetlinks/core/config/ConfigStorageManager.java
  57. 18 0
      jetlinks-core/src/main/java/org/jetlinks/core/config/SimpleConfigKey.java
  58. 94 0
      jetlinks-core/src/main/java/org/jetlinks/core/config/StorageConfigurable.java
  59. 41 0
      jetlinks-core/src/main/java/org/jetlinks/core/defaults/Authenticator.java
  60. 395 0
      jetlinks-core/src/main/java/org/jetlinks/core/defaults/CompositeProtocolSupport.java
  61. 40 0
      jetlinks-core/src/main/java/org/jetlinks/core/defaults/CompositeProtocolSupports.java
  62. 305 0
      jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultDeviceMessageSender.java
  63. 458 0
      jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultDeviceOperator.java
  64. 137 0
      jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultDeviceProductOperator.java
  65. 125 0
      jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultFunctionInvokeMessageSender.java
  66. 58 0
      jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultReadPropertyMessageSender.java
  67. 72 0
      jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultWritePropertyMessageSender.java
  68. 28 0
      jetlinks-core/src/main/java/org/jetlinks/core/defaults/ExpandsConfigMetadataSupplier.java
  69. 85 0
      jetlinks-core/src/main/java/org/jetlinks/core/defaults/StaticExpandsConfigMetadataSupplier.java
  70. 9 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/AuthenticationRequest.java
  71. 46 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/AuthenticationResponse.java
  72. 42 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/CompositeDeviceMessageSenderInterceptor.java
  73. 47 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceConfigKey.java
  74. 79 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceInfo.java
  75. 106 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceMessageSender.java
  76. 59 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceOperationBroker.java
  77. 176 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceOperator.java
  78. 40 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceProductOperator.java
  79. 76 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceRegistry.java
  80. 24 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceState.java
  81. 33 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceStateChecker.java
  82. 18 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceStateInfo.java
  83. 25 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/MqttAuthenticationRequest.java
  84. 78 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/ProductInfo.java
  85. 8 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/ReplyFailureHandler.java
  86. 139 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/StandaloneDeviceMessageBroker.java
  87. 16 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/manager/BindInfo.java
  88. 30 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/manager/DeviceBindHolder.java
  89. 133 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/manager/DeviceBindManager.java
  90. 23 0
      jetlinks-core/src/main/java/org/jetlinks/core/device/manager/DeviceBindProvider.java
  91. 47 0
      jetlinks-core/src/main/java/org/jetlinks/core/enums/ErrorCode.java
  92. 146 0
      jetlinks-core/src/main/java/org/jetlinks/core/event/EventBus.java
  93. 172 0
      jetlinks-core/src/main/java/org/jetlinks/core/event/Subscription.java
  94. 185 0
      jetlinks-core/src/main/java/org/jetlinks/core/event/TopicPayload.java
  95. 29 0
      jetlinks-core/src/main/java/org/jetlinks/core/exception/DeviceOperationException.java
  96. 32 0
      jetlinks-core/src/main/java/org/jetlinks/core/ipc/DefaultIpcDefinition.java
  97. 114 0
      jetlinks-core/src/main/java/org/jetlinks/core/ipc/DefaultIpcInvoker.java
  98. 110 0
      jetlinks-core/src/main/java/org/jetlinks/core/ipc/DefaultIpcInvokerBuilder.java
  99. 9 0
      jetlinks-core/src/main/java/org/jetlinks/core/ipc/IpcCode.java
  100. 56 0
      jetlinks-core/src/main/java/org/jetlinks/core/ipc/IpcDefinition.java

+ 2 - 0
jetlinks-components/network-component/network-core/src/main/java/org/jetlinks/community/support/JetlinksExtendTopicMessageCodec.java

@@ -141,6 +141,7 @@ public class JetlinksExtendTopicMessageCodec {
             mqttData.put("url", firmwareMessage.getUrl());
             mqttData.put("sign", firmwareMessage.getSign());
             mqttData.put("version", firmwareMessage.getVersion());
+            mqttData.put("taskId",firmwareMessage.getTaskId());
             mqttData.put("signMethod", firmwareMessage.getSignMethod());
             mqttData.put("parameters", firmwareMessage.getParameters());
             mqttData.put("deviceId", deviceId);
@@ -168,6 +169,7 @@ public class JetlinksExtendTopicMessageCodec {
             mqttData.put("version", firmwareMessage.getVersion());
             mqttData.put("signMethod", firmwareMessage.getSignMethod());
             mqttData.put("parameters", firmwareMessage.getParameters());
+            mqttData.put("taskId",firmwareMessage.getTaskId());
             mqttData.put("deviceId", deviceId);
             return new JetlinksExtendTopicMessageCodec.EncodedTopic(topic, mqttData);
         } else if (message instanceof ChildDeviceMessage) {

+ 3 - 4
jetlinks-components/notify-component/notify-webhook/src/main/java/org/jetlinks/community/notify/webhook/WebHookNotifier.java

@@ -3,7 +3,6 @@ package org.jetlinks.community.notify.webhook;
 import cn.hutool.http.HttpRequest;
 import cn.hutool.http.HttpResponse;
 import cn.hutool.http.HttpUtil;
-import cn.hutool.http.Method;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import org.hswebframework.web.exception.BusinessException;
@@ -15,7 +14,6 @@ import reactor.core.publisher.Mono;
 import javax.annotation.Nonnull;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -86,7 +84,7 @@ public class WebHookNotifier extends AbstractNotifier<WebHookTemplate> {
         return Mono.defer(()->{
                 try {
                     String method = template.getMethod();
-                    switch (method){
+                    switch (method.toLowerCase()){
                         case "get":
                             request=HttpUtil.createGet(template.getUrl());
                             Map<String, String> headers = new HashMap<>();
@@ -103,7 +101,8 @@ public class WebHookNotifier extends AbstractNotifier<WebHookTemplate> {
                     request.setConnectionTimeout(Long.valueOf(TimeUnit.SECONDS.toMillis(template.getConnectionTimeout())).intValue());
                     HttpResponse response = request.execute();
                     if (200!=response.getStatus()) {
-                        return Mono.error(new BusinessException(response.body(), response.getStatus()));
+//                        return Mono.error(new BusinessException(response.body(), response.getStatus()));
+                        return Mono.error(new BusinessException(response.body()));
                     }
                 }catch (Exception e){
                     return Mono.error(e);

+ 0 - 3
jetlinks-components/notify-component/notify-webhook/src/main/java/org/jetlinks/community/notify/webhook/WebHookNotifyProvider.java

@@ -1,8 +1,6 @@
 package org.jetlinks.community.notify.webhook;
 
-import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
-import io.vertx.core.http.HttpMethod;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.hswebframework.web.validator.ValidatorUtils;
@@ -18,7 +16,6 @@ import org.springframework.stereotype.Component;
 import reactor.core.publisher.Mono;
 
 import javax.annotation.Nonnull;
-import java.util.HashMap;
 
 /**
  * @author lifang

+ 1 - 1
jetlinks-components/notify-component/notify-webhook/src/main/java/org/jetlinks/community/notify/webhook/WebHookProvider.java

@@ -15,7 +15,7 @@ import org.jetlinks.community.notify.*;
 @Getter
 @AllArgsConstructor
 public enum  WebHookProvider  implements Provider {
-    webHook("HTTP_CLIENT");
+    webHook("webHook");
 
     private String name;
 

+ 11 - 0
jetlinks-core/.travis.yml

@@ -0,0 +1,11 @@
+language: java
+sudo: false
+jdk:
+- openjdk8
+script:
+- mvn test
+after_success:
+- bash <(curl -s https://codecov.io/bash)
+cache:
+  directories:
+  - '$HOME/.m2/repository'

+ 86 - 0
jetlinks-core/pom.xml

@@ -0,0 +1,86 @@
+<?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</artifactId>
+        <groupId>org.jetlinks</groupId>
+        <version>1.1.7-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>jetlinks-core</artifactId>
+    <name>JetLinks Core</name>
+    <description>JetLinks 核心包</description>
+    <dependencies>
+        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.projectreactor.addons</groupId>
+            <artifactId>reactor-extra</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-buffer</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+            <version>2.0.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.vavr</groupId>
+            <artifactId>vavr</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.vavr</groupId>
+            <artifactId>vavr-match</artifactId>
+            <version>0.9.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-core</artifactId>
+            <version>${hsweb.framework.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.californium</groupId>
+            <artifactId>californium-core</artifactId>
+            <version>2.0.0</version>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>guava</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 109 - 0
jetlinks-core/src/main/java/org/jetlinks/core/ByteBufPayload.java

@@ -0,0 +1,109 @@
+package org.jetlinks.core;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.util.Recycler;
+import io.netty.util.ReferenceCountUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.core.utils.RecyclerUtils;
+
+import javax.annotation.Nonnull;
+
+@Slf4j
+class ByteBufPayload implements Payload {
+
+    public static boolean POOL_ENABLED = Boolean.parseBoolean(System.getProperty("jetlinks.eventbus.payload.pool.enabled","true"));
+
+    private static final Recycler<ByteBufPayload> RECYCLER = RecyclerUtils.newRecycler(ByteBufPayload.class,ByteBufPayload::new);
+
+    private final Recycler.Handle<ByteBufPayload> handle;
+
+    ByteBufPayload(Recycler.Handle<ByteBufPayload> handle) {
+        this.handle = handle;
+    }
+
+    private ByteBuf body;
+
+    private String caller;
+
+    static Payload of(ByteBuf body) {
+        ByteBufPayload payload;
+        if (POOL_ENABLED) {
+            try {
+                payload = RECYCLER.get();
+
+            } catch (Exception e) {
+                payload = new ByteBufPayload(null);
+            }
+        } else {
+            payload = new ByteBufPayload(null);
+        }
+        if (log.isTraceEnabled()) {
+            for (StackTraceElement element : (new Exception()).getStackTrace()) {
+                if (!"org.jetlinks.core.Payload".equals(element.getClassName()) &&
+                        !"org.jetlinks.core.ByteBufPayload".equals(element.getClassName()) &&
+                        !element.getClassName().startsWith("org.jetlinks.core.codec")
+                ) {
+                    payload.caller = element.toString();
+                    break;
+                }
+            }
+        }
+        payload.body = body;
+        return payload;
+    }
+
+
+    @Override
+    public boolean release() {
+        return handleRelease(ReferenceCountUtil.release(body));
+    }
+
+    @Override
+    public boolean release(int dec) {
+        return handleRelease(ReferenceCountUtil.release(body, dec));
+    }
+
+    @Override
+    public Payload retain(int inc) {
+        ReferenceCountUtil.retain(body, inc);
+        return this;
+    }
+
+    @Override
+    public Payload retain() {
+        ReferenceCountUtil.retain(body);
+        return this;
+    }
+
+    @Nonnull
+    @Override
+    public ByteBuf getBody() {
+        return body;
+    }
+
+    protected boolean handleRelease(boolean release) {
+        if (release && handle != null) {
+            body = null;
+            caller = null;
+            handle.recycle(this);
+        }
+        return release;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        int refCnt = ReferenceCountUtil.refCnt(body);
+        if (refCnt > 0 ) {
+            log.trace("payload {} was not release properly, release() was not called before it's garbage-collected. refCnt={}. caller: {}", body, refCnt, caller);
+        }
+        super.finalize();
+    }
+
+    @Override
+    public String toString() {
+        return "ByteBufPayload{" +
+                "body=" + body +
+                ", caller='" + caller + '\'' +
+                '}';
+    }
+}

+ 134 - 0
jetlinks-core/src/main/java/org/jetlinks/core/Configurable.java

@@ -0,0 +1,134 @@
+package org.jetlinks.core;
+
+import org.jetlinks.core.config.ConfigKey;
+import org.jetlinks.core.config.ConfigKeyValue;
+import reactor.core.publisher.Mono;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 可配置接口
+ *
+ * @author zhouhao
+ * @since 1.0.0
+ */
+public interface Configurable {
+
+    /**
+     * 获取配置,如果值不存在则返回{@link Mono#empty()}
+     *
+     * @param key key
+     * @return 结果包装器, 不会为null
+     * @see Value#get()
+     */
+    Mono<Value> getConfig(String key);
+
+    /**
+     * 获取多个配置信息
+     *
+     * @param keys 配置key集合
+     * @return 配置信息
+     */
+    Mono<Values> getConfigs(Collection<String> keys);
+
+    /**
+     * 设置一个配置,配置最好以基本数据类型或者json为主
+     *
+     * @param key   配置key
+     * @param value 值 不能为null
+     */
+    Mono<Boolean> setConfig(String key, Object value);
+
+    default Mono<Boolean> setConfig(ConfigKeyValue<?> keyValue) {
+        return setConfig(keyValue.getKey(), keyValue.getValue());
+    }
+
+    default <T> Mono<Boolean> setConfig(ConfigKey<T> key, T value) {
+        return setConfig(key.getKey(), value);
+    }
+
+    default Mono<Boolean> setConfigs(ConfigKeyValue<?>... keyValues) {
+        return setConfigs(Arrays.stream(keyValues)
+                                .filter(ConfigKeyValue::isNotNull)
+                                .collect(Collectors.toMap(ConfigKeyValue::getKey, ConfigKeyValue::getValue)));
+    }
+
+    default <V> Mono<V> getConfig(ConfigKey<V> key) {
+        return getConfig(key.getKey())
+                .flatMap(value -> Mono.justOrEmpty(value.as(key.getType())));
+    }
+
+    default Mono<Values> getConfigs(ConfigKey<?>... key) {
+        return getConfigs(Arrays.stream(key)
+                                .map(ConfigKey::getKey)
+                                .collect(Collectors.toSet()));
+    }
+
+    /**
+     * 获取多个配置,如果未指定key,则获取全部配置
+     *
+     * @return 所有配置结果集合
+     */
+    default Mono<Values> getConfigs(String... keys) {
+        return getConfigs(Arrays.asList(keys));
+    }
+
+    /**
+     * 批量设置配置
+     *
+     * @param conf 配置内容
+     */
+    Mono<Boolean> setConfigs(Map<String, Object> conf);
+
+    /**
+     * 删除配置
+     *
+     * @param key key
+     */
+    Mono<Boolean> removeConfig(String key);
+
+    /**
+     * 获取并删除配置
+     *
+     * @param key key
+     * @return 被删除的配置
+     * @since 1.1.1
+     */
+    Mono<Value> getAndRemoveConfig(String key);
+
+    /**
+     * 删除配置
+     *
+     * @param key key
+     * @return 被删除的值,不存在则返回empty
+     */
+    Mono<Boolean> removeConfigs(Collection<String> key);
+
+    /**
+     * 刷新配置信息
+     *
+     * @return key
+     */
+    Mono<Void> refreshConfig(Collection<String> keys);
+
+    /**
+     * 刷新全部配置信息
+     * @return key
+     */
+    Mono<Void> refreshAllConfig();
+
+    /**
+     * 删除多个配置信息
+     *
+     * @param key key
+     * @return 删除结果
+     */
+    default Mono<Boolean> removeConfigs(ConfigKey<?>... key) {
+        return removeConfigs(Arrays.stream(key).map(ConfigKey::getKey).collect(Collectors.toSet()));
+    }
+
+
+}

+ 240 - 0
jetlinks-core/src/main/java/org/jetlinks/core/NativePayload.java

@@ -0,0 +1,240 @@
+package org.jetlinks.core;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.AbstractReferenceCounted;
+import io.netty.util.Recycler;
+import io.netty.util.ReferenceCountUtil;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.jetlinks.core.codec.Decoder;
+import org.jetlinks.core.codec.Encoder;
+import org.jetlinks.core.metadata.Jsonable;
+import org.jetlinks.core.utils.RecyclerUtils;
+
+import javax.annotation.Nonnull;
+import java.util.*;
+import java.util.function.Supplier;
+
+@Getter
+@Setter
+@Slf4j
+public class NativePayload<T> extends AbstractReferenceCounted implements Payload {
+
+    private T nativeObject;
+
+    private Encoder<T> encoder;
+
+    private volatile Payload ref;
+
+    private ByteBuf buf;
+
+    private static Recycler<NativePayload> POOL = RecyclerUtils.newRecycler(NativePayload.class, NativePayload::new, 1);
+
+    private final io.netty.util.Recycler.Handle<NativePayload> handle;
+
+    private NativePayload(io.netty.util.Recycler.Handle<NativePayload> handle) {
+        this.handle = handle;
+    }
+
+    @Override
+    public Payload slice() {
+        return ref != null ? ref.slice() : this;
+    }
+
+    public static <T> NativePayload<T> of(T nativeObject, Encoder<T> encoder) {
+        NativePayload<T> payload;
+        try {
+            payload = POOL.get();
+        } catch (Exception e) {
+            payload = new NativePayload<>(null);
+        }
+        payload.setRefCnt(1);
+        payload.nativeObject = nativeObject;
+        payload.encoder = encoder;
+        return payload;
+    }
+
+    public static <T> NativePayload<T> of(T nativeObject, Supplier<Payload> bodySupplier) {
+        return of(nativeObject, v -> bodySupplier.get());
+    }
+
+    @Override
+    @SuppressWarnings("all")
+    @SneakyThrows
+    public <T> T decode(Decoder<T> decoder, boolean release) {
+        try {
+            if (decoder.isDecodeFrom(nativeObject)) {
+                return (T) nativeObject;
+            }
+        } finally {
+            if (release) {
+                ReferenceCountUtil.safeRelease(this);
+            }
+        }
+        Class<T> type = decoder.forType();
+        if (type == JSONObject.class || type == Map.class) {
+            return (T) bodyToJson(release);
+        }
+        if (Map.class.isAssignableFrom(decoder.forType())) {
+            return bodyToJson(release).toJavaObject(decoder.forType());
+        }
+        return Payload.super.decode(decoder, release);
+    }
+
+    @Override
+    public Object decode() {
+        return decode(true);
+    }
+
+    @Override
+    public Object decode(boolean release) {
+        try {
+            return nativeObject;
+        } finally {
+            if (release) {
+                ReferenceCountUtil.safeRelease(this);
+            }
+        }
+    }
+
+    @Nonnull
+    @Override
+    public ByteBuf getBody() {
+        if (buf == null) {
+            synchronized (this) {
+                if (buf != null) {
+                    return buf;
+                }
+                ref = encoder.encode(nativeObject);
+                buf = Unpooled.unreleasableBuffer(ref.getBody());
+            }
+        }
+        return buf;
+    }
+
+    @Override
+    public int refCnt() {
+        return super.refCnt();
+    }
+
+    @Override
+    protected void deallocate() {
+        this.buf = null;
+        this.nativeObject = null;
+        this.encoder = null;
+        if (this.ref != null) {
+            ReferenceCountUtil.safeRelease(this.ref);
+            this.ref = null;
+        }
+        if (handle != null) {
+            handle.recycle(this);
+        }
+    }
+
+    @Override
+    public NativePayload<T> touch(Object o) {
+        return this;
+    }
+
+    @Override
+    public NativePayload<T> touch() {
+        super.touch();
+        return this;
+    }
+
+    @Override
+    public NativePayload<T> retain() {
+        return retain(1);
+    }
+
+    @Override
+    public NativePayload<T> retain(int inc) {
+        super.retain(inc);
+        return this;
+    }
+
+    @Override
+    public boolean release() {
+        return this.release(1);
+    }
+
+    @Override
+    public boolean release(int decrement) {
+        return super.release(decrement);
+    }
+
+    @Override
+    public String bodyToString(boolean release) {
+        try {
+            return nativeObject.toString();
+        } finally {
+            if (release) {
+                ReferenceCountUtil.safeRelease(this);
+            }
+        }
+    }
+
+    @Override
+    @SuppressWarnings("all")
+    public JSONArray bodyToJsonArray(boolean release) {
+        try {
+            if (nativeObject == null) {
+                return new JSONArray();
+            }
+            if (nativeObject instanceof JSONArray) {
+                return ((JSONArray) nativeObject);
+            }
+            List<Object> collection;
+            if (nativeObject instanceof List) {
+                collection = ((List<Object>) nativeObject);
+            } else if (nativeObject instanceof Collection) {
+                collection = new ArrayList<>(((Collection<Object>) nativeObject));
+            } else if (nativeObject instanceof Object[]) {
+                collection = Arrays.asList(((Object[]) nativeObject));
+            } else {
+                throw new UnsupportedOperationException("body is not arry");
+            }
+            return new JSONArray(collection);
+        } finally {
+            if (release) {
+                ReferenceCountUtil.safeRelease(this);
+            }
+        }
+    }
+
+    @Override
+    public JSONObject bodyToJson(boolean release) {
+        try {
+            if (nativeObject == null) {
+                return new JSONObject();
+            }
+            if (nativeObject instanceof Jsonable) {
+                return ((Jsonable) nativeObject).toJson();
+            }
+            return FastBeanCopier.copy(nativeObject, JSONObject::new);
+        } finally {
+            if (release) {
+                ReferenceCountUtil.safeRelease(this);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return nativeObject == null ? "null" : nativeObject.toString();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (refCnt() > 0) {
+            log.warn("payload {} was not release properly, release() was not called before it's garbage-collected. refCnt={}", nativeObject, refCnt());
+        }
+        super.finalize();
+    }
+}

+ 191 - 0
jetlinks-core/src/main/java/org/jetlinks/core/Payload.java

@@ -0,0 +1,191 @@
+package org.jetlinks.core;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.util.ReferenceCountUtil;
+import io.netty.util.ReferenceCounted;
+import org.jetlinks.core.codec.Codecs;
+import org.jetlinks.core.codec.Decoder;
+import org.jetlinks.core.codec.Encoder;
+
+import javax.annotation.Nonnull;
+import java.nio.charset.StandardCharsets;
+import java.util.function.Function;
+
+/**
+ * 消息负载
+ *
+ * @author zhouhao
+ * @since 1.1
+ */
+public interface Payload extends ReferenceCounted {
+
+    @Nonnull
+    ByteBuf getBody();
+
+    default Payload slice() {
+        return Payload.of(getBody().slice());
+    }
+
+    default <T> T decode(Decoder<T> decoder, boolean release) {
+        try {
+            return decoder.decode(this);
+        } finally {
+            if (release) {
+                ReferenceCountUtil.safeRelease(this);
+            }
+        }
+    }
+
+    default <T> T decode(Decoder<T> decoder) {
+        return decode(decoder, true);
+    }
+
+    default <T> T decode(Class<T> decoder) {
+        return decode(decoder, true);
+    }
+
+    default <T> T decode(Class<T> decoder, boolean release) {
+        return decode(Codecs.lookup(decoder), release);
+    }
+
+    default Object decode(boolean release) {
+        byte[] payload = getBytes(false);
+        //maybe json
+        if (/* { }*/(payload[0] == 123 && payload[payload.length - 1] == 125)
+                || /* [ ] */(payload[0] == 91 && payload[payload.length - 1] == 93)
+        ) {
+            try {
+                return JSON.parse(new String(payload));
+            } finally {
+                if (release) {
+                    ReferenceCountUtil.safeRelease(this);
+                }
+            }
+        }
+        return decode(Object.class, release);
+    }
+
+    default Object decode() {
+        return decode(true);
+    }
+
+    default <T> T convert(Function<ByteBuf, T> mapper) {
+        return convert(mapper, true);
+    }
+
+    default <T> T convert(Function<ByteBuf, T> mapper, boolean release) {
+        ByteBuf body = getBody();
+        try {
+            return mapper.apply(body);
+        } finally {
+            if (release) {
+                ReferenceCountUtil.safeRelease(this);
+            }
+        }
+    }
+
+    default Payload retain() {
+        return retain(1);
+    }
+
+    default Payload retain(int inc) {
+        getBody().retain(inc);
+        return this;
+    }
+
+    default boolean release(int dec) {
+        if (refCnt() >= dec) {
+            return ReferenceCountUtil.release(getBody(), dec);
+        }
+        return true;
+    }
+
+    default boolean release() {
+        return release(1);
+    }
+
+    default byte[] getBytes() {
+        return getBytes(true);
+    }
+
+    default byte[] getBytes(boolean release) {
+        return convert(ByteBufUtil::getBytes, release);
+    }
+
+    default byte[] getBytes(int offset, int length, boolean release) {
+        return convert(byteBuf -> ByteBufUtil.getBytes(byteBuf, offset, length), release);
+    }
+
+    default String bodyToString() {
+        return bodyToString(true);
+    }
+
+    default String bodyToString(boolean release) {
+        try {
+            return getBody().toString(StandardCharsets.UTF_8);
+        } finally {
+            if (release) {
+                ReferenceCountUtil.safeRelease(this);
+            }
+        }
+    }
+
+    default JSONObject bodyToJson(boolean release) {
+        return decode(JSONObject.class,release);
+    }
+
+    default JSONObject bodyToJson() {
+        return bodyToJson(true);
+    }
+
+    default JSONArray bodyToJsonArray() {
+        return bodyToJsonArray(true);
+    }
+
+    default JSONArray bodyToJsonArray(boolean release) {
+        return decode(JSONArray.class);
+    }
+
+    @Override
+    default int refCnt() {
+        return getBody().refCnt();
+    }
+
+    @Override
+    default Payload touch() {
+        getBody().touch();
+        return this;
+    }
+
+    @Override
+    default Payload touch(Object o) {
+        getBody().touch(o);
+        return this;
+    }
+
+    Payload voidPayload = Payload.of(Unpooled.EMPTY_BUFFER);
+
+    static Payload of(ByteBuf body) {
+        return ByteBufPayload.of(body);
+    }
+
+    static Payload of(byte[] body) {
+        return of(Unpooled.wrappedBuffer(body));
+    }
+
+    static Payload of(String body) {
+        return of(body.getBytes());
+    }
+
+    static <T> Payload of(T body, Encoder<T> encoder) {
+        if (body instanceof Payload) {
+            return encoder.encode(body);
+        }
+        return NativePayload.of(body, encoder);
+    }
+}

+ 304 - 0
jetlinks-core/src/main/java/org/jetlinks/core/ProtocolSupport.java

@@ -0,0 +1,304 @@
+package org.jetlinks.core;
+
+import org.jetlinks.core.device.*;
+import org.jetlinks.core.message.codec.DeviceMessageCodec;
+import org.jetlinks.core.message.codec.Transport;
+import org.jetlinks.core.message.interceptor.DeviceMessageSenderInterceptor;
+import org.jetlinks.core.metadata.*;
+import org.jetlinks.core.server.ClientConnection;
+import org.jetlinks.core.server.DeviceGatewayContext;
+import reactor.core.Disposable;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Nonnull;
+import java.util.Map;
+
+/**
+ * 消息协议支持接口,通过实现此接口来自定义消息协议
+ *
+ * @author zhouhao
+ * @since 1.0.0
+ */
+public interface ProtocolSupport extends Disposable {
+    /**
+     * @return 协议ID
+     */
+    @Nonnull
+    String getId();
+
+    /**
+     * @return 协议名称
+     */
+    String getName();
+
+    /**
+     * @return 说明
+     */
+    String getDescription();
+
+    /**
+     * @return 获取支持的协议类型
+     */
+    Flux<? extends Transport> getSupportedTransport();
+
+    /**
+     * 获取设备消息编码解码器
+     * <ul>
+     * <li>用于将平台统一的消息对象转码为设备的消息</li>
+     * <li>用于将设备发送的消息转吗为平台统一的消息对象</li>
+     * </ul>
+     *
+     * @return 消息编解码器
+     */
+    @Nonnull
+    Mono<? extends DeviceMessageCodec> getMessageCodec(Transport transport);
+
+    /**
+     * 获取设备消息发送拦截器, 用于拦截发送消息的行为.
+     *
+     * @return 监听器
+     */
+    default Mono<DeviceMessageSenderInterceptor> getSenderInterceptor() {
+        return Mono.just(DeviceMessageSenderInterceptor.DO_NOTING);
+    }
+
+    /**
+     * 获取默认的设备物模型编解码器
+     * <ul>
+     * <li>用于将平台统一的设备定义规范转码为协议的规范</li>
+     * <li>用于将协议的规范转为平台统一的设备定义规范</li>
+     *
+     * </ul>
+     *
+     * @return 物模型编解码器
+     */
+    @Nonnull
+    DeviceMetadataCodec getMetadataCodec();
+
+    /**
+     * 获取所有支持的物模型编解码器
+     *
+     * @return 物模型
+     * @since 1.1.4
+     */
+    default Flux<DeviceMetadataCodec> getMetadataCodecs() {
+        return Flux.just(getMetadataCodec());
+    }
+
+    /**
+     * 进行设备认证
+     *
+     * @param request         认证请求,不同的连接方式实现不同
+     * @param deviceOperation 设备操作接口,可用于配置设备
+     * @return 认证结果
+     * @see MqttAuthenticationRequest
+     */
+    @Nonnull
+    Mono<AuthenticationResponse> authenticate(
+            @Nonnull AuthenticationRequest request,
+            @Nonnull DeviceOperator deviceOperation);
+
+    /**
+     * 对不明确的设备进行认证
+     *
+     * @param request  认证请求
+     * @param registry 注册中心
+     * @return 认证结果
+     */
+    @Nonnull
+    default Mono<AuthenticationResponse> authenticate(
+            @Nonnull AuthenticationRequest request,
+            @Nonnull DeviceRegistry registry) {
+        return Mono.error(new UnsupportedOperationException());
+    }
+
+    /**
+     * 获取自定义设备状态检查器,用于检查设备状态.
+     *
+     * @return 设备状态检查器
+     */
+    @Nonnull
+    default Mono<DeviceStateChecker> getStateChecker() {
+        return Mono.empty();
+    }
+
+    /**
+     * 获取协议所需的配置信息定义
+     *
+     * @return 配置定义
+     * @see DeviceOperator#getConfigs(String...)
+     * @see DeviceOperator#setConfigs(Map)
+     */
+    default Mono<ConfigMetadata> getConfigMetadata(Transport transport) {
+        return Mono.empty();
+    }
+
+    /**
+     * 获取协议初始化所需要的配置定义
+     *
+     * @return 配置定义
+     * @since 1.1.2
+     */
+    default Mono<ConfigMetadata> getInitConfigMetadata() {
+        return Mono.empty();
+    }
+
+    /**
+     * 初始化协议
+     *
+     * @param configuration 配置信息
+     */
+    default void init(Map<String, Object> configuration) {
+
+    }
+
+    /**
+     * 销毁协议
+     */
+    @Override
+    default void dispose() {
+    }
+
+    /**
+     * 获取默认物模型
+     *
+     * @param transport 传输协议
+     * @return 物模型信息
+     * @since 1.1.4
+     */
+    default Mono<DeviceMetadata> getDefaultMetadata(Transport transport) {
+        return Mono.empty();
+    }
+
+    /**
+     * 获取物模型拓展配置定义
+     *
+     * @param transport    传输协议类型
+     * @param metadataType 物模型类型
+     * @param dataTypeId   数据类型ID {@link DataType#getId()}
+     * @param metadataId   物模型标识
+     * @return 配置定义
+     * @since 1.1.4
+     */
+    default Flux<ConfigMetadata> getMetadataExpandsConfig(Transport transport,
+                                                          DeviceMetadataType metadataType,
+                                                          String metadataId,
+                                                          String dataTypeId) {
+        return Flux.empty();
+    }
+
+    /**
+     * 当设备注册生效后调用
+     *
+     * @param operator 设备操作接口
+     * @return void
+     * @since 1.1.5
+     */
+    default Mono<Void> onDeviceRegister(DeviceOperator operator) {
+        return Mono.empty();
+    }
+
+    /**
+     * 当设备注销前调用
+     *
+     * @param operator 设备操作接口
+     * @return void
+     * @since 1.1.5
+     */
+    default Mono<Void> onDeviceUnRegister(DeviceOperator operator) {
+        return Mono.empty();
+    }
+
+    /**
+     * 当产品注册后调用
+     *
+     * @param operator 产品操作接口
+     * @return void
+     * @since 1.1.5
+     */
+    default Mono<Void> onProductRegister(DeviceProductOperator operator) {
+        return Mono.empty();
+    }
+
+    /**
+     * 当产品注销前调用
+     *
+     * @param operator 产品操作接口
+     * @return void
+     * @since 1.1.5
+     */
+    default Mono<Void> onProductUnRegister(DeviceProductOperator operator) {
+        return Mono.empty();
+    }
+
+    /**
+     * 当产品物模型变更时调用
+     *
+     * @param operator 产品操作接口
+     * @return void
+     * @since 1.1.6
+     */
+    default Mono<Void> onProductMetadataChanged(DeviceProductOperator operator) {
+        return Mono.empty();
+    }
+
+    /**
+     * 当设备物模型变更时调用
+     *
+     * @param operator 设备操作接口
+     * @return void
+     * @since 1.1.6
+     */
+    default Mono<Void> onDeviceMetadataChanged(DeviceOperator operator) {
+        return Mono.empty();
+    }
+
+    /**
+     * 客户端创建连接时调用,返回设备ID,表示此设备上线.
+     *
+     * @param transport  传输协议
+     * @param connection 客户端连接
+     * @return void
+     * @since 1.1.6
+     */
+    default Mono<Void> onClientConnect(Transport transport,
+                                       ClientConnection connection,
+                                       DeviceGatewayContext context) {
+        return Mono.empty();
+    }
+
+    /**
+     * 触发手动绑定子设备到网关设备
+     *
+     * @param gateway 网关
+     * @param child   子设备流
+     * @return void
+     * @since 1.1.6
+     */
+    default Mono<Void> onChildBind(DeviceOperator gateway, Flux<DeviceOperator> child) {
+        return Mono.empty();
+    }
+
+    /**
+     * 触发手动接触绑定子设备到网关设备
+     *
+     * @param gateway 网关
+     * @param child   子设备流
+     * @return void
+     * @since 1.1.6
+     */
+    default Mono<Void> onChildUnbind(DeviceOperator gateway, Flux<DeviceOperator> child) {
+        return Mono.empty();
+    }
+
+    /**
+     * 获取协议支持的某些自定义特性
+     *
+     * @return 特性集
+     * @since 1.1.6
+     */
+    default Flux<Feature> getFeatures(Transport transport) {
+        return Flux.empty();
+    }
+}

+ 17 - 0
jetlinks-core/src/main/java/org/jetlinks/core/ProtocolSupports.java

@@ -0,0 +1,17 @@
+package org.jetlinks.core;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * @author zhouhao
+ * @since 1.0.0
+ */
+public interface ProtocolSupports {
+
+    boolean isSupport(String protocol);
+
+    Mono<ProtocolSupport> getProtocol(String protocol);
+
+    Flux<ProtocolSupport> getProtocols();
+}

+ 28 - 0
jetlinks-core/src/main/java/org/jetlinks/core/SimpleValue.java

@@ -0,0 +1,28 @@
+package org.jetlinks.core;
+
+import lombok.AllArgsConstructor;
+import org.hswebframework.web.bean.FastBeanCopier;
+
+@AllArgsConstructor(staticName = "of")
+class SimpleValue implements Value {
+
+    private Object nativeValue;
+
+    @Override
+    public Object get() {
+        return nativeValue;
+    }
+
+    @Override
+    public <T> T as(Class<T> type) {
+        if (nativeValue == null) {
+            return null;
+        }
+        if(type.isInstance(nativeValue)){
+            return (T)nativeValue;
+        }
+        return FastBeanCopier.DEFAULT_CONVERT.convert(
+                nativeValue, type, FastBeanCopier.EMPTY_CLASS_ARRAY
+        );
+    }
+}

+ 80 - 0
jetlinks-core/src/main/java/org/jetlinks/core/SimpleValues.java

@@ -0,0 +1,80 @@
+package org.jetlinks.core;
+
+import lombok.AllArgsConstructor;
+import lombok.NonNull;
+import org.apache.commons.collections.MapUtils;
+import org.hswebframework.web.bean.FastBeanCopier;
+
+import java.util.*;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+@AllArgsConstructor(staticName = "of")
+class SimpleValues implements Values {
+
+    @NonNull
+    private final Map<String, Object> values;
+
+    @Override
+    public Map<String, Object> getAllValues() {
+        return new HashMap<>(values);
+    }
+
+    @Override
+    public Optional<Value> getValue(String key) {
+        return Optional
+                .ofNullable(key)
+                .map(values::get)
+                .map(Value::simple);
+    }
+
+    @Override
+    public Values merge(Values source) {
+        Map<String, Object> merged = new HashMap<>();
+        merged.putAll(this.values);
+        merged.putAll(source.getAllValues());
+        return Values.of(merged);
+    }
+
+    @Override
+    public int size() {
+        return values.size();
+    }
+
+    @Override
+    public Set<String> getNonExistentKeys(Collection<String> keys) {
+        return keys
+                .stream()
+                .filter(has -> !values.containsKey(has))
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public String getString(String key, Supplier<String> defaultValue) {
+        if (MapUtils.isEmpty(values)) {
+            return defaultValue.get();
+        }
+        Object val = values.get(key);
+        if (val == null) {
+            return defaultValue.get();
+        }
+        return String.valueOf(val);
+    }
+
+    @Override
+    public Number getNumber(String key, Supplier<Number> defaultValue) {
+        if (MapUtils.isEmpty(values)) {
+            return defaultValue.get();
+        }
+        Object val = values.get(key);
+        if (val == null) {
+            return defaultValue.get();
+        }
+        if(val instanceof Number){
+            return ((Number) val);
+        }
+        return FastBeanCopier.DEFAULT_CONVERT.convert(
+                val, Number.class, FastBeanCopier.EMPTY_CLASS_ARRAY
+        );
+    }
+}

+ 38 - 0
jetlinks-core/src/main/java/org/jetlinks/core/Value.java

@@ -0,0 +1,38 @@
+package org.jetlinks.core;
+
+import java.util.Date;
+
+public interface Value {
+    default String asString() {
+        return String.valueOf(get());
+    }
+
+    default int asInt() {
+        return as(Integer.class);
+    }
+
+    default long asLong() {
+        return as(Long.class);
+    }
+
+    default boolean asBoolean() {
+        return Boolean.TRUE.equals(get())
+                || "true".equals(get());
+    }
+
+    default Number asNumber() {
+        return as(Number.class);
+    }
+
+    default Date asDate() {
+        return as(Date.class);
+    }
+
+    Object get();
+
+    <T> T as(Class<T> type);
+
+    static Value simple(Object value) {
+        return SimpleValue.of(value);
+    }
+}

+ 55 - 0
jetlinks-core/src/main/java/org/jetlinks/core/Values.java

@@ -0,0 +1,55 @@
+package org.jetlinks.core;
+
+import org.jetlinks.core.config.ConfigKey;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+
+public interface Values {
+
+    Map<String, Object> getAllValues();
+
+    Optional<Value> getValue(String key);
+
+    Values merge(Values source);
+
+    int size();
+
+    Set<String> getNonExistentKeys(Collection<String> keys);
+
+    default boolean isEmpty() {
+        return size() == 0;
+    }
+
+    default boolean isNoEmpty() {
+        return size() > 0;
+    }
+
+    default <T> Optional<T> getValue(ConfigKey<T> key) {
+        return getValue(key.getKey())
+                .map(val -> (val.as(key.getType())));
+    }
+
+    default String getString(String key, Supplier<String> defaultValue) {
+        return getValue(key).map(Value::asString).orElseGet(defaultValue);
+    }
+
+    default String getString(String key, String defaultValue) {
+        return getString(key, () -> defaultValue);
+    }
+
+    default Number getNumber(String key, Supplier<Number> defaultValue) {
+        return getValue(key).map(Value::asNumber).orElseGet(defaultValue);
+    }
+
+    default Number getNumber(String key, Number defaultValue) {
+        return getNumber(key, () -> defaultValue);
+    }
+
+    static Values of(Map<String, ?> values) {
+        return SimpleValues.of((Map) values);
+    }
+}

+ 53 - 0
jetlinks-core/src/main/java/org/jetlinks/core/cache/Caches.java

@@ -0,0 +1,53 @@
+package org.jetlinks.core.cache;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.google.common.cache.CacheBuilder;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Supplier;
+
+public class Caches {
+
+    private static final Supplier<ConcurrentMap<Object, Object>> cacheSupplier;
+
+    private static boolean caffeinePresent() {
+        if (Boolean.getBoolean("jetlinks.cache.caffeine.disabled")) {
+            return false;
+        }
+        try {
+            Class.forName("com.github.benmanes.caffeine.cache.Cache");
+            return true;
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
+
+    private static boolean guavaPresent() {
+        return !Boolean.getBoolean("jetlinks.cache.guava.disabled");
+    }
+
+    private static ConcurrentMap<Object, Object> createCaffeine() {
+        return Caffeine.newBuilder().build().asMap();
+    }
+
+    private static ConcurrentMap<Object, Object> createGuava() {
+        return CacheBuilder.newBuilder().build().asMap();
+    }
+
+    static {
+        if (caffeinePresent()) {
+            cacheSupplier = Caches::createCaffeine;
+        } else if (guavaPresent()) {
+            cacheSupplier = Caches::createGuava;
+        } else {
+            cacheSupplier = ConcurrentHashMap::new;
+        }
+    }
+
+    @SuppressWarnings("all")
+    public static <K, V> ConcurrentMap<K, V> newCache() {
+        return (ConcurrentMap) cacheSupplier.get();
+    }
+
+}

+ 143 - 0
jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterCache.java

@@ -0,0 +1,143 @@
+package org.jetlinks.core.cluster;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * 集群缓存,通常用于集群见共享数据.
+ *
+ * @param <K> Key
+ * @param <V> Value
+ * @author zhouhao
+ * @since 1.0
+ */
+public interface ClusterCache<K, V> {
+
+    /**
+     * 根据Key获取值,值不存在时返回{@link Mono#empty()}
+     *
+     * @param key Key
+     * @return 值
+     */
+    Mono<V> get(K key);
+
+    /**
+     * 批量获取缓存
+     *
+     * @param key key集合
+     * @return 键值对
+     */
+    Flux<Map.Entry<K, V>> get(Collection<K> key);
+
+    /**
+     * 设置值
+     *
+     * @param key   key
+     * @param value value
+     * @return 是否成功
+     */
+    Mono<Boolean> put(K key, V value);
+
+    /**
+     * 设置值,如果值以及存在则忽略.
+     *
+     * @param key   key
+     * @param value value
+     * @return 是否成功
+     */
+    Mono<Boolean> putIfAbsent(K key, V value);
+
+    /**
+     * 根据key删除缓存
+     *
+     * @param key key
+     * @return 是否删除成功
+     */
+    Mono<Boolean> remove(K key);
+
+    /**
+     * 获取值然后删除
+     *
+     * @param key key
+     * @return value
+     */
+    Mono<V> getAndRemove(K key);
+
+    /**
+     * 批量删除缓存
+     *
+     * @param key key
+     * @return 是否删除成
+     */
+    Mono<Boolean> remove(Collection<K> key);
+
+    /**
+     * 判断缓存中是否包含key
+     *
+     * @param key key
+     * @return 是否包含key
+     */
+    Mono<Boolean> containsKey(K key);
+
+    /**
+     * 获取缓存的所有key
+     *
+     * @return key流
+     */
+    Flux<K> keys();
+
+    /**
+     * 获取缓存的所有值
+     *
+     * @return value 流
+     */
+    Flux<V> values();
+
+    /**
+     * 批量设置值
+     *
+     * @param multi 批量缓存
+     * @return 是否成功
+     */
+    Mono<Boolean> putAll(Map<? extends K, ? extends V> multi);
+
+    /**
+     * @return 缓存数量
+     */
+    Mono<Integer> size();
+
+    /**
+     * @return 所有键值对
+     */
+    Flux<Map.Entry<K, V>> entries();
+
+    /**
+     * 清空缓存
+     *
+     * @return 清空结果
+     */
+    Mono<Void> clear();
+
+    /**
+     * 刷新缓存信息
+     *
+     * @return void
+     * @since 1.1.6
+     */
+    default Mono<Void> refresh(Collection<? extends K> keys) {
+        return Mono.empty();
+    }
+
+    /**
+     * 刷新全部缓存信息
+     *
+     * @return void
+     * @since 1.1.6
+     */
+    default Mono<Void> refresh() {
+        return Mono.empty();
+    }
+}

+ 75 - 0
jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterCounter.java

@@ -0,0 +1,75 @@
+package org.jetlinks.core.cluster;
+
+import reactor.core.publisher.Mono;
+
+public interface ClusterCounter {
+
+    /**
+     * 递增 n
+     *
+     * @param delta 增量
+     * @return 最新值
+     */
+    Mono<Double> increment(double delta);
+
+    /**
+     * 递增 1
+     *
+     * @return 自增后的值
+     */
+    default Mono<Double> increment() {
+        return increment(1D);
+    }
+
+    /**
+     * 递减 1
+     *
+     * @return 递减后等值
+     */
+    default Mono<Double> decrement() {
+        return decrement(1D);
+    }
+
+    /**
+     * 递减
+     *
+     * @param delta 减量
+     * @return 最新值
+     */
+    default Mono<Double> decrement(double delta) {
+        return increment(-delta);
+    }
+
+
+    /**
+     * 获取当前值
+     *
+     * @return 当前值
+     */
+    Mono<Double> get();
+
+    /**
+     * 设置值
+     *
+     * @param value 新的值
+     * @return 旧的值
+     */
+    Mono<Double> set(double value);
+
+    /**
+     * 设置值并返回最新的值
+     *
+     * @param value 值
+     * @return 最新的值
+     */
+    Mono<Double> setAndGet(double value);
+
+    /**
+     * 获取值然后更新
+     *
+     * @param value 值
+     * @return 更新前的值
+     */
+    Mono<Double> getAndSet(double value);
+
+}

+ 75 - 0
jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterManager.java

@@ -0,0 +1,75 @@
+package org.jetlinks.core.cluster;
+
+/**
+ * 集群管理器
+ *
+ * @author zhouhao
+ * @since 1.0
+ */
+public interface ClusterManager {
+
+    /**
+     * @return 集群名称
+     */
+    String getClusterName();
+
+    /**
+     * @return 当前服务节点ID
+     */
+    String getCurrentServerId();
+
+    /**
+     * @return 集群通知器
+     * @see org.jetlinks.core.event.EventBus
+     */
+    @Deprecated
+    ClusterNotifier getNotifier();
+
+    /**
+     * @return 高可用管理器
+     */
+    HaManager getHaManager();
+
+    /**
+     * 获取集群队列
+     *
+     * @param queueId 队列标识
+     * @param <T>     队列中元素类型
+     * @return 集群队列
+     */
+    <T> ClusterQueue<T> getQueue(String queueId);
+
+    /**
+     * 获取集群广播
+     *
+     * @param topic 广播标识
+     * @param <T>   数据类型
+     * @return 集群广播
+     */
+    <T> ClusterTopic<T> getTopic(String topic);
+
+    /**
+     * 获取集群缓存
+     *
+     * @param cache 缓存标识
+     * @param <K>   Key类型
+     * @param <V>   Value类型
+     * @return 集群缓存
+     */
+    <K, V> ClusterCache<K, V> getCache(String cache);
+
+    /**
+     * 获取Set结构
+     * @param name 名称
+     * @param <V> 集合元素类型
+     * @return ClusterSet
+     */
+    <V> ClusterSet<V> getSet(String name);
+
+    /**
+     * 获取计数器
+     * @param name 名称
+     * @return 计数器
+     */
+    ClusterCounter getCounter(String name);
+}

+ 59 - 0
jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterNotifier.java

@@ -0,0 +1,59 @@
+package org.jetlinks.core.cluster;
+
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.function.Function;
+
+/**
+ * 集群通知器,用于向集群节点发送通知
+ *
+ * @author zhouhao
+ * @since 1.0
+ * @see org.jetlinks.core.event.EventBus
+ */
+@Deprecated
+public interface ClusterNotifier {
+
+    /**
+     * 发送通知给指定的服务节点
+     *
+     * @param serverNodeId 服务节点ID
+     * @param address      消息地址
+     * @param payload      消息内容
+     * @return 发送结果
+     */
+    Mono<Boolean> sendNotify(String serverNodeId, String address, Publisher<?> payload);
+
+    /**
+     * 发送通知给指定的服务节点并等待返回,目标服务必须使用{@link ClusterNotifier#handleNotify(String, Function)}进行监听
+     *
+     * @param serverNodeId 服务节点
+     * @param address      消息地址
+     * @param payload      消息内容
+     * @param <T>          返回值结果类型
+     * @return 返回结果
+     */
+    <T> Flux<T> sendNotifyAndReceive(String serverNodeId, String address, Publisher<?> payload);
+
+    /**
+     * 调用此方法开始处理通知,当收到对应消息地址上的消息时,消息会进入Flux下游
+     *
+     * @param address 消息地址
+     * @param <T>     消息类型
+     * @return 通知消息流
+     */
+    <T> Flux<T> handleNotify(String address);
+
+    /**
+     * 调用此方法开始处理通知并回复处理结果
+     *
+     * @param address      消息地址
+     * @param replyHandler 处理器
+     * @param <T>          消息类型
+     * @param <R>          处理结果类型
+     */
+    <T, R> Mono<Void> handleNotify(String address, Function<T, Publisher<R>> replyHandler);
+
+}

+ 75 - 0
jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterQueue.java

@@ -0,0 +1,75 @@
+package org.jetlinks.core.cluster;
+
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Nonnull;
+import java.util.Collection;
+
+/**
+ * 集群队列
+ *
+ * @param <T> 队列中元素的类型
+ * @author zhouhao
+ */
+public interface ClusterQueue<T> {
+
+    /**
+     * 订阅队列中的数据
+     *
+     * @return 数据流
+     */
+    @Nonnull
+    Flux<T> subscribe();
+
+    /**
+     * 消费队列中的数据,队列为空时返回{@link Mono#empty()}
+     *
+     * @return 单个数据流
+     */
+    @Nonnull
+    Mono<T> poll();
+
+    /**
+     * 向队列中添加数据
+     *
+     * @param publisher 数据流
+     * @return 添加结果
+     */
+    Mono<Boolean> add(Publisher<T> publisher);
+
+    /**
+     * 向队列中批量添加数据
+     *
+     * @param publisher 数据流
+     * @return 添加结果
+     */
+    Mono<Boolean> addBatch(Publisher<? extends Collection<T>> publisher);
+
+    /**
+     * 设置本地消费占比,如果当前队列在同一个进程中存在消费者,则根据此因子进行本地消费.而不发送到集群.
+     *
+     * @param percent 0-10之间的值
+     */
+    void setLocalConsumerPercent(float percent);
+
+    /**
+     * 停止队列,停止后不再消费队列数据.
+     */
+    void stop();
+
+    /**
+     * 获取队列中消息数量
+     *
+     * @return 消息数量
+     */
+    Mono<Integer> size();
+
+    void setPollMod(Mod mod);
+
+    enum Mod {
+        FIFO,//先进先出
+        LIFO//后进先出
+    }
+}

+ 20 - 0
jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterSet.java

@@ -0,0 +1,20 @@
+package org.jetlinks.core.cluster;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Collection;
+
+public interface ClusterSet<T> {
+
+    Mono<Boolean> add(T value);
+
+    Mono<Boolean> add(Collection<T> values);
+
+    Mono<Boolean> remove(T value);
+
+    Mono<Boolean> remove(Collection<T> values);
+
+    Flux<T> values();
+
+}

+ 45 - 0
jetlinks-core/src/main/java/org/jetlinks/core/cluster/ClusterTopic.java

@@ -0,0 +1,45 @@
+package org.jetlinks.core.cluster;
+
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * 集群广播,用于向集群中发送广播消息
+ *
+ * @param <T>
+ */
+public interface ClusterTopic<T> {
+
+    /**
+     * 按通配符进行订阅,通配符支持 *, 如: message/*
+     *
+     * @return 消息流
+     */
+    Flux<TopicMessage<T>> subscribePattern();
+
+    /**
+     * 发送广播消息
+     *
+     * @param publisher 消息流
+     * @return 接收到消息到订阅者数量
+     */
+    Mono<Integer> publish(Publisher<? extends T> publisher);
+
+    /**
+     * 订阅消息
+     *
+     * @return 消息流
+     */
+    default Flux<T> subscribe() {
+        return subscribePattern()
+                .map(TopicMessage::getMessage);
+    }
+
+    interface TopicMessage<T> {
+        String getTopic();
+
+        T getMessage();
+    }
+
+}

+ 43 - 0
jetlinks-core/src/main/java/org/jetlinks/core/cluster/HaManager.java

@@ -0,0 +1,43 @@
+package org.jetlinks.core.cluster;
+
+import reactor.core.Disposable;
+import reactor.core.publisher.Flux;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * 集群高可用管理器,可用于监听集群中的节点上下线信息.
+ *
+ * @author zhouhao
+ * @since 1.0
+ */
+public interface HaManager {
+
+    /**
+     * @return 当前服务节点信息
+     */
+    ServerNode currentServer();
+
+    /**
+     * @return 订阅服务节点上线信息
+     */
+    Flux<ServerNode> subscribeServerOnline();
+
+    /**
+     * @return 订阅服务节点下线信息
+     */
+    Flux<ServerNode> subscribeServerOffline();
+
+    /**
+     * @return 所有可用服务节点
+     */
+    List<ServerNode> getAllNode();
+
+    /**
+     * 监听集群重新负载事件
+     *
+     * @param runnable 监听器
+     */
+    Disposable doOnReBalance(Consumer<List<ServerNode>> runnable);
+}

+ 49 - 0
jetlinks-core/src/main/java/org/jetlinks/core/cluster/ServerNode.java

@@ -0,0 +1,49 @@
+package org.jetlinks.core.cluster;
+
+import lombok.*;
+import org.hswebframework.web.bean.FastBeanCopier;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Optional;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ServerNode implements Serializable {
+    private static final long serialVersionUID = -6849794470754667710L;
+
+    @NonNull
+    private String id;
+
+    private String name;
+
+    private String host;
+
+    private Map<String, Object> tags;
+
+    private boolean leader;
+
+    private long uptime;
+
+    private long lastKeepAlive;
+
+    public boolean hasTag(String tag) {
+        return tags != null && tags.containsKey(tag);
+    }
+
+    public Optional<Object> getTag(String tag) {
+        return Optional.ofNullable(tags)
+                .map(t -> t.get(tag));
+    }
+
+    public boolean isSame(ServerNode another) {
+        return id.equals(another.getId());
+    }
+
+    public ServerNode copy(){
+        return FastBeanCopier.copy(this,new ServerNode());
+    }
+}

+ 6 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/Codec.java

@@ -0,0 +1,6 @@
+package org.jetlinks.core.codec;
+
+public interface Codec<T> extends Decoder<T>, Encoder<T> {
+
+
+}

+ 57 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/Codecs.java

@@ -0,0 +1,57 @@
+package org.jetlinks.core.codec;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.core.cache.Caches;
+import org.reactivestreams.Publisher;
+import org.springframework.core.ResolvableType;
+
+import javax.annotation.Nonnull;
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+@SuppressWarnings("all")
+@Slf4j
+public final class Codecs {
+
+    private static Map<ResolvableType, Codec<?>> mapping = Caches.newCache();
+
+    private static List<CodecsSupport> allCodec = new CopyOnWriteArrayList<>();
+
+    static {
+        ServiceLoader
+                .load(CodecsSupport.class)
+                .forEach(allCodec::add);
+
+        allCodec.sort(Comparator.comparingInt(CodecsSupport::getOrder));
+    }
+
+    public static final void register(CodecsSupport support) {
+        allCodec.add(support);
+        allCodec.sort(Comparator.comparingInt(CodecsSupport::getOrder));
+    }
+
+    @Nonnull
+    private static Codec<?> resolve(ResolvableType target) {
+        for (CodecsSupport support : allCodec) {
+            Optional<Codec<?>> lookup = (Optional) support.lookup(target);
+
+            if (lookup.isPresent()) {
+                log.debug("lookup codec [{}] for [{}]", lookup.get(), target);
+                return lookup.get();
+            }
+        }
+        throw new UnsupportedOperationException("unsupported codec for " + target);
+    }
+
+    public static <T> Codec<T> lookup(@Nonnull Class<? extends T> target) {
+        return lookup(ResolvableType.forType(target));
+    }
+
+    public static <T> Codec<T> lookup(ResolvableType type) {
+        if (Publisher.class.isAssignableFrom(type.toClass())) {
+            type = type.getGeneric(0);
+        }
+        return (Codec<T>) mapping.computeIfAbsent(type,Codecs::resolve);
+    }
+
+}

+ 12 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/CodecsSupport.java

@@ -0,0 +1,12 @@
+package org.jetlinks.core.codec;
+
+import org.springframework.core.ResolvableType;
+
+import java.util.Optional;
+
+public interface CodecsSupport {
+
+    <T> Optional<Codec<T>> lookup(ResolvableType type);
+
+    int getOrder();
+}

+ 20 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/Decoder.java

@@ -0,0 +1,20 @@
+package org.jetlinks.core.codec;
+
+import org.jetlinks.core.Payload;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public interface Decoder<T> {
+
+    Class<T> forType();
+
+    T decode(@Nonnull Payload payload);
+
+    default boolean isDecodeFrom(Object nativeObject){
+        if(nativeObject==null){
+            return false;
+        }
+        return forType().isInstance(nativeObject);
+    }
+}

+ 9 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/Encoder.java

@@ -0,0 +1,9 @@
+package org.jetlinks.core.codec;
+
+import org.jetlinks.core.Payload;
+
+public interface Encoder<T> {
+
+    Payload encode(T body);
+
+}

+ 33 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/BooleanCodec.java

@@ -0,0 +1,33 @@
+package org.jetlinks.core.codec.defaults;
+
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+
+public class BooleanCodec implements Codec<Boolean> {
+
+    public static BooleanCodec INSTANCE = new BooleanCodec();
+
+    private BooleanCodec() {
+
+    }
+
+    @Override
+    public Class<Boolean> forType() {
+        return Boolean.class;
+    }
+
+    @Override
+    public Boolean decode(@Nonnull Payload payload) {
+        byte[] data = payload.getBytes(false);
+
+        return data.length > 0 && data[0] > 0;
+    }
+
+    @Override
+    public Payload encode(Boolean body) {
+        return Payload.of(new byte[]{body ? (byte) 1 : 0});
+    }
+
+}

+ 27 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/ByteBufCodec.java

@@ -0,0 +1,27 @@
+package org.jetlinks.core.codec.defaults;
+
+import io.netty.buffer.ByteBuf;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+
+public class ByteBufCodec implements Codec<ByteBuf> {
+
+    public static final ByteBufCodec INSTANCE = new ByteBufCodec();
+
+    @Override
+    public Class<ByteBuf> forType() {
+        return ByteBuf.class;
+    }
+
+    @Override
+    public ByteBuf decode(@Nonnull Payload payload) {
+        return payload.getBody();
+    }
+
+    @Override
+    public Payload encode(ByteBuf body) {
+        return Payload.of(body);
+    }
+}

+ 36 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/ByteCodec.java

@@ -0,0 +1,36 @@
+package org.jetlinks.core.codec.defaults;
+
+import io.netty.buffer.ByteBuf;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+
+public class ByteCodec implements Codec<Byte> {
+
+    public static ByteCodec INSTANCE = new ByteCodec();
+
+    private ByteCodec() {
+
+    }
+
+    @Override
+    public Class<Byte> forType() {
+        return Byte.class;
+    }
+
+    @Override
+    public Byte decode(@Nonnull Payload payload) {
+        ByteBuf buf = payload.getBody();
+        byte val = buf.getByte(0);
+        buf.resetReaderIndex();
+        return val;
+    }
+
+    @Override
+    public Payload encode(Byte body) {
+        return Payload.of(new byte[]{body});
+    }
+
+
+}

+ 32 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/BytesCodec.java

@@ -0,0 +1,32 @@
+package org.jetlinks.core.codec.defaults;
+
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+
+public class BytesCodec implements Codec<byte[]> {
+
+    public static BytesCodec INSTANCE = new BytesCodec();
+
+    private BytesCodec() {
+
+    }
+
+    @Override
+    public Class<byte[]> forType() {
+        return byte[].class;
+    }
+
+    @Override
+    public byte[] decode(@Nonnull Payload payload) {
+        return payload.getBytes(false);
+    }
+
+    @Override
+    public Payload encode(byte[] body) {
+        return Payload.of(body);
+    }
+
+
+}

+ 112 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/DefaultCodecsSupport.java

@@ -0,0 +1,112 @@
+package org.jetlinks.core.codec.defaults;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import io.netty.buffer.ByteBuf;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+import org.jetlinks.core.codec.CodecsSupport;
+import org.jetlinks.core.event.Subscription;
+import org.jetlinks.core.event.TopicPayload;
+import org.jetlinks.core.message.DeviceMessage;
+import org.jetlinks.core.message.Message;
+import org.reactivestreams.Publisher;
+import org.springframework.core.ResolvableType;
+
+import java.util.*;
+
+@SuppressWarnings("all")
+public class DefaultCodecsSupport implements CodecsSupport {
+
+    private static Map<Class, Codec> staticCodec = new HashMap<>();
+
+    static {
+
+
+        staticCodec.put(byte.class, ByteCodec.INSTANCE);
+        staticCodec.put(Byte.class, ByteCodec.INSTANCE);
+
+        staticCodec.put(int.class, IntegerCodec.INSTANCE);
+        staticCodec.put(Integer.class, IntegerCodec.INSTANCE);
+
+        staticCodec.put(long.class, LongCodec.INSTANCE);
+        staticCodec.put(Long.class, LongCodec.INSTANCE);
+
+        staticCodec.put(double.class, DoubleCodec.INSTANCE);
+        staticCodec.put(Double.class, DoubleCodec.INSTANCE);
+
+        staticCodec.put(float.class, FloatCodec.INSTANCE);
+        staticCodec.put(Float.class, FloatCodec.INSTANCE);
+
+        staticCodec.put(boolean.class, BooleanCodec.INSTANCE);
+        staticCodec.put(Boolean.class, BooleanCodec.INSTANCE);
+
+        staticCodec.put(String.class, StringCodec.UTF8);
+        staticCodec.put(byte[].class, BytesCodec.INSTANCE);
+
+        staticCodec.put(Void.class, VoidCodec.INSTANCE);
+        staticCodec.put(void.class, VoidCodec.INSTANCE);
+
+        staticCodec.put(DeviceMessage.class, DeviceMessageCodec.INSTANCE);
+        staticCodec.put(Message.class, DeviceMessageCodec.INSTANCE);
+
+        {
+            JsonCodec<Map> codec = JsonCodec.of(Map.class);
+            staticCodec.put(Map.class, codec);
+            staticCodec.put(HashMap.class, codec);
+            staticCodec.put(LinkedHashMap.class, codec);
+        }
+
+        staticCodec.put(TopicPayload.class, TopicPayloadCodec.INSTANCE);
+        staticCodec.put(Subscription.class, SubscriptionCodec.INSTANCE);
+
+        staticCodec.put(ByteBuf.class, ByteBufCodec.INSTANCE);
+
+        staticCodec.put(JSONObject.class, FastJsonCodec.INSTANCE);
+
+        staticCodec.put(JSONArray.class, FastJsonArrayCodec.INSTANCE);
+
+    }
+
+    @Override
+    public <T> Optional<Codec<T>> lookup(ResolvableType type) {
+        ResolvableType ref = type;
+        if (Publisher.class.isAssignableFrom(ref.toClass())) {
+            ref = ref.getGeneric(0);
+        }
+        Class refType = ref.toClass();
+
+        Codec<T> codec = staticCodec.get(refType);
+        if (codec == null) {
+            if (List.class.isAssignableFrom(refType)) {
+                codec = (Codec<T>) JsonArrayCodec.of(ref.getGeneric(0).toClass());
+            } else if (ref.toClass().isEnum()) {
+                codec = (Codec<T>) EnumCodec.of((Enum[]) ref.toClass().getEnumConstants());
+            } else if (Payload.class.isAssignableFrom(refType)) {
+                codec = (Codec<T>) DirectCodec.INSTANCE;
+            } else if (Set.class.isAssignableFrom(ref.toClass())) {
+                codec = (Codec<T>) JsonArrayCodec.of(ref.getGeneric(0).toClass(), HashSet.class, HashSet::new);
+            } else if (ByteBuf.class.isAssignableFrom(refType)) {
+                codec = (Codec<T>) ByteBufCodec.INSTANCE;
+            } else if (Message.class.isAssignableFrom(refType)) {
+                codec = (Codec<T>) DeviceMessageCodec.INSTANCE;
+            }
+        }
+
+        if (codec != null) {
+            return Optional.of(codec);
+        }
+        if (refType.isInterface()) {
+            return Optional.empty();
+        }
+        if (codec == null) {
+            codec = JsonCodec.of(refType);
+        }
+        return Optional.of(codec);
+    }
+
+    @Override
+    public int getOrder() {
+        return Integer.MAX_VALUE;
+    }
+}

+ 37 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/DeviceMessageCodec.java

@@ -0,0 +1,37 @@
+package org.jetlinks.core.codec.defaults;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+import org.jetlinks.core.message.Message;
+import org.jetlinks.core.message.MessageType;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DeviceMessageCodec implements Codec<Message> {
+    public static DeviceMessageCodec INSTANCE = new DeviceMessageCodec();
+
+    @Override
+    public Class<Message> forType() {
+        return Message.class;
+    }
+
+    @Nullable
+    @Override
+    public Message decode(@Nonnull Payload payload) {
+        JSONObject json = JSON.parseObject(payload.bodyToString(false));
+        return MessageType
+                .convertMessage(json)
+                .orElseThrow(() -> new UnsupportedOperationException("unsupported message : " + json));
+    }
+
+    @Override
+    public Payload encode(Message body) {
+        return Payload.of(JSON.toJSONBytes(body.toJson()));
+    }
+}

+ 31 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/DirectCodec.java

@@ -0,0 +1,31 @@
+package org.jetlinks.core.codec.defaults;
+
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+
+
+public class DirectCodec implements Codec<Payload> {
+
+    public static final DirectCodec INSTANCE = new DirectCodec();
+
+    public static <T extends Payload> Codec<T> instance() {
+        return (Codec<T>)INSTANCE;
+    }
+
+    @Override
+    public Class<Payload> forType() {
+        return Payload.class;
+    }
+
+    @Override
+    public Payload decode(@Nonnull Payload payload) {
+        return payload;
+    }
+
+    @Override
+    public Payload encode(Payload body) {
+        return body;
+    }
+}

+ 33 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/DoubleCodec.java

@@ -0,0 +1,33 @@
+package org.jetlinks.core.codec.defaults;
+
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+import org.jetlinks.core.utils.BytesUtils;
+
+import javax.annotation.Nonnull;
+
+public class DoubleCodec implements Codec<Double> {
+
+    public static DoubleCodec INSTANCE = new DoubleCodec();
+
+    private DoubleCodec() {
+
+    }
+
+    @Override
+    public Class<Double> forType() {
+        return Double.class;
+    }
+
+    @Override
+    public Double decode(@Nonnull Payload payload) {
+        return BytesUtils.beToDouble(payload.getBytes(false));
+    }
+
+    @Override
+    public Payload encode(Double body) {
+        return Payload.of(BytesUtils.doubleToBe(body));
+    }
+
+
+}

+ 37 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/EnumCodec.java

@@ -0,0 +1,37 @@
+package org.jetlinks.core.codec.defaults;
+
+import lombok.AllArgsConstructor;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+import java.util.Arrays;
+
+@AllArgsConstructor(staticName = "of")
+public class EnumCodec<T extends Enum<?>> implements Codec<T> {
+
+    private final T[] values;
+
+    @Override
+    @SuppressWarnings("all")
+    public Class<T> forType() {
+        return (Class<T>) values[0].getDeclaringClass();
+    }
+
+    @Override
+    public T decode(@Nonnull Payload payload) {
+        byte[] bytes = payload.getBytes(false);
+
+        if (bytes.length > 0 && bytes[0] <= values.length) {
+            return values[bytes[0] & 0xFF];
+        }
+        throw new IllegalArgumentException("can not decode payload " + Arrays.toString(bytes) + " to enums " + Arrays.toString(values));
+    }
+
+    @Override
+    public Payload encode(T body) {
+        return  Payload.of(new byte[]{(byte) body.ordinal()});
+    }
+
+
+}

+ 41 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/ErrorCodec.java

@@ -0,0 +1,41 @@
+package org.jetlinks.core.codec.defaults;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+import java.util.function.Function;
+
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class ErrorCodec implements Codec<Throwable> {
+
+    public static ErrorCodec RUNTIME = of(RuntimeException::new);
+
+
+    public static ErrorCodec DEFAULT = RUNTIME;
+
+
+    public static ErrorCodec of(Function<String, Throwable> mapping) {
+        return new ErrorCodec(mapping);
+    }
+
+    Function<String, Throwable> mapping;
+
+    @Override
+    public Class<Throwable> forType() {
+        return Throwable.class;
+    }
+
+    @Override
+    public Throwable decode(@Nonnull Payload payload) {
+        return mapping.apply(payload.bodyToString(false));
+    }
+
+    @Override
+    public Payload encode(Throwable body) {
+        String message = body.getMessage() == null ? body.getClass().getSimpleName() : body.getMessage();
+        return Payload.of(message);
+    }
+}

+ 29 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/FastJsonArrayCodec.java

@@ -0,0 +1,29 @@
+package org.jetlinks.core.codec.defaults;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+
+public class FastJsonArrayCodec implements Codec<JSONArray> {
+
+    public static final FastJsonArrayCodec INSTANCE=new FastJsonArrayCodec();
+
+    @Override
+    public Class<JSONArray> forType() {
+        return JSONArray.class;
+    }
+
+    @Override
+    public JSONArray decode(@Nonnull Payload payload) {
+        return JSON.parseArray(payload.bodyToString(false));
+    }
+
+    @Override
+    public Payload encode(JSONArray body) {
+        return Payload.of(JSON.toJSONBytes(body));
+    }
+
+}

+ 29 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/FastJsonCodec.java

@@ -0,0 +1,29 @@
+package org.jetlinks.core.codec.defaults;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+
+public class FastJsonCodec implements Codec<JSONObject> {
+
+    public static final FastJsonCodec INSTANCE = new FastJsonCodec();
+
+    @Override
+    public Class<JSONObject> forType() {
+        return JSONObject.class;
+    }
+
+    @Override
+    public JSONObject decode(@Nonnull Payload payload) {
+        return JSON.parseObject(payload.bodyToString(false));
+    }
+
+    @Override
+    public Payload encode(JSONObject body) {
+        return Payload.of(JSON.toJSONBytes(body));
+    }
+
+}

+ 33 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/FloatCodec.java

@@ -0,0 +1,33 @@
+package org.jetlinks.core.codec.defaults;
+
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+import org.jetlinks.core.utils.BytesUtils;
+
+import javax.annotation.Nonnull;
+
+public class FloatCodec implements Codec<Float> {
+
+    public static FloatCodec INSTANCE = new FloatCodec();
+
+    private FloatCodec() {
+
+    }
+
+    @Override
+    public Class<Float> forType() {
+        return Float.class;
+    }
+
+    @Override
+    public Float decode(@Nonnull Payload payload) {
+        return BytesUtils.beToFloat(payload.getBytes(false));
+    }
+
+    @Override
+    public Payload encode(Float body) {
+        return Payload.of(BytesUtils.floatToBe(body));
+    }
+
+
+}

+ 33 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/IntegerCodec.java

@@ -0,0 +1,33 @@
+package org.jetlinks.core.codec.defaults;
+
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+import org.jetlinks.core.utils.BytesUtils;
+
+import javax.annotation.Nonnull;
+
+public class IntegerCodec implements Codec<Integer> {
+
+    public static IntegerCodec INSTANCE = new IntegerCodec();
+
+    private IntegerCodec() {
+
+    }
+
+    @Override
+    public Class<Integer> forType() {
+        return Integer.class;
+    }
+
+    @Override
+    public Integer decode(@Nonnull Payload payload) {
+        return BytesUtils.beToInt(payload.getBytes(false));
+    }
+
+    @Override
+    public Payload encode(Integer body) {
+        return Payload.of(BytesUtils.intToBe(body));
+    }
+
+
+}

+ 50 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/JsonArrayCodec.java

@@ -0,0 +1,50 @@
+package org.jetlinks.core.codec.defaults;
+
+import com.alibaba.fastjson.JSON;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+import java.util.function.Function;
+
+public class JsonArrayCodec<T, R> implements Codec<R> {
+
+    private final Class<T> type;
+
+    private final Class<R> resultType;
+
+    private final Function<List<T>, R> mapper;
+
+    private JsonArrayCodec(Class<T> type, Class<R> resultType, Function<List<T>, R> mapper) {
+        this.type = type;
+        this.resultType = resultType;
+        this.mapper = mapper;
+    }
+
+    @SuppressWarnings("all")
+    public static <T> JsonArrayCodec<T, List<T>> of(Class<T> type) {
+        return JsonArrayCodec.of(type,(Class) List.class, Function.identity());
+    }
+
+    public static <T, R> JsonArrayCodec<T, R> of(Class<T> type, Class<R> resultType, Function<List<T>, R> function) {
+        return new JsonArrayCodec<>(type, resultType,function);
+    }
+
+    @Override
+    public Class<R> forType() {
+        return resultType;
+    }
+
+    @Override
+    public R decode(@Nonnull Payload payload) {
+        return mapper.apply(JSON.parseArray(payload.bodyToString(false), type));
+    }
+
+    @Override
+    public Payload encode(R body) {
+        return Payload.of(JSON.toJSONBytes(body));
+    }
+
+
+}

+ 40 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/JsonCodec.java

@@ -0,0 +1,40 @@
+package org.jetlinks.core.codec.defaults;
+
+import com.alibaba.fastjson.JSON;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+import org.jetlinks.core.metadata.Jsonable;
+
+import javax.annotation.Nonnull;
+
+public class JsonCodec<T> implements Codec<T> {
+
+    private final Class<? extends T> type;
+
+    private JsonCodec(Class<? extends T> type) {
+        this.type = type;
+    }
+
+    public static <T> JsonCodec<T> of(Class<? extends T> type) {
+        return new JsonCodec<>(type);
+    }
+
+    @Override
+    public Class<T> forType() {
+        return (Class<T>) type;
+    }
+
+    @Override
+    public T decode(@Nonnull Payload payload) {
+        return JSON.parseObject(payload.getBytes(false), type);
+    }
+
+    @Override
+    public Payload encode(T body) {
+        if(body instanceof Jsonable){
+            return Payload.of(JSON.toJSONBytes(((Jsonable) body).toJson()));
+        }
+        return Payload.of(JSON.toJSONBytes(body));
+    }
+
+}

+ 33 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/LongCodec.java

@@ -0,0 +1,33 @@
+package org.jetlinks.core.codec.defaults;
+
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+import org.jetlinks.core.utils.BytesUtils;
+
+import javax.annotation.Nonnull;
+
+public class LongCodec implements Codec<Long> {
+
+    public static LongCodec INSTANCE = new LongCodec();
+
+    private LongCodec() {
+
+    }
+
+    @Override
+    public Class<Long> forType() {
+        return Long.class;
+    }
+
+    @Override
+    public Long decode(@Nonnull Payload payload) {
+        return BytesUtils.beToLong(payload.getBytes(false));
+    }
+
+    @Override
+    public Payload encode(Long body) {
+        return Payload.of(BytesUtils.longToBe(body));
+    }
+
+
+}

+ 44 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/StringCodec.java

@@ -0,0 +1,44 @@
+package org.jetlinks.core.codec.defaults;
+
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+public class StringCodec implements Codec<String> {
+
+    public static StringCodec DEFAULT = of(Charset.defaultCharset());
+
+    public static StringCodec UTF8 = of(StandardCharsets.UTF_8);
+
+    public static StringCodec ASCII = of(StandardCharsets.US_ASCII);
+
+    public static StringCodec of(Charset charset) {
+        return new StringCodec(charset);
+    }
+
+    private final Charset charset;
+
+    private StringCodec(Charset charset) {
+        this.charset = charset;
+    }
+
+    @Override
+    public Class<String> forType() {
+        return String.class;
+    }
+
+    @Override
+    public String decode(@Nonnull Payload payload) {
+        return payload.getBody().toString(charset);
+    }
+
+    @Override
+    public Payload encode(String body) {
+        return Payload.of(body.getBytes(charset));
+    }
+
+
+}

+ 70 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/SubscriptionCodec.java

@@ -0,0 +1,70 @@
+package org.jetlinks.core.codec.defaults;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.hswebframework.web.dict.EnumDict;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+import org.jetlinks.core.event.Subscription;
+import org.jetlinks.core.utils.BytesUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.nio.charset.StandardCharsets;
+
+public class SubscriptionCodec implements Codec<Subscription> {
+
+    public static final SubscriptionCodec INSTANCE = new SubscriptionCodec();
+
+    @Override
+    public Class<Subscription> forType() {
+        return Subscription.class;
+    }
+
+    @Nullable
+    @Override
+    public Subscription decode(@Nonnull Payload payload) {
+            ByteBuf body = payload.getBody();
+
+            byte[] subscriberLenArr = new byte[4];
+            body.getBytes(0, subscriberLenArr);
+            int subscriberLen = BytesUtils.beToInt(subscriberLenArr);
+
+            byte[] subscriber = new byte[subscriberLen];
+            body.getBytes(4, subscriber);
+            String subscriberStr = new String(subscriber);
+
+            byte[] featureBytes = new byte[8];
+            body.getBytes(4 + subscriberLen, featureBytes);
+            Subscription.Feature[] features = EnumDict
+                    .getByMask(Subscription.Feature.class, BytesUtils.beToLong(featureBytes))
+                    .toArray(new Subscription.Feature[0]);
+
+            int headerLen = 12 + subscriberLen;
+            body.resetReaderIndex();
+            return Subscription.of(subscriberStr, body.slice(headerLen, body.readableBytes() - headerLen)
+                                                      .toString(StandardCharsets.UTF_8)
+                                                      .split("[\t]"), features);
+    }
+
+    @Override
+    public Payload encode(Subscription body) {
+
+        // bytes 4
+        byte[] subscriber = body.getSubscriber().getBytes();
+        byte[] subscriberLen = BytesUtils.intToBe(subscriber.length);
+
+        // bytes 8
+        long features = EnumDict.toMask(body.getFeatures());
+        byte[] featureBytes = BytesUtils.longToBe(features);
+
+        byte[] topics = String.join("\t", body.getTopics()).getBytes();
+
+        return Payload.of(Unpooled
+                .buffer(subscriberLen.length + subscriber.length + featureBytes.length + topics.length)
+                .writeBytes(subscriberLen)
+                .writeBytes(subscriber)
+                .writeBytes(featureBytes)
+                .writeBytes(topics));
+    }
+}

+ 89 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/TopicPayloadCodec.java

@@ -0,0 +1,89 @@
+package org.jetlinks.core.codec.defaults;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+import org.jetlinks.core.event.TopicPayload;
+import org.jetlinks.core.utils.BytesUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+@Slf4j
+public class TopicPayloadCodec implements Codec<TopicPayload> {
+
+    public static final TopicPayloadCodec INSTANCE = new TopicPayloadCodec();
+
+    @Override
+    public Class<TopicPayload> forType() {
+        return TopicPayload.class;
+    }
+
+
+    public static TopicPayload doDecode(ByteBuf byteBuf){
+        byte[] topicLen = new byte[4];
+
+        byteBuf.getBytes(0, topicLen);
+        int bytes = BytesUtils.beToInt(topicLen);
+
+        byte[] topicBytes = new byte[bytes];
+
+        byteBuf.getBytes(4, topicBytes);
+        String topic = new String(topicBytes);
+
+        int idx = 4 + bytes;
+
+        ByteBuf body = byteBuf.slice(idx, byteBuf.writerIndex() - idx);
+        byteBuf.resetReaderIndex();
+        return TopicPayload.of(topic, Payload.of(body));
+    }
+
+    public static ByteBuf doEncode(TopicPayload body){
+        byte[] topic = body.getTopic().getBytes();
+        byte[] topicLen = BytesUtils.intToBe(topic.length);
+        try {
+            ByteBuf bodyBuf = body.getBody();
+            return ByteBufAllocator.DEFAULT
+                    .buffer(topicLen.length + topic.length + bodyBuf.writerIndex())
+                    .writeBytes(topicLen)
+                    .writeBytes(topic)
+                    .writeBytes(bodyBuf, 0, bodyBuf.writerIndex());
+        } catch (Throwable e) {
+            log.error("encode topic [{}] payload error", body.getTopic());
+            throw e;
+        }
+    }
+
+    @Nullable
+    @Override
+    public TopicPayload decode(@Nonnull Payload payload) {
+        return doDecode(payload.getBody());
+    }
+
+    @Override
+    public Payload encode(TopicPayload body) {
+
+        byte[] topic = body.getTopic().getBytes();
+        byte[] topicLen = BytesUtils.intToBe(topic.length);
+        try {
+            ByteBuf bodyBuf = body.getBody();
+            return Payload.of(ByteBufAllocator.DEFAULT
+                                      .buffer(topicLen.length + topic.length + bodyBuf.writerIndex())
+                                      .writeBytes(topicLen)
+                                      .writeBytes(topic)
+                                      .writeBytes(bodyBuf, 0, bodyBuf.writerIndex()));
+        } catch (Throwable e) {
+            log.error("encode topic [{}] payload error", body.getTopic());
+            throw e;
+        }
+
+//        return Payload.of(Unpooled.buffer()
+//                .writeBytes(topicLen)
+//                .writeBytes(topic)
+//                .writeBytes(body.getBytes())
+//        );
+
+    }
+}

+ 33 - 0
jetlinks-core/src/main/java/org/jetlinks/core/codec/defaults/VoidCodec.java

@@ -0,0 +1,33 @@
+package org.jetlinks.core.codec.defaults;
+
+
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codec;
+
+import javax.annotation.Nonnull;
+
+public class VoidCodec implements Codec<Void> {
+
+    public static VoidCodec INSTANCE = new VoidCodec();
+
+    @Override
+    public Class<Void> forType() {
+        return Void.class;
+    }
+
+    @Override
+    public Void decode(@Nonnull Payload payload) {
+        return null;
+    }
+
+    @Override
+    public Payload encode(Void body) {
+
+        return Payload.of(new byte[0]);
+    }
+
+    @Override
+    public boolean isDecodeFrom(Object nativeObject) {
+        return nativeObject == null;
+    }
+}

+ 50 - 0
jetlinks-core/src/main/java/org/jetlinks/core/config/ConfigKey.java

@@ -0,0 +1,50 @@
+package org.jetlinks.core.config;
+
+public interface ConfigKey<V> {
+    String getKey();
+
+    default String getName() {
+        return getKey();
+    }
+
+    default Class<V> getType() {
+        return (Class<V>) Object.class;
+    }
+
+    static <T> ConfigKey<T> of(String key) {
+        return of(key, key);
+    }
+
+    static <T> ConfigKey<T> of(String key, String name) {
+        return SimpleConfigKey.of(key, name, (Class<T>) Object.class);
+    }
+
+    static <T> ConfigKey<T> of(String key, String name, Class<T> type) {
+        return SimpleConfigKey.of(key, name, type);
+    }
+
+
+    default ConfigKeyValue<V> value(V value) {
+        return new ConfigKeyValue<V>() {
+            @Override
+            public V getValue() {
+                return value;
+            }
+
+            @Override
+            public String getKey() {
+                return ConfigKey.this.getKey();
+            }
+
+            @Override
+            public String getName() {
+                return ConfigKey.this.getName();
+            }
+
+            @Override
+            public Class<V> getType() {
+                return ConfigKey.this.getType();
+            }
+        };
+    }
+}

+ 14 - 0
jetlinks-core/src/main/java/org/jetlinks/core/config/ConfigKeyValue.java

@@ -0,0 +1,14 @@
+package org.jetlinks.core.config;
+
+public interface ConfigKeyValue<V> extends ConfigKey<V> {
+    V getValue();
+
+    default boolean isNull() {
+        return null == getValue();
+    }
+
+    default boolean isNotNull() {
+        return null != getValue();
+    }
+
+}

+ 43 - 0
jetlinks-core/src/main/java/org/jetlinks/core/config/ConfigStorage.java

@@ -0,0 +1,43 @@
+package org.jetlinks.core.config;
+
+import org.jetlinks.core.Value;
+import org.jetlinks.core.Values;
+import reactor.core.publisher.Mono;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * 配置存储
+ */
+public interface ConfigStorage {
+
+    Mono<Value> getConfig(String key);
+
+    default Mono<Values> getConfigs(String... key) {
+        return getConfigs(Arrays.asList(key));
+    }
+
+    Mono<Values> getConfigs(Collection<String> key);
+
+    Mono<Boolean> setConfigs(Map<String, Object> values);
+
+    Mono<Boolean> setConfig(String key, Object value);
+
+    Mono<Boolean> remove(String key);
+
+    Mono<Value> getAndRemove(String key);
+
+    Mono<Boolean> remove(Collection<String> key);
+
+    Mono<Boolean> clear();
+
+    default Mono<Void> refresh(Collection<String> keys){
+        return Mono.empty();
+    }
+
+    default Mono<Void> refresh(){
+        return Mono.empty();
+    }
+}

+ 9 - 0
jetlinks-core/src/main/java/org/jetlinks/core/config/ConfigStorageManager.java

@@ -0,0 +1,9 @@
+package org.jetlinks.core.config;
+
+import reactor.core.publisher.Mono;
+
+public interface ConfigStorageManager {
+
+    Mono<ConfigStorage> getStorage(String id);
+
+}

+ 18 - 0
jetlinks-core/src/main/java/org/jetlinks/core/config/SimpleConfigKey.java

@@ -0,0 +1,18 @@
+package org.jetlinks.core.config;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor(staticName = "of")
+class SimpleConfigKey<V> implements ConfigKey<V> {
+
+    private String key;
+
+    private String name;
+
+    private Class<V> type;
+
+}

+ 94 - 0
jetlinks-core/src/main/java/org/jetlinks/core/config/StorageConfigurable.java

@@ -0,0 +1,94 @@
+package org.jetlinks.core.config;
+
+import org.jetlinks.core.Configurable;
+import org.jetlinks.core.Value;
+import org.jetlinks.core.Values;
+import reactor.core.publisher.Mono;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+public interface StorageConfigurable extends Configurable {
+
+    Mono<ConfigStorage> getReactiveStorage();
+
+    default Mono<? extends Configurable> getParent() {
+        return Mono.empty();
+    }
+
+    @Override
+    default Mono<Value> getConfig(String key) {
+        return getConfig(key, true);
+    }
+
+    default Mono<Value> getConfig(String key, boolean fallbackParent) {
+        return getReactiveStorage()
+                .flatMap(store -> store.getConfig(key))
+                .switchIfEmpty(Mono.defer(() -> fallbackParent ? getParent().flatMap(parent -> parent.getConfig(key)) : Mono
+                        .empty()));
+    }
+
+    default Mono<Values> getConfigs(Collection<String> keys, boolean fallbackParent) {
+        return getReactiveStorage()
+                .flatMap(store -> store.getConfigs(keys))
+                .flatMap(values -> {
+                    //尝试获取上一级的配置
+                    if (!keys.isEmpty() && values.size() != keys.size() && fallbackParent) {
+                        Set<String> nonExistent = values.getNonExistentKeys(keys);
+                        return getParent()
+                                .flatMap(parent -> parent.getConfigs(nonExistent))
+                                .map(parentValues -> parentValues.merge(values))
+                                .defaultIfEmpty(values);
+                    }
+                    return Mono.just(values);
+                });
+    }
+
+    @Override
+    default Mono<Values> getConfigs(Collection<String> keys) {
+        return getConfigs(keys, true);
+    }
+
+    @Override
+    default Mono<Boolean> setConfig(String key, Object value) {
+        return getReactiveStorage()
+                .flatMap(store -> store.setConfig(key, value));
+    }
+
+    @Override
+    default Mono<Boolean> setConfigs(Map<String, Object> conf) {
+        return getReactiveStorage()
+                .flatMap(storage -> storage.setConfigs(conf));
+    }
+
+    @Override
+    default Mono<Boolean> removeConfig(String key) {
+        return getReactiveStorage()
+                .flatMap(storage -> storage.remove(key));
+    }
+
+    @Override
+    default Mono<Value> getAndRemoveConfig(String key) {
+        return getReactiveStorage()
+                .flatMap(storage -> storage.getAndRemove(key));
+    }
+
+    @Override
+    default Mono<Boolean> removeConfigs(Collection<String> key) {
+        return getReactiveStorage()
+                .flatMap(storage -> storage.remove(key));
+    }
+
+    @Override
+    default Mono<Void> refreshConfig(Collection<String> keys) {
+        return getReactiveStorage()
+                .flatMap(storage -> storage.refresh(keys));
+    }
+
+    @Override
+    default Mono<Void> refreshAllConfig(){
+        return getReactiveStorage()
+                .flatMap(ConfigStorage::refresh);
+    }
+}

+ 41 - 0
jetlinks-core/src/main/java/org/jetlinks/core/defaults/Authenticator.java

@@ -0,0 +1,41 @@
+package org.jetlinks.core.defaults;
+
+import org.jetlinks.core.device.AuthenticationRequest;
+import org.jetlinks.core.device.AuthenticationResponse;
+import org.jetlinks.core.device.DeviceOperator;
+import org.jetlinks.core.device.DeviceRegistry;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Nonnull;
+
+/**
+ * 认证器,用于设备连接的时候进行认证
+ *
+ * @author zhouhao
+ * @since 1.0
+ */
+public interface Authenticator {
+
+    /**
+     * 对指定对设备进行认证
+     *
+     * @param request 认证请求
+     * @param device  设备
+     * @return 认证结果
+     */
+    Mono<AuthenticationResponse> authenticate(@Nonnull AuthenticationRequest request,
+                                              @Nonnull DeviceOperator device);
+
+    /**
+     * 在网络连接建立的时候,可能无法获取设备的标识(如:http,websocket等),则会调用此方法来进行认证.
+     * 注意: 认证通过后,需要设置设备ID.{@link AuthenticationResponse#success(String)}
+     *
+     * @param request  认证请求
+     * @param registry 设备注册中心
+     * @return 认证结果
+     */
+    default Mono<AuthenticationResponse> authenticate(@Nonnull AuthenticationRequest request,
+                                                      @Nonnull DeviceRegistry registry) {
+        return Mono.just(AuthenticationResponse.error(500,"不支持的认证方式"));
+    }
+}

+ 395 - 0
jetlinks-core/src/main/java/org/jetlinks/core/defaults/CompositeProtocolSupport.java

@@ -0,0 +1,395 @@
+package org.jetlinks.core.defaults;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import org.jetlinks.core.ProtocolSupport;
+import org.jetlinks.core.device.*;
+import org.jetlinks.core.message.codec.DeviceMessageCodec;
+import org.jetlinks.core.message.codec.Transport;
+import org.jetlinks.core.message.interceptor.DeviceMessageSenderInterceptor;
+import org.jetlinks.core.metadata.*;
+import org.jetlinks.core.server.ClientConnection;
+import org.jetlinks.core.server.DeviceGatewayContext;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@Getter
+@Setter
+public class CompositeProtocolSupport implements ProtocolSupport {
+
+    private String id;
+
+    private String name;
+
+    private String description;
+
+    private DeviceMetadataCodec metadataCodec;
+
+    @Getter(AccessLevel.PRIVATE)
+    private final Map<String, Supplier<Mono<ConfigMetadata>>> configMetadata = new ConcurrentHashMap<>();
+
+    @Getter(AccessLevel.PRIVATE)
+    private final Map<String, Supplier<Mono<DeviceMetadata>>> defaultDeviceMetadata = new ConcurrentHashMap<>();
+
+    @Getter(AccessLevel.PRIVATE)
+    private final Map<String, Supplier<Mono<DeviceMessageCodec>>> messageCodecSupports = new ConcurrentHashMap<>();
+
+    @Getter(AccessLevel.PRIVATE)
+    private Map<String, ExpandsConfigMetadataSupplier> expandsConfigSupplier = new ConcurrentHashMap<>();
+
+
+    @Getter(AccessLevel.PRIVATE)
+    @Setter(AccessLevel.PRIVATE)
+    private DeviceMessageSenderInterceptor deviceMessageSenderInterceptor;
+
+    @Getter(AccessLevel.PRIVATE)
+    private Map<String, Authenticator> authenticators = new ConcurrentHashMap<>();
+
+    private DeviceStateChecker deviceStateChecker;
+
+    private volatile boolean disposed;
+
+    private Disposable.Composite composite = Disposables.composite();
+
+    private Mono<ConfigMetadata> initConfigMetadata = Mono.empty();
+
+    private List<DeviceMetadataCodec> metadataCodecs = new ArrayList<>();
+
+    private List<Consumer<Map<String, Object>>> doOnInit = new CopyOnWriteArrayList<>();
+
+    private Function<DeviceOperator, Mono<Void>> onDeviceRegister;
+    private Function<DeviceOperator, Mono<Void>> onDeviceUnRegister;
+    private Function<DeviceOperator, Mono<Void>> onDeviceMetadataChanged;
+
+    private Function<DeviceProductOperator, Mono<Void>> onProductRegister;
+    private Function<DeviceProductOperator, Mono<Void>> onProductUnRegister;
+    private Function<DeviceProductOperator, Mono<Void>> onProductMetadataChanged;
+
+    private BiFunction<DeviceOperator, Flux<DeviceOperator>, Mono<Void>> onChildBind;
+    private BiFunction<DeviceOperator, Flux<DeviceOperator>, Mono<Void>> onChildUnbind;
+
+    private Map<String, BiFunction<ClientConnection, DeviceGatewayContext, Mono<Void>>> connectionHandlers = new ConcurrentHashMap<>();
+
+    private Map<String, Flux<Feature>> features = new ConcurrentHashMap<>();
+
+    @Override
+    public void dispose() {
+        if (disposed) {
+            return;
+        }
+        disposed = true;
+        composite.dispose();
+        configMetadata.clear();
+        defaultDeviceMetadata.clear();
+        messageCodecSupports.clear();
+        expandsConfigSupplier.clear();
+    }
+
+    public void setInitConfigMetadata(ConfigMetadata metadata) {
+        initConfigMetadata = Mono.just(metadata);
+    }
+
+    @Override
+    public void init(Map<String, Object> configuration) {
+        for (Consumer<Map<String, Object>> mapConsumer : doOnInit) {
+            mapConsumer.accept(configuration);
+        }
+    }
+
+    public CompositeProtocolSupport doOnDispose(Disposable disposable) {
+        composite.add(disposable);
+        return this;
+    }
+
+    public CompositeProtocolSupport doOnInit(Consumer<Map<String, Object>> runnable) {
+        doOnInit.add(runnable);
+        return this;
+    }
+
+    public void addMessageCodecSupport(Transport transport, Supplier<Mono<DeviceMessageCodec>> supplier) {
+        messageCodecSupports.put(transport.getId(), supplier);
+    }
+
+    public void addMessageCodecSupport(Transport transport, DeviceMessageCodec codec) {
+        messageCodecSupports.put(transport.getId(), () -> Mono.just(codec));
+    }
+
+    public void addMessageCodecSupport(DeviceMessageCodec codec) {
+        addMessageCodecSupport(codec.getSupportTransport(), codec);
+    }
+
+    public void addAuthenticator(Transport transport, Authenticator authenticator) {
+        authenticators.put(transport.getId(), authenticator);
+    }
+
+    public void addDefaultMetadata(Transport transport, Mono<DeviceMetadata> metadata) {
+        defaultDeviceMetadata.put(transport.getId(), () -> metadata);
+    }
+
+    public void addDefaultMetadata(Transport transport, DeviceMetadata metadata) {
+        defaultDeviceMetadata.put(transport.getId(), () -> Mono.just(metadata));
+    }
+
+    @Override
+    public Mono<DeviceMessageSenderInterceptor> getSenderInterceptor() {
+        return Mono.justOrEmpty(deviceMessageSenderInterceptor)
+                   .defaultIfEmpty(DeviceMessageSenderInterceptor.DO_NOTING);
+    }
+
+    public synchronized void addMessageSenderInterceptor(DeviceMessageSenderInterceptor interceptor) {
+        if (this.deviceMessageSenderInterceptor == null) {
+            this.deviceMessageSenderInterceptor = interceptor;
+        } else {
+            CompositeDeviceMessageSenderInterceptor composite;
+            if (!(this.deviceMessageSenderInterceptor instanceof CompositeDeviceMessageSenderInterceptor)) {
+                composite = new CompositeDeviceMessageSenderInterceptor();
+                composite.addInterceptor(this.deviceMessageSenderInterceptor);
+            } else {
+                composite = ((CompositeDeviceMessageSenderInterceptor) this.deviceMessageSenderInterceptor);
+            }
+            composite.addInterceptor(interceptor);
+            this.deviceMessageSenderInterceptor = composite;
+        }
+    }
+
+    public void addConfigMetadata(Transport transport, Supplier<Mono<ConfigMetadata>> metadata) {
+        configMetadata.put(transport.getId(), metadata);
+    }
+
+    public void addConfigMetadata(Transport transport, ConfigMetadata metadata) {
+        configMetadata.put(transport.getId(), () -> Mono.just(metadata));
+    }
+
+
+    public void setExpandsConfigMetadata(Transport transport,
+                                         ExpandsConfigMetadataSupplier supplier) {
+        expandsConfigSupplier.put(transport.getId(), supplier);
+    }
+
+
+    @Override
+    public Flux<ConfigMetadata> getMetadataExpandsConfig(Transport transport,
+                                                         DeviceMetadataType metadataType,
+                                                         String metadataId,
+                                                         String dataTypeId) {
+
+        return Optional
+                .ofNullable(expandsConfigSupplier.get(transport.getId()))
+                .map(supplier -> supplier.getConfigMetadata(metadataType, metadataId, dataTypeId))
+                .orElse(Flux.empty());
+    }
+
+    @Override
+    public Mono<DeviceMetadata> getDefaultMetadata(Transport transport) {
+        return Optional
+                .ofNullable(defaultDeviceMetadata.get(transport.getId()))
+                .map(Supplier::get)
+                .orElse(Mono.empty());
+    }
+
+    @Override
+    public Flux<Transport> getSupportedTransport() {
+        return Flux.fromIterable(messageCodecSupports.values())
+                   .flatMap(Supplier::get)
+                   .map(DeviceMessageCodec::getSupportTransport)
+                   .distinct(Transport::getId);
+    }
+
+    @Nonnull
+    @Override
+    public Mono<? extends DeviceMessageCodec> getMessageCodec(Transport transport) {
+        return messageCodecSupports.getOrDefault(transport.getId(), Mono::empty).get();
+    }
+
+    @Nonnull
+    @Override
+    public DeviceMetadataCodec getMetadataCodec() {
+        return metadataCodec;
+    }
+
+    public Flux<DeviceMetadataCodec> getMetadataCodecs() {
+        return Flux.merge(Flux.just(metadataCodec), Flux.fromIterable(metadataCodecs));
+    }
+
+    public void addDeviceMetadataCodec(DeviceMetadataCodec codec) {
+        metadataCodecs.add(codec);
+    }
+
+    @Nonnull
+    @Override
+    public Mono<AuthenticationResponse> authenticate(@Nonnull AuthenticationRequest request,
+                                                     @Nonnull DeviceOperator deviceOperation) {
+        return Mono.justOrEmpty(authenticators.get(request.getTransport().getId()))
+                   .flatMap(at -> at
+                           .authenticate(request, deviceOperation)
+                           .defaultIfEmpty(AuthenticationResponse.error(400, "无法获取认证结果")))
+                   .switchIfEmpty(Mono.error(() -> new UnsupportedOperationException("不支持的认证请求:" + request)));
+    }
+
+    @Nonnull
+    @Override
+    public Mono<AuthenticationResponse> authenticate(@Nonnull AuthenticationRequest request,
+                                                     @Nonnull DeviceRegistry registry) {
+        return Mono.justOrEmpty(authenticators.get(request.getTransport().getId()))
+                   .flatMap(at -> at
+                           .authenticate(request, registry)
+                           .defaultIfEmpty(AuthenticationResponse.error(400, "无法获取认证结果")))
+                   .switchIfEmpty(Mono.error(() -> new UnsupportedOperationException("不支持的认证请求:" + request)));
+    }
+
+    @Override
+    public Mono<ConfigMetadata> getConfigMetadata(Transport transport) {
+        return configMetadata.getOrDefault(transport.getId(), Mono::empty).get();
+    }
+
+    public Mono<ConfigMetadata> getInitConfigMetadata() {
+        return initConfigMetadata;
+    }
+
+    @Nonnull
+    @Override
+    public Mono<DeviceStateChecker> getStateChecker() {
+        return Mono.justOrEmpty(deviceStateChecker);
+    }
+
+    public CompositeProtocolSupport doOnDeviceRegister(Function<DeviceOperator, Mono<Void>> executor) {
+        this.onDeviceRegister = executor;
+        return this;
+    }
+
+    public CompositeProtocolSupport doOnDeviceUnRegister(Function<DeviceOperator, Mono<Void>> executor) {
+        this.onDeviceUnRegister = executor;
+        return this;
+    }
+
+    public CompositeProtocolSupport doOnProductRegister(Function<DeviceProductOperator, Mono<Void>> executor) {
+        this.onProductRegister = executor;
+        return this;
+    }
+
+    public CompositeProtocolSupport doOnProductUnRegister(Function<DeviceProductOperator, Mono<Void>> executor) {
+        this.onProductUnRegister = executor;
+        return this;
+    }
+
+    public CompositeProtocolSupport doOnProductMetadataChanged(Function<DeviceProductOperator, Mono<Void>> executor) {
+        this.onProductMetadataChanged = executor;
+        return this;
+    }
+
+    public CompositeProtocolSupport doOnDeviceMetadataChanged(Function<DeviceOperator, Mono<Void>> executor) {
+        this.onDeviceMetadataChanged = executor;
+        return this;
+    }
+
+    /**
+     * 监听客户端连接,只有部分协议支持此操作,如:
+     * <pre>
+     *     support.doOnClientConnect((connection,context)->{
+     *       //客户端创建连接时,发送消息给客户端
+     *       return connection
+     *       .sendMessage(createHelloMessage())
+     *       .then();
+     *     })
+     * </pre>
+     *
+     * @param transport 通信协议,如: {@link org.jetlinks.core.message.codec.DefaultTransport#TCP}
+     * @param handler   处理器
+     * @since 1.1.6
+     */
+    public void doOnClientConnect(Transport transport,
+                                  BiFunction<ClientConnection, DeviceGatewayContext, Mono<Void>> handler) {
+        connectionHandlers.put(transport.getId(), handler);
+    }
+
+    @Override
+    public Mono<Void> onDeviceRegister(DeviceOperator operator) {
+        return onDeviceRegister != null ? onDeviceRegister.apply(operator) : Mono.empty();
+    }
+
+    @Override
+    public Mono<Void> onDeviceUnRegister(DeviceOperator operator) {
+        return onDeviceUnRegister != null ? onDeviceUnRegister.apply(operator) : Mono.empty();
+    }
+
+    @Override
+    public Mono<Void> onProductRegister(DeviceProductOperator operator) {
+        return onProductRegister != null ? onProductRegister.apply(operator) : Mono.empty();
+    }
+
+    @Override
+    public Mono<Void> onProductUnRegister(DeviceProductOperator operator) {
+        return onProductUnRegister != null ? onProductUnRegister.apply(operator) : Mono.empty();
+    }
+
+    @Override
+    public Mono<Void> onDeviceMetadataChanged(DeviceOperator operator) {
+        return onDeviceMetadataChanged != null ? onDeviceMetadataChanged.apply(operator) : Mono.empty();
+    }
+
+    @Override
+    public Mono<Void> onProductMetadataChanged(DeviceProductOperator operator) {
+        return onProductMetadataChanged != null ? onProductMetadataChanged.apply(operator) : Mono.empty();
+    }
+
+    public void doOnChildBind(BiFunction<DeviceOperator, Flux<DeviceOperator>, Mono<Void>> onChildBind) {
+        this.onChildBind = onChildBind;
+    }
+
+    public void doOnChildUnbind(BiFunction<DeviceOperator, Flux<DeviceOperator>, Mono<Void>> onChildUnbind) {
+        this.onChildUnbind = onChildUnbind;
+    }
+
+    @Override
+    public Mono<Void> onClientConnect(Transport transport,
+                                      ClientConnection connection,
+                                      DeviceGatewayContext context) {
+        BiFunction<ClientConnection, DeviceGatewayContext, Mono<Void>> function = connectionHandlers.get(transport.getId());
+        if (function == null) {
+            return Mono.empty();
+        }
+        return function.apply(connection, context);
+    }
+
+    @Override
+    public Mono<Void> onChildBind(DeviceOperator gateway, Flux<DeviceOperator> child) {
+        return onChildBind == null ? Mono.empty() : onChildBind.apply(gateway, child);
+    }
+
+    @Override
+    public Mono<Void> onChildUnbind(DeviceOperator gateway, Flux<DeviceOperator> child) {
+        return onChildUnbind == null ? Mono.empty() : onChildUnbind.apply(gateway, child);
+    }
+
+    public void addFeature(Transport transport, Feature... features) {
+        addFeature(transport, Flux.just(features));
+    }
+
+    public void addFeature(Transport transport, Iterable<Feature> features) {
+        addFeature(transport, Flux.fromIterable(features));
+    }
+
+    public void addFeature(Transport transport, Flux<Feature> features) {
+        this.features.put(transport.getId(), features);
+    }
+
+    @Override
+    public Flux<Feature> getFeatures(Transport transport) {
+        return features.getOrDefault(transport.getId(), Flux.empty());
+    }
+}

+ 40 - 0
jetlinks-core/src/main/java/org/jetlinks/core/defaults/CompositeProtocolSupports.java

@@ -0,0 +1,40 @@
+package org.jetlinks.core.defaults;
+
+import org.jetlinks.core.ProtocolSupport;
+import org.jetlinks.core.ProtocolSupports;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class CompositeProtocolSupports implements ProtocolSupports {
+
+    private final List<ProtocolSupports> supports = new CopyOnWriteArrayList<>();
+
+    public void register(ProtocolSupports supports) {
+        this.supports.add(supports);
+    }
+
+    @Override
+    public boolean isSupport(String protocol) {
+        return supports
+                .stream()
+                .anyMatch(supports -> supports.isSupport(protocol));
+    }
+
+    @Override
+    public Mono<ProtocolSupport> getProtocol(String protocol) {
+        return supports.stream()
+                .filter(supports -> supports.isSupport(protocol))
+                .findFirst()
+                .map(supports -> supports.getProtocol(protocol))
+                .orElseGet(() -> Mono.error(new UnsupportedOperationException("不支持的协议:" + protocol)));
+    }
+
+    @Override
+    public Flux<ProtocolSupport> getProtocols() {
+        return Flux.fromIterable(supports)
+                .flatMap(ProtocolSupports::getProtocols);
+    }
+}

+ 305 - 0
jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultDeviceMessageSender.java

@@ -0,0 +1,305 @@
+package org.jetlinks.core.defaults;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.core.ProtocolSupport;
+import org.jetlinks.core.device.*;
+import org.jetlinks.core.enums.ErrorCode;
+import org.jetlinks.core.exception.DeviceOperationException;
+import org.jetlinks.core.message.*;
+import org.jetlinks.core.message.interceptor.DeviceMessageSenderInterceptor;
+import org.jetlinks.core.utils.DeviceMessageTracer;
+import org.reactivestreams.Publisher;
+import org.springframework.util.StringUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Function;
+
+import static org.jetlinks.core.device.DeviceConfigKey.connectionServerId;
+
+@Slf4j
+public class DefaultDeviceMessageSender implements DeviceMessageSender {
+
+    private final DeviceOperationBroker handler;
+
+    private final DeviceOperator operator;
+
+    private final DeviceRegistry registry;
+
+    private static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toMillis(Integer.getInteger("jetlinks.device.message.default-timeout", 10));
+
+    @Setter
+    @Getter
+    private long defaultTimeout = DEFAULT_TIMEOUT;
+
+    private final DeviceMessageSenderInterceptor globalInterceptor;
+
+    public DefaultDeviceMessageSender(DeviceOperationBroker handler,
+                                      DeviceOperator operator,
+                                      DeviceRegistry registry,
+                                      DeviceMessageSenderInterceptor interceptor) {
+        this.handler = handler;
+        this.operator = operator;
+        this.registry = registry;
+        this.globalInterceptor = interceptor;
+    }
+
+    @Override
+    public <R extends DeviceMessageReply> Flux<R> send(Publisher<RepayableDeviceMessage<R>> message) {
+        return send(message, this::convertReply);
+    }
+
+    protected <T extends DeviceMessageReply> T convertReply(Message sent, Object reply) {
+        if (reply instanceof ChildDeviceMessageReply) {
+            if (!(sent instanceof ChildDeviceMessage)) {
+                ChildDeviceMessageReply messageReply = ((ChildDeviceMessageReply) reply);
+                if (!messageReply.isSuccess()) {
+                    //如果是可识别的错误则直接抛出异常
+                    ErrorCode.of(messageReply.getCode())
+                             .map(DeviceOperationException::new)
+                             .ifPresent(err -> {
+                                 throw err;
+                             });
+                }
+                if (messageReply.getChildDeviceMessage() == null) {
+                    ErrorCode.of(messageReply.getCode())
+                             .map(DeviceOperationException::new)
+                             .ifPresent(err -> {
+                                 throw err;
+                             });
+                    throw new DeviceOperationException(ErrorCode.NO_REPLY);
+                }
+                return convertReply(((ChildDeviceMessageReply) reply).getChildDeviceMessage());
+            }
+        }
+        return convertReply(reply);
+    }
+
+    @SuppressWarnings("all")
+    protected <T extends DeviceMessage> T convertReply(Object obj) {
+        DeviceMessage result = null;
+        if (obj instanceof DeviceMessageReply) {
+            DeviceMessageReply reply = ((DeviceMessageReply) obj);
+            if (!reply.isSuccess()) {
+                //如果是可识别的错误则直接抛出异常
+                ErrorCode.of(reply.getCode())
+                         .map(code -> {
+                             String msg = reply.getHeader("errorMessage")
+                                               .map(String::valueOf)
+                                               .orElse(code.getText());
+                             return new DeviceOperationException(code, msg);
+                         })
+                         .ifPresent(err -> {
+                             throw err;
+                         });
+            }
+            result = reply;
+        } else if (obj instanceof DeviceMessage) {
+            result = (DeviceMessage) obj;
+        } else if (obj instanceof Map) {
+            result = (DeviceMessage) MessageType.convertMessage(((Map) obj)).orElse(null);
+        }
+        if (result == null) {
+            throw new DeviceOperationException(ErrorCode.SYSTEM_ERROR, new ClassCastException("can not cast " + obj + " to DeviceMessageReply"));
+        }
+        return (T) result;
+    }
+
+    private <R extends DeviceMessage> Flux<R> logReply(DeviceMessage msg, Flux<R> flux) {
+        if (log.isDebugEnabled()) {
+            return flux
+                    .doOnNext(r -> log.debug(
+                            "receive device[{}] message[{}]: {}",
+                            operator.getDeviceId(),
+                            r.getMessageId(), r))
+
+                    .doOnComplete(() -> log.debug(
+                            "complete receive device[{}] message[{}]",
+                            operator.getDeviceId(),
+                            msg.getMessageId()))
+
+                    .doOnCancel(() -> log.debug(
+                            "cancel receive device[{}] message[{}]",
+                            operator.getDeviceId(),
+                            msg.getMessageId()));
+        }
+        return flux;
+    }
+
+    @Override
+    public <R extends DeviceMessage> Flux<R> send(DeviceMessage message) {
+        return send(Mono.just(message), this::convertReply);
+    }
+
+    private Mono<String> refreshAndGetConnectionServerId() {
+        return Mono
+                .defer(() -> operator
+                        .refreshConfig(Collections.singleton(connectionServerId.getKey()))
+                        .then(operator.getConnectionServerId()));
+    }
+
+    private Flux<DeviceMessage> sendToParentDevice(String parentId,
+                                                   DeviceMessage message) {
+        if (parentId.equals(operator.getDeviceId())) {
+            return Flux
+                    .error(
+                            new DeviceOperationException(ErrorCode.CYCLIC_DEPENDENCE, "子设备与父设备不能为相同的设备")
+                    );
+        }
+
+        ChildDeviceMessage children = new ChildDeviceMessage();
+        children.setDeviceId(parentId);
+        children.setMessageId(message.getMessageId());
+        children.setTimestamp(message.getTimestamp());
+        children.setChildDeviceId(operator.getDeviceId());
+        children.setChildDeviceMessage(message);
+
+        // https://github.com/jetlinks/jetlinks-pro/issues/19
+        if (null != message.getHeaders()) {
+            children.setHeaders(new ConcurrentHashMap<>(message.getHeaders()));
+        }
+        message.addHeader(Headers.dispatchToParent, true);
+        children.validate();
+        return registry
+                .getDevice(parentId)
+                .switchIfEmpty(Mono.error(() -> new DeviceOperationException(ErrorCode.UNKNOWN_PARENT_DEVICE, "未知的父设备:" + parentId)))
+                .flatMapMany(parent -> parent
+                        .messageSender()
+                        .send(Mono.just(children), resp -> this.convertReply(message, resp)))
+                ;
+    }
+
+    public <R extends DeviceMessage> Flux<R> send(Publisher<? extends DeviceMessage> message, Function<Object, R> replyMapping) {
+        return Mono
+                .zip(
+                        //当前设备连接的服务器ID
+                        operator.getConnectionServerId()
+                                .switchIfEmpty(refreshAndGetConnectionServerId())
+                                .defaultIfEmpty(""),
+                        //拦截器
+                        operator.getProtocol()
+                                .flatMap(ProtocolSupport::getSenderInterceptor)
+                                .defaultIfEmpty(DeviceMessageSenderInterceptor.DO_NOTING),
+                        //网关id
+                        operator.getSelfConfig(DeviceConfigKey.parentGatewayId).defaultIfEmpty("")
+                )
+                .flatMapMany(serverAndInterceptor -> {
+
+                    DeviceMessageSenderInterceptor interceptor = serverAndInterceptor
+                            .getT2()
+                            .andThen(globalInterceptor);
+                    String server = serverAndInterceptor.getT1();
+                    String parentGatewayId = serverAndInterceptor.getT3();
+                    //设备未连接,有上级网关设备则通过父级设备发送消息
+                    if (StringUtils.isEmpty(server) && StringUtils.hasText(parentGatewayId)) {
+                        return Flux
+                                .from(message)
+                                .flatMap(msg -> interceptor.preSend(operator, msg))
+                                .flatMap(msg -> this
+                                        .sendToParentDevice(parentGatewayId, msg)
+                                        .as(flux -> interceptor.afterSent(operator, msg, flux)))
+                                .map(r -> (R) r);
+                    }
+                    return Flux
+                            .from(message)
+                            .flatMap(msg -> interceptor.preSend(operator, msg))
+                            .concatMap(msg -> {
+                                DeviceMessageTracer.trace(msg, "send.before");
+                                if (StringUtils.isEmpty(server)) {
+                                    return interceptor.afterSent(operator, msg, Flux.error(new DeviceOperationException(ErrorCode.CLIENT_OFFLINE)));
+                                }
+                                boolean forget = msg.getHeader(Headers.sendAndForget).orElse(false);
+                                //定义处理来自设备的回复.
+                                Flux<R> replyStream = forget
+                                        ? Flux.empty()
+                                        : handler
+                                        .handleReply(msg.getDeviceId(),
+                                                     msg.getMessageId(),
+                                                     Duration.ofMillis(msg.getHeader(Headers.timeout)
+                                                                          .orElse(defaultTimeout)))
+                                        .map(replyMapping)
+                                        .onErrorResume(DeviceOperationException.class, error -> {
+                                            if (error.getCode() == ErrorCode.CLIENT_OFFLINE) {
+                                                //返回离线错误,重新检查状态,以矫正设备缓存的状态
+                                                return operator
+                                                        .checkState()
+                                                        .then(Mono.error(error));
+                                            }
+                                            return Mono.error(error);
+                                        })
+                                        .onErrorMap(TimeoutException.class, timeout -> new DeviceOperationException(ErrorCode.TIME_OUT, timeout))
+                                        .as(flux -> this.logReply(msg, flux));
+
+                                //发送消息到设备连接的服务器
+                                return handler
+                                        .send(server, Mono.just(msg))
+                                        .defaultIfEmpty(-1)
+                                        .flatMapMany(len -> {
+                                            //设备未连接到服务器
+                                            if (len == 0) {
+                                                //尝试发起状态检查,同步设备的真实状态
+                                                return operator
+                                                        .checkState()
+                                                        .flatMapMany(state -> {
+                                                            if (DeviceState.online != state) {
+                                                                return interceptor.afterSent(operator, msg, Flux.error(new DeviceOperationException(ErrorCode.CLIENT_OFFLINE)));
+                                                            }
+                                                            /*
+                                                              设备在线,但是serverId对应的服务没有监听处理消息
+                                                                 1. 服务挂了
+                                                                 2. 设备缓存的serverId不对
+                                                             */
+                                                            //尝试发送给父设备
+                                                            if (StringUtils.hasText(parentGatewayId)) {
+                                                                log.debug("Device [{}] Cached Server [{}] Not Available,Dispatch To Parent [{}]",
+                                                                          operator.getDeviceId(),
+                                                                          server,
+                                                                          parentGatewayId);
+
+                                                                return interceptor
+                                                                        .afterSent(operator, msg, sendToParentDevice(parentGatewayId, msg))
+                                                                        .map(r -> (R) r);
+                                                            }
+                                                            log.warn("Device [{}] Cached Server [{}] Not Available",
+                                                                     operator.getDeviceId(),
+                                                                     server);
+                                                            
+                                                            return interceptor.afterSent(operator, msg, Flux.error(new DeviceOperationException(ErrorCode.SERVER_NOT_AVAILABLE)));
+                                                        });
+                                            } else if (len == -1) {
+                                                return interceptor.afterSent(operator, msg, Flux.error(new DeviceOperationException(ErrorCode.CLIENT_OFFLINE)));
+                                            }
+                                            log.debug("send device[{}] message complete", operator.getDeviceId());
+                                            return interceptor.afterSent(operator, msg, replyStream);
+                                        })
+                                        .doOnNext(r -> DeviceMessageTracer.trace(r, "send.reply"))
+                                        ;
+                            });
+                });
+
+    }
+
+    @Override
+    public FunctionInvokeMessageSender invokeFunction(String function) {
+        return new DefaultFunctionInvokeMessageSender(operator, function);
+    }
+
+    @Override
+    public ReadPropertyMessageSender readProperty(String... property) {
+        return new DefaultReadPropertyMessageSender(operator)
+                .read(property);
+    }
+
+    @Override
+    public WritePropertyMessageSender writeProperty() {
+        return new DefaultWritePropertyMessageSender(operator);
+    }
+}

+ 458 - 0
jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultDeviceOperator.java

@@ -0,0 +1,458 @@
+package org.jetlinks.core.defaults;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.core.ProtocolSupport;
+import org.jetlinks.core.ProtocolSupports;
+import org.jetlinks.core.Value;
+import org.jetlinks.core.Values;
+import org.jetlinks.core.config.ConfigKey;
+import org.jetlinks.core.config.ConfigStorage;
+import org.jetlinks.core.config.ConfigStorageManager;
+import org.jetlinks.core.config.StorageConfigurable;
+import org.jetlinks.core.device.*;
+import org.jetlinks.core.message.ChildDeviceMessage;
+import org.jetlinks.core.message.ChildDeviceMessageReply;
+import org.jetlinks.core.message.DeviceMessageReply;
+import org.jetlinks.core.message.DisconnectDeviceMessage;
+import org.jetlinks.core.message.interceptor.DeviceMessageSenderInterceptor;
+import org.jetlinks.core.message.state.DeviceStateCheckMessage;
+import org.jetlinks.core.message.state.DeviceStateCheckMessageReply;
+import org.jetlinks.core.metadata.DeviceMetadata;
+import org.jetlinks.core.utils.IdUtils;
+import org.springframework.util.StringUtils;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+import static org.jetlinks.core.device.DeviceConfigKey.*;
+
+@Slf4j
+public class DefaultDeviceOperator implements DeviceOperator, StorageConfigurable {
+    public static final DeviceStateChecker DEFAULT_STATE_CHECKER = device -> checkState0(((DefaultDeviceOperator) device));
+
+    private static final ConfigKey<Long> lastMetadataTimeKey = ConfigKey.of("lst_metadata_time");
+
+    private static final AtomicReferenceFieldUpdater<DefaultDeviceOperator, DeviceMetadata> METADATA_UPDATER =
+            AtomicReferenceFieldUpdater.newUpdater(DefaultDeviceOperator.class, DeviceMetadata.class, "metadataCache");
+    private static final AtomicLongFieldUpdater<DefaultDeviceOperator> METADATA_TIME_UPDATER =
+            AtomicLongFieldUpdater.newUpdater(DefaultDeviceOperator.class, "lastMetadataTime");
+
+    private final String id;
+
+    private final DeviceOperationBroker handler;
+
+    private final DeviceRegistry registry;
+
+    private final DeviceMessageSender messageSender;
+
+    private final Mono<ConfigStorage> storageMono;
+
+    private final Mono<ProtocolSupport> protocolSupportMono;
+
+    private final Mono<DeviceMetadata> metadataMono;
+
+    private final DeviceStateChecker stateChecker;
+
+    private volatile long lastMetadataTime = -1;
+
+    private volatile DeviceMetadata metadataCache;
+
+    public DefaultDeviceOperator(String id,
+                                 ProtocolSupports supports,
+                                 ConfigStorageManager storageManager,
+                                 DeviceOperationBroker handler,
+                                 DeviceRegistry registry) {
+        this(id, supports, storageManager, handler, registry, DeviceMessageSenderInterceptor.DO_NOTING);
+
+    }
+
+    public DefaultDeviceOperator(String id,
+                                 ProtocolSupports supports,
+                                 ConfigStorageManager storageManager,
+                                 DeviceOperationBroker handler,
+                                 DeviceRegistry registry,
+                                 DeviceMessageSenderInterceptor interceptor) {
+        this(id, supports, storageManager, handler, registry, interceptor, DEFAULT_STATE_CHECKER);
+    }
+
+    public DefaultDeviceOperator(String id,
+                                 ProtocolSupports supports,
+                                 ConfigStorageManager storageManager,
+                                 DeviceOperationBroker handler,
+                                 DeviceRegistry registry,
+                                 DeviceMessageSenderInterceptor interceptor,
+                                 DeviceStateChecker deviceStateChecker) {
+        this.id = id;
+        this.registry = registry;
+        this.handler = handler;
+        this.messageSender = new DefaultDeviceMessageSender(handler, this, registry, interceptor);
+        this.storageMono = storageManager.getStorage("device:" + id);
+
+//        this.metadataMono = getParent().flatMap(DeviceProductOperator::getMetadata);
+        this.protocolSupportMono = getProduct().flatMap(DeviceProductOperator::getProtocol);
+        this.stateChecker = deviceStateChecker;
+        this.metadataMono = this
+                //获取最后更新物模型的时间
+                .getSelfConfig(lastMetadataTimeKey)
+                .flatMap(i -> {
+                    //如果有时间,则表示设备有独立的物模型.
+                    //如果时间一致,则直接返回物模型缓存.
+                    if (i.equals(lastMetadataTime) && metadataCache != null) {
+                        return Mono.just(metadataCache);
+                    }
+                    METADATA_TIME_UPDATER.set(this, i);
+                    //加载真实的物模型
+                    return Mono
+                            .zip(getSelfConfig(metadata),
+                                 protocolSupportMono)
+                            .flatMap(tp2 -> tp2
+                                    .getT2()
+                                    .getMetadataCodec()
+                                    .decode(tp2.getT1())
+                                    .doOnNext(metadata -> METADATA_UPDATER.set(this, metadata)));
+
+                })
+                //如果上游为空,则使用产品的物模型
+                .switchIfEmpty(this.getParent()
+                                   .flatMap(DeviceProductOperator::getMetadata));
+
+    }
+
+    @Override
+    public Mono<ConfigStorage> getReactiveStorage() {
+        return storageMono;
+    }
+
+    @Override
+    public String getDeviceId() {
+        return id;
+    }
+
+    @Override
+    public Mono<String> getConnectionServerId() {
+        return getSelfConfig(connectionServerId.getKey())
+                .map(Value::asString);
+    }
+
+    @Override
+    public Mono<String> getSessionId() {
+        return getSelfConfig(sessionId.getKey())
+                .map(Value::asString);
+    }
+
+    @Override
+    public Mono<String> getAddress() {
+        return getConfig("address")
+                .map(Value::asString);
+    }
+
+    @Override
+    public Mono<Void> setAddress(String address) {
+        return setConfig("address", address)
+                .then();
+    }
+
+    @Override
+    public Mono<Boolean> putState(byte state) {
+        return setConfig("state", state);
+    }
+
+    @Override
+    public Mono<Byte> getState() {
+        return this
+                .getSelfConfigs(Arrays.asList("state", parentGatewayId.getKey(), selfManageState.getKey()))
+                .flatMap(values -> {
+                    Byte state = values
+                            .getValue("state")
+                            .map(val -> val.as(Byte.class))
+                            .orElse(DeviceState.unknown);
+
+                    boolean isSelfManageState = values
+                            .getValue(selfManageState.getKey())
+                            .map(val -> val.as(Boolean.class))
+                            .orElse(false);
+                    if (isSelfManageState) {
+                        return Mono.just(state);
+                    }
+                    String parentGatewayId = values
+                            .getValue(DeviceConfigKey.parentGatewayId)
+                            .orElse(null);
+                    if(getDeviceId().equals(parentGatewayId)){
+                        log.warn("设备[{}]存在循环依赖",parentGatewayId);
+                        return Mono.just(state);
+                    }
+                    //获取父级设备状态
+                    if (!state.equals(DeviceState.online) && StringUtils.hasText(parentGatewayId)) {
+                        return registry
+                                .getDevice(parentGatewayId)
+                                .flatMap(DeviceOperator::getState);
+                    }
+                    return Mono.just(state);
+                })
+                .defaultIfEmpty(DeviceState.unknown);
+    }
+
+    private Mono<Byte> doCheckState() {
+        return Mono
+                .defer(() -> this
+                        .getSelfConfigs(Arrays.asList(
+                                connectionServerId.getKey(),
+                                parentGatewayId.getKey(),
+                                selfManageState.getKey(),
+                                "state"))
+                        .flatMap(values -> {
+
+                            //当前设备连接到的服务器
+                            String server = values
+                                    .getValue(connectionServerId)
+                                    .orElse(null);
+
+                            //设备缓存的状态
+                            Byte state = values.getValue("state")
+                                               .map(val -> val.as(Byte.class))
+                                               .orElse(DeviceState.unknown);
+
+
+                            //如果缓存中存储有当前设备所在服务信息则尝试发起状态检查
+                            if (StringUtils.hasText(server)) {
+                                return handler
+                                        .getDeviceState(server, Collections.singletonList(id))
+                                        .map(DeviceStateInfo::getState)
+                                        .singleOrEmpty()
+                                        .timeout(Duration.ofSeconds(1), Mono.just(state))
+                                        .defaultIfEmpty(state);
+                            }
+
+                            //网关设备ID
+                            String parentGatewayId = values
+                                    .getValue(DeviceConfigKey.parentGatewayId)
+                                    .orElse(null);
+
+                            if(getDeviceId().equals(parentGatewayId)){
+                                log.warn("设备[{}]存在循环依赖",parentGatewayId);
+                                return Mono.just(state);
+                            }
+                            //如果关联了上级网关设备则获取父设备状态
+                            if (StringUtils.hasText(parentGatewayId)) {
+                                return registry
+                                        .getDevice(parentGatewayId)
+                                        .flatMap(device -> device
+                                                .messageSender()
+                                                //发送设备状态检查指令给网关设备
+                                                .<ChildDeviceMessageReply>
+                                                        send(ChildDeviceMessage.create(parentGatewayId, DeviceStateCheckMessage.create(getDeviceId())))
+                                                .singleOrEmpty()
+                                                .map(msg -> {
+                                                    if (msg.getChildDeviceMessage() instanceof DeviceStateCheckMessageReply) {
+                                                        return ((DeviceStateCheckMessageReply) msg.getChildDeviceMessage())
+                                                                .getState();
+                                                    }
+                                                    log.warn("子设备状态检查返回消息错误{}", msg);
+                                                    return DeviceState.online;
+                                                })
+                                                .onErrorResume(err ->{
+                                                    //子设备是否自己管理状态
+                                                    if (values.getValue(selfManageState).orElse(false)) {
+                                                        return Mono.just(state);
+                                                    }
+                                                    return device.checkState();
+                                                }));
+//                                return registry
+//                                        .getDevice(parentGatewayId)
+//                                        .flatMap(DeviceOperator::checkState);
+                            }
+
+                            //如果是在线状态,则改为离线,否则保持状态不变
+                            if (state.equals(DeviceState.online)) {
+                                return Mono.just(DeviceState.offline);
+                            } else {
+                                return Mono.just(state);
+                            }
+                        }));
+    }
+
+    @Override
+    public Mono<Byte> checkState() {
+        return Mono
+                .zip(
+                        stateChecker
+                                .checkState(this)
+                                .switchIfEmpty(Mono.defer(() -> DEFAULT_STATE_CHECKER.checkState(this)))
+                                .defaultIfEmpty(DeviceState.online),
+                        this.getState()
+                )
+                .flatMap(tp2 -> {
+                    byte newer = tp2.getT1();
+                    byte old = tp2.getT2();
+                    //状态不一致?
+                    if (newer != old) {
+                        log.info("device[{}] state changed from {} to {}", this.getDeviceId(), old, newer);
+                        Map<String, Object> configs = new HashMap<>();
+                        configs.put("state", newer);
+                        if (newer == DeviceState.online) {
+                            configs.put("onlineTime", System.currentTimeMillis());
+                        } else if (newer == DeviceState.offline) {
+                            configs.put("offlineTime", System.currentTimeMillis());
+                        }
+                        return this
+                                .setConfigs(configs)
+                                .thenReturn(newer);
+                    }
+                    return Mono.just(newer);
+                });
+    }
+
+    @Override
+    public Mono<Long> getOnlineTime() {
+        return this
+                .getSelfConfig("onlineTime")
+                .map(val -> val.as(Long.class))
+                .switchIfEmpty(Mono.defer(() -> this
+                        .getSelfConfig(parentGatewayId)
+                        .flatMap(registry::getDevice)
+                        .flatMap(DeviceOperator::getOnlineTime)));
+    }
+
+    @Override
+    public Mono<Long> getOfflineTime() {
+        return this
+                .getSelfConfig("offlineTime")
+                .map(val -> val.as(Long.class))
+                .switchIfEmpty(Mono.defer(() -> this
+                        .getSelfConfig(parentGatewayId)
+                        .flatMap(registry::getDevice)
+                        .flatMap(DeviceOperator::getOfflineTime)));
+    }
+
+    @Override
+    public Mono<Boolean> offline() {
+        return this
+                .setConfigs(
+                        //selfManageState.value(true),
+                        connectionServerId.value(""),
+                        sessionId.value(""),
+                        ConfigKey.of("offlineTime").value(System.currentTimeMillis()),
+                        ConfigKey.of("state").value(DeviceState.offline)
+                )
+                .doOnError(err -> log.error("offline device error", err));
+    }
+
+    @Override
+    public Mono<Boolean> online(String serverId, String sessionId, String address) {
+        return this
+                .setConfigs(
+                      //  selfManageState.value(true),
+                        connectionServerId.value(serverId),
+                        DeviceConfigKey.sessionId.value(sessionId),
+                        ConfigKey.of("address").value(address),
+                        ConfigKey.of("onlineTime").value(System.currentTimeMillis()),
+                        ConfigKey.of("state").value(DeviceState.online)
+                )
+                .doOnError(err -> log.error("online device error", err));
+    }
+
+    @Override
+    public Mono<Value> getSelfConfig(String key) {
+        return getConfig(key, false);
+    }
+
+    @Override
+    public Mono<Values> getSelfConfigs(Collection<String> keys) {
+        return getConfigs(keys, false);
+    }
+
+
+    @Override
+    public Mono<Boolean> disconnect() {
+        DisconnectDeviceMessage disconnect = new DisconnectDeviceMessage();
+        disconnect.setDeviceId(getDeviceId());
+        disconnect.setMessageId(IdUtils.newUUID());
+        return messageSender()
+                .send(Mono.just(disconnect))
+                .next()
+                .map(DeviceMessageReply::isSuccess);
+    }
+
+    @Override
+    public Mono<AuthenticationResponse> authenticate(AuthenticationRequest request) {
+        return getProtocol()
+                .flatMap(protocolSupport -> protocolSupport.authenticate(request, this));
+    }
+
+    @Override
+    public Mono<DeviceMetadata> getMetadata() {
+        return metadataMono;
+    }
+
+
+    @Override
+    public Mono<DeviceProductOperator> getParent() {
+        return getReactiveStorage()
+                .flatMap(store -> store.getConfig(productId.getKey()))
+                .map(Value::asString)
+                .flatMap(registry::getProduct);
+    }
+
+    @Override
+    public Mono<ProtocolSupport> getProtocol() {
+        return protocolSupportMono;
+    }
+
+    @Override
+    public Mono<DeviceProductOperator> getProduct() {
+        return getParent();
+    }
+
+    @Override
+    public DeviceMessageSender messageSender() {
+        return messageSender;
+    }
+
+    @Override
+    public Mono<Boolean> updateMetadata(String metadata) {
+        Map<String, Object> configs = new HashMap<>();
+        configs.put(DeviceConfigKey.metadata.getKey(), metadata);
+        return setConfigs(configs);
+    }
+
+    @Override
+    public Mono<Void> resetMetadata() {
+        METADATA_UPDATER.set(this, null);
+        METADATA_TIME_UPDATER.set(this, -1);
+        return removeConfigs(metadata, lastMetadataTimeKey)
+                .then(this.getProtocol()
+                          .flatMap(support -> support.onDeviceMetadataChanged(this))
+                );
+    }
+
+    @Override
+    public Mono<Boolean> setConfigs(Map<String, Object> conf) {
+        Map<String, Object> configs = new HashMap<>(conf);
+        if (conf.containsKey(metadata.getKey())) {
+            configs.put(lastMetadataTimeKey.getKey(), lastMetadataTime = System.currentTimeMillis());
+
+            return StorageConfigurable.super
+                    .setConfigs(configs)
+                    .doOnNext(suc -> {
+                        this.metadataCache = null;
+                    })
+                    .then(this.getProtocol()
+                              .flatMap(support -> support.onDeviceMetadataChanged(this))
+                    )
+                    .thenReturn(true);
+        }
+        return StorageConfigurable.super.setConfigs(configs);
+    }
+
+    private static Mono<Byte> checkState0(DefaultDeviceOperator operator) {
+        return operator
+                .getProtocol()
+                .flatMap(ProtocolSupport::getStateChecker) //协议自定义了状态检查逻辑
+                .flatMap(deviceStateChecker -> deviceStateChecker.checkState(operator))
+                .switchIfEmpty(operator.doCheckState()) //默认的检查
+                ;
+    }
+}

+ 137 - 0
jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultDeviceProductOperator.java

@@ -0,0 +1,137 @@
+package org.jetlinks.core.defaults;
+
+import lombok.Getter;
+import org.jetlinks.core.ProtocolSupport;
+import org.jetlinks.core.ProtocolSupports;
+import org.jetlinks.core.config.ConfigKey;
+import org.jetlinks.core.config.ConfigStorage;
+import org.jetlinks.core.config.ConfigStorageManager;
+import org.jetlinks.core.config.StorageConfigurable;
+import org.jetlinks.core.device.DeviceConfigKey;
+import org.jetlinks.core.device.DeviceOperator;
+import org.jetlinks.core.device.DeviceProductOperator;
+import org.jetlinks.core.metadata.DeviceMetadata;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class DefaultDeviceProductOperator implements DeviceProductOperator, StorageConfigurable {
+
+    @Getter
+    private final String id;
+
+    private volatile DeviceMetadata metadata;
+
+    private final Mono<ConfigStorage> storageMono;
+
+    private final Supplier<Flux<DeviceOperator>> devicesSupplier;
+
+    private long lstMetadataChangeTime;
+
+    private static final ConfigKey<Long> lastMetadataTimeKey = ConfigKey.of("lst_metadata_time");
+
+    private final Mono<DeviceMetadata> inLocalMetadata;
+
+    private final Mono<DeviceMetadata> metadataMono;
+
+    private final Mono<ProtocolSupport> protocolSupportMono;
+
+    @Deprecated
+    public DefaultDeviceProductOperator(String id,
+                                        ProtocolSupports supports,
+                                        ConfigStorageManager manager) {
+        this(id, supports, manager, Flux::empty);
+    }
+
+    public DefaultDeviceProductOperator(String id,
+                                        ProtocolSupports supports,
+                                        ConfigStorageManager manager,
+                                        Supplier<Flux<DeviceOperator>> supplier) {
+        this.id = id;
+//        this.protocolSupports = supports;
+        this.storageMono = manager.getStorage("device-product:".concat(id));
+        this.devicesSupplier = supplier;
+        this.inLocalMetadata = Mono.fromSupplier(() -> metadata);
+        this.protocolSupportMono = this
+                .getConfig(DeviceConfigKey.protocol)
+                .flatMap(supports::getProtocol);
+
+        Mono<DeviceMetadata> loadMetadata = Mono
+                .zip(
+                        this.getProtocol().map(ProtocolSupport::getMetadataCodec),
+                        this.getConfig(DeviceConfigKey.metadata),
+                        this.getConfig(lastMetadataTimeKey)
+                            .switchIfEmpty(Mono.defer(() -> {
+                                long now = System.currentTimeMillis();
+                                return this
+                                        .setConfig(lastMetadataTimeKey, now)
+                                        .thenReturn(now);
+                            }))
+                )
+                .flatMap(tp3 -> tp3
+                        .getT1()
+                        .decode(tp3.getT2())
+                        .doOnNext(decode -> {
+                            this.metadata = decode;
+                            this.lstMetadataChangeTime = tp3.getT3();
+                        }));
+        this.metadataMono = this
+                .getConfig(lastMetadataTimeKey)
+                .flatMap(time -> {
+                    if (time.equals(lstMetadataChangeTime)) {
+                        return inLocalMetadata;
+                    }
+                    return Mono.empty();
+                })
+                .switchIfEmpty(loadMetadata);
+    }
+
+
+    @Override
+    public Mono<DeviceMetadata> getMetadata() {
+        return this.metadataMono;
+
+    }
+
+    @Override
+    public Mono<Boolean> setConfigs(Map<String, Object> conf) {
+        if (conf.containsKey(DeviceConfigKey.metadata.getKey())) {
+            conf.put(lastMetadataTimeKey.getKey(), System.currentTimeMillis());
+            return StorageConfigurable.super
+                    .setConfigs(conf)
+                    .doOnNext(s -> {
+                        metadata = null;
+                    })
+                    .then(this.getProtocol()
+                              .flatMap(support -> support.onProductMetadataChanged(this))
+                    )
+                    .thenReturn(true);
+        }
+        return StorageConfigurable.super.setConfigs(conf);
+    }
+
+    @Override
+    public Mono<Boolean> updateMetadata(String metadata) {
+        Map<String, Object> configs = new HashMap<>();
+        configs.put(DeviceConfigKey.metadata.getKey(), metadata);
+        return this.setConfigs(configs);
+    }
+
+    @Override
+    public Mono<ProtocolSupport> getProtocol() {
+        return protocolSupportMono;
+    }
+
+    @Override
+    public Mono<ConfigStorage> getReactiveStorage() {
+        return storageMono;
+    }
+
+    @Override
+    public Flux<DeviceOperator> getDevices() {
+        return devicesSupplier == null ? Flux.empty() : devicesSupplier.get();
+    }
+}

+ 125 - 0
jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultFunctionInvokeMessageSender.java

@@ -0,0 +1,125 @@
+package org.jetlinks.core.defaults;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.core.device.DeviceOperator;
+import org.jetlinks.core.message.FunctionInvokeMessageSender;
+import org.jetlinks.core.message.Headers;
+import org.jetlinks.core.message.exception.FunctionUndefinedException;
+import org.jetlinks.core.message.exception.IllegalParameterException;
+import org.jetlinks.core.message.function.FunctionInvokeMessage;
+import org.jetlinks.core.message.function.FunctionInvokeMessageReply;
+import org.jetlinks.core.message.function.FunctionParameter;
+import org.jetlinks.core.metadata.PropertyMetadata;
+import org.jetlinks.core.metadata.ValidateResult;
+import org.jetlinks.core.utils.IdUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Slf4j
+public class DefaultFunctionInvokeMessageSender implements FunctionInvokeMessageSender {
+
+    private FunctionInvokeMessage message = new FunctionInvokeMessage();
+
+    private DeviceOperator operator;
+
+    public DefaultFunctionInvokeMessageSender(DeviceOperator operator, String functionId) {
+        this.operator = operator;
+        message.setMessageId(IdUtils.newUUID());
+        message.setFunctionId(functionId);
+        message.setDeviceId(operator.getDeviceId());
+    }
+
+    @Override
+    public FunctionInvokeMessageSender custom(Consumer<FunctionInvokeMessage> messageConsumer) {
+        messageConsumer.accept(message);
+        return this;
+    }
+
+    @Override
+    public FunctionInvokeMessageSender addParameter(FunctionParameter parameter) {
+        message.addInput(parameter);
+        return this;
+    }
+
+    @Override
+    public FunctionInvokeMessageSender setParameter(List<FunctionParameter> parameter) {
+        message.setInputs(new ArrayList<>(parameter));
+        return this;
+    }
+
+    @Override
+    public FunctionInvokeMessageSender messageId(String messageId) {
+        message.setMessageId(messageId);
+        return this;
+    }
+
+    @Override
+    public FunctionInvokeMessageSender header(String header, Object value) {
+        message.addHeader(header, value);
+        return this;
+    }
+
+    @Override
+    public Mono<FunctionInvokeMessageSender> validate() {
+        String function = message.getFunctionId();
+
+        return operator
+                .getMetadata()
+                .flatMap(metadata -> Mono.justOrEmpty(metadata.getFunction(function)))
+                .switchIfEmpty(Mono.error(() -> new FunctionUndefinedException(function, "功能[" + function + "]未定义")))
+                .doOnNext(functionMetadata -> {
+                    List<PropertyMetadata> metadataInputs = functionMetadata.getInputs();
+                    List<FunctionParameter> inputs = message.getInputs();
+
+                    Map<String, FunctionParameter> properties = inputs.stream()
+                            .collect(Collectors.toMap(FunctionParameter::getName, Function.identity(), (t1, t2) -> t1));
+                    for (PropertyMetadata metadata : metadataInputs) {
+                        FunctionParameter parameter = properties.get(metadata.getId());
+                        Object value = Optional
+                                .ofNullable(parameter)
+                                .map(FunctionParameter::getValue)
+                                .orElse(null);
+                        if (value == null) {
+                            continue;
+                        }
+
+                        ValidateResult validateResult = metadata.getValueType().validate(value);
+
+                        validateResult.ifFail(result -> {
+                            throw new IllegalParameterException(metadata.getId(), result.getErrorMsg());
+                        });
+                        if (validateResult.getValue() != null) {
+                            parameter.setValue(validateResult.getValue());
+                        }
+                    }
+                })
+                .thenReturn(this)
+                ;
+    }
+
+    @Override
+    public Flux<FunctionInvokeMessageReply> send() {
+        if (message.getHeader(Headers.async).isPresent()) {
+            return doSend();
+        }
+        return operator
+                .getMetadata()
+                .flatMap(meta -> Mono.justOrEmpty(meta.getFunction(message.getFunctionId())))
+                .doOnNext(func -> async(func.isAsync()))
+                .thenMany(doSend());
+    }
+
+    private Flux<FunctionInvokeMessageReply> doSend() {
+        return operator
+                .messageSender()
+                .send(Mono.just(message));
+    }
+}

+ 58 - 0
jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultReadPropertyMessageSender.java

@@ -0,0 +1,58 @@
+package org.jetlinks.core.defaults;
+
+import org.jetlinks.core.device.DeviceOperator;
+import org.jetlinks.core.message.ReadPropertyMessageSender;
+import org.jetlinks.core.message.property.ReadPropertyMessage;
+import org.jetlinks.core.message.property.ReadPropertyMessageReply;
+import org.jetlinks.core.utils.IdUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.function.Consumer;
+
+public class DefaultReadPropertyMessageSender implements ReadPropertyMessageSender {
+
+    private ReadPropertyMessage message = new ReadPropertyMessage();
+
+    private DeviceOperator operator;
+
+    public DefaultReadPropertyMessageSender(DeviceOperator operator) {
+        this.operator = operator;
+        message.setMessageId(IdUtils.newUUID());
+        message.setDeviceId(operator.getDeviceId());
+    }
+
+    @Override
+    public ReadPropertyMessageSender read(Collection<String> property) {
+        message.setProperties(new ArrayList<>(property));
+        return this;
+    }
+
+    @Override
+    public ReadPropertyMessageSender custom(Consumer<ReadPropertyMessage> messageConsumer) {
+        messageConsumer.accept(message);
+        return this;
+    }
+
+    @Override
+    public ReadPropertyMessageSender header(String header, Object value) {
+        message.addHeader(header, value);
+        return this;
+    }
+
+    @Override
+    public ReadPropertyMessageSender messageId(String messageId) {
+        message.setMessageId(messageId);
+        return this;
+    }
+
+    @Override
+    public Flux<ReadPropertyMessageReply> send() {
+
+        return operator
+                .messageSender()
+                .send(Mono.just(message));
+    }
+}

+ 72 - 0
jetlinks-core/src/main/java/org/jetlinks/core/defaults/DefaultWritePropertyMessageSender.java

@@ -0,0 +1,72 @@
+package org.jetlinks.core.defaults;
+
+import org.jetlinks.core.device.DeviceOperator;
+import org.jetlinks.core.message.WritePropertyMessageSender;
+import org.jetlinks.core.message.property.WritePropertyMessage;
+import org.jetlinks.core.message.property.WritePropertyMessageReply;
+import org.jetlinks.core.metadata.PropertyMetadata;
+import org.jetlinks.core.utils.IdUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class DefaultWritePropertyMessageSender implements WritePropertyMessageSender {
+
+    private final WritePropertyMessage message = new WritePropertyMessage();
+
+    private final DeviceOperator operator;
+
+    public DefaultWritePropertyMessageSender(DeviceOperator operator) {
+        this.operator = operator;
+        message.setMessageId(IdUtils.newUUID());
+        message.setDeviceId(operator.getDeviceId());
+    }
+
+    @Override
+    public WritePropertyMessageSender custom(Consumer<WritePropertyMessage> messageConsumer) {
+        messageConsumer.accept(message);
+        return this;
+    }
+
+    @Override
+    public WritePropertyMessageSender header(String header, Object value) {
+        message.addHeader(header, value);
+        return this;
+    }
+
+    @Override
+    public WritePropertyMessageSender messageId(String messageId) {
+        message.setMessageId(messageId);
+        return this;
+    }
+
+    @Override
+    public WritePropertyMessageSender write(String property, Object value) {
+        message.addProperty(property, value);
+        return this;
+    }
+
+    @Override
+    public Mono<WritePropertyMessageSender> validate() {
+        Map<String, Object> properties = message.getProperties();
+
+        return operator
+                .getMetadata()
+                .doOnNext(metadata -> {
+                    for (PropertyMetadata meta : metadata.getProperties()) {
+                        Object property = properties.get(meta.getId());
+                        if (property == null) {
+                            continue;
+                        }
+                        properties.put(meta.getId(), meta.getValueType().validate(property).assertSuccess());
+                    }
+                }).thenReturn(this);
+    }
+
+    @Override
+    public Flux<WritePropertyMessageReply> send() {
+        return operator.messageSender().send(Mono.just(message));
+    }
+}

+ 28 - 0
jetlinks-core/src/main/java/org/jetlinks/core/defaults/ExpandsConfigMetadataSupplier.java

@@ -0,0 +1,28 @@
+package org.jetlinks.core.defaults;
+
+import org.jetlinks.core.metadata.ConfigMetadata;
+import org.jetlinks.core.metadata.DeviceMetadataType;
+import reactor.core.publisher.Flux;
+
+public interface ExpandsConfigMetadataSupplier {
+
+
+    static StaticExpandsConfigMetadataSupplier create() {
+        return new StaticExpandsConfigMetadataSupplier();
+    }
+
+
+    /**
+     * 获取物模型拓展配置信息
+     *
+     * @param metadataType 物模型类型
+     * @param metadataId   物模型标识
+     * @param dataTypeId   数据类型ID
+     * @return 配置信息
+     */
+    Flux<ConfigMetadata> getConfigMetadata(DeviceMetadataType metadataType,
+                                           String metadataId,
+                                           String dataTypeId);
+
+
+}

+ 85 - 0
jetlinks-core/src/main/java/org/jetlinks/core/defaults/StaticExpandsConfigMetadataSupplier.java

@@ -0,0 +1,85 @@
+package org.jetlinks.core.defaults;
+
+import org.jetlinks.core.metadata.ConfigMetadata;
+import org.jetlinks.core.metadata.DeviceMetadataType;
+import reactor.core.publisher.Flux;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class StaticExpandsConfigMetadataSupplier implements ExpandsConfigMetadataSupplier {
+
+    private final Map<String, List<ConfigMetadata>> metadata = new ConcurrentHashMap<>();
+
+    private List<ConfigMetadata> getOrCreateConfigs(String id) {
+        return metadata.computeIfAbsent(id, ignore -> new ArrayList<>());
+    }
+
+    /**
+     * 添加配置,所有物模型都有此配置
+     *
+     * @param configMetadata 配置信息
+     * @return this
+     */
+    public StaticExpandsConfigMetadataSupplier addConfigMetadata(ConfigMetadata configMetadata) {
+        getOrCreateConfigs("any:any").add(configMetadata);
+        return this;
+    }
+
+    /**
+     * 添加通用配置,根据类型来指定配置
+     *
+     * @param typeId         类型ID
+     * @param configMetadata 配置
+     * @return this
+     */
+    public StaticExpandsConfigMetadataSupplier addConfigMetadata(String typeId,
+                                                                 ConfigMetadata configMetadata) {
+
+        getOrCreateConfigs(String.join(":", "any", typeId)).add(configMetadata);
+
+        return this;
+    }
+
+    /**
+     * 添加通用配置,指定都物模型都使用指定都配置
+     *
+     * @param metadataType   物模型类型
+     * @param configMetadata 配置
+     * @return this
+     */
+    public StaticExpandsConfigMetadataSupplier addConfigMetadata(DeviceMetadataType metadataType,
+                                                                 ConfigMetadata configMetadata) {
+
+        return addConfigMetadata(metadataType, "any", configMetadata);
+    }
+
+    /**
+     * 添加通用配置,指定都物模型以及数据类型使用指定的配置
+     *
+     * @param metadataType   物模型类型
+     * @param configMetadata 配置
+     * @return this
+     */
+    public StaticExpandsConfigMetadataSupplier addConfigMetadata(DeviceMetadataType metadataType,
+                                                                 String typeId,
+                                                                 ConfigMetadata configMetadata) {
+        getOrCreateConfigs(String.join(":", metadataType.name(), typeId)).add(configMetadata);
+        return this;
+    }
+
+    @Override
+    public Flux<ConfigMetadata> getConfigMetadata(DeviceMetadataType metadataType,
+                                                  String metadataId,
+                                                  String dataTypeId) {
+        return Flux.merge(
+                Flux.fromIterable(metadata.getOrDefault("any:any", Collections.emptyList())),
+                Flux.fromIterable(metadata.getOrDefault(String.join(":", "any", dataTypeId), Collections.emptyList())),
+                Flux.fromIterable(metadata.getOrDefault(String.join(":", metadataType.name(), "any"), Collections.emptyList())),
+                Flux.fromIterable(metadata.getOrDefault(String.join(":", metadataType.name(), dataTypeId), Collections.emptyList()))
+        );
+    }
+}

+ 9 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/AuthenticationRequest.java

@@ -0,0 +1,9 @@
+package org.jetlinks.core.device;
+
+import org.jetlinks.core.message.codec.Transport;
+
+import java.io.Serializable;
+
+public interface AuthenticationRequest extends Serializable {
+    Transport getTransport();
+}

+ 46 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/AuthenticationResponse.java

@@ -0,0 +1,46 @@
+package org.jetlinks.core.device;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * @author zhouhao
+ * @since 1.0.0
+ */
+@Getter
+@Setter
+@ToString
+public class AuthenticationResponse {
+    private boolean success;
+
+    private int code;
+
+    private String message;
+
+    private String deviceId;
+
+    public static AuthenticationResponse success() {
+        return success(null);
+    }
+
+
+    public static AuthenticationResponse success(String deviceId) {
+        AuthenticationResponse response = new AuthenticationResponse();
+        response.success = true;
+        response.code = 200;
+        response.message = "授权通过";
+        response.deviceId = deviceId;
+        return response;
+    }
+
+    public static AuthenticationResponse error(int code, String message) {
+        AuthenticationResponse response = new AuthenticationResponse();
+        response.success = false;
+        response.code = code;
+        response.message = message;
+        return response;
+    }
+
+
+}

+ 42 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/CompositeDeviceMessageSenderInterceptor.java

@@ -0,0 +1,42 @@
+package org.jetlinks.core.device;
+
+import org.jetlinks.core.message.DeviceMessage;
+import org.jetlinks.core.message.interceptor.DeviceMessageSenderInterceptor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class CompositeDeviceMessageSenderInterceptor implements DeviceMessageSenderInterceptor {
+    private final List<DeviceMessageSenderInterceptor> interceptors = new CopyOnWriteArrayList<>();
+
+    public void addInterceptor(DeviceMessageSenderInterceptor interceptor) {
+        interceptors.add(interceptor);
+        //重新排序
+        interceptors.sort(Comparator.comparingInt(DeviceMessageSenderInterceptor::getOrder));
+    }
+
+    @Override
+    public Mono<DeviceMessage> preSend(DeviceOperator device, DeviceMessage message) {
+        Mono<DeviceMessage> mono = Mono.just(message);
+
+        for (DeviceMessageSenderInterceptor interceptor : interceptors) {
+            mono = mono.flatMap(msg -> interceptor.preSend(device, msg));
+        }
+        return mono;
+    }
+
+    @Override
+    public <R extends DeviceMessage> Flux<R> afterSent(DeviceOperator device, DeviceMessage message, Flux<R> reply) {
+
+        Flux<R> flux = reply;
+
+        for (DeviceMessageSenderInterceptor interceptor : interceptors) {
+            flux = interceptor.afterSent(device, message, flux);
+        }
+        return flux;
+
+    }
+}

+ 47 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceConfigKey.java

@@ -0,0 +1,47 @@
+package org.jetlinks.core.device;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.jetlinks.core.config.ConfigKey;
+
+@AllArgsConstructor
+@Getter
+public enum DeviceConfigKey implements ConfigKey<String> {
+    id("ID"),
+
+    metadata("物模型"),
+
+    productId("产品ID"),
+
+    protocol("消息协议"),
+
+    parentGatewayId("上级网关设备ID"),
+
+    connectionServerId("当前设备连接的服务ID"),
+
+    sessionId("设备会话ID"),
+
+    shadow("设备影子"),
+
+    //遗言,用于缓存消息,等设备上线时发送指令
+    will("遗言");
+
+    String name;
+
+    public static ConfigKey<Boolean> isGatewayDevice = ConfigKey.of("isGatewayDevice", "是否为网关设备");
+
+    //通常用于子设备状态
+    public static ConfigKey<Boolean> selfManageState = ConfigKey.of("selfManageState", "状态自管理");
+
+    public static ConfigKey<Long> firstPropertyTime = ConfigKey.of("firstProperty", "首次上报属性的时间");
+
+    @Override
+    public String getKey() {
+        return name();
+    }
+
+    @Override
+    public Class<String> getType() {
+        return String.class;
+    }
+}

+ 79 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceInfo.java

@@ -0,0 +1,79 @@
+package org.jetlinks.core.device;
+
+import lombok.*;
+import org.jetlinks.core.ProtocolSupport;
+import org.jetlinks.core.config.ConfigKey;
+import org.jetlinks.core.config.ConfigKeyValue;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author zhouhao
+ * @since 1.0.0
+ */
+@Getter
+@Setter
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class DeviceInfo implements Serializable {
+    private static final long serialVersionUID = -6849794470754667710L;
+
+    /**
+     * 设备ID
+     */
+    private String id;
+
+    /**
+     * 产品-型号ID
+     */
+    private String productId;
+
+    /**
+     * 消息协议
+     *
+     * @see ProtocolSupport#getId()
+     */
+    private String protocol;
+
+    /**
+     * 物模型
+     */
+    private String metadata;
+
+    /**
+     * 其他配置
+     */
+    private Map<String, Object> configuration = new HashMap<>();
+
+    public DeviceInfo addConfig(String key, Object value) {
+//        if (value == null) {
+//            return this;
+//        }
+        if (configuration == null) {
+            configuration = new HashMap<>();
+        }
+        configuration.put(key, value);
+        return this;
+    }
+
+    public DeviceInfo addConfigs(Map<String, ?> configs) {
+        if (configs == null) {
+            return this;
+        }
+        configs.forEach(this::addConfig);
+        return this;
+    }
+
+    public <T> DeviceInfo addConfig(ConfigKey<T> key, T value) {
+        addConfig(key.getKey(), value);
+        return this;
+    }
+
+    public <T> DeviceInfo addConfig(ConfigKeyValue<T> keyValue) {
+        addConfig(keyValue.getKey(), keyValue.getValue());
+        return this;
+    }
+}

+ 106 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceMessageSender.java

@@ -0,0 +1,106 @@
+package org.jetlinks.core.device;
+
+import org.jetlinks.core.message.*;
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.function.Function;
+
+/**
+ * 消息发送器,用于发送消息给设备.
+ *
+ * @author zhouhao
+ * @since 1.0.0
+ */
+public interface DeviceMessageSender {
+
+    /**
+     * 发送一个支持回复的消息.
+     * <p>
+     * ⚠️: 请勿自己实现消息对象,而应该使用框架定义的3种消息.
+     * ⚠️: 如果是异步消息,将直接返回<code>{"success":true,"code":"REQUEST_HANDLING"}</code>
+     *
+     * @param message 具体的消息对象
+     * @param <R>     返回类型
+     * @return 异步发送结果
+     * @see org.jetlinks.core.message.property.ReadPropertyMessage
+     * @see org.jetlinks.core.message.property.ReadPropertyMessageReply
+     * @see org.jetlinks.core.message.property.WritePropertyMessage
+     * @see org.jetlinks.core.message.property.WritePropertyMessageReply
+     * @see org.jetlinks.core.message.function.FunctionInvokeMessage
+     * @see org.jetlinks.core.message.function.FunctionInvokeMessageReply
+     * @see org.jetlinks.core.enums.ErrorCode#CLIENT_OFFLINE
+     * @see org.jetlinks.core.enums.ErrorCode#REQUEST_HANDLING
+     * @see org.jetlinks.core.message.interceptor.DeviceMessageSenderInterceptor
+     */
+    <R extends DeviceMessageReply> Flux<R> send(Publisher<RepayableDeviceMessage<R>> message);
+
+    /**
+     * 发送消息并自定义返回结果转换器
+     *
+     * @param message      消息
+     * @param replyMapping 消息回复转换器
+     * @param <R>          回复类型
+     * @return 异步发送结果
+     * @see DeviceMessageSender#send(Publisher)
+     */
+    <R extends DeviceMessage> Flux<R> send(Publisher<? extends DeviceMessage> message, Function<Object, R> replyMapping);
+
+    /**
+     * 发送消息并获取返回
+     *
+     * @param message 消息
+     * @param <R>     回复类型
+     * @return 异步发送结果
+     * @see DeviceMessageSender#send(Publisher)
+     */
+    <R extends DeviceMessage> Flux<R> send(DeviceMessage message);
+
+    /**
+     * 发送消息后返回结果,不等待回复
+     *
+     * @param message 消息
+     * @return void
+     * @since 1.1.5
+     */
+    default Mono<Void> sendAndForget(DeviceMessage message) {
+        return this
+                .send(message.addHeader(Headers.async, true)
+                             .addHeader(Headers.sendAndForget, true))
+                .then();
+    }
+
+    /**
+     * 发送{@link org.jetlinks.core.message.function.FunctionInvokeMessage}消息更便捷的API
+     *
+     * @param function 要执行的功能
+     * @return FunctionInvokeMessageSender
+     * @see DeviceMessageSender#send(Publisher)
+     * @see org.jetlinks.core.message.function.FunctionInvokeMessage
+     * @see FunctionInvokeMessageSender
+     */
+    FunctionInvokeMessageSender invokeFunction(String function);
+
+    /**
+     * 发送{@link org.jetlinks.core.message.property.ReadPropertyMessage}消息更便捷的API
+     *
+     * @param property 要获取的属性列表
+     * @return ReadPropertyMessageSender
+     * @see DeviceMessageSender#send(Publisher)
+     * @see org.jetlinks.core.message.property.ReadPropertyMessage
+     * @see ReadPropertyMessageSender
+     */
+    ReadPropertyMessageSender readProperty(String... property);
+
+    /**
+     * 发送{@link org.jetlinks.core.message.property.WritePropertyMessage}消息更便捷的API
+     *
+     * @return WritePropertyMessageSender
+     * @see DeviceMessageSender#send(Publisher)
+     * @see org.jetlinks.core.message.property.WritePropertyMessage
+     * @see WritePropertyMessageSender
+     */
+    WritePropertyMessageSender writeProperty();
+
+}

+ 59 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceOperationBroker.java

@@ -0,0 +1,59 @@
+package org.jetlinks.core.device;
+
+import org.jetlinks.core.cluster.ServerNode;
+import org.jetlinks.core.message.BroadcastMessage;
+import org.jetlinks.core.message.DeviceMessageReply;
+import org.jetlinks.core.message.Message;
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.util.Collection;
+
+/**
+ * 设备操作代理,用于管理集群间设备指令发送
+ *
+ * @author zhouhao
+ * @since 1.0
+ */
+public interface DeviceOperationBroker {
+
+    /**
+     * 获取指定服务里设备状态
+     *
+     * @param deviceGatewayServerId 设备所在服务ID {@link ServerNode#getId()}
+     * @param deviceIdList          设备列表
+     * @return 设备状态
+     * @see DeviceOperator#getConnectionServerId()
+     */
+    Flux<DeviceStateInfo> getDeviceState(String deviceGatewayServerId, Collection<String> deviceIdList);
+
+    /**
+     * 根据消息ID监听响应
+     *
+     * @param deviceId  设备ID
+     * @param messageId 消息ID
+     * @param timeout   超时时间
+     * @return 消息返回
+     */
+    Flux<DeviceMessageReply> handleReply(String deviceId, String messageId, Duration timeout);
+
+    /**
+     * 发送设备消息到指定到服务
+     *
+     * @param deviceGatewayServerId 设备所在服务ID {@link ServerNode#getId()}
+     * @return 有多少服务收到了此消息
+     * @see DeviceOperator#getConnectionServerId()
+     */
+    Mono<Integer> send(String deviceGatewayServerId, Publisher<? extends Message> message);
+
+    /**
+     * 发送广播消息
+     *
+     * @param message 广播消息
+     * @return 有多少服务收到了此消息
+     */
+    Mono<Integer> send(Publisher<? extends BroadcastMessage> message);
+
+}

+ 176 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceOperator.java

@@ -0,0 +1,176 @@
+package org.jetlinks.core.device;
+
+
+import org.jetlinks.core.Configurable;
+import org.jetlinks.core.ProtocolSupport;
+import org.jetlinks.core.Value;
+import org.jetlinks.core.Values;
+import org.jetlinks.core.config.ConfigKey;
+import org.jetlinks.core.metadata.DeviceMetadata;
+import reactor.core.publisher.Mono;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+/**
+ * 设备操作接口
+ *
+ * @author zhouhao
+ * @since 1.0.0
+ */
+public interface DeviceOperator extends Configurable {
+
+    /**
+     * @return 设备ID
+     */
+    String getDeviceId();
+
+    /**
+     * @return 当前设备连接所在服务器ID,如果设备未上线 {@link DeviceState#online},则返回<code>null</code>
+     */
+    Mono<String> getConnectionServerId();
+
+    /**
+     * @return 当前设备连接会话ID
+     */
+    Mono<String> getSessionId();
+
+    /**
+     * 获取设备地址,通常是ip地址.
+     *
+     * @return 地址
+     */
+    Mono<String> getAddress();
+
+    /**
+     * 设置设备地址
+     *
+     * @param address 地址
+     * @return Mono
+     */
+    Mono<Void> setAddress(String address);
+
+    /**
+     * @param state 状态
+     * @see DeviceState#online
+     */
+    Mono<Boolean> putState(byte state);
+
+    /**
+     * @return 获取当前状态
+     * @see DeviceState
+     */
+    Mono<Byte> getState();
+
+    /**
+     * 检查设备的真实状态
+     */
+    Mono<Byte> checkState();
+
+    /**
+     * @return 上线时间
+     */
+    Mono<Long> getOnlineTime();
+
+    /**
+     * @return 离线时间
+     */
+    Mono<Long> getOfflineTime();
+
+    /**
+     * 设备上线
+     *
+     * @param serverId  设备所在服务ID
+     * @param sessionId 会话ID
+     */
+    default Mono<Boolean> online(String serverId, String sessionId) {
+        return online(serverId, sessionId, null);
+    }
+
+    Mono<Boolean> online(String serverId, String sessionId, String address);
+
+    Mono<Value> getSelfConfig(String key);
+
+    Mono<Values> getSelfConfigs(Collection<String> keys);
+
+    default Mono<Values> getSelfConfigs(String... keys) {
+        return getSelfConfigs(Arrays.asList(keys));
+    }
+
+    default <V> Mono<V> getSelfConfig(ConfigKey<V> key) {
+        return getSelfConfig(key.getKey())
+                .map(value -> value.as(key.getType()));
+    }
+
+    default Mono<Values> getSelfConfigs(ConfigKey<?>... keys) {
+        return getSelfConfigs(Arrays.stream(keys).map(ConfigKey::getKey).collect(Collectors.toSet()));
+    }
+
+    /**
+     * @return 是否在线
+     */
+    default Mono<Boolean> isOnline() {
+        return checkState()
+                .map(state -> state.equals(DeviceState.online))
+                .defaultIfEmpty(false);
+    }
+
+    /**
+     * 设置设备离线
+     *
+     * @see DeviceState#offline
+     */
+    Mono<Boolean> offline();
+
+    /**
+     * 断开设备连接
+     *
+     * @return 断开结果
+     */
+    Mono<Boolean> disconnect();
+
+    /**
+     * 进行授权
+     *
+     * @param request 授权请求
+     * @return 授权结果
+     * @see MqttAuthenticationRequest
+     */
+    Mono<AuthenticationResponse> authenticate(AuthenticationRequest request);
+
+    /**
+     * @return 获取设备的元数据
+     */
+    Mono<DeviceMetadata> getMetadata();
+
+    /**
+     * @return 获取此设备使用的协议支持
+     */
+    Mono<ProtocolSupport> getProtocol();
+
+    /**
+     * @return 消息发送器, 用于发送消息给设备
+     */
+    DeviceMessageSender messageSender();
+
+    /**
+     * 设置当前设备的独立物模型,如果没有设置,这使用产品的物模型配置
+     *
+     * @param metadata 物模型
+     */
+    Mono<Boolean> updateMetadata(String metadata);
+
+    /**
+     * 重置当前设备的独立物模型
+     *
+     * @return void
+     * @since 1.1.6
+     */
+    Mono<Void> resetMetadata();
+
+    /**
+     * @return 获取设备对应的产品操作接口
+     */
+    Mono<DeviceProductOperator> getProduct();
+}

+ 40 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceProductOperator.java

@@ -0,0 +1,40 @@
+package org.jetlinks.core.device;
+
+import org.jetlinks.core.Configurable;
+import org.jetlinks.core.ProtocolSupport;
+import org.jetlinks.core.metadata.DeviceMetadata;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * 设备产品型号操作
+ *
+ * @author zhouhao
+ * @since 1.0.0
+ */
+public interface DeviceProductOperator extends Configurable {
+
+    String getId();
+
+    /**
+     * @return 设备产品物模型
+     */
+    Mono<DeviceMetadata> getMetadata();
+
+    /**
+     * 更新设备型号元数据信息
+     *
+     * @param metadata 元数据信息
+     */
+    Mono<Boolean> updateMetadata(String metadata);
+
+    /**
+     * @return 获取协议支持
+     */
+    Mono<ProtocolSupport> getProtocol();
+
+    /**
+     * @return 获取产品下的所有设备
+     */
+    Flux<DeviceOperator> getDevices();
+}

+ 76 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceRegistry.java

@@ -0,0 +1,76 @@
+package org.jetlinks.core.device;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Collection;
+
+/**
+ * 设备注册中心
+ *
+ * @author zhouhao
+ * @since 1.0.0
+ */
+public interface DeviceRegistry {
+
+    /**
+     * 获取设备操作接口.
+     *
+     * @param deviceId 设备ID
+     * @return 设备操作接口
+     */
+    Mono<DeviceOperator> getDevice(String deviceId);
+
+    /**
+     * 批量检查设备真实状态
+     *
+     * @param id ID
+     * @return 设备状态信息流
+     */
+    default Flux<DeviceStateInfo> checkDeviceState(Flux<? extends Collection<String>> id) {
+        return Flux.error(new UnsupportedOperationException());
+    }
+
+    /**
+     * 获取设备产品操作,请勿缓存返回值,注册中心已经实现本地缓存.
+     *
+     * @param productId 产品ID
+     * @return 设备操作接口
+     */
+    Mono<DeviceProductOperator> getProduct(String productId);
+
+    /**
+     * 注册设备,并返回设备操作接口,请勿缓存返回值,注册中心已经实现本地缓存.
+     *
+     * @param deviceInfo 设备基础信息
+     * @return 设备操作接口
+     * @see DeviceRegistry#getDevice(String)
+     */
+    Mono<DeviceOperator> register(DeviceInfo deviceInfo);
+
+    /**
+     * 注册产品(型号)信息
+     *
+     * @param productInfo 产品(型号)信息
+     * @return 注册结果
+     */
+    Mono<DeviceProductOperator> register(ProductInfo productInfo);
+
+    /**
+     * 注销设备
+     *
+     * @param deviceId 设备ID
+     * @return void
+     */
+    Mono<Void> unregisterDevice(String deviceId);
+
+    /**
+     * 注销产品型号
+     *
+     * @param productId 产品型号ID
+     * @return void
+     */
+    Mono<Void> unregisterProduct(String productId);
+
+
+}

+ 24 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceState.java

@@ -0,0 +1,24 @@
+package org.jetlinks.core.device;
+
+/**
+ * @author zhouhao
+ * @since 1.0.0
+ */
+public interface DeviceState {
+
+    //未知
+    byte unknown = 0;
+
+    //在线
+    byte online = 1;
+
+    //未激活
+    byte noActive = -3;
+
+    //离线
+    byte offline = -1;
+
+    //检查状态超时
+    byte timeout = -2;
+
+}

+ 33 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceStateChecker.java

@@ -0,0 +1,33 @@
+package org.jetlinks.core.device;
+
+import reactor.core.publisher.Mono;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 设备状态检查器,用于自定义设备状态检查
+ *
+ * @since 1.0.2
+ */
+public interface DeviceStateChecker {
+
+    /**
+     * 检查设备状态
+     *
+     * @param device 设备操作接口
+     * @return 设备状态 {@link DeviceState}
+     * @see DeviceState
+     */
+    @NotNull
+    Mono<Byte> checkState(@NotNull DeviceOperator device);
+
+    /**
+     * 排序需要,值越小优先级越高
+     *
+     * @return 序号
+     * @since 1.1.5
+     */
+    default long order() {
+        return Long.MAX_VALUE;
+    }
+}

+ 18 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/DeviceStateInfo.java

@@ -0,0 +1,18 @@
+package org.jetlinks.core.device;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class DeviceStateInfo implements Serializable {
+    private String deviceId;
+
+    private byte state;
+}

+ 25 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/MqttAuthenticationRequest.java

@@ -0,0 +1,25 @@
+package org.jetlinks.core.device;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.jetlinks.core.message.codec.Transport;
+
+/**
+ * @author zhouhao
+ * @since 1.0.0
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class MqttAuthenticationRequest implements AuthenticationRequest {
+    private String clientId;
+
+    private String username;
+
+    private String password;
+
+    private Transport transport;
+}

+ 78 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/ProductInfo.java

@@ -0,0 +1,78 @@
+package org.jetlinks.core.device;
+
+import lombok.*;
+import org.jetlinks.core.config.ConfigKey;
+import org.jetlinks.core.config.ConfigKeyValue;
+import org.springframework.util.StringUtils;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author zhouhao
+ * @since 1.0.0
+ */
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductInfo implements Serializable {
+    private static final long serialVersionUID = -6849794470754667710L;
+
+    /**
+     * 设备ID
+     */
+    private String id;
+
+    /**
+     * 消息协议
+     */
+    private String protocol;
+
+    /**
+     * 元数据
+     */
+    private String metadata;
+
+    /**
+     * 其他配置
+     */
+    private Map<String, Object> configuration = new HashMap<>();
+
+    public ProductInfo(String id, String protocol, String metadata) {
+        this.id = id;
+        this.protocol = protocol;
+        this.metadata = metadata;
+    }
+
+    public ProductInfo addConfig(String key, Object value) {
+        if (StringUtils.isEmpty(value)) {
+            return this;
+        }
+        if (configuration == null) {
+            configuration = new HashMap<>();
+        }
+        configuration.put(key, value);
+        return this;
+    }
+
+    public ProductInfo addConfigs(Map<String, ?> configs) {
+        if (configs == null) {
+            return this;
+        }
+        configs.forEach(this::addConfig);
+        return this;
+    }
+
+    public <T> ProductInfo addConfig(ConfigKey<T> key, T value) {
+        addConfig(key.getKey(), value);
+        return this;
+    }
+
+    public <T> ProductInfo addConfig(ConfigKeyValue<T> keyValue) {
+        addConfig(keyValue.getKey(), keyValue.getValue());
+        return this;
+    }
+}

+ 8 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/ReplyFailureHandler.java

@@ -0,0 +1,8 @@
+package org.jetlinks.core.device;
+
+import org.jetlinks.core.message.DeviceMessageReply;
+
+public interface ReplyFailureHandler {
+
+    void handle(Throwable err, DeviceMessageReply message);
+}

+ 139 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/StandaloneDeviceMessageBroker.java

@@ -0,0 +1,139 @@
+package org.jetlinks.core.device;
+
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.core.enums.ErrorCode;
+import org.jetlinks.core.exception.DeviceOperationException;
+import org.jetlinks.core.message.BroadcastMessage;
+import org.jetlinks.core.message.DeviceMessageReply;
+import org.jetlinks.core.message.Headers;
+import org.jetlinks.core.message.Message;
+import org.jetlinks.core.server.MessageHandler;
+import org.reactivestreams.Publisher;
+import org.springframework.util.StringUtils;
+import reactor.core.Disposable;
+import reactor.core.publisher.*;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+
+@Slf4j
+public class StandaloneDeviceMessageBroker implements DeviceOperationBroker, MessageHandler {
+
+    private final FluxProcessor<Message, Message> messageEmitterProcessor;
+
+
+    private final FluxSink<Message> sink;
+
+    private final Map<String, FluxProcessor<DeviceMessageReply, DeviceMessageReply>> replyProcessor = new ConcurrentHashMap<>();
+
+    private final Map<String, AtomicInteger> partCache = new ConcurrentHashMap<>();
+
+    @Setter
+    private ReplyFailureHandler replyFailureHandler = (error, message) -> StandaloneDeviceMessageBroker.log.warn("unhandled reply message:{}", message, error);
+
+    private final Map<String, Function<Publisher<String>, Flux<DeviceStateInfo>>> stateHandler = new ConcurrentHashMap<>();
+
+    public StandaloneDeviceMessageBroker() {
+        this(EmitterProcessor.create(false));
+
+    }
+
+    public StandaloneDeviceMessageBroker(FluxProcessor<Message, Message> processor) {
+        this.messageEmitterProcessor = processor;
+        this.sink=processor.sink(FluxSink.OverflowStrategy.BUFFER);
+    }
+
+    @Override
+    public Flux<Message> handleSendToDeviceMessage(String serverId) {
+        return messageEmitterProcessor
+                .map(Function.identity());
+    }
+
+    @Override
+    public Disposable handleGetDeviceState(String serverId, Function<Publisher<String>, Flux<DeviceStateInfo>> stateMapper) {
+        stateHandler.put(serverId, stateMapper);
+        return ()->stateHandler.remove(serverId);
+    }
+
+    @Override
+    public Flux<DeviceStateInfo> getDeviceState(String serviceId, Collection<String> deviceIdList) {
+        return Mono.justOrEmpty(stateHandler.get(serviceId))
+                .flatMapMany(fun -> fun.apply(Flux.fromIterable(deviceIdList)));
+    }
+
+    @Override
+    public Mono<Boolean> reply(DeviceMessageReply message) {
+        return Mono.defer(() -> {
+
+            String messageId = message.getMessageId();
+            if (StringUtils.isEmpty(messageId)) {
+                log.warn("reply message messageId is empty: {}", message);
+                return Mono.just(false);
+            }
+
+            String partMsgId = message.getHeader(Headers.fragmentBodyMessageId).orElse(null);
+            if (partMsgId != null) {
+                FluxProcessor<DeviceMessageReply, DeviceMessageReply> processor = replyProcessor.getOrDefault(partMsgId, replyProcessor.get(messageId));
+
+                if (processor == null || processor.isDisposed()) {
+                    replyFailureHandler.handle(new NullPointerException("no reply handler"), message);
+                    replyProcessor.remove(partMsgId);
+                    return Mono.just(false);
+                }
+                int partTotal = message.getHeader(Headers.fragmentNumber).orElse(1);
+                AtomicInteger counter = partCache.computeIfAbsent(partMsgId, ignore -> new AtomicInteger(partTotal));
+
+                processor.onNext(message);
+                if (counter.decrementAndGet() <= 0) {
+                    processor.onComplete();
+                    replyProcessor.remove(partMsgId);
+                }
+                return Mono.just(true);
+            }
+            FluxProcessor<DeviceMessageReply, DeviceMessageReply> processor = replyProcessor.get(messageId);
+
+            if (processor != null && !processor.isDisposed()) {
+                processor.onNext(message);
+                processor.onComplete();
+            } else {
+                replyProcessor.remove(messageId);
+                replyFailureHandler.handle(new NullPointerException("no reply handler"), message);
+                return Mono.just(false);
+            }
+            return Mono.just(true);
+        }).doOnError(err -> replyFailureHandler.handle(err, message));
+    }
+
+    @Override
+    public Flux<DeviceMessageReply> handleReply(String deviceId,String messageId, Duration timeout) {
+
+        return replyProcessor
+                .computeIfAbsent(messageId, ignore -> UnicastProcessor.create())
+                .timeout(timeout, Mono.error(() -> new DeviceOperationException(ErrorCode.TIME_OUT)))
+                .doFinally(signal -> replyProcessor.remove(messageId));
+    }
+
+    @Override
+    public Mono<Integer> send(String serverId, Publisher<? extends Message> message) {
+        if (!messageEmitterProcessor.hasDownstreams()) {
+            return Mono.just(0);
+        }
+
+        return Flux.from(message)
+                .doOnNext(sink::next)
+                .then(Mono.just(Long.valueOf(messageEmitterProcessor.downstreamCount()).intValue()));
+    }
+
+    @Override
+    public Mono<Integer> send(Publisher<? extends BroadcastMessage> message) {
+        // TODO: 2019-10-19 发送广播消息
+        return Mono.just(0);
+    }
+
+
+}

+ 16 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/manager/BindInfo.java

@@ -0,0 +1,16 @@
+package org.jetlinks.core.device.manager;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+public class BindInfo {
+    private String key;
+    private String deviceId;
+    private String description;
+}

+ 30 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/manager/DeviceBindHolder.java

@@ -0,0 +1,30 @@
+package org.jetlinks.core.device.manager;
+
+import reactor.core.Disposable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+public final class DeviceBindHolder {
+
+    private static final Map<String, DeviceBindProvider> suppliers = new ConcurrentHashMap<>();
+
+    public static void addSupplier(DeviceBindProvider supplier) {
+        DeviceBindProvider old = suppliers.put(supplier.getId(), supplier);
+        if (old instanceof Disposable) {
+            ((Disposable) old).dispose();
+        }
+    }
+
+    public static Optional<DeviceBindProvider> lookup(String id) {
+        return Optional.ofNullable(suppliers.get(id));
+    }
+
+    public static List<DeviceBindProvider> getAllProvider(){
+        return new ArrayList<>(suppliers.values());
+    }
+
+}

+ 133 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/manager/DeviceBindManager.java

@@ -0,0 +1,133 @@
+package org.jetlinks.core.device.manager;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Nonnull;
+import java.util.Collection;
+
+/**
+ * 设备绑定管理器,通常用于将设备ID与第三方平台进行绑定等操作
+ *
+ * @author zhouhao
+ * @since 1.1.3
+ */
+public interface DeviceBindManager {
+
+    /**
+     * 绑定设备,类型与key组合成唯一键
+     *
+     * @param type     类型 {@link DeviceBindProvider#getId()}
+     * @param key      绑定key
+     * @param deviceId 平台的设备ID
+     * @return void
+     */
+    default Mono<Void> bind(@Nonnull String type,
+                            @Nonnull String key,
+                            @Nonnull String deviceId) {
+        return bind(type, key, deviceId, null);
+    }
+
+    /**
+     * 绑定设备,类型与key组合成唯一键
+     *
+     * @param type        类型 {@link DeviceBindProvider#getId()}
+     * @param key         绑定key
+     * @param deviceId    平台的设备ID
+     * @param description 说明
+     * @return void
+     */
+    Mono<Void> bind(@Nonnull String type,
+                    @Nonnull String key,
+                    @Nonnull String deviceId,
+                    String description);
+
+    /**
+     * 批量绑定设备
+     *
+     * @param type      类型
+     * @param bindInfos 绑定信息
+     * @return void
+     * @since 1.1.4
+     */
+    default Mono<Void> bindBatch(@Nonnull String type, Collection<BindInfo> bindInfos) {
+        return Flux
+                .fromIterable(bindInfos)
+                .flatMap(bindInfo -> bind(type, bindInfo.getKey(), bindInfo.getDeviceId(), bindInfo.getDescription()))
+                .then();
+    }
+
+    /**
+     * 解绑设备
+     *
+     * @param type 类型
+     * @param key  绑定key
+     * @return void
+     */
+    Mono<Void> unbind(@Nonnull String type,
+                      @Nonnull String key);
+
+    /**
+     * 按设备id解绑
+     *
+     * @param type     类型
+     * @param deviceId 设备ID
+     * @return void
+     */
+    Mono<Void> unbindByDevice(@Nonnull String type,
+                              @Nonnull Collection<String> deviceId);
+
+    /**
+     * 根据key获取设备ID
+     *
+     * @param type 类型
+     * @param key  绑定key
+     * @return deviceId
+     */
+    Mono<BindInfo> getBindInfo(@Nonnull String type,
+                               @Nonnull String key);
+
+    /**
+     * 根据deviceId获取绑定信息
+     *
+     * @param type     类型
+     * @param deviceId deviceId
+     * @return deviceId
+     */
+    default Mono<BindInfo> getBindInfoByDeviceId(@Nonnull String type,
+                                                 @Nonnull String deviceId) {
+        return Mono.error(new UnsupportedOperationException());
+    }
+
+    /**
+     * 根据deviceId获取绑定信息
+     *
+     * @param type     类型
+     * @param deviceId deviceId
+     * @return deviceId
+     */
+    default Flux<BindInfo> getBindInfoByDeviceId(@Nonnull String type,
+                                                 @Nonnull Collection<String> deviceId) {
+        return Flux.error(new UnsupportedOperationException());
+    }
+
+
+    /**
+     * 获取指定key对应的绑定信息
+     *
+     * @param type 类型
+     * @return 绑定信息
+     */
+    Flux<BindInfo> getBindInfo(@Nonnull String type,
+                               @Nonnull Collection<String> keys);
+
+    /**
+     * 获取类型下所有的绑定信息
+     *
+     * @param type 类型
+     * @return 绑定信息s
+     */
+    Flux<BindInfo> getBindInfo(@Nonnull String type);
+
+
+}

+ 23 - 0
jetlinks-core/src/main/java/org/jetlinks/core/device/manager/DeviceBindProvider.java

@@ -0,0 +1,23 @@
+package org.jetlinks.core.device.manager;
+
+/**
+ * 设备绑定提供者,通常用于标识绑定的是哪个第三方平台
+ *
+ * @author zhouhao
+ * @since 1.1.4
+ */
+public interface DeviceBindProvider {
+    /**
+     * 绑定标识,通常就是{@link BindInfo#getKey()}
+     *
+     * @return 绑定标识
+     */
+    String getId();
+
+    /**
+     * 绑定名称
+     *
+     * @return 绑定名称
+     */
+    String getName();
+}

+ 47 - 0
jetlinks-core/src/main/java/org/jetlinks/core/enums/ErrorCode.java

@@ -0,0 +1,47 @@
+package org.jetlinks.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Optional;
+
+/**
+ * @author bsetfeng
+ * @author zhouhao
+ * @version 1.0
+ **/
+@Getter
+@AllArgsConstructor
+public enum ErrorCode {
+    /* 设备消息相关*/
+    REQUEST_HANDLING("请求处理中"),
+    CLIENT_OFFLINE("设备未在线"),
+    CONNECTION_LOST("设备连接已丢失"),
+    NO_REPLY("设备未回复"),
+    TIME_OUT("超时"),
+    SYSTEM_ERROR("系统错误"),
+    UNSUPPORTED_MESSAGE("不支持的操作"),
+    PARAMETER_ERROR("参数错误"),
+    PARAMETER_UNDEFINED("参数未定义"),
+    FUNCTION_UNDEFINED("功能未定义"),
+    PROPERTY_UNDEFINED("属性未定义"),
+    UNKNOWN_PARENT_DEVICE("未知的父设备"),
+    CYCLIC_DEPENDENCE("循环依赖"),
+    SERVER_NOT_AVAILABLE("服务不可用"),
+    UNKNOWN("未知错误");
+
+    private final String text;
+
+    public static Optional<ErrorCode> of(String code) {
+        if (code == null) {
+            return Optional.empty();
+        }
+        for (ErrorCode value : values()) {
+            if (value.name().equalsIgnoreCase(code)) {
+                return Optional.of(value);
+            }
+        }
+        return Optional.empty();
+    }
+
+}

+ 146 - 0
jetlinks-core/src/main/java/org/jetlinks/core/event/EventBus.java

@@ -0,0 +1,146 @@
+package org.jetlinks.core.event;
+
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Codecs;
+import org.jetlinks.core.codec.Decoder;
+import org.jetlinks.core.codec.Encoder;
+import org.jetlinks.core.codec.defaults.DirectCodec;
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Scheduler;
+
+/**
+ * 基于订阅发布的事件总线,可用于事件传递,消息转发等.
+ *
+ * @author zhouhao
+ * @see org.jetlinks.core.topic.Topic
+ * @since 1.1
+ */
+public interface EventBus {
+
+    /**
+     * 从事件总线中订阅事件
+     * <p>
+     * 特别注意!!!
+     * <p>
+     * 如果没有调用
+     * {@link TopicPayload#bodyToString()},
+     * {@link TopicPayload#bodyToJson()},
+     * {@link TopicPayload#bodyToJsonArray()},
+     * {@link TopicPayload#getBytes()}
+     * 使用TopicPayload后需要手动调用{@link TopicPayload#release()}释放.
+     *
+     * @param subscription 订阅信息
+     * @return 事件流
+     */
+    Flux<TopicPayload> subscribe(Subscription subscription);
+
+    /**
+     * 从事件总线中订阅事件,并按照指定的解码器进行数据转换
+     *
+     * @param subscription 订阅信息
+     * @param decoder      解码器
+     * @param <T>          解码后结果类型
+     * @return 事件流
+     */
+    <T> Flux<T> subscribe(Subscription subscription, Decoder<T> decoder);
+
+    /**
+     * 推送消息流到事件总线,并返回有多少订阅者订阅了此topic,默认自动根据元素类型进行序列化
+     *
+     * @param topic topic
+     * @param event 事件流
+     * @param <T>   事件流元素类型
+     * @return 订阅者数量
+     */
+    <T> Mono<Long> publish(String topic, Publisher<T> event);
+
+
+    /**
+     * 推送消息流,并指定编码器用于进行事件序列化
+     *
+     * @param topic       topic
+     * @param encoder     编码器
+     * @param eventStream 事件流
+     * @param <T>         类型
+     * @return 订阅者数量
+     */
+    <T> Mono<Long> publish(String topic, Encoder<T> encoder, Publisher<? extends T> eventStream);
+
+    /**
+     * 推送消息流,并指定编码器用于进行事件序列化
+     *
+     * @param topic       topic
+     * @param encoder     编码器
+     * @param eventStream 事件流
+     * @param scheduler   调度器
+     * @param <T>         void
+     * @return 订阅者数量
+     */
+    <T> Mono<Long> publish(String topic,
+                           Encoder<T> encoder,
+                           Publisher<? extends T> eventStream,
+                           Scheduler scheduler);
+
+    /**
+     * 订阅主题并将事件数据转换为指定的类型
+     *
+     * @param subscription 订阅信息
+     * @param type         类型
+     * @param <T>          类型
+     * @return 事件流
+     */
+    default <T> Flux<T> subscribe(Subscription subscription, Class<T> type) {
+        return subscribe(subscription, Codecs.lookup(type));
+    }
+
+    /**
+     * 推送单个数据到事件总线中,并指定编码器用于将事件数据进行序列化
+     *
+     * @param topic   主题
+     * @param encoder 编码器
+     * @param event   事件数据
+     * @param <T>     事件类型
+     * @return 订阅者数量
+     */
+    default <T> Mono<Long> publish(String topic, Encoder<T> encoder, T event) {
+        return publish(topic, encoder, Mono.just(event));
+    }
+
+    default <T> Mono<Long> publish(String topic, Encoder<T> encoder, T event, Scheduler scheduler) {
+        return publish(topic, encoder, Mono.just(event), scheduler);
+    }
+
+    /**
+     * 推送单个数据到事件流中,默认自动根据事件类型进行序列化
+     *
+     * @param topic 主题
+     * @param event 事件数据
+     * @param <T>   事件类型
+     * @return 订阅者数量
+     */
+    @SuppressWarnings("all")
+    default <T> Mono<Long> publish(String topic, T event) {
+        if (event instanceof Payload) {
+            return publish(topic, ((Payload) event));
+        }
+        return publish(topic, Codecs.lookup(event.getClass()), event);
+    }
+
+    default <T> Mono<Long> publish(String topic, T event, Scheduler scheduler) {
+        if (event instanceof Payload) {
+            return publish(topic, ((Payload) event), scheduler);
+        }
+        return publish(topic, Codecs.lookup(event.getClass()), event, scheduler);
+    }
+
+
+    default Mono<Long> publish(String topic, Payload event) {
+        return publish(topic, DirectCodec.INSTANCE, event);
+    }
+
+    default Mono<Long> publish(String topic, Payload event, Scheduler scheduler) {
+        return publish(topic, DirectCodec.INSTANCE, event, scheduler);
+    }
+}

+ 172 - 0
jetlinks-core/src/main/java/org/jetlinks/core/event/Subscription.java

@@ -0,0 +1,172 @@
+package org.jetlinks.core.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.hswebframework.web.dict.Dict;
+import org.hswebframework.web.dict.EnumDict;
+import org.jetlinks.core.utils.TopicUtils;
+import org.springframework.util.Assert;
+
+import java.io.Serializable;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@AllArgsConstructor
+@Getter
+public class Subscription implements Serializable {
+    private static final long serialVersionUID = -6849794470754667710L;
+
+    public static final Feature[] DEFAULT_FEATURES = Subscription.Feature.values();
+
+    //订阅者标识
+    private final String subscriber;
+
+    //订阅主题,主题以/分割,如: /device/TS-01/09012/message 支持通配符 /device/**
+    private final String[] topics;
+
+    //订阅特性
+    private final Feature[] features;
+
+    private Runnable doOnSubscribe;
+
+    public static Subscription of(String subscriber, String... topic) {
+
+        return Subscription
+                .builder()
+                .subscriberId(subscriber)
+                .topics(topic)
+                .build();
+//        return new Subscription(subscriber, topic, DEFAULT_FEATURES, null);
+    }
+
+    public static Subscription of(String subscriber, String[] topic, Feature... features) {
+        return Subscription
+                .builder()
+                .subscriberId(subscriber)
+                .topics(topic)
+                .features(features)
+                .build();
+    }
+
+    public static Subscription of(String subscriber, String topic, Feature... features) {
+        return Subscription
+                .builder()
+                .subscriberId(subscriber)
+                .topics(topic)
+                .features(features)
+                .build();
+        //return new Subscription(subscriber, new String[]{topic}, features, null);
+    }
+
+    public Subscription copy(Feature... newFeatures) {
+        return new Subscription(subscriber, topics, newFeatures, null);
+    }
+
+    public Subscription onSubscribe(Runnable sub) {
+        this.doOnSubscribe = sub;
+        return this;
+    }
+
+    public boolean hasFeature(Subscription.Feature feature) {
+        return feature.in(this.features);
+    }
+
+    @AllArgsConstructor
+    @Getter
+    @Dict("subscription-feature")
+    public enum Feature implements EnumDict<String> {
+
+        //如果相同的订阅者,只有一个订阅者收到消息
+        shared("shared"),
+        //订阅本地消息
+        local("订阅本地消息"),
+        //订阅来自代理的消息
+        broker("订阅代理消息");
+
+        private final String text;
+
+        @Override
+        public String getValue() {
+            return name();
+        }
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        //订阅者标识
+        private String subscriber;
+
+        //订阅主题,主题以/分割,如: /device/TS-01/09012/message 支持通配符 /device/**
+        private final Set<String> topics = new HashSet<>();
+
+        //订阅特性
+        private final Set<Feature> features = new HashSet<>();
+
+        private Runnable doOnSubscribe;
+
+        public Builder randomSubscriberId() {
+            return subscriberId(UUID.randomUUID().toString());
+        }
+
+        public Builder subscriberId(String id) {
+            this.subscriber = id;
+            return this;
+        }
+
+        public Builder topics(String... topics) {
+            return topics(Arrays.asList(topics));
+        }
+
+        public Builder topics(Collection<String> topics) {
+            this.topics.addAll(topics.stream()
+                                     .flatMap(topic -> TopicUtils.expand(topic).stream())
+                                     .collect(Collectors.toSet()));
+            return this;
+        }
+
+        public Builder features(Feature... features) {
+            this.features.addAll(Arrays.asList(features));
+            return this;
+        }
+
+        public Builder doOnSubscribe(Runnable runnable) {
+            this.doOnSubscribe = runnable;
+            return this;
+        }
+
+        public Builder justLocal() {
+            this.features.clear();
+            return features(Feature.local);
+        }
+
+        public Builder justBroker() {
+            this.features.clear();
+            return features(Feature.broker);
+        }
+
+        public Builder local() {
+            return features(Feature.local);
+        }
+
+        public Builder broker() {
+            return features(Feature.broker);
+        }
+
+        public Builder shared() {
+            return features(Feature.shared);
+        }
+
+        public Subscription build() {
+            if (features.isEmpty()) {
+                local();
+            }
+            Assert.notEmpty(topics, "topic cannot be empty");
+            Assert.hasText(subscriber, "subscriber cannot be empty");
+            return new Subscription(subscriber, topics.toArray(new String[0]), features.toArray(new Feature[0]), doOnSubscribe);
+        }
+
+    }
+}

+ 185 - 0
jetlinks-core/src/main/java/org/jetlinks/core/event/TopicPayload.java

@@ -0,0 +1,185 @@
+package org.jetlinks.core.event;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import io.netty.buffer.ByteBuf;
+import io.netty.util.Recycler;
+import io.netty.util.ReferenceCountUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.core.Payload;
+import org.jetlinks.core.codec.Decoder;
+import org.jetlinks.core.utils.RecyclerUtils;
+import org.jetlinks.core.utils.TopicUtils;
+
+import javax.annotation.Nonnull;
+import java.util.Map;
+
+@Getter
+@AllArgsConstructor(staticName = "of")
+@Slf4j
+public class TopicPayload implements Payload {
+    public static boolean POOL_ENABLED = Boolean.parseBoolean(System.getProperty("jetlinks.eventbus.payload.pool.enabled", "true"));
+
+    public static Recycler<TopicPayload> RECYCLER = RecyclerUtils.newRecycler(TopicPayload.class, TopicPayload::new, 1);
+
+    private String topic;
+
+    private Payload payload;
+
+    private final Recycler.Handle<TopicPayload> handle;
+
+    private TopicPayload(Recycler.Handle<TopicPayload> handle) {
+        this.handle = handle;
+    }
+
+    public static TopicPayload of(String topic, Payload payload) {
+        if (POOL_ENABLED) {
+            try {
+                TopicPayload topicPayload = RECYCLER.get();
+                topicPayload.topic = topic;
+                topicPayload.payload = payload;
+                return topicPayload;
+            } catch (Exception e) {
+                return TopicPayload.of(topic, payload, null);
+            }
+        }
+        return TopicPayload.of(topic, payload, null);
+    }
+
+    @Nonnull
+    @Override
+    public ByteBuf getBody() {
+        return payload.getBody();
+    }
+
+    @Override
+    public TopicPayload slice() {
+        return TopicPayload.of(topic, payload.slice());
+    }
+
+    @Override
+    public boolean release() {
+        return topic == null || handleRelease(ReferenceCountUtil.release(payload));
+    }
+
+    @Override
+    public boolean release(int dec) {
+        return topic == null || handleRelease(ReferenceCountUtil.release(payload, dec));
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (handle != null && refCnt() != 0) {
+            log.debug("topic [{}] payload was not release properly, release() was not called before it's garbage-collected. refCnt={}", toString(), refCnt());
+        }
+        super.finalize();
+    }
+
+    protected boolean handleRelease(boolean success) {
+        if (success) {
+            deallocate();
+        }
+        return success;
+    }
+
+    protected void deallocate() {
+        if (handle != null) {
+            payload = null;
+            topic = null;
+            handle.recycle(this);
+        }
+    }
+
+    @Override
+    public TopicPayload retain() {
+        payload.retain();
+        return this;
+    }
+
+    @Override
+    public TopicPayload retain(int inc) {
+        payload.retain(inc);
+        return this;
+    }
+
+    @Override
+    public TopicPayload touch(Object o) {
+        payload.touch(o);
+        return this;
+    }
+
+    @Override
+    public TopicPayload touch() {
+        payload.touch();
+        return this;
+    }
+
+    @Override
+    public int refCnt() {
+        return payload == null ? 0 : payload.refCnt();
+    }
+
+    @Override
+    public String toString() {
+        return "{" +
+                "topic='" + topic + '\'' +
+                ", payload=" + payload +
+                '}';
+    }
+
+    @Override
+    public JSONObject bodyToJson(boolean release) {
+        return payload.bodyToJson(release);
+    }
+
+    @Override
+    public JSONArray bodyToJsonArray(boolean release) {
+        return payload.bodyToJsonArray(release);
+    }
+
+    @Override
+    public String bodyToString() {
+        return payload.bodyToString();
+    }
+
+    @Override
+    public String bodyToString(boolean release) {
+        return payload.bodyToString(release);
+    }
+
+    @Override
+    public Object decode() {
+        return payload.decode();
+    }
+
+    @Override
+    public Object decode(boolean release) {
+        return payload.decode(release);
+    }
+
+    @Override
+    public <T> T decode(Class<T> decoder) {
+        return payload.decode(decoder);
+    }
+
+    @Override
+    public <T> T decode(Class<T> decoder, boolean release) {
+        return payload.decode(decoder, release);
+    }
+
+    @Override
+    public <T> T decode(Decoder<T> decoder) {
+        return payload.decode(decoder);
+    }
+
+    @Override
+    public <T> T decode(Decoder<T> decoder, boolean release) {
+        return payload.decode(decoder, release);
+    }
+
+    public Map<String, String> getTopicVars(String pattern) {
+        return TopicUtils.getPathVariables(pattern, getTopic());
+    }
+}

+ 29 - 0
jetlinks-core/src/main/java/org/jetlinks/core/exception/DeviceOperationException.java

@@ -0,0 +1,29 @@
+package org.jetlinks.core.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.jetlinks.core.enums.ErrorCode;
+
+@AllArgsConstructor
+public class DeviceOperationException extends RuntimeException {
+
+    @Getter
+    private final ErrorCode code;
+
+    private final String message;
+
+    public DeviceOperationException(ErrorCode errorCode) {
+        this(errorCode, errorCode.getText());
+    }
+
+    public DeviceOperationException(ErrorCode errorCode, Throwable cause) {
+        super(cause);
+        this.code = errorCode;
+        this.message = cause.getMessage();
+    }
+
+    @Override
+    public String getMessage() {
+        return message == null ? code.getText() : message;
+    }
+}

+ 32 - 0
jetlinks-core/src/main/java/org/jetlinks/core/ipc/DefaultIpcDefinition.java

@@ -0,0 +1,32 @@
+package org.jetlinks.core.ipc;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.jetlinks.core.codec.Codec;
+import org.jetlinks.core.rpc.RpcDefinition;
+
+@AllArgsConstructor
+@Getter
+class DefaultIpcDefinition<REQ, RES> implements IpcDefinition<REQ, RES> {
+    private final String address;
+
+    private final Codec<REQ> requestCodec;
+    private final Codec<RES> responseCodec;
+
+
+    @Override
+    public Codec<REQ> requestCodec() {
+        return requestCodec;
+    }
+
+    @Override
+    public Codec<RES> responseCodec() {
+        return responseCodec;
+    }
+
+    @Override
+    public String toString() {
+        return "IPC:" + address  + "";
+    }
+}

+ 114 - 0
jetlinks-core/src/main/java/org/jetlinks/core/ipc/DefaultIpcInvoker.java

@@ -0,0 +1,114 @@
+package org.jetlinks.core.ipc;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.reactivestreams.Publisher;
+import reactor.core.Disposable;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@AllArgsConstructor
+class DefaultIpcInvoker<REQ, RES> implements IpcInvoker<REQ, RES> {
+
+    @Getter
+    private final String name;
+
+    private final Function<Publisher<REQ>, Flux<RES>> channelRequester;
+
+    private final Supplier<Flux<RES>> noArgStreamRequester;
+    private final Function<REQ, Flux<RES>> streamRequester;
+
+    private final Supplier<Mono<RES>> noArgRequester;
+    private final Function<REQ, Mono<RES>> requester;
+
+    private final Supplier<Mono<Void>> noArgFireAndForgetRequester;
+    private final Function<REQ, Mono<Void>> fireAndForgetRequester;
+
+    private final Disposable disposable;
+
+    @Override
+    public Flux<RES> requestChannel(Publisher<REQ> req) {
+        if (channelRequester == null) {
+            return IpcInvoker.super.requestChannel(req);
+        }
+        return channelRequester.apply(req);
+    }
+
+    @Override
+    public Flux<RES> requestStream() {
+        if (noArgStreamRequester == null) {
+            return IpcInvoker.super.requestStream();
+        }
+        return noArgStreamRequester.get();
+    }
+
+    @Override
+    public Flux<RES> requestStream(REQ req) {
+        if (streamRequester == null) {
+            return IpcInvoker.super.requestStream(req);
+        }
+        return streamRequester.apply(req);
+    }
+
+    @Override
+    public Mono<RES> request() {
+        if (noArgRequester == null) {
+            return IpcInvoker.super.request();
+        }
+        return noArgRequester.get();
+    }
+
+    @Override
+    public Mono<RES> request(REQ req) {
+        if (requester == null) {
+            return IpcInvoker.super.request(req);
+        }
+        return requester.apply(req);
+    }
+
+    @Override
+    public Mono<Void> fireAndForget(REQ req) {
+        if (fireAndForgetRequester == null) {
+            return IpcInvoker.super.fireAndForget(req);
+        }
+        return fireAndForgetRequester.apply(req);
+    }
+
+    @Override
+    public Mono<Void> fireAndForget() {
+        if (noArgFireAndForgetRequester == null) {
+            return IpcInvoker.super.fireAndForget();
+        }
+        return noArgFireAndForgetRequester.get();
+    }
+
+    @Override
+    public void dispose() {
+        if (disposable != null) {
+            disposable.dispose();
+        }
+    }
+
+    public IpcInvokerBuilder<REQ, RES> copyToBuilder() {
+        return IpcInvokerBuilder.<REQ, RES>newBuilder()
+                .name(name)
+                .forRequestChannel(channelRequester)
+                .forRequestStream(streamRequester)
+                .forRequestStream(noArgStreamRequester)
+                .forRequest(requester)
+                .forRequest(noArgRequester)
+                .forFireAndForget(fireAndForgetRequester)
+                .forFireAndForget(noArgFireAndForgetRequester)
+                ;
+    }
+
+    @Override
+    public String toString() {
+        return "IpcInvoker{" +
+                '\'' + name + '\'' +
+                '}';
+    }
+}

+ 110 - 0
jetlinks-core/src/main/java/org/jetlinks/core/ipc/DefaultIpcInvokerBuilder.java

@@ -0,0 +1,110 @@
+package org.jetlinks.core.ipc;
+
+import org.reactivestreams.Publisher;
+import reactor.core.Disposable;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class DefaultIpcInvokerBuilder<REQ, RES> implements IpcInvokerBuilder<REQ, RES> {
+    private String name;
+
+    private Function<Publisher<REQ>, Flux<RES>> channelRequester;
+
+    private Supplier<Flux<RES>> noArgStreamRequester;
+    private Function<REQ, Flux<RES>> streamRequester;
+
+    private Supplier<Mono<RES>> noArgRequester;
+    private Function<REQ, Mono<RES>> requester;
+
+    private Supplier<Mono<Void>> noArgFireAndForgetRequester;
+    private Function<REQ, Mono<Void>> fireAndForgetRequester;
+
+
+    private Disposable disposable;
+
+    private Duration timeout;
+
+    @Override
+    public IpcInvokerBuilder<REQ, RES> timeout(Duration timeout) {
+        this.timeout = timeout;
+        return this;
+    }
+
+    @Override
+    public IpcInvokerBuilder<REQ, RES> doOnDispose(Disposable disposable) {
+        this.disposable = disposable;
+        return this;
+    }
+
+    @Override
+    public IpcInvokerBuilder<REQ, RES> name(String name) {
+        this.name = name;
+        return this;
+    }
+
+    @Override
+    public IpcInvokerBuilder<REQ, RES> forFireAndForget(Supplier<Mono<Void>> requester) {
+        this.noArgFireAndForgetRequester = Objects.requireNonNull(requester);
+        return this;
+    }
+
+    @Override
+    public IpcInvokerBuilder<REQ, RES> forFireAndForget(Function<REQ, Mono<Void>> requester) {
+        this.fireAndForgetRequester = Objects.requireNonNull(requester);
+        return this;
+    }
+
+    @Override
+    public IpcInvokerBuilder<REQ, RES> forRequest(Supplier<Mono<RES>> requester) {
+        this.noArgRequester = Objects.requireNonNull(requester);
+        return this;
+    }
+
+    @Override
+    public IpcInvokerBuilder<REQ, RES> forRequest(Function<REQ, Mono<RES>> requester) {
+        this.requester = Objects.requireNonNull(requester);
+        return this;
+    }
+
+    @Override
+    public IpcInvokerBuilder<REQ, RES> forRequestStream(Function<REQ, Flux<RES>> requester) {
+        this.streamRequester = Objects.requireNonNull(requester);
+        return this;
+    }
+
+    @Override
+    public IpcInvokerBuilder<REQ, RES> forRequestStream(Supplier<Flux<RES>> requester) {
+        this.noArgStreamRequester = Objects.requireNonNull(requester);
+        return this;
+    }
+
+    @Override
+    public IpcInvokerBuilder<REQ, RES> forRequestChannel(Function<Publisher<REQ>, Flux<RES>> requester) {
+        this.channelRequester = Objects.requireNonNull(requester);
+        return this;
+    }
+
+    @Override
+    public IpcInvoker<REQ, RES> build() {
+        IpcInvoker<REQ, RES> invoker = new DefaultIpcInvoker<>(
+                name,
+                channelRequester,
+                noArgStreamRequester,
+                streamRequester,
+                noArgRequester,
+                requester,
+                noArgFireAndForgetRequester,
+                fireAndForgetRequester,
+                disposable
+        );
+        if (timeout != null) {
+            invoker = new TimeoutIpcInvoker<>(timeout, invoker);
+        }
+        return invoker;
+    }
+}

+ 9 - 0
jetlinks-core/src/main/java/org/jetlinks/core/ipc/IpcCode.java

@@ -0,0 +1,9 @@
+package org.jetlinks.core.ipc;
+
+public enum IpcCode {
+
+    ipcServiceUnavailable,
+    unsupported,
+    timeout,
+    error;
+}

+ 56 - 0
jetlinks-core/src/main/java/org/jetlinks/core/ipc/IpcDefinition.java

@@ -0,0 +1,56 @@
+package org.jetlinks.core.ipc;
+
+
+import org.jetlinks.core.codec.Codec;
+import org.jetlinks.core.codec.Codecs;
+import org.jetlinks.core.codec.defaults.ErrorCodec;
+
+/**
+ * IPC定义信息
+ *
+ * @param <REQ> 请求类型
+ * @param <RES> 响应类型
+ */
+public interface IpcDefinition<REQ, RES> {
+
+    /**
+     * 通信地址
+     *
+     * @return 地址
+     */
+    String getAddress();
+
+    /**
+     * @return 请求编解码器
+     */
+    Codec<REQ> requestCodec();
+
+    /**
+     * @return 响应编解码器
+     */
+    Codec<RES> responseCodec();
+
+    /**
+     * @return 错误相应编解码器
+     */
+    default Codec<Throwable> errorCodec() {
+        return ErrorCodec.DEFAULT;
+    }
+
+    static <REQ, RES> IpcDefinition<REQ, RES> of(String address,
+                                                 Codec<REQ> requestCodec,
+                                                 Codec<RES> responseCodec) {
+        return new DefaultIpcDefinition<>(address, requestCodec, responseCodec);
+    }
+
+    static IpcDefinition<Void, Void> of(String address) {
+        return new DefaultIpcDefinition<>(address, Codecs.lookup(Void.class), Codecs.lookup(Void.class));
+    }
+
+    static <REQ, RES> IpcDefinition<REQ, RES> of(String address,
+                                                 Class<REQ> requestType,
+                                                 Class<RES> responseType) {
+        return new DefaultIpcDefinition<>(address, Codecs.lookup(requestType), Codecs.lookup(responseType));
+    }
+
+}

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff