Browse Source

Merge remote-tracking branch 'origin/dev' into dev

七步才子 3 năm trước cách đây
mục cha
commit
e956fa4a6a
84 tập tin đã thay đổi với 2249 bổ sung696 xóa
  1. BIN
      .DS_Store
  2. 7 5
      README.md
  3. 74 24
      common/src/main/java/cc/iotkit/common/ComponentClassLoader.java
  4. 1 2
      common/src/main/java/cc/iotkit/common/utils/CodecUtil.java
  5. 1 1
      common/src/main/java/cc/iotkit/common/utils/JsonUtil.java
  6. 32 0
      dao/src/main/java/cc/iotkit/dao/CategoryCache.java
  7. 16 7
      dao/src/main/java/cc/iotkit/dao/DeviceCache.java
  8. 2 0
      dao/src/main/java/cc/iotkit/dao/DeviceRepository.java
  9. 6 0
      dao/src/main/java/cc/iotkit/dao/HomeRepository.java
  10. 25 2
      dao/src/main/java/cc/iotkit/dao/ProductCache.java
  11. 16 0
      dao/src/main/java/cc/iotkit/dao/ProductModelRepository.java
  12. 3 0
      dao/src/main/java/cc/iotkit/dao/ProductRepository.java
  13. 33 0
      dao/src/main/java/cc/iotkit/dao/SpaceCache.java
  14. 12 0
      dao/src/main/java/cc/iotkit/dao/SpaceDeviceRepository.java
  15. 7 0
      dao/src/main/java/cc/iotkit/dao/SpaceRepository.java
  16. 4 2
      dao/src/main/java/cc/iotkit/dao/ThirdUserSessionRepository.java
  17. BIN
      doc/ma.png
  18. BIN
      manager/.DS_Store
  19. 2 2
      manager/pom.xml
  20. 32 6
      manager/src/main/java/cc/iotkit/manager/config/CacheConfig.java
  21. 26 1
      manager/src/main/java/cc/iotkit/manager/config/KeycloakSecurityConfig.java
  22. 58 0
      manager/src/main/java/cc/iotkit/manager/config/ResponseResultHandler.java
  23. 34 37
      manager/src/main/java/cc/iotkit/manager/controller/DeviceController.java
  24. 44 4
      manager/src/main/java/cc/iotkit/manager/controller/ProductController.java
  25. 18 12
      manager/src/main/java/cc/iotkit/manager/controller/ProtocolController.java
  26. 85 53
      manager/src/main/java/cc/iotkit/manager/controller/SpaceController.java
  27. 267 0
      manager/src/main/java/cc/iotkit/manager/controller/SpaceDeviceController.java
  28. 0 30
      manager/src/main/java/cc/iotkit/manager/controller/SystemController.java
  29. 106 0
      manager/src/main/java/cc/iotkit/manager/controller/ThirdAuthController.java
  30. 0 44
      manager/src/main/java/cc/iotkit/manager/controller/aligenie/AligenieDeviceController.java
  31. 0 16
      manager/src/main/java/cc/iotkit/manager/controller/api/AccountController.java
  32. 4 76
      manager/src/main/java/cc/iotkit/manager/controller/api/DeviceController.java
  33. 3 4
      manager/src/main/java/cc/iotkit/manager/controller/api/SpaceController.java
  34. 0 47
      manager/src/main/java/cc/iotkit/manager/controller/mp/SystemController.java
  35. 26 0
      manager/src/main/java/cc/iotkit/manager/model/query/DeviceQuery.java
  36. 19 0
      manager/src/main/java/cc/iotkit/manager/model/vo/SpaceDeviceVo.java
  37. 0 43
      manager/src/main/java/cc/iotkit/manager/service/AccountService.java
  38. 175 0
      manager/src/main/java/cc/iotkit/manager/service/DeferredDataConsumer.java
  39. 1 2
      manager/src/main/java/cc/iotkit/manager/service/SpaceDeviceService.java
  40. 1 1
      manager/src/main/resources/application.yml
  41. 6 7
      model/src/main/java/cc/iotkit/model/InvokeResult.java
  42. 40 0
      model/src/main/java/cc/iotkit/model/ThirdUserSession.java
  43. 3 8
      model/src/main/java/cc/iotkit/model/UserInfo.java
  44. 5 3
      model/src/main/java/cc/iotkit/model/device/DeviceInfo.java
  45. 9 0
      model/src/main/java/cc/iotkit/model/product/Product.java
  46. 45 0
      model/src/main/java/cc/iotkit/model/product/ProductModel.java
  47. 7 1
      model/src/main/java/cc/iotkit/model/space/Home.java
  48. 4 1
      model/src/main/java/cc/iotkit/model/space/Space.java
  49. 10 3
      model/src/main/java/cc/iotkit/model/space/SpaceDevice.java
  50. 12 5
      pom.xml
  51. BIN
      protocol-gateway/.DS_Store
  52. 10 0
      protocol-gateway/component-server/pom.xml
  53. 186 0
      protocol-gateway/component-server/src/main/java/cc/iotkit/comps/ApiTool.java
  54. 8 3
      protocol-gateway/component-server/src/main/java/cc/iotkit/comps/BizComponentManager.java
  55. 48 0
      protocol-gateway/component-server/src/main/java/cc/iotkit/comps/ComponentManager.java
  56. 35 8
      protocol-gateway/component-server/src/main/java/cc/iotkit/comps/DeviceComponentManager.java
  57. 6 1
      protocol-gateway/component-server/src/main/java/cc/iotkit/comps/DeviceMessageHandler.java
  58. 87 38
      protocol-gateway/component-server/src/main/java/cc/iotkit/comps/service/DeviceBehaviourService.java
  59. 1 1
      protocol-gateway/component-server/src/main/java/cc/iotkit/comps/service/DeviceStateHolder.java
  60. 4 0
      protocol-gateway/component/src/main/java/cc/iotkit/comp/AbstractDeviceComponent.java
  61. 5 0
      protocol-gateway/component/src/main/java/cc/iotkit/comp/IComponent.java
  62. 5 0
      protocol-gateway/component/src/main/java/cc/iotkit/comp/IDeviceComponent.java
  63. 12 0
      protocol-gateway/converter/src/main/java/cc/iotkit/converter/Device.java
  64. 1 0
      protocol-gateway/converter/src/main/java/cc/iotkit/converter/IConverter.java
  65. 10 1
      protocol-gateway/converter/src/main/java/cc/iotkit/converter/ScriptConverter.java
  66. 3 3
      protocol-gateway/emqx-component/dependency-reduced-pom.xml
  67. BIN
      protocol-gateway/emqx-component/src/main/java/cc/iotkit/comp/emqx/EmqxDeviceComponent.java
  68. 69 0
      protocol-gateway/http-biz-component/dependency-reduced-pom.xml
  69. 38 6
      protocol-gateway/http-biz-component/pom.xml
  70. 0 137
      protocol-gateway/http-biz-component/src/main/java/cc/iotkit/comp/biz/ApiTool.java
  71. 72 42
      protocol-gateway/http-biz-component/src/main/java/cc/iotkit/comp/biz/HttpBizComponent.java
  72. 36 0
      protocol-gateway/http-biz-component/src/main/java/cc/iotkit/comp/biz/HttpConfig.java
  73. 1 0
      protocol-gateway/http-biz-component/src/main/resources/component.spi
  74. 7 1
      protocol-gateway/mqtt-client-simulator/src/main/java/cc/iotkit/simulator/service/Gateway.java
  75. 15 2
      protocol-gateway/mqtt-component/dependency-reduced-pom.xml
  76. 11 0
      protocol-gateway/mqtt-component/pom.xml
  77. 20 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/IScripter.java
  78. 19 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/JsScripter.java
  79. 126 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/LuaScripter.java
  80. 20 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/MqttDeviceComponent.java
  81. 88 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentConverter.java
  82. 21 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentMsg.java
  83. 2 2
      rule-engine/src/main/java/cc/iotkit/ruleengine/filter/DeviceCondition.java
  84. 2 2
      standalone-package/pom.xml

BIN
.DS_Store


+ 7 - 5
README.md

@@ -4,12 +4,14 @@
 此仓库为奇特物联(iotkit)物联网平台开源项目。
 奇特物联是一个开源的物联网基础开发平台,提供了物联网及相关业务开发的常见基础功能, 能帮助你快速搭建自己的物联网相关业务平台。
 
-系统包含了品类、物模型、消息转换、通讯组件、设备管理、规则引擎、数据流转、数据可视化、报警中心等模块。
+系统包含了品类、物模型、消息转换、通讯组件(mqtt通讯组件、小度音箱接入组件、onenet Studio接入组件)、云端低代码设备开发、设备管理、规则引擎、第三方平台接入、数据流转、数据可视化、报警中心等模块和智能家居APP(小程序)
 
  **前端项目见:** https://gitee.com/iotkit-open-source/iot-console-web
 
  **演示地址:** [演示地址](http://120.76.96.206),账号:guest1,密码:guest123  (只读权限)
 
+ **智能家居小程序:** https://gitee.com/iotkit-open-source/iot-mp-home 
+
 
 #### 软件架构
 软件架构说明
@@ -19,14 +21,13 @@
 #### 安装教程
 
 见:https://ztktkv.yuque.com/docs/share/b32da919-0108-4112-9406-fe5c6672e0d7?# 《安装和配置》
-链接有效期至 2022-04-08 20:20:01
 
 #### 使用说明
 
 1.  技术文档
-    邀你加入「iot平台技术文档」知识库: https://ztktkv.yuque.com/g/ztktkv/gb3v6g/collaborator/join?token=zz5PUmXzGQqc4h9t# (只读成员)
+    邀你加入「iot平台技术文档」知识库: https://ztktkv.yuque.com/g/ztktkv/gb3v6g/collaborator/join?token=zz5PUmXzGQqc4h9t# 
     
-      **这是我宝贵的技术文档分享,看完后帮忙给本仓库点个star :sparkles:** 
+      **这是我宝贵的技术文档分享,请给本仓库点个star :sparkles: 支持一下,谢谢!** 
 2.  系统操作文档
 
 #### 待办事项
@@ -52,4 +53,5 @@
 
 微信群:
 
-![输入图片说明](doc/ma.png)
+![输入图片说明](doc/ma.png)
+

+ 74 - 24
common/src/main/java/cc/iotkit/common/ComponentClassLoader.java

@@ -1,5 +1,7 @@
 package cc.iotkit.common;
 
+import lombok.Data;
+
 public interface Constants {
 
     String PRODUCT_SECRET = "xdkKUymrEGSCYWswqCvSPyRSFvH5j7CU";
@@ -10,6 +12,10 @@ public interface Constants {
 
     String DEVICE_CACHE = "device_cache";
 
+    String CATEGORY_CACHE = "category_cache";
+
+    String SPACE_CACHE = "space_cache";
+
     String THING_MODEL_CACHE = "thing_model_cache";
 
     String WECHAT_APP_ID = "wx791cb7bf75950e0c";
@@ -18,20 +24,7 @@ public interface Constants {
 
     String APP_DESIGN_CACHE = "app_design_cache";
 
-    /**
-     * 天猫精灵平台
-     */
-    String PLATFORM_ALIGENIE = "aligenie";
-
-    /**
-     * topic前缀第三方接入网关
-     */
-    String TOPIC_PREFIX_GATEWAY = "gateway";
-
-    /**
-     * topic前缀APP
-     */
-    String TOPIC_PREFIX_APP = "app";
+    String PRODUCT_SCRIPT_CACHE = "product_script_cache";
 
     /**
      * 管理员角色
@@ -64,43 +57,100 @@ public interface Constants {
     String PWD_SYSTEM_USER = "s123456";
 
     /**
-     * 设备原始上报消息的topic
+     * 设备物模型消息的topic
      */
-    String DEVICE_RAW_MESSAGE_TOPIC = "device_raw";
+    String THING_MODEL_MESSAGE_TOPIC = "device_thing";
 
     /**
-     * 设备物模型消息的topic
+     * http消费设备信息的topic
      */
-    String THING_MODEL_MESSAGE_TOPIC = "device_thing";
+    String HTTP_CONSUMER_DEVICE_INFO_TOPIC = "device_info:";
+
+    /**
+     * 三方平台类型
+     */
+    enum ThirdPlatform {
+        dueros("小度"),
+        aligenie("天猫精灵"),
+        miiot("小爱");
 
+        public String desc;
 
-    interface API {
+        ThirdPlatform(String desc) {
+            this.desc = desc;
+        }
+
+    }
+
+    interface API_DEVICE {
 
         /**
          * 设备-基路径
          */
-        String DEVICE_BASE = "/device";
+        String BASE = "/device";
 
         /**
          * 设备-设备列表
          */
-        String DEVICE_LIST = "/list/{size}/{page}";
+        String LIST = "/list/{size}/{page}";
 
         /**
          * 设备-设备详情
          */
-        String DEVICE_DETAIL = "/{deviceId}/detail";
+        String DETAIL = "/{deviceId}/detail";
 
         /**
          * 设备-属性设置
          */
-        String DEVICE_SET_PROPERTIES = "/{deviceId}/service/property/set";
+        String SET_PROPERTIES = "/{deviceId}/service/property/set";
 
         /**
          * 设备-服务调用
          */
-        String DEVICE_INVOKE_SERVICE = "/{deviceId}/service/{service}/invoke";
+        String INVOKE_SERVICE = "/{deviceId}/service/{service}/invoke";
 
     }
 
+    interface API_SPACE {
+
+        /**
+         * 空间-基路径
+         */
+        String BASE = "/space";
+
+        /**
+         * 最近使用设备列表
+         */
+        String RECENT_DEVICES = "/myRecentDevices";
+
+        /**
+         * 我的空间设备列表
+         */
+        String SPACE_DEVICES = "/myDevices/{spaceId}";
+
+        /**
+         * 查找设备
+         */
+        String FIND_DEVICE = "/findDevice";
+
+        /**
+         * 空间添加设备
+         */
+        String ADD_DEVICE = "/addDevice";
+
+        /**
+         * 空间删除设备
+         */
+        String REMOVE_DEVICE = "/removeDevice";
+
+        /**
+         * 空间修改设备
+         */
+        String SAVE_DEVICE = "/saveDevice";
+
+        /**
+         * 获取空间设备信息
+         */
+        String GET_DEVICE = "/device/{deviceId}";
+    }
 }

+ 1 - 2
common/src/main/java/cc/iotkit/common/utils/CodecUtil.java

@@ -2,7 +2,6 @@ package cc.iotkit.common.utils;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.lang3.StringUtils;
-import sun.misc.BASE64Decoder;
 
 import javax.crypto.Cipher;
 import javax.crypto.KeyGenerator;
@@ -30,7 +29,7 @@ public class CodecUtil {
      * @throws Exception 抛出异常
      */
     private static byte[] base64Decode(String base64Code) throws Exception {
-        return StringUtils.isEmpty(base64Code) ? null : new BASE64Decoder().decodeBuffer(base64Code);
+        return StringUtils.isEmpty(base64Code) ? null : new Base64().decode(base64Code);
     }
 
 

+ 1 - 1
common/src/main/java/cc/iotkit/common/utils/JsonUtil.java

@@ -41,7 +41,7 @@ public final class JsonUtil {
 
     public static Object toObject(ScriptObjectMirror mirror) {
         if (mirror.isEmpty()) {
-            return null;
+            return new Object();
         }
         if (mirror.isArray()) {
             List<Object> list = new ArrayList<>();

+ 32 - 0
dao/src/main/java/cc/iotkit/dao/CategoryCache.java

@@ -0,0 +1,32 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.common.Constants;
+import cc.iotkit.model.product.Category;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.PostConstruct;
+
+@Repository
+public class CategoryCache {
+
+    @Autowired
+    private CategoryRepository categoryRepository;
+
+    private static CategoryCache INSTANCE;
+
+    @PostConstruct
+    public void init() {
+        INSTANCE = this;
+    }
+
+    public static CategoryCache getInstance() {
+        return INSTANCE;
+    }
+
+    @Cacheable(value = Constants.CATEGORY_CACHE, key = "#id")
+    public Category getById(String id) {
+        return categoryRepository.findById(id).orElse(null);
+    }
+}

+ 16 - 7
dao/src/main/java/cc/iotkit/dao/DeviceCache.java

@@ -6,24 +6,33 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Repository;
 
+import javax.annotation.PostConstruct;
+
 @Repository
 public class DeviceCache {
 
     @Autowired
     private DeviceRepository deviceRepository;
 
-    @Cacheable(value = Constants.DEVICE_CACHE, key = "#pk+'_'+#dn")
-    public DeviceInfo findByProductKeyAndDeviceName(String pk, String dn) {
-        return deviceRepository.findByProductKeyAndDeviceName(pk, dn);
+    private static DeviceCache INSTANCE;
+
+    @PostConstruct
+    public void init() {
+        INSTANCE = this;
     }
 
-    @Cacheable(value = Constants.DEVICE_CACHE, key = "#deviceId")
-    public DeviceInfo findByDeviceId(String deviceId) {
-        return deviceRepository.findByDeviceId(deviceId);
+    public static DeviceCache getInstance() {
+        return INSTANCE;
+    }
+
+    @Cacheable(value = Constants.DEVICE_CACHE, key = "#pk+'_'+#dn")
+    public DeviceInfo getDeviceInfo(String pk, String dn) {
+        return deviceRepository.findByProductKeyAndDeviceName(pk, dn);
     }
 
     @Cacheable(value = Constants.DEVICE_CACHE, key = "#deviceId")
     public DeviceInfo get(String deviceId) {
-        return deviceRepository.findById(deviceId).orElse(new DeviceInfo());
+        return deviceRepository.findById(deviceId).orElse(null);
     }
+
 }

+ 2 - 0
dao/src/main/java/cc/iotkit/dao/DeviceRepository.java

@@ -15,4 +15,6 @@ public interface DeviceRepository extends MongoRepository<DeviceInfo, String> {
 
     List<DeviceInfo> findByParentId(String parentId);
 
+    List<DeviceInfo> findByDeviceName(String deviceName);
+
 }

+ 6 - 0
dao/src/main/java/cc/iotkit/dao/HomeRepository.java

@@ -4,6 +4,12 @@ import cc.iotkit.model.space.Home;
 import org.springframework.data.mongodb.repository.MongoRepository;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
+
 @Repository
 public interface HomeRepository extends MongoRepository<Home, String> {
+
+    List<Home> findByUid(String uid);
+
+    Home findByUidAndCurrent(String uid,boolean current);
 }

+ 25 - 2
dao/src/main/java/cc/iotkit/dao/ProductCache.java

@@ -2,11 +2,14 @@ package cc.iotkit.dao;
 
 import cc.iotkit.common.Constants;
 import cc.iotkit.model.product.Product;
+import cc.iotkit.model.product.ProductModel;
 import cc.iotkit.model.product.ThingModel;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Repository;
 
+import javax.annotation.PostConstruct;
+
 @Repository
 public class ProductCache {
 
@@ -14,14 +17,34 @@ public class ProductCache {
     private ProductRepository productRepository;
     @Autowired
     private ThingModelRepository thingModelRepository;
+    @Autowired
+    private ProductModelRepository productModelRepository;
+
+    private static ProductCache INSTANCE;
 
-    @Cacheable(value = Constants.PRODUCT_CACHE, key = "'pk'+#pk", unless = "#result == null")
+    @PostConstruct
+    public void init() {
+        INSTANCE = this;
+    }
+
+    public static ProductCache getInstance() {
+        return INSTANCE;
+    }
+
+    @Cacheable(value = Constants.PRODUCT_CACHE, key = "'product'+#pk")
     public Product findById(String pk) {
         return productRepository.findById(pk).orElse(new Product());
     }
 
-    @Cacheable(value = Constants.THING_MODEL_CACHE, key = "'pk'+#pk", unless = "#result == null")
+    @Cacheable(value = Constants.THING_MODEL_CACHE, key = "'thing_model'+#pk")
     public ThingModel getThingModel(String pk) {
         return thingModelRepository.findByProductKey(pk);
     }
+
+    @Cacheable(value = Constants.PRODUCT_SCRIPT_CACHE, key = "'product_script'+#model")
+    public ProductModel getProductScriptByModel(String model) {
+        return productModelRepository.findByModel(model);
+    }
+
+
 }

+ 16 - 0
dao/src/main/java/cc/iotkit/dao/ProductModelRepository.java

@@ -0,0 +1,16 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.product.ProductModel;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface ProductModelRepository extends MongoRepository<ProductModel, String> {
+
+    ProductModel findByModel(String model);
+
+    List<ProductModel> findByProductKey(String productKey);
+
+}

+ 3 - 0
dao/src/main/java/cc/iotkit/dao/ProductRepository.java

@@ -6,4 +6,7 @@ import org.springframework.stereotype.Repository;
 
 @Repository
 public interface ProductRepository extends MongoRepository<Product, String> {
+
+
+
 }

+ 33 - 0
dao/src/main/java/cc/iotkit/dao/SpaceCache.java

@@ -0,0 +1,33 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.common.Constants;
+import cc.iotkit.model.space.Space;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.PostConstruct;
+
+@Repository
+public class SpaceCache {
+
+    @Autowired
+    private SpaceRepository spaceRepository;
+
+    private static SpaceCache INSTANCE;
+
+    @PostConstruct
+    public void init() {
+        INSTANCE = this;
+    }
+
+    public static SpaceCache getInstance() {
+        return INSTANCE;
+    }
+
+    @Cacheable(value = Constants.SPACE_CACHE, key = "#spaceId")
+    public Space getSpace(String spaceId) {
+        return spaceRepository.findById(spaceId).orElse(null);
+    }
+
+}

+ 12 - 0
dao/src/main/java/cc/iotkit/dao/SpaceDeviceRepository.java

@@ -4,6 +4,18 @@ import cc.iotkit.model.space.SpaceDevice;
 import org.springframework.data.mongodb.repository.MongoRepository;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
+
 @Repository
 public interface SpaceDeviceRepository extends MongoRepository<SpaceDevice, String> {
+
+    List<SpaceDevice> findByUidOrderByUseAtDesc(String uid);
+
+    List<SpaceDevice> findByUidOrderByAddAtDesc(String uid);
+
+    List<SpaceDevice> findByUidAndSpaceIdOrderByAddAtDesc(String uid, String spaceId);
+
+    SpaceDevice findByDeviceId(String deviceId);
+
+    SpaceDevice findByDeviceIdAndUid(String deviceId, String uid);
 }

+ 7 - 0
dao/src/main/java/cc/iotkit/dao/SpaceRepository.java

@@ -4,6 +4,13 @@ import cc.iotkit.model.space.Space;
 import org.springframework.data.mongodb.repository.MongoRepository;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
+
 @Repository
 public interface SpaceRepository extends MongoRepository<Space, String> {
+
+    List<Space> findByUidOrderByCreateAtDesc(String uid);
+
+    List<Space> findByUidAndHomeIdOrderByCreateAtDesc(String uid, String homeId);
+
 }

+ 4 - 2
dao/src/main/java/cc/iotkit/dao/UserAccountRepository.java → dao/src/main/java/cc/iotkit/dao/ThirdUserSessionRepository.java

@@ -1,9 +1,11 @@
 package cc.iotkit.dao;
 
-import cc.iotkit.model.UserAccount;
+import cc.iotkit.model.ThirdUserSession;
 import org.springframework.data.mongodb.repository.MongoRepository;
 import org.springframework.stereotype.Repository;
 
 @Repository
-public interface UserAccountRepository extends MongoRepository<UserAccount, String> {
+public interface ThirdUserSessionRepository extends MongoRepository<ThirdUserSession, String> {
+
+
 }

BIN
doc/ma.png


BIN
manager/.DS_Store


+ 2 - 2
manager/pom.xml

@@ -172,8 +172,8 @@
                     <artifactId>maven-compiler-plugin</artifactId>
                     <version>3.0</version>
                     <configuration>
-                        <source>1.8</source>
-                        <target>1.8</target>
+                        <source>11</source>
+                        <target>11</target>
                         <encoding>UTF-8</encoding>
                     </configuration>
                 </plugin>

+ 32 - 6
manager/src/main/java/cc/iotkit/manager/config/CacheConfig.java

@@ -1,16 +1,19 @@
 package cc.iotkit.manager.config;
 
+import cc.iotkit.common.Constants;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.google.common.collect.Lists;
 import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.cache.caffeine.CaffeineCache;
 import org.springframework.cache.support.SimpleCacheManager;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
 
 import java.util.concurrent.TimeUnit;
 
-//@Configuration
-//@EnableCaching
+@Configuration
+@EnableCaching
 public class CacheConfig {
 
     /**
@@ -20,23 +23,46 @@ public class CacheConfig {
     public CacheManager cacheManager() {
         SimpleCacheManager manager = new SimpleCacheManager();
         manager.setCaches(Lists.newArrayList(new CaffeineCache(
-                        "device_cache",
+                        Constants.DEVICE_CACHE,
                         Caffeine.newBuilder()
                                 .expireAfterWrite(5, TimeUnit.MINUTES)
                                 .build()
                 ),
                 new CaffeineCache(
-                        "product_cache",
+                        Constants.PRODUCT_CACHE,
                         Caffeine.newBuilder()
                                 .expireAfterWrite(5, TimeUnit.MINUTES)
                                 .build()
                 ),
                 new CaffeineCache(
-                        "app_design_cache",
+                        Constants.APP_DESIGN_CACHE,
                         Caffeine.newBuilder()
                                 .expireAfterWrite(5, TimeUnit.MINUTES)
                                 .build()
-                )));
+                ),
+                new CaffeineCache(
+                        Constants.THING_MODEL_CACHE,
+                        Caffeine.newBuilder()
+                                .expireAfterWrite(5, TimeUnit.MINUTES)
+                                .build()
+                ),
+                new CaffeineCache(
+                        Constants.PRODUCT_SCRIPT_CACHE,
+                        Caffeine.newBuilder()
+                                .expireAfterWrite(5, TimeUnit.MINUTES)
+                                .build()
+                ), new CaffeineCache(
+                        Constants.SPACE_CACHE,
+                        Caffeine.newBuilder()
+                                .expireAfterWrite(5, TimeUnit.MINUTES)
+                                .build()
+                ), new CaffeineCache(
+                        Constants.CATEGORY_CACHE,
+                        Caffeine.newBuilder()
+                                .expireAfterWrite(5, TimeUnit.MINUTES)
+                                .build()
+                )
+        ));
         return manager;
     }
 

+ 26 - 1
manager/src/main/java/cc/iotkit/manager/config/KeycloakSecurityConfig.java

@@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
+import org.springframework.http.HttpMethod;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -53,14 +54,38 @@ public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter
         super.configure(http);
         http
                 .authorizeRequests()
-                .antMatchers("/*.html", "/favicon.ico","/v2/api-docs", "/webjars/**", "/swagger-resources/**", "/*.js").permitAll()
+                .antMatchers("/*.html", "/favicon.ico", "/v2/api-docs", "/webjars/**", "/swagger-resources/**", "/*.js").permitAll()
                 .antMatchers("/api/**").hasRole("iot_client_user")
                 .antMatchers("/aligenieDevice/invoke/**").hasRole("iot_client_user")
+                //客户端用户写权限
+                .antMatchers("/space/addSpace/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/saveSpace/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/delSpace/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/saveHome/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/currentHome/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/myRecentDevices/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/spaces/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/myDevices/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/findDevice/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/addDevice/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/saveDevice").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/removeDevice").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/device/*").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/device/*/consumer/*").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/device/*/service/property/set").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/device/*/service/*/invoke").hasAnyRole("iot_write","iot_client_user")
+
+
+                .antMatchers(HttpMethod.DELETE).hasRole("iot_write")
+                .antMatchers(HttpMethod.PUT).hasRole("iot_write")
                 .antMatchers("/**/save*/**").hasRole("iot_write")
+                .antMatchers("/**/remove*/**").hasRole("iot_write")
                 .antMatchers("/**/del*/**").hasRole("iot_write")
                 .antMatchers("/**/add*/**").hasRole("iot_write")
                 .antMatchers("/**/clear*/**").hasRole("iot_write")
                 .antMatchers("/**/set*/**").hasRole("iot_write")
+                .antMatchers("/**/set").hasRole("iot_write")
+                .antMatchers("/**/invoke").hasRole("iot_write")
                 .antMatchers("/**").hasAnyRole(systemRole)
                 .and().csrf().disable();
     }

+ 58 - 0
manager/src/main/java/cc/iotkit/manager/config/ResponseResultHandler.java

@@ -0,0 +1,58 @@
+package cc.iotkit.manager.config;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+@ControllerAdvice
+public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
+    @Override
+    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
+        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
+        String wrapResponse = request.getHeader("wrap-response");
+        return "json".equals(wrapResponse);
+    }
+
+    @Override
+    public Object beforeBodyWrite(Object body, MethodParameter returnType,
+                                  MediaType selectedContentType,
+                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
+                                  ServerHttpRequest request, ServerHttpResponse response) {
+        if (body instanceof GlobalExceptionHandler.RequestResult) {
+            GlobalExceptionHandler.RequestResult requestResult = (GlobalExceptionHandler.RequestResult) body;
+            return new ApiResponse(Integer.parseInt(requestResult.getCode()), requestResult.getMessage(),
+                    "", System.currentTimeMillis());
+        } else if (body instanceof Map) {
+            Map map = (Map) body;
+            //spring mvc内部异常
+            if (map.containsKey("timestamp") && map.containsKey("status") && map.containsKey("error")) {
+                return new ApiResponse((Integer) map.get("status"), (String) map.get("error"),
+                        "", System.currentTimeMillis());
+            }
+        }
+
+        return new ApiResponse(200, "", body, System.currentTimeMillis());
+    }
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class ApiResponse {
+        private int status;
+        private String message;
+        private Object data;
+        private long timestamp;
+    }
+}

+ 34 - 37
manager/src/main/java/cc/iotkit/manager/controller/DeviceController.java

@@ -8,8 +8,10 @@ import cc.iotkit.comps.service.DeviceBehaviourService;
 import cc.iotkit.dao.*;
 import cc.iotkit.manager.model.query.DeviceQuery;
 import cc.iotkit.manager.service.DataOwnerService;
+import cc.iotkit.manager.service.DeferredDataConsumer;
 import cc.iotkit.manager.service.DeviceService;
 import cc.iotkit.manager.utils.AuthUtil;
+import cc.iotkit.model.InvokeResult;
 import cc.iotkit.model.Paging;
 import cc.iotkit.model.device.DeviceInfo;
 import cc.iotkit.model.device.message.DeviceProperty;
@@ -23,6 +25,7 @@ import org.springframework.context.annotation.Lazy;
 import org.springframework.data.domain.Example;
 import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.async.DeferredResult;
 
 import java.util.List;
 import java.util.Map;
@@ -53,46 +56,23 @@ public class DeviceController {
     private DevicePropertyDao devicePropertyDao;
     @Autowired
     private DeviceBehaviourService behaviourService;
+    @Autowired
+    DeferredDataConsumer deferredDataConsumer;
 
-    @PostMapping(Constants.API.DEVICE_INVOKE_SERVICE)
-    public String invokeService(@PathVariable("deviceId") String deviceId,
-                                @PathVariable("service") String service,
-                                @RequestBody Map<String, Object> args) {
+    @PostMapping(Constants.API_DEVICE.INVOKE_SERVICE)
+    public InvokeResult invokeService(@PathVariable("deviceId") String deviceId,
+                                      @PathVariable("service") String service,
+                                      @RequestBody Map<String, Object> args) {
         if (StringUtils.isBlank(deviceId) || StringUtils.isBlank(service)) {
             throw new RuntimeException("deviceId/service is blank.");
         }
-        dataOwnerService.checkWriteRole();
-        return deviceService.invokeService(deviceId, service, args);
-    }
-
-    @PostMapping(Constants.API.DEVICE_SET_PROPERTIES)
-    public String setProperty(@PathVariable("deviceId") String deviceId,
-                              @RequestBody Map<String, Object> args) {
-        dataOwnerService.checkWriteRole();
-        return deviceService.setProperty(deviceId, args);
+        return new InvokeResult(deviceService.invokeService(deviceId, service, args));
     }
 
-    @PostMapping("/list")
-    public Paging<DeviceInfo> getDevices(int page,
-                                         int size,
-                                         String pk,
-                                         Boolean online,
-                                         String dn) {
-        Criteria condition = new Criteria();
-        if (!AuthUtil.isAdmin()) {
-            condition.and("uid").is(AuthUtil.getUserId());
-        }
-        if (StringUtils.isNotBlank(pk)) {
-            condition.and("productKey").is(pk);
-        }
-        if (StringUtils.isNotBlank(dn)) {
-            condition.and("deviceName").regex(".*" + dn + ".*");
-        }
-        if (online != null) {
-            condition.and("state.online").is(online);
-        }
-
-        return deviceDao.find(condition, size, page);
+    @PostMapping(Constants.API_DEVICE.SET_PROPERTIES)
+    public InvokeResult setProperty(@PathVariable("deviceId") String deviceId,
+                                    @RequestBody Map<String, Object> args) {
+        return new InvokeResult(deviceService.setProperty(deviceId, args));
     }
 
     @PostMapping("/list/{size}/{page}")
@@ -121,8 +101,8 @@ public class DeviceController {
             condition.and("deviceName").regex(".*" + dn + ".*");
         }
         String state = query.getState();
-        if (state != null) {
-            condition.and("state.online").is(state);
+        if (StringUtils.isNotBlank(state)) {
+            condition.and("state.online").is(state.equals("online"));
         }
 
         return deviceDao.find(condition, size, page);
@@ -156,7 +136,7 @@ public class DeviceController {
                                 .build())));
     }
 
-    @GetMapping(Constants.API.DEVICE_DETAIL)
+    @GetMapping(Constants.API_DEVICE.DETAIL)
     public DeviceInfo getDetail(@PathVariable("deviceId") String deviceId) {
         return dataOwnerService.checkOwner(deviceRepository.findById(deviceId).orElse(new DeviceInfo()));
     }
@@ -227,4 +207,21 @@ public class DeviceController {
         message.setTime(System.currentTimeMillis());
         behaviourService.reportMessage(message);
     }
+
+    /**
+     * 消费设备信息消息(实时推送设备信息)
+     */
+    @GetMapping("/{deviceId}/consumer/{clientId}")
+    public DeferredResult<ThingModelMessage> consumerDeviceInfo(
+            @PathVariable("deviceId") String deviceId,
+            @PathVariable("clientId") String clientId
+    ) {
+        String uid = AuthUtil.getUserId();
+        DeviceInfo deviceInfo = deviceRepository.findByDeviceId(deviceId);
+        dataOwnerService.checkOwner(deviceInfo);
+
+        //按用户+客户端ID订阅
+        return deferredDataConsumer.newConsumer(uid + clientId,
+                Constants.HTTP_CONSUMER_DEVICE_INFO_TOPIC + deviceId);
+    }
 }

+ 44 - 4
manager/src/main/java/cc/iotkit/manager/controller/ProductController.java

@@ -1,14 +1,17 @@
 package cc.iotkit.manager.controller;
 
+import cc.iotkit.common.exception.BizException;
 import cc.iotkit.common.utils.JsonUtil;
 import cc.iotkit.dao.CategoryRepository;
 import cc.iotkit.dao.ProductRepository;
+import cc.iotkit.dao.ProductModelRepository;
 import cc.iotkit.dao.ThingModelRepository;
 import cc.iotkit.manager.config.AliyunConfig;
 import cc.iotkit.manager.service.DataOwnerService;
 import cc.iotkit.model.Paging;
 import cc.iotkit.model.product.Category;
 import cc.iotkit.model.product.Product;
+import cc.iotkit.model.product.ProductModel;
 import cc.iotkit.model.product.ThingModel;
 import com.aliyun.oss.OSS;
 import com.aliyun.oss.OSSClientBuilder;
@@ -17,12 +20,16 @@ import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 
 @Slf4j
 @RestController
@@ -39,13 +46,21 @@ public class ProductController {
     private DataOwnerService dataOwnerService;
     @Autowired
     private AliyunConfig aliyunConfig;
+    @Autowired
+    private ProductModelRepository productModelRepository;
+
     private OSS ossClient;
 
-    @PostMapping("/list")
-    public Paging<Product> getProducts(Product form) {
+    @PostMapping("/list/{size}/{page}")
+    public Paging<Product> getProducts(
+            @PathVariable("size") int size,
+            @PathVariable("page") int page,
+            Product form) {
         form = dataOwnerService.wrapExample(form);
-        return new Paging<>(productRepository.count(Example.of(form)),
-                productRepository.findAll(Example.of(form)));
+        Page<Product> products = productRepository.findAll(Example.of(form),
+                PageRequest.of(page - 1, size, Sort.by(Sort.Order.desc("createAt")))
+        );
+        return new Paging<>(products.getTotalElements(), products.getContent());
     }
 
     @PostMapping("/save")
@@ -121,4 +136,29 @@ public class ProductController {
         return ossClient.generatePresignedUrl(bucket, fileName,
                 new Date(new Date().getTime() + 3600L * 1000 * 24 * 365 * 10)).toString();
     }
+
+    @GetMapping("/{productKey}/models")
+    public List<ProductModel> getModels(@PathVariable("productKey") String productKey) {
+        dataOwnerService.checkOwner(productRepository, productKey);
+        return productModelRepository.findByProductKey(productKey);
+    }
+
+    @PostMapping("/saveProductModel")
+    public void saveProductModel(ProductModel productModel) {
+        String model = productModel.getModel();
+        String productKey = productModel.getProductKey();
+        Optional<Product> optProduct = productRepository.findById(productKey);
+        if (!optProduct.isPresent()) {
+            throw new BizException("product does not exist");
+        }
+        dataOwnerService.checkOwner(optProduct.get());
+
+        ProductModel oldScript = productModelRepository.findByModel(model);
+        if (oldScript != null && !oldScript.getProductKey().equals(productKey)) {
+            throw new BizException("model already exists");
+        }
+
+        productModel.setModifyAt(System.currentTimeMillis());
+        productModelRepository.save(productModel);
+    }
 }

+ 18 - 12
manager/src/main/java/cc/iotkit/manager/controller/ProtocolController.java

@@ -3,7 +3,7 @@ package cc.iotkit.manager.controller;
 import cc.iotkit.common.exception.BizException;
 import cc.iotkit.common.utils.JsonUtil;
 import cc.iotkit.common.utils.ReflectUtil;
-import cc.iotkit.comps.DeviceComponentManager;
+import cc.iotkit.comps.ComponentManager;
 import cc.iotkit.comps.config.ComponentConfig;
 import cc.iotkit.dao.ProtocolComponentRepository;
 import cc.iotkit.dao.ProtocolConverterRepository;
@@ -50,7 +50,7 @@ public class ProtocolController {
     private UserInfoRepository userInfoRepository;
 
     @Autowired
-    private DeviceComponentManager deviceComponentManager;
+    private ComponentManager componentManager;
 
     @PostMapping("/uploadJar")
     public String uploadJar(@RequestParam("file") MultipartFile file, String id) {
@@ -112,8 +112,9 @@ public class ProtocolController {
 
         ProtocolComponent oldComponent = getAndCheckComponent(id);
         component = ReflectUtil.copyNoNulls(component, oldComponent);
+
         try {
-            deviceComponentManager.deRegister(id);
+            componentManager.deRegister(id);
             protocolComponentRepository.save(component);
         } catch (Throwable e) {
             throw new BizException("add protocol component error", e);
@@ -144,7 +145,7 @@ public class ProtocolController {
             script = JsonUtil.parse(script, String.class);
             FileUtils.writeStringToFile(file, script, "UTF-8", false);
 
-            deviceComponentManager.deRegister(id);
+            componentManager.deRegister(id);
         } catch (Throwable e) {
             throw new BizException("save protocol component script error", e);
         }
@@ -164,7 +165,7 @@ public class ProtocolController {
     public void deleteComponent(@PathVariable("id") String id) {
         ProtocolComponent component = getAndCheckComponent(id);
         try {
-            deviceComponentManager.deRegister(id);
+            componentManager.deRegister(id);
 
             Path path = Paths.get(String.format("%s/%s", componentConfig.getComponentDir(), id))
                     .toAbsolutePath().normalize();
@@ -190,8 +191,10 @@ public class ProtocolController {
             @PathVariable("page") int page) {
         Page<ProtocolComponent> components = protocolComponentRepository.findAll(
                 PageRequest.of(page - 1, size, Sort.by(Sort.Order.desc("createAt"))));
-        components.getContent().forEach(c -> c.setState(deviceComponentManager.isRunning(c.getId()) ?
-                ProtocolComponent.STATE_RUNNING : ProtocolComponent.STATE_STOPPED));
+        components.getContent().forEach(c -> c.setState(
+                componentManager.isRunning(c.getId()) ?
+                        ProtocolComponent.STATE_RUNNING : ProtocolComponent.STATE_STOPPED
+        ));
         return new Paging<>(components.getTotalElements(), components.getContent());
     }
 
@@ -294,14 +297,17 @@ public class ProtocolController {
     public void changeComponentState(@PathVariable("id") String id,
                                      @PathVariable("state") String state) {
         ProtocolComponent component = getAndCheckComponent(id);
-        String converterId = component.getConverter();
-        getAndCheckConverter(converterId);
+        if(ProtocolComponent.TYPE_DEVICE.equals(component.getType())){
+            String converterId = component.getConverter();
+            getAndCheckConverter(converterId);
+        }
+
         if (ProtocolComponent.STATE_RUNNING.equals(state)) {
-            deviceComponentManager.register(component);
-            deviceComponentManager.start(component.getId());
+            componentManager.register(component);
+            componentManager.start(component.getId());
             component.setState(ProtocolComponent.STATE_RUNNING);
         } else {
-            deviceComponentManager.deRegister(id);
+            componentManager.deRegister(id);
             component.setState(ProtocolComponent.STATE_STOPPED);
         }
         protocolComponentRepository.save(component);

+ 85 - 53
manager/src/main/java/cc/iotkit/manager/controller/SpaceController.java

@@ -1,22 +1,18 @@
 package cc.iotkit.manager.controller;
 
-import cc.iotkit.dao.*;
-import cc.iotkit.manager.model.vo.SpaceDeviceVo;
-import cc.iotkit.manager.model.vo.SpaceInfo;
-import cc.iotkit.model.*;
-import cc.iotkit.model.device.DeviceInfo;
-import cc.iotkit.model.product.Product;
-import cc.iotkit.model.space.SpaceDevice;
+import cc.iotkit.common.exception.BizException;
+import cc.iotkit.dao.HomeRepository;
+import cc.iotkit.dao.SpaceRepository;
+import cc.iotkit.manager.service.DataOwnerService;
+import cc.iotkit.manager.utils.AuthUtil;
+import cc.iotkit.model.space.Home;
+import cc.iotkit.model.space.Space;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Example;
-import org.springframework.data.domain.Sort;
-import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Optional;
 
 
 @RestController
@@ -24,51 +20,87 @@ import java.util.stream.Collectors;
 public class SpaceController {
 
     @Autowired
-    private SpaceDeviceRepository spaceDeviceRepository;
+    private SpaceRepository spaceRepository;
     @Autowired
-    private DeviceRepository deviceRepository;
+    private HomeRepository homeRepository;
     @Autowired
-    private DeviceCache deviceCache;
-    @Autowired
-    private ProductCache productCache;
+    private DataOwnerService dataOwnerService;
+
+    /**
+     * 取用户当前家庭
+     */
+    @GetMapping("/currentHome")
+    public Home getCurrentHome() {
+        return homeRepository.findByUidAndCurrent(AuthUtil.getUserId(), true);
+    }
+
+    /**
+     * 保存家庭信息
+     */
+    @PostMapping("/saveHome/{id}")
+    public void saveHome(@PathVariable("id") String id, Home home) {
+        Optional<Home> optHome = homeRepository.findById(id);
+        if (!optHome.isPresent()) {
+            throw new BizException("home does not exist");
+        }
+        Home oldHome = optHome.get();
+        dataOwnerService.checkOwner(oldHome);
+        if (StringUtils.isNotBlank(home.getName())) {
+            oldHome.setName(home.getName());
+        }
+        if (StringUtils.isNotBlank(home.getAddress())) {
+            oldHome.setName(home.getAddress());
+        }
+        homeRepository.save(oldHome);
+    }
+
+    /**
+     * 我的空间列表
+     */
+    @GetMapping("/spaces/{homeId}")
+    public List<Space> getSpaces(@PathVariable("homeId") String homeId) {
+        return spaceRepository.findByUidAndHomeIdOrderByCreateAtDesc(AuthUtil.getUserId(), homeId);
+    }
+
+    /**
+     * 在当前家庭中添加空间
+     */
+    @PostMapping("/addSpace")
+    public void addSpace(String name) {
+        String uid = AuthUtil.getUserId();
+        Home currHome = homeRepository.findByUidAndCurrent(uid, true);
+        if (currHome == null) {
+            throw new BizException("current home does not exist");
+        }
+        spaceRepository.save(Space.builder()
+                .homeId(currHome.getId())
+                .name(name)
+                .uid(uid)
+                .createAt(System.currentTimeMillis())
+                .build());
+    }
+
+    @DeleteMapping("/delSpace/{id}")
+    public void delSpace(@PathVariable("id") String id) {
+        checkExistAndOwner(id);
+        spaceRepository.deleteById(id);
+    }
+
+    @PostMapping("/saveSpace/{id}")
+    public void saveSpace(@PathVariable("id") String id, String name) {
+        Space oldSpace = checkExistAndOwner(id);
+        oldSpace.setName(name);
+        spaceRepository.save(oldSpace);
+    }
 
-//    @PostMapping("/list")
-//    public Paging<SpaceInfo> getDevices(int page,
-//                                            int limit,
-//                                            String address) {
-//        Criteria condition = new Criteria();
-//        if (StringUtils.isNotBlank(address)) {
-//            condition.and("address").regex(".*" + address + ".*");
-//        }
-//        List<UserInfo> userInfoList = userInfoDao.find(condition, (page - 1) * limit,
-//                limit, Sort.Order.desc("createAt"));
-//
-//        List<SpaceInfo> spaces = userInfoList.stream().map((u ->
-//                new SpaceInfo(u.getAddress(), u.getUid())))
-//                .collect(Collectors.toList());
-//
-//        return new Paging<>(userInfoDao.count(condition),
-//                spaces);
-//    }
+    private Space checkExistAndOwner(String id) {
+        Optional<Space> optSpace = spaceRepository.findById(id);
+        if (!optSpace.isPresent()) {
+            throw new BizException("space does not exist");
+        }
 
-    @GetMapping("/{userId}/devices")
-    public List<SpaceDeviceVo> getDevices(@PathVariable("userId") String userId) {
-        List<SpaceDeviceVo> deviceVos = new ArrayList<>();
-        List<SpaceDevice> devices = spaceDeviceRepository.findAll(Example.of(SpaceDevice.builder().uid(userId).build()));
-        devices.forEach(sd -> {
-            DeviceInfo deviceInfo = deviceCache.findByDeviceId(sd.getDeviceId());
-            Product product = productCache.findById(deviceInfo.getProductKey());
-            deviceVos.add(SpaceDeviceVo.builder()
-                    .deviceId(sd.getDeviceId())
-                    .name(sd.getName())
-                    .picUrl(product.getImg())
-                    .spaceName(sd.getSpaceName())
-                    .online(deviceInfo.getState().isOnline())
-                    .property(deviceInfo.getProperty())
-                    .productKey(deviceInfo.getProductKey())
-                    .build());
-        });
-        return deviceVos;
+        dataOwnerService.checkOwner(optSpace.get());
+        return optSpace.get();
     }
 
 }

+ 267 - 0
manager/src/main/java/cc/iotkit/manager/controller/SpaceDeviceController.java

@@ -0,0 +1,267 @@
+package cc.iotkit.manager.controller;
+
+import cc.iotkit.common.Constants;
+import cc.iotkit.common.exception.BizException;
+import cc.iotkit.dao.*;
+import cc.iotkit.manager.model.vo.FindDeviceVo;
+import cc.iotkit.manager.model.vo.SpaceDeviceVo;
+import cc.iotkit.manager.service.DataOwnerService;
+import cc.iotkit.manager.utils.AuthUtil;
+import cc.iotkit.model.UserInfo;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.product.Category;
+import cc.iotkit.model.product.Product;
+import cc.iotkit.model.space.Space;
+import cc.iotkit.model.space.SpaceDevice;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+
+@RestController
+@RequestMapping("/space")
+public class SpaceDeviceController {
+
+    @Autowired
+    private SpaceDeviceRepository spaceDeviceRepository;
+    @Autowired
+    private DeviceRepository deviceRepository;
+    @Autowired
+    private DeviceCache deviceCache;
+    @Autowired
+    private ProductCache productCache;
+    @Autowired
+    private CategoryCache categoryCache;
+    @Autowired
+    private SpaceCache spaceCache;
+    @Autowired
+    private SpaceRepository spaceRepository;
+    @Autowired
+    private DataOwnerService dataOwnerService;
+    @Autowired
+    private UserInfoRepository userInfoRepository;
+
+    /**
+     * 我最近使用的设备列表
+     */
+    @GetMapping(Constants.API_SPACE.RECENT_DEVICES)
+    public List<SpaceDeviceVo> getMyRecentDevices() {
+        List<SpaceDevice> spaceDevices = spaceDeviceRepository.findByUidOrderByUseAtDesc(AuthUtil.getUserId());
+        return spaceDevices.stream().map((this::parseSpaceDevice)).collect(Collectors.toList());
+    }
+
+    /**
+     * 我的空间设备列表-按空间获取
+     *
+     * @param spaceId 空间id
+     */
+    @GetMapping(Constants.API_SPACE.SPACE_DEVICES)
+    public List<SpaceDeviceVo> getMyDevices(@PathVariable("spaceId") String spaceId) {
+        String uid = AuthUtil.getUserId();
+        List<SpaceDevice> spaceDevices;
+        if ("all".equals(spaceId)) {
+            //全部设备
+            spaceDevices = spaceDeviceRepository.findByUidOrderByUseAtDesc(uid);
+        } else {
+            //按空间获取
+            spaceDevices = spaceDeviceRepository.
+                    findByUidAndSpaceIdOrderByAddAtDesc(uid, spaceId);
+        }
+        return spaceDevices.stream().map((this::parseSpaceDevice)).collect(Collectors.toList());
+    }
+
+    private SpaceDeviceVo parseSpaceDevice(SpaceDevice sd) {
+        DeviceInfo device = deviceRepository.findByDeviceId(sd.getDeviceId());
+        Space space = spaceCache.getSpace(sd.getSpaceId());
+        Product product = productCache.findById(device.getProductKey());
+        Category category = categoryCache.getById(product.getCategory());
+        DeviceInfo.State state = device.getState();
+
+        return SpaceDeviceVo.builder()
+                .id(sd.getId())
+                .deviceId(sd.getDeviceId())
+                .deviceName(device.getDeviceName())
+                .name(sd.getName())
+                .spaceId(sd.getSpaceId())
+                .spaceName(space.getName())
+                .productKey(device.getProductKey())
+                .productName(product.getName())
+                .category(product.getCategory())
+                .categoryName(category.getName())
+                .picUrl(product.getImg())
+                .online(state != null && state.isOnline())
+                .property(device.getProperty())
+                .uid(sd.getUid())
+                .build();
+    }
+
+    @PreAuthorize("hasRole('iot_system_user')")
+    @GetMapping("/{userId}/devices")
+    public List<SpaceDeviceVo> getDevices(@PathVariable("userId") String userId) {
+        List<SpaceDevice> spaceDevices = spaceDeviceRepository.findAll(Example.of(SpaceDevice.builder().uid(userId).build()));
+        return spaceDevices.stream().map((this::parseSpaceDevice)).collect(Collectors.toList());
+    }
+
+    @GetMapping(Constants.API_SPACE.FIND_DEVICE)
+    List<FindDeviceVo> findDevice(String mac) {
+        if (StringUtils.isBlank(mac)) {
+            throw new BizException("mac is blank");
+        }
+
+        List<FindDeviceVo> findDeviceVos = new ArrayList<>();
+        List<DeviceInfo> devices = deviceRepository.findByDeviceName(mac);
+        if (devices == null) {
+            return findDeviceVos;
+        }
+
+        //查找网关下子设备
+        List<DeviceInfo> subDevices = new ArrayList<>();
+        for (DeviceInfo device : devices) {
+            if (device.getParentId() == null) {
+                subDevices = deviceRepository.findByParentId(device.getDeviceId());
+            }
+        }
+        devices.addAll(subDevices);
+
+        //查找空间设备
+        for (DeviceInfo device : devices) {
+            SpaceDevice spaceDevice = spaceDeviceRepository.findByDeviceId(device.getDeviceId());
+            if (spaceDevice == null) {
+                //没有被其它人占用
+                findDeviceVos.add(getFindDeviceVo(device));
+            }
+        }
+        return findDeviceVos;
+    }
+
+    private FindDeviceVo getFindDeviceVo(DeviceInfo device) {
+        FindDeviceVo findDeviceVo = FindDeviceVo.builder()
+                .deviceId(device.getDeviceId())
+                .deviceName(device.getDeviceName())
+                .productKey(device.getProductKey())
+                .build();
+
+        Product product = productCache.findById(device.getProductKey());
+        Category category = categoryCache.getById(product.getCategory());
+        findDeviceVo.setProductName(product.getName());
+        findDeviceVo.setProductImg(product.getImg());
+        findDeviceVo.setCategoryName(category.getName());
+        return findDeviceVo;
+    }
+
+    @PostMapping(Constants.API_SPACE.ADD_DEVICE)
+    public void addDevice(SpaceDevice device) {
+        String deviceId = device.getDeviceId();
+        DeviceInfo deviceInfo = deviceRepository.findByDeviceId(deviceId);
+        if (deviceInfo == null) {
+            throw new BizException("device does not exist");
+        }
+        String spaceId = device.getSpaceId();
+        Optional<Space> optSpace = spaceRepository.findById(spaceId);
+        if (!optSpace.isPresent()) {
+            throw new BizException("space does not exist");
+        }
+
+        SpaceDevice oldSpaceDevice = spaceDeviceRepository.findByDeviceId(deviceId);
+        if (oldSpaceDevice != null) {
+            throw new BizException("device has been added");
+        }
+
+        Space space = optSpace.get();
+
+        SpaceDevice spaceDevice = SpaceDevice.builder()
+                .deviceId(deviceId)
+                .spaceId(spaceId)
+                .deviceId(deviceId)
+                .name(device.getName())
+                .homeId(space.getHomeId())
+                .uid(AuthUtil.getUserId())
+                .addAt(System.currentTimeMillis())
+                .build();
+        spaceDeviceRepository.save(spaceDevice);
+
+        //更新设备子用户列表
+        List<String> subUid = deviceInfo.getSubUid();
+        if (subUid == null) {
+            subUid = new ArrayList<>();
+            deviceInfo.setSubUid(subUid);
+        }
+
+        String uid = AuthUtil.getUserId();
+        Optional<UserInfo> optUser = userInfoRepository.findById(uid);
+        if (!optUser.isPresent()) {
+            throw new BizException("user does not exist");
+        }
+        if (!subUid.contains(uid)) {
+            subUid.add(uid);
+        }
+
+        //更新设备标签
+        List<String> platforms = optUser.get().getUsePlatforms();
+        Map<String, DeviceInfo.Tag> tags = deviceInfo.getTag();
+        for (String platform : platforms) {
+            Constants.ThirdPlatform thirdPlatform = Constants.ThirdPlatform.valueOf(platform);
+            tags.put(platform, new DeviceInfo.Tag(platform, thirdPlatform.desc, "是"));
+        }
+
+        deviceRepository.save(deviceInfo);
+    }
+
+    @DeleteMapping(Constants.API_SPACE.REMOVE_DEVICE)
+    public void removeDevice(String deviceId) {
+        String uid = AuthUtil.getUserId();
+        SpaceDevice spaceDevice = spaceDeviceRepository.findByDeviceIdAndUid(deviceId, uid);
+        if (spaceDevice == null) {
+            throw new BizException("space device does not exist");
+        }
+        dataOwnerService.checkOwner(spaceDevice);
+
+        spaceDeviceRepository.deleteById(spaceDevice.getId());
+        DeviceInfo deviceInfo = deviceRepository.findByDeviceId(deviceId);
+        Optional<UserInfo> optUser = userInfoRepository.findById(uid);
+        if (!optUser.isPresent()) {
+            throw new BizException("user does not exist");
+        }
+
+        List<String> platforms = optUser.get().getUsePlatforms();
+        List<String> subUid = deviceInfo.getSubUid();
+        subUid.remove(uid);
+        //删除设备标签
+        for (String platform : platforms) {
+            deviceInfo.getTag().remove(platform);
+        }
+
+        deviceRepository.save(deviceInfo);
+    }
+
+    @PostMapping(Constants.API_SPACE.SAVE_DEVICE)
+    public void saveDevice(SpaceDevice spaceDevice) {
+        dataOwnerService.checkOwner(spaceDevice);
+        Optional<SpaceDevice> optData = spaceDeviceRepository.findById(spaceDevice.getId());
+        if (!optData.isPresent()) {
+            throw new BizException("space device does not exist");
+        }
+        SpaceDevice oldData = optData.get();
+        oldData.setName(spaceDevice.getName());
+        oldData.setSpaceId(spaceDevice.getSpaceId());
+        spaceDeviceRepository.save(oldData);
+    }
+
+    @GetMapping(Constants.API_SPACE.GET_DEVICE)
+    public SpaceDeviceVo getSpaceDevice(@PathVariable("deviceId") String deviceId) {
+        String uid = AuthUtil.getUserId();
+        SpaceDevice spaceDevice = spaceDeviceRepository.findByDeviceIdAndUid(deviceId, uid);
+        //更新设备使用时间
+        spaceDevice.setUseAt(System.currentTimeMillis());
+        spaceDeviceRepository.save(spaceDevice);
+        return parseSpaceDevice(spaceDevice);
+    }
+}

+ 0 - 30
manager/src/main/java/cc/iotkit/manager/controller/SystemController.java

@@ -1,30 +0,0 @@
-package cc.iotkit.manager.controller;
-
-import cc.iotkit.common.Constants;
-import cc.iotkit.common.utils.CodecUtil;
-import cc.iotkit.dao.UserAccountRepository;
-import cc.iotkit.model.UserAccount;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-public class SystemController {
-
-    @Autowired
-    private UserAccountRepository userAccountRepository;
-
-    @PostMapping("/user/login")
-    public String login(String uid, String pwd) throws Exception {
-        UserAccount account = userAccountRepository.findById(uid).orElse(new UserAccount());
-        String encodePwd = CodecUtil.aesEncrypt(uid + pwd, Constants.ACCOUNT_SECRET);
-        if (encodePwd.equals(account.getPwd())) {
-            return CodecUtil.aesEncrypt(System.currentTimeMillis() + "_" + uid, Constants.ACCOUNT_SECRET);
-        }
-        throw new RuntimeException("用户名或密码错误");
-    }
-
-    public static void main(String[] args) throws Exception {
-        System.out.println(CodecUtil.aesEncrypt("aaa123", Constants.ACCOUNT_SECRET));
-    }
-}

+ 106 - 0
manager/src/main/java/cc/iotkit/manager/controller/ThirdAuthController.java

@@ -0,0 +1,106 @@
+package cc.iotkit.manager.controller;
+
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.dao.ThirdUserSessionRepository;
+import cc.iotkit.model.ThirdUserSession;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Base64;
+import java.util.Enumeration;
+
+/**
+ * 第三方接入认证
+ */
+@Slf4j
+@RestController
+@RequestMapping("/auth")
+public class ThirdAuthController {
+
+    private final OkHttpClient client = new OkHttpClient().newBuilder().build();
+
+    @Autowired
+    private ThirdUserSessionRepository thirdUserSessionRepository;
+
+    @PostMapping("/token/{type}")
+    public void getToken(
+            @PathVariable("type") String type,
+            HttpServletRequest servletRequest,
+            HttpServletResponse servletResponse)
+            throws UnsupportedEncodingException {
+
+        log.info("request:{}", JsonUtil.toJsonString(servletRequest.getParameterMap()));
+        Enumeration<String> names = servletRequest.getParameterNames();
+        StringBuilder sb = new StringBuilder();
+        while (names.hasMoreElements()) {
+            String name = names.nextElement();
+            sb.append(name).append("=")
+                    .append(URLEncoder.encode(servletRequest.getParameter(name), "UTF-8"))
+                    .append("&");
+        }
+
+        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
+        RequestBody body = RequestBody.create(mediaType, sb.toString());
+
+        Request.Builder builder = new Request.Builder();
+        Request request = builder.url("https://auth.iotkit.cc/realms/iotkit/protocol/openid-connect/token")
+                .method("POST", body)
+                .build();
+        log.info("send request body:{}", sb.toString());
+
+        Response response;
+        try {
+            response = client.newCall(request).execute();
+            servletResponse.setStatus(response.code());
+            Headers headers = response.headers();
+            for (String name : headers.names()) {
+                log.info("response header,name:{},value:{}", name, headers.get(name));
+                servletResponse.setHeader(name, headers.get(name));
+            }
+            String bodyStr = response.body().string();
+            log.info("response body:{}", bodyStr);
+            TokenInfo tokenInfo = JsonUtil.parse(bodyStr, TokenInfo.class);
+            String accessToken = tokenInfo.getAccess_token();
+            String[] tokenParts = accessToken.split("\\.");
+            Base64.Decoder decoder = Base64.getUrlDecoder();
+            String payloadStr = new String(decoder.decode(tokenParts[1]));
+            TokenPayload payload = JsonUtil.parse(payloadStr, TokenPayload.class);
+            log.info("token payload:{}", payloadStr);
+
+            //保存用户授权token
+            String uid = payload.getSub();
+            thirdUserSessionRepository.save(ThirdUserSession.builder()
+                    .uid(uid)
+                    .token(accessToken)
+                    .type(type)
+                    .authAt(System.currentTimeMillis())
+                    .build());
+
+            servletResponse.setContentType("application/json");
+            servletResponse.getWriter().write(bodyStr);
+        } catch (IOException e) {
+            log.error("request error", e);
+        }
+    }
+
+    @Data
+    public static class TokenInfo {
+        private String access_token;
+    }
+
+    @Data
+    public static class TokenPayload {
+        private String sub;
+    }
+}

+ 0 - 44
manager/src/main/java/cc/iotkit/manager/controller/aligenie/AligenieDeviceController.java

@@ -2,23 +2,17 @@ package cc.iotkit.manager.controller.aligenie;
 
 import cc.iotkit.common.Constants;
 import cc.iotkit.common.exception.BizException;
-import cc.iotkit.common.exception.OfflineException;
-import cc.iotkit.common.utils.JsonUtil;
 import cc.iotkit.common.utils.UniqueIdUtil;
 import cc.iotkit.dao.*;
 import cc.iotkit.manager.service.DataOwnerService;
 import cc.iotkit.manager.service.DeviceService;
-import cc.iotkit.manager.utils.AuthUtil;
-import cc.iotkit.model.InvokeResult;
 import cc.iotkit.model.UserInfo;
 import cc.iotkit.model.aligenie.AligenieDevice;
 import cc.iotkit.model.aligenie.AligenieProduct;
 import cc.iotkit.model.device.DeviceInfo;
 import cc.iotkit.model.device.message.ThingModelMessage;
-import io.swagger.annotations.ApiOperation;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.pulsar.client.api.Producer;
 import org.apache.pulsar.client.api.PulsarClient;
 import org.apache.pulsar.client.api.PulsarClientException;
@@ -131,44 +125,6 @@ public class AligenieDeviceController {
                 .build());
     }
 
-    @ApiOperation("设备服务调用")
-    @PostMapping("/invoke/{deviceId}/{service}")
-    public InvokeResult invokeService(@PathVariable("deviceId") String deviceId,
-                                      @PathVariable("service") String service,
-                                      String args) {
-        InvokeResult result = new InvokeResult("", InvokeResult.FAILED_UNKNOWN);
-        AligenieDevice device = aligenieDeviceRepository.findByUidAndDeviceId(AuthUtil.getUserId(), deviceId);
-
-        if (device == null) {
-            result.setCode(InvokeResult.FAILED_NO_AUTH);
-            return result;
-        }
-
-        if (StringUtils.isBlank(deviceId) || StringUtils.isBlank(service)) {
-            log.error("deviceId/service is blank");
-            result.setCode(InvokeResult.FAILED_PARAM_ERROR);
-            return result;
-        }
-
-        try {
-            String requestId;
-            if ("set".equals(service)) {
-                requestId = deviceService.setProperty(deviceId,
-                        JsonUtil.parse(args, Map.class), false);
-            } else {
-                requestId = deviceService.invokeService(deviceId,
-                        service, JsonUtil.parse(args, Map.class), false);
-            }
-            result.setRequestId(requestId);
-            result.setCode(InvokeResult.SUCCESS);
-        } catch (OfflineException e) {
-            log.error("sendMsg failed", e);
-            result.setCode(InvokeResult.FAILED_OFFLINE);
-            return result;
-        }
-        return result;
-    }
-
     @Data
     public static class Device {
         private String deviceId;

+ 0 - 16
manager/src/main/java/cc/iotkit/manager/controller/api/AccountController.java

@@ -3,8 +3,6 @@ package cc.iotkit.manager.controller.api;
 import cc.iotkit.dao.AppInfoRepository;
 import cc.iotkit.dao.HomeRepository;
 import cc.iotkit.dao.UserInfoRepository;
-import cc.iotkit.manager.model.vo.LoginResult;
-import cc.iotkit.manager.service.AccountService;
 import cc.iotkit.manager.utils.AuthUtil;
 import cc.iotkit.model.AppInfo;
 import cc.iotkit.model.space.Home;
@@ -22,8 +20,6 @@ import org.springframework.web.bind.annotation.RestController;
 @RequestMapping("/api/account")
 public class AccountController {
 
-    @Autowired
-    private AccountService accountService;
     @Autowired
     private HomeRepository homeRepository;
     @Autowired
@@ -31,18 +27,6 @@ public class AccountController {
     @Autowired
     private AppInfoRepository appInfoRepository;
 
-    @ApiOperation("用户注册")
-    @PostMapping("/register")
-    public void register(String uid, String pwd) {
-        accountService.register(uid, pwd);
-    }
-
-    @ApiOperation("用户登录")
-    @PostMapping("/login")
-    public LoginResult login(String uid, String pwd) {
-        return new LoginResult(accountService.login(uid, pwd));
-    }
-
     @ApiOperation("设置当前家庭")
     @PostMapping("/setHomeId")
     public void setHomeId(String homeId) {

+ 4 - 76
manager/src/main/java/cc/iotkit/manager/controller/api/DeviceController.java

@@ -1,16 +1,12 @@
 package cc.iotkit.manager.controller.api;
 
-import cc.iotkit.common.exception.OfflineException;
-import cc.iotkit.common.utils.JsonUtil;
 import cc.iotkit.dao.AppDesignRepository;
 import cc.iotkit.dao.DeviceRepository;
 import cc.iotkit.dao.SpaceDeviceRepository;
-import cc.iotkit.dao.UserActionLogRepository;
 import cc.iotkit.manager.model.vo.AppPageNode;
 import cc.iotkit.manager.service.AppDesignService;
 import cc.iotkit.manager.service.DeviceService;
 import cc.iotkit.manager.utils.AuthUtil;
-import cc.iotkit.model.InvokeResult;
 import cc.iotkit.model.device.DeviceInfo;
 import cc.iotkit.model.space.SpaceDevice;
 import io.swagger.annotations.ApiImplicitParam;
@@ -20,10 +16,12 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Example;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
-import java.util.Map;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -40,8 +38,6 @@ public class DeviceController {
     @Autowired
     private AppDesignRepository appDesignRepository;
     @Autowired
-    private UserActionLogRepository userActionLogRepository;
-    @Autowired
     private AppDesignService appDesignService;
 
     @ApiOperation("设备列表")
@@ -72,74 +68,6 @@ public class DeviceController {
                 .collect(Collectors.toList());
     }
 
-    @ApiOperation("设备服务调用")
-    @ApiImplicitParams({
-            @ApiImplicitParam(value = "设备ID", name = "deviceId", required = true, dataType = "String"),
-            @ApiImplicitParam(value = "服务名", name = "service", required = true, dataType = "String"),
-            @ApiImplicitParam(value = "参数", name = "args", required = true, dataType = "String"),
-    })
-    @PostMapping("/{deviceId}/service/{service}")
-    public InvokeResult invokeService(@PathVariable("deviceId") String deviceId,
-                                      @PathVariable("service") String service,
-                                      String args) {
-        InvokeResult result = new InvokeResult("", InvokeResult.FAILED_UNKNOWN);
-        SpaceDevice device = checkOwner(deviceId);
-
-        if (device == null) {
-            result.setCode(InvokeResult.FAILED_NO_AUTH);
-            return result;
-        }
-
-        if (StringUtils.isBlank(deviceId) || StringUtils.isBlank(service)) {
-            log.error("deviceId/service is blank");
-            result.setCode(InvokeResult.FAILED_PARAM_ERROR);
-            return result;
-        }
-
-        try {
-            String requestId;
-            if ("property/set".equals(service)) {
-                requestId = deviceService.setProperty(deviceId,
-                        JsonUtil.parse(args, Map.class));
-            } else {
-                requestId = deviceService.invokeService(deviceId, service,
-                        JsonUtil.parse(args, Map.class));
-            }
-            result.setRequestId(requestId);
-            result.setCode(InvokeResult.SUCCESS);
-        } catch (OfflineException e) {
-            log.error("sendMsg failed", e);
-            result.setCode(InvokeResult.FAILED_OFFLINE);
-            return result;
-        }
-
-        return result;
-    }
-
-    @ApiOperation("设备属性调用")
-    @ApiImplicitParams({
-            @ApiImplicitParam(value = "设备ID", name = "deviceId", required = true, dataType = "String"),
-            @ApiImplicitParam(value = "参数", name = "args", required = true, dataType = "String"),
-    })
-    @PostMapping("/{deviceId}/service/property/set")
-    public InvokeResult setProperty(@PathVariable("deviceId") String deviceId,
-                                    String args) {
-        checkOwner(deviceId);
-        if (StringUtils.isBlank(deviceId) || StringUtils.isBlank(args)) {
-            throw new RuntimeException("deviceId/args is blank.");
-        }
-        return invokeService(deviceId, "property/set", args);
-    }
-
-    /**
-     * 检查设备是否属于该用户
-     */
-    private SpaceDevice checkOwner(String deviceId) {
-        return spaceDeviceRepository.findOne(Example.of(SpaceDevice.builder()
-                .uid(AuthUtil.getUserId()).deviceId(deviceId).build()))
-                .orElse(null);
-    }
-
     @GetMapping("/detailPage/{deviceId}")
     public List<AppPageNode> deviceDetailPage(@PathVariable("deviceId") String deviceId) {
         DeviceInfo device = deviceRepository.findById(deviceId).orElseThrow(() -> new RuntimeException("device not found"));

+ 3 - 4
manager/src/main/java/cc/iotkit/manager/controller/api/SpaceController.java

@@ -134,7 +134,7 @@ public class SpaceController {
         List<SpaceDeviceVo> spaceDeviceVos = new ArrayList<>();
         spaceDevices.forEach(sd -> spaceDeviceVos.add(buildSpaceDeviceVo(
                 sd.getId(), sd.getDeviceId(),
-                sd.getUid(), sd.getName(), sd.getSpaceName())));
+                sd.getUid(), sd.getName(), "")));
         return spaceDeviceVos;
     }
 
@@ -146,7 +146,7 @@ public class SpaceController {
 
         return buildSpaceDeviceVo(device.getId(), device.getDeviceId(),
                 AuthUtil.getUserId(), device.getName(),
-                device.getSpaceName());
+               "");
     }
 
     @ApiOperation("使用mac获取设备信息")
@@ -170,7 +170,7 @@ public class SpaceController {
     }
 
     private SpaceDeviceVo buildSpaceDeviceVo(String id, String deviceId, String uid, String name, String spaceName) {
-        DeviceInfo deviceInfo = deviceCache.findByDeviceId(deviceId);
+        DeviceInfo deviceInfo = deviceCache.get(deviceId);
         Product product = productCache.findById(deviceInfo.getProductKey());
         return SpaceDeviceVo.builder()
                 .id(id)
@@ -211,7 +211,6 @@ public class SpaceController {
                 .name(name == null ? product.getName() : name)
                 .homeId(space.getHomeId())
                 .spaceId(space.getId())
-                .spaceName(space.getName())
                 .build());
     }
 

+ 0 - 47
manager/src/main/java/cc/iotkit/manager/controller/mp/SystemController.java

@@ -1,47 +0,0 @@
-package cc.iotkit.manager.controller.mp;
-
-import cc.iotkit.dao.UserInfoRepository;
-import cc.iotkit.manager.model.vo.LoginResult;
-import cc.iotkit.manager.service.WeChatService;
-import cc.iotkit.manager.utils.AuthUtil;
-import cc.iotkit.model.UserInfo;
-import io.swagger.annotations.ApiImplicitParam;
-import io.swagger.annotations.ApiImplicitParams;
-import io.swagger.annotations.ApiOperation;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController("mp-sys")
-@RequestMapping("/mp/sys")
-public class SystemController {
-
-    @Autowired
-    private WeChatService weChatService;
-    @Autowired
-    private UserInfoRepository userInfoRepository;
-
-    @ApiOperation("用户登录")
-    @ApiImplicitParams({
-            @ApiImplicitParam(value = "加密的用户信息", name = "userInfo", required = true, dataType = "String"),
-            @ApiImplicitParam(value = "加密向量", name = "iv", required = true, dataType = "String"),
-            @ApiImplicitParam(value = "登录码", name = "loginCode", required = true, dataType = "String"),
-    })
-    @PostMapping("/login")
-    public LoginResult login(String userInfo, String iv, String loginCode) {
-        return new LoginResult(weChatService.login(userInfo, iv, loginCode));
-    }
-
-    @ApiOperation("用户设置")
-    @ApiImplicitParams({
-            @ApiImplicitParam(value = "地址", name = "address", required = true, dataType = "String"),
-    })
-    @PostMapping("/settings")
-    public void settings(String address) {
-        UserInfo userInfo = userInfoRepository.findById(AuthUtil.getUserId()).get();
-        userInfo.setAddress(address);
-        userInfoRepository.save(userInfo);
-    }
-
-}

+ 26 - 0
manager/src/main/java/cc/iotkit/manager/model/query/DeviceQuery.java

@@ -0,0 +1,26 @@
+package cc.iotkit.manager.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class FindDeviceVo {
+
+    private String deviceId;
+
+    private String productKey;
+
+    private String deviceName;
+
+    private String productName;
+
+    private String categoryName;
+
+    private String productImg;
+
+}

+ 19 - 0
manager/src/main/java/cc/iotkit/manager/model/vo/SpaceDeviceVo.java

@@ -43,6 +43,11 @@ public class SpaceDeviceVo {
      */
     private String picUrl;
 
+    /**
+     * 空间ID
+     */
+    private String spaceId;
+
     /**
      * 空间名称
      */
@@ -63,4 +68,18 @@ public class SpaceDeviceVo {
      */
     private String productKey;
 
+    /**
+     * 产品名
+     */
+    private String productName;
+
+    /**
+     * 品类
+     */
+    private String category;
+
+    /**
+     * 品类名
+     */
+    private String categoryName;
 }

+ 0 - 43
manager/src/main/java/cc/iotkit/manager/service/AccountService.java

@@ -1,43 +0,0 @@
-package cc.iotkit.manager.service;
-
-import cc.iotkit.common.Constants;
-import cc.iotkit.common.utils.CodecUtil;
-import cc.iotkit.dao.UserAccountRepository;
-import cc.iotkit.dao.UserInfoRepository;
-import cc.iotkit.model.UserAccount;
-import cc.iotkit.model.UserInfo;
-import lombok.SneakyThrows;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Example;
-import org.springframework.stereotype.Service;
-
-@Service
-public class AccountService {
-    @Autowired
-    private UserAccountRepository accountRepository;
-    @Autowired
-    private UserInfoRepository userInfoRepository;
-
-    @SneakyThrows
-    public String login(String uid, String pwd) {
-        UserAccount account = accountRepository.findOne(Example.of(UserAccount.builder().uid(uid).build()))
-                .orElseThrow(() -> new RuntimeException("用户名或密码错误"));
-        String encodePwd = CodecUtil.aesEncrypt(uid + pwd, Constants.ACCOUNT_SECRET);
-        if (!account.getPwd().equals(encodePwd)) {
-            throw new RuntimeException("用户名或密码错误");
-        }
-        return CodecUtil.aesEncrypt(System.currentTimeMillis() + "_" + uid, Constants.ACCOUNT_SECRET);
-    }
-
-    @SneakyThrows
-    public void register(String uid, String pwd) {
-        if (accountRepository.exists(Example.of(UserAccount.builder().uid(uid).build()))) {
-            throw new RuntimeException("用户名已存在");
-        }
-        accountRepository.save(UserAccount.builder().uid(uid)
-                .pwd(CodecUtil.aesEncrypt(uid + pwd, Constants.ACCOUNT_SECRET))
-                .build());
-        userInfoRepository.save(UserInfo.builder().uid(uid).build());
-    }
-
-}

+ 175 - 0
manager/src/main/java/cc/iotkit/manager/service/DeferredDataConsumer.java

@@ -0,0 +1,175 @@
+package cc.iotkit.manager.service;
+
+import cc.iotkit.common.Constants;
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.comps.config.ServerConfig;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.device.message.ThingModelMessage;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.pulsar.client.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import javax.annotation.PostConstruct;
+import java.util.*;
+import java.util.concurrent.*;
+
+@Slf4j
+@Component
+public class DeferredDataConsumer implements MessageListener<ThingModelMessage>, Runnable {
+
+    private final Map<String, Set<String>> topicConsumers = new ConcurrentHashMap<>();
+    private final Map<String, DeferredResultInfo> consumerDeferred = new ConcurrentHashMap<>();
+    private final DelayQueue<DelayedPush> delayedPushes = new DelayQueue<>();
+
+    @Autowired
+    private ServerConfig serverConfig;
+
+    @PostConstruct
+    public void init() throws PulsarClientException {
+        PulsarClient client = PulsarClient.builder()
+                .serviceUrl(this.serverConfig.getPulsarBrokerUrl())
+                .build();
+
+        client.newConsumer(Schema.JSON(ThingModelMessage.class))
+                .topic("persistent://iotkit/default/" + Constants.THING_MODEL_MESSAGE_TOPIC)
+                .subscriptionName("device-info-push")
+                .consumerName("device-info-push-consumer")
+                .messageListener(this).subscribe();
+
+        Executors.newCachedThreadPool().submit(this);
+    }
+
+    public <T> DeferredResult<T> newConsumer(String consumerId, String topic) {
+        topicConsumers.putIfAbsent(topic, new HashSet<>());
+        Set<String> consumers = topicConsumers.get(topic);
+        consumers.add(consumerId);
+
+        String consumerKey = getConsumerKey(consumerId, topic);
+        DeferredResult<T> result = new DeferredResult<>(10000L, new DeviceInfo());
+        DeferredResultInfo resultInfo = new DeferredResultInfo(result, false);
+        result.onCompletion(() -> resultInfo.setCompleted(true));
+        result.onTimeout(() -> resultInfo.setCompleted(true));
+
+        consumerDeferred.put(consumerKey, resultInfo);
+        return result;
+    }
+
+    public <T> void publish(String topic, T msg, boolean republish) {
+        Set<String> consumers = topicConsumers.get(topic);
+        if (consumers == null) {
+            return;
+        }
+        for (String consumer : consumers) {
+            String consumerKey = getConsumerKey(consumer, topic);
+            DeferredResultInfo result = consumerDeferred.get(consumerKey);
+            if (result == null) {
+                continue;
+            }
+
+            //如果已经推送完成了,等待1秒再尝试发送,让客户端有时间重连
+            if (!republish && result.isCompleted() && !result.isExpired()) {
+                delayedPushes.offer(new DelayedPush<>(topic, System.currentTimeMillis(), msg),
+                        3, TimeUnit.SECONDS);
+            } else {
+                log.info("push {} to {},msg:{}", topic, consumer, JsonUtil.toJsonString(msg));
+                result.getDeferredResult().setResult(msg);
+            }
+        }
+    }
+
+    public <T> void publish(String topic, T msg) {
+        publish(topic, msg, false);
+    }
+
+    private String getConsumerKey(String consumerId, String topic) {
+        return consumerId + topic;
+    }
+
+    @Override
+    public void received(Consumer<ThingModelMessage> consumer, Message<ThingModelMessage> msg) {
+        ThingModelMessage thingModelMessage = msg.getValue();
+        String type = thingModelMessage.getType();
+        String identifier = thingModelMessage.getIdentifier();
+        //属性上报和上下线消息
+        if ((ThingModelMessage.TYPE_PROPERTY.equals(type) && "report".equals(identifier)) ||
+                ThingModelMessage.TYPE_STATE.equals(type)) {
+            publish(Constants.HTTP_CONSUMER_DEVICE_INFO_TOPIC + thingModelMessage.getDeviceId(),
+                    thingModelMessage);
+        }
+    }
+
+    @Override
+    public void reachedEndOfTopic(Consumer<ThingModelMessage> consumer) {
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            try {
+                DelayedPush<ThingModelMessage> delayedPush = delayedPushes.take();
+                ThingModelMessage modelMessage = delayedPush.getMsg();
+                publish(delayedPush.getTopic(), modelMessage, true);
+            } catch (Throwable e) {
+                log.error("delayed push error", e);
+            }
+        }
+    }
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class DeferredResultInfo {
+        private DeferredResult deferredResult;
+        private boolean completed;
+        private long completedTime;
+
+        public DeferredResultInfo(DeferredResult deferredResult, boolean completed) {
+            this.deferredResult = deferredResult;
+            this.completed = completed;
+            this.completedTime = System.currentTimeMillis();
+        }
+
+        public void setCompleted(boolean completed) {
+            this.completed = completed;
+            this.completedTime = System.currentTimeMillis();
+        }
+
+        public boolean isExpired() {
+            //完成超过3后视为过期,客户端可能已断开
+            return completed && System.currentTimeMillis() - completedTime > 3 * 1000L;
+        }
+    }
+
+    @Data
+    public static class DelayedPush<T> implements Delayed {
+
+        private String topic;
+
+        private T msg;
+
+        private long addTime;
+
+        public DelayedPush(String topic, long addTime, T message) {
+            this.topic = topic;
+            this.addTime = addTime;
+            this.msg = message;
+        }
+
+        @Override
+        public long getDelay(TimeUnit unit) {
+            return unit.convert(addTime - System.nanoTime(), TimeUnit.NANOSECONDS);
+        }
+
+        @Override
+        public int compareTo(Delayed o) {
+            long diff = o.getDelay(TimeUnit.NANOSECONDS) - getDelay(TimeUnit.NANOSECONDS);
+            return diff == 0 ? 0 : (diff > 0 ? 1 : -1);
+        }
+    }
+
+}

+ 1 - 2
manager/src/main/java/cc/iotkit/manager/service/SpaceDeviceService.java

@@ -33,14 +33,13 @@ public class SpaceDeviceService {
         List<SpaceDevice> spaceDevices = spaceDeviceRepository.findAll(Example.of(device));
         List<SpaceDeviceVo> spaceDeviceVos = new ArrayList<>();
         spaceDevices.forEach(sd -> {
-            DeviceInfo deviceInfo = deviceCache.findByDeviceId(sd.getDeviceId());
+            DeviceInfo deviceInfo = deviceCache.get(sd.getDeviceId());
             Product product = productCache.findById(deviceInfo.getProductKey());
             spaceDeviceVos.add(SpaceDeviceVo.builder()
                     .uid(sd.getUid())
                     .deviceId(sd.getDeviceId())
                     .name(sd.getName())
                     .picUrl(product.getImg())
-                    .spaceName(sd.getSpaceName())
                     .online(deviceInfo.getState().isOnline())
                     .property(deviceInfo.getProperty())
                     .productKey(deviceInfo.getProductKey())

+ 1 - 1
manager/src/main/resources/application.yml

@@ -24,7 +24,7 @@ spring:
   cache:
     cache-names: foo,bar
     caffeine:
-      spec: maximumSize=5000,expireAfterAccess=120s
+      spec: maximumSize=5000,expireAfterAccess=300s
   mvc:
     pathmatch:
       matching-strategy: ant_path_matcher

+ 6 - 7
model/src/main/java/cc/iotkit/model/InvokeResult.java

@@ -9,13 +9,12 @@ import lombok.NoArgsConstructor;
 @AllArgsConstructor
 public class InvokeResult {
 
-    public static final String SUCCESS = "success";
-    public static final String FAILED_UNKNOWN = "unknown";
-    public static final String FAILED_OFFLINE = "offline";
-    public static final String FAILED_PARAM_ERROR = "param_error";
-    public static final String FAILED_NO_AUTH = "no_auth";
-
     private String requestId;
 
-    private String code;
+    private long time;
+
+    public InvokeResult(String requestId) {
+        this.requestId = requestId;
+        this.time = System.currentTimeMillis();
+    }
 }

+ 40 - 0
model/src/main/java/cc/iotkit/model/ThirdUserSession.java

@@ -0,0 +1,40 @@
+package cc.iotkit.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+/**
+ * 第三方接入用户会话
+ */
+@Data
+@Document
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ThirdUserSession {
+
+    /**
+     * 账号id
+     */
+    @Id
+    private String uid;
+
+    /**
+     * 账号类型
+     */
+    private String type;
+
+    /**
+     * 登录授权后的token
+     */
+    private String token;
+
+    /**
+     * 授权时间
+     */
+    private Long authAt;
+}

+ 3 - 8
model/src/main/java/cc/iotkit/model/UserInfo.java

@@ -8,6 +8,7 @@ import lombok.NoArgsConstructor;
 import org.springframework.data.annotation.Id;
 import org.springframework.data.mongodb.core.mapping.Document;
 
+import java.util.ArrayList;
 import java.util.List;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
@@ -72,16 +73,10 @@ public class UserInfo implements Owned {
 
     /**
      * 用户使用的平台
+     * 见:Constants.THIRD_PLATFORM
      */
-    private Platforms usePlatforms = new Platforms();
+    private List<String> usePlatforms = new ArrayList<>();
 
     private Long createAt;
 
-    @Data
-    public static class Platforms {
-        /**
-         * 天猫精灵
-         */
-        private boolean aligenie;
-    }
 }

+ 5 - 3
model/src/main/java/cc/iotkit/model/device/DeviceInfo.java

@@ -8,6 +8,8 @@ import lombok.NoArgsConstructor;
 import org.springframework.data.annotation.Id;
 import org.springframework.data.mongodb.core.mapping.Document;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -39,16 +41,16 @@ public class DeviceInfo implements Owned {
     /**
      * 关联子用户ID列表
      */
-    private List<String> subUid;
+    private List<String> subUid = new ArrayList<>();
 
     private State state = new State();
 
-    private Map<String, Object> property;
+    private Map<String, Object> property = new HashMap<>();
 
     /**
      * 设备标签
      */
-    private Map<String, Tag> tag;
+    private Map<String, Tag> tag = new HashMap<>();
 
     private Long createAt;
 

+ 9 - 0
model/src/main/java/cc/iotkit/model/product/Product.java

@@ -31,5 +31,14 @@ public class Product implements Owned {
 
     private String img;
 
+    /**
+     * 是否透传
+     */
+    private Boolean transparent;
+
     private Long createAt;
+
+    public boolean isTransparent() {
+        return transparent != null && transparent;
+    }
 }

+ 45 - 0
model/src/main/java/cc/iotkit/model/product/ProductModel.java

@@ -0,0 +1,45 @@
+package cc.iotkit.model.product;
+
+import cc.iotkit.model.Owned;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Document
+public class ProductModel {
+
+    public static final String TYPE_JS = "JavaScript";
+    public static final String TYPE_LUA = "LuaScript";
+
+    public static final String STATE_DEV = "dev";
+    public static final String STATE_PUBLISH = "publish";
+
+    /**
+     * 型号在所有产品中唯一
+     */
+    @Id
+    private String model;
+
+    private String name;
+
+    private String productKey;
+
+    private String type;
+
+    private String script;
+
+    /**
+     * 脚本状态,只有发布状态才生效
+     */
+    private String state;
+
+    private Long modifyAt;
+
+}

+ 7 - 1
model/src/main/java/cc/iotkit/model/space/Home.java

@@ -1,5 +1,6 @@
 package cc.iotkit.model.space;
 
+import cc.iotkit.model.Owned;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -12,7 +13,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
 @NoArgsConstructor
 @AllArgsConstructor
 @Document
-public class Home {
+public class Home implements Owned {
 
     @Id
     private String id;
@@ -41,4 +42,9 @@ public class Home {
      * 设备数量
      */
     private Integer deviceNum;
+
+    /**
+     * 是否为用户当前使用的家庭
+     */
+    private Boolean current;
 }

+ 4 - 1
model/src/main/java/cc/iotkit/model/space/Space.java

@@ -1,5 +1,6 @@
 package cc.iotkit.model.space;
 
+import cc.iotkit.model.Owned;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -12,7 +13,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
 @NoArgsConstructor
 @AllArgsConstructor
 @Document
-public class Space {
+public class Space implements Owned {
 
     @Id
     private String id;
@@ -36,4 +37,6 @@ public class Space {
      * 设备数量
      */
     private Integer deviceNum;
+
+    private Long createAt;
 }

+ 10 - 3
model/src/main/java/cc/iotkit/model/space/SpaceDevice.java

@@ -1,5 +1,6 @@
 package cc.iotkit.model.space;
 
+import cc.iotkit.model.Owned;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -12,7 +13,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
 @NoArgsConstructor
 @AllArgsConstructor
 @Document
-public class SpaceDevice {
+public class SpaceDevice implements Owned {
 
     @Id
     private String id;
@@ -48,7 +49,13 @@ public class SpaceDevice {
     private String spaceId;
 
     /**
-     * 空间名称
+     * 添加时间
      */
-    private String spaceName;
+    private Long addAt;
+
+    /**
+     * 使用时间
+     */
+    private Long useAt;
+
 }

+ 12 - 5
pom.xml

@@ -25,8 +25,9 @@
     <name>iotkit-parent</name>
     <description>iotkit parent</description>
     <properties>
-        <java.version>1.8</java.version>
+        <java.version>11</java.version>
         <keycloak-spring.version>17.0.0</keycloak-spring.version>
+        <vertx.version>4.2.2</vertx.version>
     </properties>
 
     <dependencyManagement>
@@ -172,28 +173,34 @@
                 <version>1.2.5</version>
             </dependency>
 
+            <dependency>
+                <groupId>org.luaj</groupId>
+                <artifactId>luaj-jse</artifactId>
+                <version>3.0.1</version>
+            </dependency>
+
             <dependency>
                 <groupId>io.vertx</groupId>
                 <artifactId>vertx-core</artifactId>
-                <version>4.2.6</version>
+                <version>${vertx.version}</version>
             </dependency>
 
             <dependency>
                 <groupId>io.vertx</groupId>
                 <artifactId>vertx-mqtt</artifactId>
-                <version>4.2.6</version>
+                <version>${vertx.version}</version>
             </dependency>
 
             <dependency>
                 <groupId>io.vertx</groupId>
                 <artifactId>vertx-web-proxy</artifactId>
-                <version>4.2.6</version>
+                <version>${vertx.version}</version>
             </dependency>
 
             <dependency>
                 <groupId>io.vertx</groupId>
                 <artifactId>vertx-web-client</artifactId>
-                <version>4.2.6</version>
+                <version>${vertx.version}</version>
             </dependency>
 
             <dependency>

BIN
protocol-gateway/.DS_Store


+ 10 - 0
protocol-gateway/component-server/pom.xml

@@ -42,6 +42,11 @@
             <artifactId>slf4j-api</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>io.vertx</groupId>
+            <artifactId>vertx-web-client</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
@@ -57,6 +62,11 @@
             <artifactId>pulsar-client-original</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.luaj</groupId>
+            <artifactId>luaj-jse</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>

+ 186 - 0
protocol-gateway/component-server/src/main/java/cc/iotkit/comps/ApiTool.java

@@ -0,0 +1,186 @@
+package cc.iotkit.comps;
+
+import cc.iotkit.common.Constants;
+import io.vertx.core.Vertx;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpMethod;
+import io.vertx.ext.web.client.HttpRequest;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.ext.web.client.WebClientOptions;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * 平台API调用工具类
+ */
+@Slf4j
+public class ApiTool {
+
+    private final Vertx vertx;
+    private final WebClient client;
+
+    private String host;
+    private int port;
+    private int timeout;
+
+    public ApiTool() {
+        if (Vertx.currentContext() == null) {
+            vertx = Vertx.vertx();
+        } else {
+            vertx = Vertx.currentContext().owner();
+        }
+
+        WebClientOptions options = new WebClientOptions()
+                .setUserAgent("component-api-tool");
+        options.setKeepAlive(false);
+        client = WebClient.create(vertx, options);
+    }
+
+    public void config(String host, int port, int timeout) {
+        this.host = host;
+        this.port = port;
+        this.timeout = timeout;
+    }
+
+    private String getDevicePath(String path) {
+        return Paths.get(Constants.API_DEVICE.BASE, path).toString();
+    }
+
+    private String getSpacePath(String path) {
+        return Paths.get(Constants.API_SPACE.BASE, path).toString();
+    }
+
+    /**
+     * 获取用户空间中设备列表
+     */
+    public ApiResponse getSpaceDevices(String token) {
+        HttpRequest<Buffer> request = client
+                .get(port, host, getSpacePath(Constants.API_SPACE.SPACE_DEVICES
+                        .replace("{spaceId}", "all")));
+        return send(token, HttpMethod.GET, request, new HashMap<>());
+    }
+
+    /**
+     * 获取空间设备详情
+     */
+    public ApiResponse getSpaceDeviceDetail(String token, String deviceId) {
+        HttpRequest<Buffer> request = client
+                .get(port, host, getSpacePath(Constants.API_SPACE.GET_DEVICE
+                        .replace("{deviceId}", deviceId)));
+        return send(token, HttpMethod.GET, request, new HashMap<>());
+    }
+
+    /**
+     * 获取用户的设备列表
+     */
+    public ApiResponse getDevices(String token) {
+        HttpRequest<Buffer> request = client
+                .post(port, host, getDevicePath(Constants.API_DEVICE.LIST
+                        .replace("{size}", "1000")
+                        .replace("{page}", "1")));
+        return send(token, HttpMethod.POST, request, new HashMap<>());
+    }
+
+    /**
+     * 获取设备详情
+     */
+    public ApiResponse getDeviceDetail(String token, String deviceId) {
+        HttpRequest<Buffer> request = client
+                .get(port, host, getDevicePath(Constants.API_DEVICE.DETAIL
+                        .replace("{deviceId}", deviceId)));
+        return send(token, HttpMethod.GET, request, new HashMap<>());
+    }
+
+    /**
+     * 设置属性
+     */
+    public ApiResponse setProperties(String token, String deviceId, Map<String, Object> properties) {
+        HttpRequest<Buffer> request = client
+                .post(port, host, getDevicePath(Constants.API_DEVICE.SET_PROPERTIES
+                        .replace("{deviceId}", deviceId)));
+        return send(token, HttpMethod.POST, request, properties);
+    }
+
+    /**
+     * 调用服务
+     */
+    public ApiResponse invokeService(String token, String deviceId, String service, Map<String, Object> params) {
+        HttpRequest<Buffer> request = client
+                .post(port, host, getDevicePath(Constants.API_DEVICE.INVOKE_SERVICE
+                        .replace("{deviceId}", deviceId)
+                        .replace("{service}", service)));
+        return send(token, HttpMethod.POST, request, params);
+    }
+
+    private ApiResponse send(String token, HttpMethod method, HttpRequest<Buffer> request, Map<String, Object> params) {
+        request = request
+                .timeout(timeout)
+                .putHeader("wrap-response", "json")
+                .putHeader("authorization", "Bearer " + token);
+
+        AtomicReference<ApiResponse> apiResponse = new AtomicReference<>(
+                new ApiResponse(500, "", null, System.currentTimeMillis()));
+        try {
+            //转为同步模式便于提供给js调用
+            CountDownLatch wait = new CountDownLatch(1);
+
+            if (method == HttpMethod.POST) {
+                request.sendJson(params)
+                        .onSuccess((response) -> {
+                            log.info("send succeed,response:{}", response.bodyAsString());
+                            apiResponse.set(response.bodyAsJson(ApiResponse.class));
+                            wait.countDown();
+                        })
+                        .onFailure((err) -> {
+                            log.error("send failed", err);
+                            wait.countDown();
+                        });
+            } else if (method == HttpMethod.GET) {
+                request.send()
+                        .onSuccess((response) -> {
+                            log.info("send succeed,response:{}", response.bodyAsString());
+                            apiResponse.set(response.bodyAsJson(ApiResponse.class));
+                            wait.countDown();
+                        })
+                        .onFailure((err) -> {
+                            log.error("send failed", err);
+                            wait.countDown();
+                        });
+            }
+            if (wait.await(timeout, TimeUnit.MILLISECONDS)) {
+                return apiResponse.get();
+            } else {
+                apiResponse.get().setStatus(500);
+                apiResponse.get().setMessage("request timeout");
+            }
+        } catch (Throwable e) {
+            apiResponse.get().setStatus(500);
+            apiResponse.get().setMessage(e.getMessage());
+            log.error("send error", e);
+        }
+        return apiResponse.get();
+    }
+
+    public void log(String msg) {
+        log.info(msg);
+    }
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class ApiResponse {
+        private int status;
+        private String message;
+        private Object data;
+        private long timestamp;
+    }
+}

+ 8 - 3
protocol-gateway/component-server/src/main/java/cc/iotkit/comps/BizComponentManager.java

@@ -6,6 +6,7 @@ import cc.iotkit.common.exception.BizException;
 import cc.iotkit.comp.CompConfig;
 import cc.iotkit.comp.IComponent;
 import cc.iotkit.comps.config.ComponentConfig;
+import cc.iotkit.comps.service.DeviceBehaviourService;
 import cc.iotkit.dao.ProtocolComponentRepository;
 import cc.iotkit.model.protocol.ProtocolComponent;
 import lombok.extern.slf4j.Slf4j;
@@ -32,6 +33,8 @@ public class BizComponentManager {
     private ComponentConfig componentConfig;
     @Autowired
     private ProtocolComponentRepository componentRepository;
+    @Autowired
+    private DeviceBehaviourService deviceBehaviourService;
 
     @PostConstruct
     public void init() {
@@ -61,16 +64,18 @@ public class BizComponentManager {
         } catch (Throwable e) {
             throw new BizException("get component instance error");
         }
-        componentInstance.create(new CompConfig(300, component.getConfig()));
-
         try {
             String componentScript = FileUtils.readFileToString(path.
                     resolve(ProtocolComponent.SCRIPT_FILE_NAME).toFile(), "UTF-8");
             componentInstance.setScript(componentScript);
-            register(id, componentInstance);
+            componentInstance.putScriptEnv("deviceBehaviour", deviceBehaviourService);
+            componentInstance.putScriptEnv("apiTool", new ApiTool());
         } catch (IOException e) {
             throw new BizException("get component script error", e);
         }
+        componentInstance.create(new CompConfig(300, component.getConfig()));
+
+        register(id, componentInstance);
     }
 
     public void register(String id, IComponent component) {

+ 48 - 0
protocol-gateway/component-server/src/main/java/cc/iotkit/comps/ComponentManager.java

@@ -0,0 +1,48 @@
+package cc.iotkit.comps;
+
+
+import cc.iotkit.model.protocol.ProtocolComponent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class ComponentManager {
+
+    @Autowired
+    private BizComponentManager bizComponentManager;
+
+    @Autowired
+    private DeviceComponentManager deviceComponentManager;
+
+
+    public void register(ProtocolComponent component) {
+        String type = component.getType();
+        if (ProtocolComponent.TYPE_BIZ.equals(type)) {
+            bizComponentManager.register(component);
+        } else if (ProtocolComponent.TYPE_DEVICE.equals(type)) {
+            deviceComponentManager.register(component);
+        }
+    }
+
+    public void deRegister(String id) {
+        bizComponentManager.deRegister(id);
+        deviceComponentManager.deRegister(id);
+    }
+
+    public void start(String id) {
+        bizComponentManager.start(id);
+        deviceComponentManager.start(id);
+    }
+
+    public void stop(String id) {
+        bizComponentManager.stop(id);
+        deviceComponentManager.stop(id);
+    }
+
+    public boolean isRunning(String id) {
+        return bizComponentManager.isRunning(id) || deviceComponentManager.isRunning(id);
+    }
+
+}

+ 35 - 8
protocol-gateway/component-server/src/main/java/cc/iotkit/comps/DeviceComponentManager.java

@@ -9,11 +9,16 @@ import cc.iotkit.comp.IDeviceComponent;
 import cc.iotkit.comps.config.CacheKey;
 import cc.iotkit.comps.config.ComponentConfig;
 import cc.iotkit.comps.service.DeviceBehaviourService;
+import cc.iotkit.converter.Device;
 import cc.iotkit.converter.DeviceMessage;
 import cc.iotkit.converter.ScriptConverter;
 import cc.iotkit.converter.ThingService;
+import cc.iotkit.dao.DeviceCache;
+import cc.iotkit.dao.ProductCache;
 import cc.iotkit.dao.ProtocolComponentRepository;
+import cc.iotkit.model.device.DeviceInfo;
 import cc.iotkit.model.device.message.ThingModelMessage;
+import cc.iotkit.model.product.Product;
 import cc.iotkit.model.protocol.ProtocolComponent;
 import cc.iotkit.model.protocol.ProtocolConverter;
 import lombok.extern.slf4j.Slf4j;
@@ -46,11 +51,16 @@ public class DeviceComponentManager {
     private ComponentConfig componentConfig;
     @Autowired
     private ProtocolComponentRepository componentRepository;
+    @Autowired
+    private DeviceCache deviceCache;
+    @Autowired
+    ProductCache productCache;
 
     @PostConstruct
     public void init() {
         try {
-            List<ProtocolComponent> componentList = componentRepository.findByState(ProtocolComponent.STATE_RUNNING);
+            List<ProtocolComponent> componentList = componentRepository.findByStateAndType(
+                    ProtocolComponent.STATE_RUNNING, ProtocolComponent.TYPE_DEVICE);
             for (ProtocolComponent component : componentList) {
                 register(component);
                 start(component.getId());
@@ -83,6 +93,7 @@ public class DeviceComponentManager {
                     resolve(ProtocolConverter.SCRIPT_FILE_NAME).toFile(), "UTF-8");
 
             scriptConverter.setScript(converterScript);
+            scriptConverter.putScriptEnv("component", componentInstance);
             componentInstance.setConverter(scriptConverter);
 
             String componentScript = FileUtils.readFileToString(path.
@@ -115,10 +126,13 @@ public class DeviceComponentManager {
         if (component == null) {
             return;
         }
-        component.setHandler(
-                new DeviceMessageHandler(this, component,
-                        component.getScript(), component.getConverter(),
-                        deviceBehaviourService));
+        DeviceMessageHandler messageHandler = new DeviceMessageHandler(this, component,
+                component.getScript(), component.getConverter(),
+                deviceBehaviourService);
+        messageHandler.putScriptEnv("apiTool", new ApiTool());
+        messageHandler.putScriptEnv("deviceBehaviour", deviceBehaviourService);
+
+        component.setHandler(messageHandler);
         component.start();
         states.put(id, true);
     }
@@ -142,16 +156,29 @@ public class DeviceComponentManager {
             throw new BizException("there is no components");
         }
 
+        DeviceInfo deviceInfo = deviceCache.getDeviceInfo(service.getProductKey(), service.getDeviceName());
+        Product product = productCache.findById(service.getProductKey());
+        String linkPk = service.getProductKey();
+        String linkDn = service.getDeviceName();
+
+        if (product.isTransparent()) {
+            //如果是透传设备,取父级设备进行链路查找
+            DeviceInfo parent = deviceCache.get(deviceInfo.getParentId());
+            linkPk = parent.getProductKey();
+            linkDn = parent.getDeviceName();
+        }
+
         for (IDeviceComponent com : components.values()) {
-            if (com.exist(service.getProductKey(), service.getDeviceName())) {
+            if (com.exist(linkPk, linkDn)) {
+                Device device = new Device(deviceInfo.getDeviceId(), deviceInfo.getModel(), product.isTransparent());
                 //对下发消息进行编码转换
-                DeviceMessage message = com.getConverter().encode(service, null);
+                DeviceMessage message = com.getConverter().encode(service, device);
                 if (message == null) {
                     throw new BizException("encode send message failed");
                 }
                 //保存设备端mid与平台mid对应关系
                 redisTemplate.opsForValue().set(
-                        CacheKey.getKeyCmdMid(service.getDeviceName(), message.getMid()),
+                        CacheKey.getKeyCmdMid(message.getDeviceName(), message.getMid()),
                         service.getMid(), com.getConfig().getCmdTimeout(), TimeUnit.SECONDS);
                 com.send(message);
 

+ 6 - 1
protocol-gateway/component-server/src/main/java/cc/iotkit/comps/DeviceMessageHandler.java

@@ -156,7 +156,7 @@ public class DeviceMessageHandler implements IMessageHandler {
 
         //服务回复需要重新对应mid
         if (thingModelMessage.getIdentifier().endsWith("_reply")) {
-            String platformMid = deviceComponentManager.getPlatformMid(message.getDeviceName(), message.getMid());
+            String platformMid = deviceComponentManager.getPlatformMid(thingModelMessage.getDeviceName(), message.getMid());
             if (platformMid == null) {
                 platformMid = UniqueIdUtil.newRequestId();
             }
@@ -196,6 +196,11 @@ public class DeviceMessageHandler implements IMessageHandler {
         }
     }
 
+    @Override
+    public void putScriptEnv(String key, Object value) {
+        engine.put(key, value);
+    }
+
     @Data
     public static class Action {
         public static final String TYPE_ACK = "ack";

+ 87 - 38
protocol-gateway/component-server/src/main/java/cc/iotkit/comps/service/DeviceBehaviourService.java

@@ -8,13 +8,13 @@ import cc.iotkit.common.utils.UniqueIdUtil;
 import cc.iotkit.comp.model.DeviceState;
 import cc.iotkit.comp.model.RegisterInfo;
 import cc.iotkit.comps.config.ServerConfig;
-import cc.iotkit.dao.DeviceCache;
-import cc.iotkit.dao.DeviceRepository;
-import cc.iotkit.dao.ProductRepository;
+import cc.iotkit.dao.*;
 import cc.iotkit.model.device.DeviceInfo;
 import cc.iotkit.model.device.message.ThingModelMessage;
 import cc.iotkit.model.product.Product;
+import cc.iotkit.model.product.ProductModel;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.pulsar.client.api.Producer;
 import org.apache.pulsar.client.api.PulsarClient;
 import org.apache.pulsar.client.api.PulsarClientException;
@@ -34,12 +34,16 @@ public class DeviceBehaviourService {
     @Autowired
     private ProductRepository productRepository;
     @Autowired
+    private ProductModelRepository productModelRepository;
+    @Autowired
+    private ProductCache productCache;
+    @Autowired
     private DeviceRepository deviceRepository;
     @Autowired
     private ServerConfig serverConfig;
     @Autowired
     private DeviceCache deviceCache;
-    @Autowired
+//    @Autowired
     private DeviceStateHolder deviceStateHolder;
 
     private Producer<ThingModelMessage> deviceMessageProducer;
@@ -69,17 +73,6 @@ public class DeviceBehaviourService {
                                     subDevice.getTag(), null));
                 }
             }
-
-            //设备注册消息
-            ThingModelMessage modelMessage = new ThingModelMessage(
-                    UniqueIdUtil.newRequestId(), "",
-                    info.getProductKey(), info.getDeviceName(),
-                    ThingModelMessage.TYPE_LIFETIME, "register",
-                    0, new HashMap<>(), System.currentTimeMillis(),
-                    System.currentTimeMillis()
-            );
-
-            reportMessage(modelMessage);
         } catch (BizException e) {
             log.error("register device error", e);
             throw e;
@@ -91,33 +84,75 @@ public class DeviceBehaviourService {
 
     public DeviceInfo register(String parentId, RegisterInfo info) {
         String pk = info.getProductKey();
+        String dn = info.getDeviceName();
+        String model = info.getModel();
+
+        //子设备注册处理
+        if (parentId != null) {
+            //透传设备:pk为空、model不为空,使用model查询产品
+            if (StringUtils.isBlank(pk) && StringUtils.isNotBlank(model)) {
+                ProductModel productModel = productModelRepository.findByModel(model);
+                if (productModel == null) {
+                    throw new BizException("product model does not exist");
+                }
+                pk = productModel.getProductKey();
+            }
+        }
+
         Optional<Product> optProduct = productRepository.findById(pk);
         if (!optProduct.isPresent()) {
             throw new BizException("Product does not exist");
         }
-        String uid = optProduct.get().getUid();
+        Product product = optProduct.get();
+        String uid = product.getUid();
         DeviceInfo device = deviceRepository.findByProductKeyAndDeviceName(pk, info.getDeviceName());
+        boolean reportMsg = false;
 
         if (device != null) {
             log.info("device already registered");
-            //更换网关重新注册更新父级ID
+            device.setModel(model);
+        } else {
+            //不存在,注册新设备
+            device = new DeviceInfo();
+            device.setId(DeviceUtil.newDeviceId(dn));
             device.setParentId(parentId);
-            deviceRepository.save(device);
-            return device;
+            device.setUid(uid);
+            device.setDeviceId(device.getId());
+            device.setProductKey(pk);
+            device.setDeviceName(dn);
+            device.setModel(model);
+            //默认离线
+            device.setState(new DeviceInfo.State(false, null, null));
+            device.setCreateAt(System.currentTimeMillis());
+            reportMsg = true;
         }
-        //不存在,注册新设备
-        device = new DeviceInfo();
-        device.setId(DeviceUtil.newDeviceId(info.getDeviceName()));
-        device.setParentId(parentId);
-        device.setUid(uid);
-        device.setDeviceId(device.getId());
-        device.setProductKey(pk);
-        device.setDeviceName(info.getDeviceName());
-        device.setState(new DeviceInfo.State(false, null, null));
-        device.setCreateAt(System.currentTimeMillis());
 
+        //透传设备,默认在线
+        if (product.isTransparent()) {
+            device.setState(new DeviceInfo.State(true, System.currentTimeMillis(), null));
+        }
+
+        if (parentId != null) {
+            //子设备更换网关重新注册更新父级ID
+            device.setParentId(parentId);
+            reportMsg = true;
+        }
         deviceRepository.save(device);
-        log.info("device registered:{}", JsonUtil.toJsonString(device));
+
+        //新设备或更换网关需要产生注册消息
+        if (reportMsg) {
+            log.info("device registered:{}", JsonUtil.toJsonString(device));
+            //新注册设备注册消息
+            ThingModelMessage modelMessage = new ThingModelMessage(
+                    UniqueIdUtil.newRequestId(), "",
+                    pk, dn,
+                    ThingModelMessage.TYPE_LIFETIME, "register",
+                    0, new HashMap<>(), System.currentTimeMillis(),
+                    System.currentTimeMillis()
+            );
+
+            reportMessage(modelMessage);
+        }
 
         return device;
     }
@@ -155,11 +190,17 @@ public class DeviceBehaviourService {
         }
         deviceStateChange(device, online);
 
-        //可能是父设备,父设备离线,子设备也要离线
-        if (!online && device.getParentId() == null) {
-            List<DeviceInfo> subDevices = deviceRepository.findByParentId(device.getDeviceId());
-            for (DeviceInfo subDevice : subDevices) {
-                deviceStateChange(subDevice, false);
+        if (device.getParentId() != null) {
+            return;
+        }
+
+        List<DeviceInfo> subDevices = deviceRepository.findByParentId(device.getDeviceId());
+        for (DeviceInfo subDevice : subDevices) {
+            Product product = productCache.findById(subDevice.getProductKey());
+            Boolean transparent = product.getTransparent();
+            //透传设备父设备上线,子设备也上线。非透传设备父设备离线,子设备才离线
+            if (transparent != null && transparent || !online) {
+                deviceStateChange(subDevice, online);
             }
         }
     }
@@ -168,11 +209,11 @@ public class DeviceBehaviourService {
         if (online) {
             device.getState().setOnline(true);
             device.getState().setOnlineTime(System.currentTimeMillis());
-            deviceStateHolder.online(device.getDeviceId());
+//            deviceStateHolder.online(device.getDeviceId());
         } else {
             device.getState().setOnline(false);
             device.getState().setOfflineTime(System.currentTimeMillis());
-            deviceStateHolder.offline(device.getDeviceId());
+//            deviceStateHolder.offline(device.getDeviceId());
         }
         deviceRepository.save(device);
 
@@ -192,7 +233,7 @@ public class DeviceBehaviourService {
 
     public void reportMessage(ThingModelMessage message) {
         try {
-            DeviceInfo device = deviceCache.findByProductKeyAndDeviceName(message.getProductKey(),
+            DeviceInfo device = deviceCache.getDeviceInfo(message.getProductKey(),
                     message.getDeviceName());
             if (device == null) {
                 return;
@@ -209,4 +250,12 @@ public class DeviceBehaviourService {
             log.error("send thing model message error", e);
         }
     }
+
+    /**
+     * 提供给js调用的方法
+     */
+    public void reportMessage(String jsonMsg) {
+        ThingModelMessage message = JsonUtil.parse(jsonMsg, ThingModelMessage.class);
+        reportMessage(message);
+    }
 }

+ 1 - 1
protocol-gateway/component-server/src/main/java/cc/iotkit/comps/service/DeviceStateHolder.java

@@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit;
  * 设备状态维持,每1分钟更新一次心跳
  */
 @Slf4j
-@Service
+//@Service
 public class DeviceStateHolder implements MessageListener<DeviceStateHolder.OfflineMessage> {
 
     private ScheduledThreadPoolExecutor stateHolderTask;

+ 4 - 0
protocol-gateway/component/src/main/java/cc/iotkit/comp/AbstractDeviceComponent.java

@@ -38,4 +38,8 @@ public abstract class AbstractDeviceComponent implements IDeviceComponent {
     public CompConfig getConfig() {
         return config;
     }
+
+    @Override
+    public void putScriptEnv(String key, Object value) {
+    }
 }

+ 5 - 0
protocol-gateway/component/src/main/java/cc/iotkit/comp/IComponent.java

@@ -15,4 +15,9 @@ public interface IComponent {
     void setScript(String script);
 
     String getScript();
+
+    /**
+     * 添加脚本环境变量
+     */
+    void putScriptEnv(String key, Object value);
 }

+ 5 - 0
protocol-gateway/component/src/main/java/cc/iotkit/comp/IDeviceComponent.java

@@ -7,4 +7,9 @@ import java.util.Map;
 public interface IMessageHandler {
 
     ReceiveResult onReceive(Map<String, Object> head, String type, String msg);
+
+    /**
+     * 添加脚本环境变量
+     */
+    void putScriptEnv(String key, Object value);
 }

+ 12 - 0
protocol-gateway/converter/src/main/java/cc/iotkit/converter/Device.java

@@ -1,9 +1,21 @@
 package cc.iotkit.converter;
 
+import lombok.AllArgsConstructor;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 @Data
+@NoArgsConstructor
+@AllArgsConstructor
 public class Device {
 
+    private String deviceId;
+
+    private String model;
+
+    /**
+     * 是否透传
+     */
+    private Boolean transparent;
 
 }

+ 1 - 0
protocol-gateway/converter/src/main/java/cc/iotkit/converter/IConverter.java

@@ -10,4 +10,5 @@ public interface IConverter {
 
     DeviceMessage encode(ThingService<?> service, Device device);
 
+    void putScriptEnv(String key, Object value);
 }

+ 10 - 1
protocol-gateway/converter/src/main/java/cc/iotkit/converter/ScriptConverter.java

@@ -29,7 +29,12 @@ public class ScriptConverter implements IConverter {
 
     public ThingModelMessage decode(DeviceMessage msg) {
         try {
-            ScriptObjectMirror result = (ScriptObjectMirror) engine.invokeMethod(scriptObj, "decode", msg);
+            Object rst = engine.invokeMethod(scriptObj, "decode", msg);
+            if (rst instanceof ThingModelMessage) {
+                return (ThingModelMessage) rst;
+            }
+
+            ScriptObjectMirror result = (ScriptObjectMirror) rst;
             ThingModelMessage modelMessage = new ThingModelMessage();
             BeanUtils.populate(modelMessage, result);
             return modelMessage;
@@ -53,4 +58,8 @@ public class ScriptConverter implements IConverter {
         return null;
     }
 
+    @Override
+    public void putScriptEnv(String key, Object value) {
+        engine.put(key, value);
+    }
 }

+ 3 - 3
protocol-gateway/emqx-component/dependency-reduced-pom.xml

@@ -43,19 +43,19 @@
     <dependency>
       <groupId>io.vertx</groupId>
       <artifactId>vertx-core</artifactId>
-      <version>4.2.6</version>
+      <version>4.2.2</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>io.vertx</groupId>
       <artifactId>vertx-web-proxy</artifactId>
-      <version>4.2.6</version>
+      <version>4.2.2</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>io.vertx</groupId>
       <artifactId>vertx-mqtt</artifactId>
-      <version>4.2.6</version>
+      <version>4.2.2</version>
       <scope>provided</scope>
     </dependency>
     <dependency>

BIN
protocol-gateway/emqx-component/src/main/java/cc/iotkit/comp/emqx/EmqxDeviceComponent.java


+ 69 - 0
protocol-gateway/http-biz-component/dependency-reduced-pom.xml

@@ -0,0 +1,69 @@
+<?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/maven-v4_0_0.xsd">
+  <parent>
+    <artifactId>protocol-gateway</artifactId>
+    <groupId>cc.iotkit</groupId>
+    <version>0.1.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>http-biz-component</artifactId>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-shade-plugin</artifactId>
+        <version>3.2.4</version>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <artifactSet>
+            <includes>
+              <include>io.vertx:vertx-web-proxy</include>
+              <include>io.vertx:vertx-web</include>
+              <include>io.vertx:vertx-bridge-common</include>
+              <include>io.vertx:vertx-http-proxy</include>
+              <include>io.vertx:vertx-core</include>
+              <include>io.netty:netty-codec-http2</include>
+            </includes>
+          </artifactSet>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>8</source>
+          <target>8</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.projectlombok</groupId>
+      <artifactId>lombok</artifactId>
+      <version>1.18.22</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>io.vertx</groupId>
+      <artifactId>vertx-web-proxy</artifactId>
+      <version>4.2.2</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>cc.iotkit</groupId>
+      <artifactId>component</artifactId>
+      <version>0.1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+  <properties>
+    <maven.compiler.target>8</maven.compiler.target>
+    <maven.compiler.source>8</maven.compiler.source>
+  </properties>
+</project>

+ 38 - 6
protocol-gateway/http-biz-component/pom.xml

@@ -28,17 +28,49 @@
             <artifactId>vertx-web-proxy</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>io.vertx</groupId>
-            <artifactId>vertx-web-client</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>cc.iotkit</groupId>
             <artifactId>component</artifactId>
         </dependency>
 
-
     </dependencies>
 
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>3.2.4</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <artifactSet>
+                        <includes>
+                            <include>io.vertx:vertx-web-proxy</include>
+                            <include>io.vertx:vertx-web</include>
+                            <include>io.vertx:vertx-bridge-common</include>
+                            <include>io.vertx:vertx-http-proxy</include>
+                            <include>io.vertx:vertx-core</include>
+                            <include>io.netty:netty-codec-http2</include>
+                        </includes>
+                    </artifactSet>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>

+ 0 - 137
protocol-gateway/http-biz-component/src/main/java/cc/iotkit/comp/biz/ApiTool.java

@@ -1,137 +0,0 @@
-package cc.iotkit.comp.biz;
-
-import cc.iotkit.common.Constants;
-import io.vertx.core.Vertx;
-import io.vertx.core.buffer.Buffer;
-import io.vertx.ext.web.client.HttpRequest;
-import io.vertx.ext.web.client.WebClient;
-import io.vertx.ext.web.client.WebClientOptions;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * 平台API调用工具类
- */
-public class ApiTool {
-
-    private static final Vertx vertx;
-    private static final WebClient client;
-
-    static {
-        if (Vertx.currentContext() == null) {
-            vertx = Vertx.vertx();
-        } else {
-            vertx = Vertx.currentContext().owner();
-        }
-
-        WebClientOptions options = new WebClientOptions()
-                .setUserAgent("component-api-tool");
-        options.setKeepAlive(false);
-        client = WebClient.create(vertx, options);
-    }
-
-    private static String host;
-    private static int port;
-    private static int timeout;
-
-    public static void config(String host, int port, int timeout) {
-        ApiTool.host = host;
-        ApiTool.port = port;
-        ApiTool.timeout = timeout;
-    }
-
-    /**
-     * 获取用户的设备列表
-     */
-    public static ApiResponse getDevices(String token) {
-        HttpRequest<Buffer> request = client
-                .post(port, host, Paths.get(Constants.API.DEVICE_BASE, Constants.API.DEVICE_LIST
-                        .replace("size", "1000")
-                        .replace("page", "1")).toString())
-                .timeout(timeout)
-                .putHeader("authorization", "Bearer " + token);
-        return sendJson(request, new HashMap<>());
-    }
-
-    /**
-     * 获取设备详情
-     */
-    public static ApiResponse getDeviceDetail(String token, String deviceId) {
-        HttpRequest<Buffer> request = client
-                .post(port, host, Paths.get(Constants.API.DEVICE_BASE, Constants.API.DEVICE_DETAIL
-                        .replace("deviceId", deviceId)).toString())
-                .timeout(timeout)
-                .putHeader("authorization", "Bearer " + token);
-        return sendJson(request, new HashMap<>());
-    }
-
-    /**
-     * 设置属性
-     */
-    public static ApiResponse setProperties(String token, String deviceId, Map<String, Object> properties) {
-        HttpRequest<Buffer> request = client
-                .post(port, host, Paths.get(Constants.API.DEVICE_BASE, Constants.API.DEVICE_SET_PROPERTIES
-                        .replace("deviceId", deviceId)).toString())
-                .timeout(timeout)
-                .putHeader("authorization", "Bearer " + token);
-        return sendJson(request, properties);
-    }
-
-    /**
-     * 调用服务
-     */
-    public static ApiResponse invokeService(String token, String deviceId, String service, Map<String, Object> params) {
-        HttpRequest<Buffer> request = client
-                .post(port, host, Paths.get(Constants.API.DEVICE_BASE, Constants.API.DEVICE_INVOKE_SERVICE
-                        .replace("deviceId", deviceId)
-                        .replace("service", service)).toString())
-                .timeout(timeout)
-                .putHeader("authorization", "Bearer " + token);
-        return sendJson(request, params);
-    }
-
-    private static ApiResponse sendJson(HttpRequest<Buffer> request, Map<String, Object> params) {
-        AtomicReference<ApiResponse> apiResponse = new AtomicReference<>(new ApiResponse(500, "", null));
-        try {
-            //转为同步模式便于提供给js调用
-            CountDownLatch wait = new CountDownLatch(1);
-            request.sendJson(params)
-                    .onSuccess((response) -> {
-                        System.out.println(response.bodyAsString());
-                        apiResponse.set(response.bodyAsJson(ApiResponse.class));
-                        wait.countDown();
-                    })
-                    .onFailure((err) -> {
-                        err.printStackTrace();
-                        wait.countDown();
-                    });
-            if (wait.await(timeout, TimeUnit.MILLISECONDS)) {
-                return apiResponse.get();
-            } else {
-                apiResponse.get().setCode(500);
-                apiResponse.get().setMessage("request timeout");
-            }
-        } catch (Throwable e) {
-            apiResponse.get().setCode(500);
-            apiResponse.get().setMessage(e.getMessage());
-        }
-        return apiResponse.get();
-    }
-
-    @Data
-    @NoArgsConstructor
-    @AllArgsConstructor
-    public static class ApiResponse {
-        private int code;
-        private String message;
-        private Object data;
-    }
-}

+ 72 - 42
protocol-gateway/http-biz-component/src/main/java/cc/iotkit/comp/biz/HttpBizComponent.java

@@ -6,6 +6,7 @@ import cc.iotkit.comp.IComponent;
 import io.vertx.core.MultiMap;
 import io.vertx.core.Vertx;
 import io.vertx.core.http.HttpServer;
+import io.vertx.core.http.HttpServerRequest;
 import io.vertx.core.http.HttpServerResponse;
 import io.vertx.core.json.JsonObject;
 import io.vertx.ext.web.Router;
@@ -22,8 +23,8 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-@Slf4j
 @Data
+@Slf4j
 public class HttpBizComponent implements IComponent {
 
     private final Vertx vertx = Vertx.vertx();
@@ -32,7 +33,9 @@ public class HttpBizComponent implements IComponent {
 
     private Object scriptObj;
 
-    private HttpConfig config;
+    private CompConfig config;
+
+    private HttpConfig httpConfig;
 
     private String script;
 
@@ -40,7 +43,7 @@ public class HttpBizComponent implements IComponent {
 
     @Override
     public void create(CompConfig config) {
-        this.config = JsonUtil.parse(config.getOther(), HttpConfig.class);
+        this.httpConfig = JsonUtil.parse(config.getOther(), HttpConfig.class);
         try {
             scriptObj = engine.eval(String.format("new (function () {\n%s})()", script));
         } catch (ScriptException e) {
@@ -54,53 +57,80 @@ public class HttpBizComponent implements IComponent {
         Router backendRouter = Router.router(vertx);
         backendRouter.route().handler(BodyHandler.create())
                 .handler(rc -> {
-                    Map<String, Object> httpHeader = getData(rc.request().headers());
-                    log.info("request header:{}", JsonUtil.toJsonString(httpHeader));
-                    Map<String, List<Object>> httpParams = getListData(rc.request().params());
-                    log.info("request params:{}", JsonUtil.toJsonString(httpParams));
-
-                    String contentType = rc.request().headers().get("Content-Type");
-                    JsonObject responseHeader = new JsonObject();
-                    if ("application/json".equals(contentType)) {
-                        String bodyStr = rc.toString();
-                        Map body = JsonUtil.parse(bodyStr, Map.class);
-                        log.info("request body:{}", bodyStr);
-
-                        String response = "unknown error";
-                        String name = "onReceive";
-                        if (((ScriptObjectMirror) scriptObj).get(name) != null) {
-                            try {
-                                Object result = engine.invokeMethod(scriptObj, name, body);
-                                Object resultObj = JsonUtil.toObject((ScriptObjectMirror) result);
-                                if (resultObj instanceof Map) {
-                                    JsonObject data = JsonObject.mapFrom(resultObj);
-                                    responseHeader = data.getJsonObject("header");
-                                    response = data.getString("content");
+                    try {
+                        Map<String, Object> httpHeader = getData(rc.request().headers());
+                        log.info("request header:{}", JsonUtil.toJsonString(httpHeader));
+                        Map<String, List<Object>> httpParams = getListData(rc.request().params());
+                        log.info("request params:{}", JsonUtil.toJsonString(httpParams));
+
+                        HttpServerRequest httpRequest = rc.request();
+                        String contentType = httpRequest.headers().get("Content-Type");
+                        JsonObject responseHeader = new JsonObject();
+                        if ("application/json".equals(contentType)) {
+                            String bodyStr = rc.getBody().toString();
+                            Map body = JsonUtil.parse(bodyStr, Map.class);
+                            log.info("request body:{}", bodyStr);
+
+                            String response = "unknown error";
+                            String name = "onReceive";
+                            if (((ScriptObjectMirror) scriptObj).get(name) != null) {
+                                try {
+                                    Object result = engine.invokeMethod(scriptObj,
+                                            name,
+                                            httpRequest.method().name(),
+                                            httpRequest.path(),
+                                            httpHeader,
+                                            httpParams,
+                                            body);
+                                    Object resultObj = JsonUtil.toObject((ScriptObjectMirror) result);
+                                    if (resultObj instanceof Map) {
+                                        JsonObject data = JsonObject.mapFrom(resultObj);
+                                        responseHeader = data.getJsonObject("header");
+                                        response = data.getString("content");
+                                        response = response == null ? "" : response;
+                                    }
+                                } catch (Throwable e) {
+                                    log.error("invokeMethod onReceive error", e);
+                                    response = e.getMessage();
                                 }
-                            } catch (Throwable e) {
-                                log.error("invokeMethod onReceive error", e);
-                                response = e.getMessage();
+                            } else {
+                                log.error("required [onReceive] method");
                             }
+
+                            HttpServerResponse httpServerResponse = rc.response();
+                            //设置响应头
+                            responseHeader.getMap().forEach((key, value) -> {
+                                //大写转换
+                                key = key.replaceAll("([A-Z])", "-$1").toLowerCase();
+                                httpServerResponse.putHeader(key, value.toString());
+                            });
+
+                            log.info("response,header:{},content:{}", responseHeader, response);
+                            //设置响应内容
+                            httpServerResponse
+                                    .end(response);
                         } else {
-                            log.error("required [onReceive] method");
+                            rc.response().end("");
                         }
+                    } catch (Throwable e) {
+                        log.error("handle request error", e);
+                        rc.response().end("server error:" + e.getMessage());
+                    }
+                });
 
-                        HttpServerResponse httpServerResponse = rc.response();
-                        //设置响应头
-                        responseHeader.getMap().forEach((key, value) -> {
-                            //大写转换
-                            key = key.replaceAll("([A-Z])", "-$1").toLowerCase();
-                            httpServerResponse.putHeader(key, value.toString());
-                        });
-
-                        log.info("response,header:{},content:{}", responseHeader, response);
-                        //设置响应内容
-                        httpServerResponse
-                                .end(response);
+        backendServer.requestHandler(backendRouter)
+                .listen(httpConfig.getPort(), (http) -> {
+                    if (http.succeeded()) {
+                        log.info("http server create succeed,port:{}", httpConfig.getPort());
+                    } else {
+                        log.error("http server create failed", http.cause());
                     }
                 });
+    }
 
-        backendServer.requestHandler(backendRouter).listen(config.getPort());
+    @Override
+    public void putScriptEnv(String key, Object value) {
+        engine.put(key, value);
     }
 
     @Override

+ 36 - 0
protocol-gateway/http-biz-component/src/main/java/cc/iotkit/comp/biz/HttpConfig.java

@@ -0,0 +1,36 @@
+//引用api工具类
+var apiTool = Java.type("cc.iotkit.comp.biz.ApiTool");
+//api配置
+apiTool.config("http://localhost",8086,3000);
+
+this.onReceive=function(method,path,header,params,body){
+  //method:post、get、delete...
+  //path:请求路径
+  //header:http请求头数据,结构:{xx:xx,yy:yy}
+  //params:请求参数,结构:{xx:[...],yy:[...]}
+  //body:请求体,当提交的数据为json格式时使用,结构:{xx:xx,yy:yy}
+  apiTool.log("onReceive method:"+method);
+  apiTool.log("onReceive path:"+path);
+  apiTool.log("onReceive header:"+header);
+  apiTool.log("onReceive params:"+params);
+  apiTool.log("onReceive body:"+body);
+  var duHeader=body.header;
+  var namespace=duHeader.namespace;
+  var requestName=duHeader.name;
+  var messageId=duHeader.messageId;
+  var duPayload=duHeader.payload;
+  var token=duHeader.accessToken;
+
+  //设备发现
+  if(namespace=="DuerOS.ConnectedHome.Discovery" && requestName=="DiscoverAppliancesRequest"){
+
+  }
+
+  return {
+    url:"xx",//不指定直接作为响应返回
+    header:{
+      contentType:"xx"
+    },
+    content:"xx"
+  }
+}

+ 1 - 0
protocol-gateway/http-biz-component/src/main/resources/component.spi

@@ -30,6 +30,7 @@ public class Application {
             gateway2.addSubDevice("cGCrkK7Ex4FESAwe", "ABE12300001", "S1");
             gateway2.addSubDevice("cGCrkK7Ex4FESAwe", "ABE12300002", "S1");
             gateway2.addSubDevice("6kYp6jszrDns2yh4", "ABE12400001", "S1");
+            gateway2.addSubDevice("", "ABE12500001", "M1");
             gateway2.start();
         }).start();
 

+ 7 - 1
protocol-gateway/mqtt-client-simulator/src/main/java/cc/iotkit/simulator/service/Gateway.java

@@ -6,6 +6,7 @@ import cc.iotkit.simulator.config.Mqtt;
 import lombok.*;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.eclipse.paho.client.mqttv3.*;
 import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
 
@@ -120,9 +121,14 @@ public class Gateway extends Device {
                     //子设备注册成功
                     if (response.code == 0) {
                         Map<String, Object> data = response.getData();
+                        String productKey = data.get("productKey").toString();
+                        if (StringUtils.isBlank(productKey)) {
+                            return;
+                        }
+
                         //订阅子设备消息
                         String subTopic = String.format("/sys/%s/%s/c/#",
-                                data.get("productKey"), data.get("deviceName"));
+                                productKey, data.get("deviceName"));
                         log.info("subscribe topic:{}", subTopic);
                         client.subscribe(subTopic);
                     }

+ 15 - 2
protocol-gateway/mqtt-component/dependency-reduced-pom.xml

@@ -26,6 +26,7 @@
               <include>io.vertx:vertx-core</include>
               <include>io.vertx:vertx-mqtt</include>
               <include>io.netty:netty-codec-mqtt</include>
+              <include>org.luaj:luaj-jse</include>
             </includes>
           </artifactSet>
         </configuration>
@@ -43,13 +44,13 @@
     <dependency>
       <groupId>io.vertx</groupId>
       <artifactId>vertx-core</artifactId>
-      <version>4.2.6</version>
+      <version>4.2.2</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>io.vertx</groupId>
       <artifactId>vertx-mqtt</artifactId>
-      <version>4.2.6</version>
+      <version>4.2.2</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
@@ -70,6 +71,12 @@
       <version>1.7.32</version>
       <scope>compile</scope>
     </dependency>
+    <dependency>
+      <groupId>org.luaj</groupId>
+      <artifactId>luaj-jse</artifactId>
+      <version>3.0.1</version>
+      <scope>provided</scope>
+    </dependency>
     <dependency>
       <groupId>cc.iotkit</groupId>
       <artifactId>common</artifactId>
@@ -82,5 +89,11 @@
       <version>0.1.0-SNAPSHOT</version>
       <scope>compile</scope>
     </dependency>
+    <dependency>
+      <groupId>cc.iotkit</groupId>
+      <artifactId>dao</artifactId>
+      <version>0.1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
   </dependencies>
 </project>

+ 11 - 0
protocol-gateway/mqtt-component/pom.xml

@@ -38,6 +38,11 @@
             <artifactId>slf4j-api</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.luaj</groupId>
+            <artifactId>luaj-jse</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>cc.iotkit</groupId>
             <artifactId>common</artifactId>
@@ -48,6 +53,11 @@
             <artifactId>component</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cc.iotkit</groupId>
+            <artifactId>dao</artifactId>
+        </dependency>
+
     </dependencies>
 
     <build>
@@ -70,6 +80,7 @@
                             <include>io.vertx:vertx-core</include>
                             <include>io.vertx:vertx-mqtt</include>
                             <include>io.netty:netty-codec-mqtt</include>
+                            <include>org.luaj:luaj-jse</include>
                         </includes>
                     </artifactSet>
                 </configuration>

+ 20 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/IScripter.java

@@ -0,0 +1,20 @@
+package cc.iotkit.comp.mqtt;
+
+import cc.iotkit.converter.DeviceMessage;
+import cc.iotkit.converter.ThingService;
+import cc.iotkit.model.device.message.ThingModelMessage;
+
+public interface IScripter {
+
+    void setScript(String script);
+
+    /**
+     * 透传解码
+     */
+    ThingModelMessage decode(TransparentMsg msg);
+
+    /**
+     * 透传编码
+     */
+    TransparentMsg encode(ThingService<?> service);
+}

+ 19 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/JsScripter.java

@@ -0,0 +1,19 @@
+package cc.iotkit.comp.mqtt;
+
+import cc.iotkit.converter.ThingService;
+import cc.iotkit.model.device.message.ThingModelMessage;
+
+public class JsScripter implements IScripter {
+
+    @Override
+    public void setScript(String script) {
+    }
+
+    public ThingModelMessage decode(TransparentMsg msg) {
+        return null;
+    }
+
+    public TransparentMsg encode(ThingService<?> service) {
+        return null;
+    }
+}

+ 126 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/LuaScripter.java

@@ -0,0 +1,126 @@
+package cc.iotkit.comp.mqtt;
+
+import cc.iotkit.converter.ThingService;
+import cc.iotkit.model.device.message.ThingModelMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.beanutils.BeanUtils;
+import org.luaj.vm2.LuaTable;
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.script.LuaScriptEngine;
+
+import javax.script.Compilable;
+import javax.script.CompiledScript;
+import javax.script.ScriptEngineManager;
+import javax.script.SimpleBindings;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+public class LuaScripter implements IScripter {
+
+    private final LuaScriptEngine engine = (LuaScriptEngine) (
+            new ScriptEngineManager().getEngineByName("luaj"));
+
+    private LuaValue decoder;
+    private LuaValue encoder;
+
+    @Override
+    public void setScript(String script) {
+        try {
+            CompiledScript compiledScript = ((Compilable) engine).compile(script);
+            SimpleBindings bindings = new SimpleBindings();
+            compiledScript.eval(bindings);
+            decoder = (LuaValue) bindings.get("decode");
+            encoder = (LuaValue) bindings.get("encode");
+        } catch (Throwable e) {
+            log.error("compile script error", e);
+        }
+    }
+
+    public ThingModelMessage decode(TransparentMsg msg) {
+        try {
+            LuaTable table = new LuaTable();
+            table.set("model", msg.getModel());
+            table.set("mac", msg.getMac());
+            table.set("data", msg.getData());
+            Map result = (Map) parse(decoder.call(table));
+            ThingModelMessage modelMessage = new ThingModelMessage();
+            BeanUtils.populate(modelMessage, result);
+
+            modelMessage.setProductKey(msg.getProductKey());
+            modelMessage.setDeviceName(msg.getMac());
+            return modelMessage;
+        } catch (Throwable e) {
+            log.error("execute decode script error", e);
+        }
+        return null;
+    }
+
+    public TransparentMsg encode(ThingService<?> service) {
+        try {
+            LuaTable table = new LuaTable();
+            table.set("identifier", service.getIdentifier());
+            table.set("type", service.getType());
+            table.set("productKey", service.getProductKey());
+            table.set("deviceName", service.getDeviceName());
+            table.set("mid", service.getMid());
+            Object params = service.getParams();
+            LuaTable tableParams = new LuaTable();
+            if (params instanceof Map) {
+                ((Map<?, ?>) params).forEach((key, val) -> tableParams.set(key.toString(), parse(val)));
+            }
+            table.set("params", tableParams);
+            LuaValue result = encoder.call(table);
+            Map map = (Map) parse(result);
+            TransparentMsg message = new TransparentMsg();
+            BeanUtils.populate(message, map);
+            return message;
+        } catch (Throwable e) {
+            log.error("execute encode script error", e);
+        }
+        return null;
+    }
+
+    private Object parse(LuaValue value) {
+        String type = value.typename();
+        switch (type) {
+            case "string":
+                return value.toString();
+            case "number":
+            case "int":
+                return value.toint();
+            case "table":
+                Map<String, Object> data = new HashMap<>();
+                LuaTable table = (LuaTable) value;
+                int arrLen = table.rawlen();
+                if (arrLen > 0) {
+                    //数组转换
+                    List<Object> list = new ArrayList<>();
+                    for (LuaValue key : table.keys()) {
+                        list.add(parse(table.get(key)));
+                    }
+                    return list;
+                } else {
+                    //map转换
+                    for (LuaValue key : table.keys()) {
+                        data.put(key.toString(), parse(table.get(key)));
+                    }
+                }
+                return data;
+        }
+        return null;
+    }
+
+    private LuaValue parse(Object value) {
+        if (value instanceof String) {
+            return LuaValue.valueOf(value.toString());
+        }
+        if (value instanceof Integer) {
+            return LuaValue.valueOf((Integer) value);
+        }
+        return new LuaTable();
+    }
+
+}

+ 20 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/MqttDeviceComponent.java

@@ -6,12 +6,15 @@ import cc.iotkit.comp.AbstractDeviceComponent;
 import cc.iotkit.comp.CompConfig;
 import cc.iotkit.comp.model.DeviceState;
 import cc.iotkit.converter.DeviceMessage;
+import cc.iotkit.converter.ThingService;
+import cc.iotkit.model.device.message.ThingModelMessage;
 import io.vertx.core.Future;
 import io.vertx.core.Vertx;
 import lombok.*;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.beanutils.BeanUtils;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
@@ -24,6 +27,7 @@ public class MqttDeviceComponent extends AbstractDeviceComponent {
     private String deployedId;
     private MqttVerticle mqttVerticle;
     private final Map<String, Device> deviceChildToParent = new HashMap<>();
+    private TransparentConverter transparentConverter = new TransparentConverter();
 
     public void create(CompConfig config) {
         super.create(config);
@@ -122,6 +126,22 @@ public class MqttDeviceComponent extends AbstractDeviceComponent {
         return config;
     }
 
+    /**
+     * 透传解码
+     */
+    public ThingModelMessage transparentDecode(Map<String, Object> msg) throws InvocationTargetException, IllegalAccessException {
+        TransparentMsg transparentMsg = new TransparentMsg();
+        BeanUtils.populate(transparentMsg, msg);
+        return transparentConverter.decode(transparentMsg);
+    }
+
+    /**
+     * 透传编码
+     */
+    public DeviceMessage transparentEncode(ThingService<?> service, cc.iotkit.converter.Device device) {
+        return transparentConverter.encode(service, device);
+    }
+
     @Data
     public static class Message {
         private String topic;

+ 88 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentConverter.java

@@ -0,0 +1,88 @@
+package cc.iotkit.comp.mqtt;
+
+
+import cc.iotkit.converter.Device;
+import cc.iotkit.converter.DeviceMessage;
+import cc.iotkit.converter.ThingService;
+import cc.iotkit.dao.DeviceCache;
+import cc.iotkit.dao.ProductCache;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.device.message.ThingModelMessage;
+import cc.iotkit.model.product.ProductModel;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+public class TransparentConverter {
+
+    private final Map<String, IScripter> scripters = new HashMap<>();
+    private final Map<String, String> scripts = new HashMap<>();
+
+    /**
+     * 透传解码
+     */
+    public ThingModelMessage decode(TransparentMsg msg) {
+        //通过上报消息中的model取得对应的产品
+        String productKey = checkScriptUpdate(msg.getModel());
+        msg.setProductKey(productKey);
+        return scripters.get(productKey).decode(msg);
+    }
+
+    /**
+     * 透传编码
+     */
+    public DeviceMessage encode(ThingService<?> service, Device device) {
+        String productKey = service.getProductKey();
+        checkScriptUpdate(device.getModel());
+        TransparentMsg transparentMsg = scripters.get(productKey).encode(service);
+        //转换成网关消息
+        String deviceName = service.getDeviceName();
+        DeviceInfo gateway = getGatewayInfo(productKey, deviceName);
+        DeviceMessage message = new DeviceMessage();
+        message.setProductKey(gateway.getProductKey());
+        message.setDeviceName(gateway.getDeviceName());
+        message.setMid(transparentMsg.getMid());
+        //透传格式消息内容,mac、model、data
+        message.setContent(transparentMsg);
+        return message;
+    }
+
+    private ProductModel getScript(String model) {
+        return ProductCache.getInstance().getProductScriptByModel(model);
+    }
+
+    private DeviceInfo getGatewayInfo(String subPk, String subDn) {
+        String parentId = DeviceCache.getInstance().getDeviceInfo(subPk, subDn).getParentId();
+        return DeviceCache.getInstance().get(parentId);
+    }
+
+    /**
+     * 检查产品脚本是否更新
+     */
+    private String checkScriptUpdate(String model) {
+        ProductModel productModel = getScript(model);
+        String productKey = productModel.getProductKey();
+        String script = productModel.getScript();
+
+        String oldScript = scripts.get(productKey);
+        if (script.equals(oldScript)) {
+            return productKey;
+        }
+
+        String type = productModel.getType();
+        if (ProductModel.TYPE_LUA.equals(type)) {
+            scripters.putIfAbsent(productKey, new LuaScripter());
+        } else if (ProductModel.TYPE_JS.equals(type)) {
+            scripters.putIfAbsent(productKey, new JsScripter());
+        }
+
+        //更新脚本
+        IScripter scripter = scripters.get(productKey);
+        scripter.setScript(script);
+        scripts.put(productKey, script);
+        return productKey;
+    }
+
+}

+ 21 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentMsg.java

@@ -0,0 +1,21 @@
+package cc.iotkit.comp.mqtt;
+
+import lombok.Data;
+
+@Data
+public class TransparentMsg {
+
+    private String productKey;
+
+    /**
+     * 生成给设备端的消息id
+     */
+    private String mid;
+
+    private String model;
+
+    private String mac;
+
+    private String data;
+
+}

+ 2 - 2
rule-engine/src/main/java/cc/iotkit/ruleengine/filter/DeviceCondition.java

@@ -38,10 +38,10 @@ public class DeviceCondition {
         String[] pkDn = device.split("/");
         if (pkDn.length < 2) {
             //用deviceId取
-            deviceInfo = deviceCache.findByDeviceId(device);
+            deviceInfo = deviceCache.get(device);
         } else {
             //用pk/dn取
-            deviceInfo = deviceCache.findByProductKeyAndDeviceName(pkDn[0], pkDn[1]);
+            deviceInfo = deviceCache.getDeviceInfo(pkDn[0], pkDn[1]);
         }
         Object left = null;
         if ("property".equals(type)) {

+ 2 - 2
standalone-package/pom.xml

@@ -18,8 +18,8 @@
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>3.0</version>
                 <configuration>
-                    <source>1.8</source>
-                    <target>1.8</target>
+                    <source>11</source>
+                    <target>11</target>
                     <encoding>UTF-8</encoding>
                 </configuration>
             </plugin>