18339543638 4 éve
szülő
commit
d87800b7d6

+ 10 - 3
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/entity/DeviceInstanceEntity.java

@@ -79,13 +79,20 @@ public class DeviceInstanceEntity extends GenericEntity<String> implements Recor
     @Schema(description = "派生物模型(预留)")
     private String deriveMetadata;
 
+    @Comment("设备影子信息")
+    @Column(name = "shadow")
+    @ColumnType(jdbcType = JDBCType.CLOB)
+    @JsonCodec
+    @Schema(description = "设备影子信息")
+    private DeviceShadowEntity shadow;
+
     @Column(name = "state",length = 16)
     @EnumCodec
     @ColumnType(javaType = String.class)
     @DefaultValue("notActive")
     @Schema(
         description = "状态(只读)"
-        //,accessMode = Schema.AccessMode.READ_ONLY
+        ,accessMode = Schema.AccessMode.READ_ONLY
         , defaultValue = "notActive"
     )
     private DeviceState state;
@@ -93,14 +100,14 @@ public class DeviceInstanceEntity extends GenericEntity<String> implements Recor
     @Column(name = "creator_id", updatable = false)
     @Schema(
         description = "创建者ID(只读)"
-//        ,accessMode = Schema.AccessMode.READ_ONLY
+        ,accessMode = Schema.AccessMode.READ_ONLY
     )
     private String creatorId;
 
     @Column(name = "creator_name", updatable = false)
     @Schema(
         description = "创建者名称(只读)"
-        // ,accessMode = Schema.AccessMode.READ_ONLY
+         ,accessMode = Schema.AccessMode.READ_ONLY
     )
     private String creatorName;
 

+ 152 - 7
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/entity/DeviceShadowEntity.java

@@ -1,5 +1,24 @@
 package org.jetlinks.community.device.entity;
 
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType;
+import org.hswebframework.web.exception.BusinessException;
+import org.jetlinks.core.metadata.DataType;
+import org.jetlinks.core.metadata.DeviceMetadata;
+import org.jetlinks.core.metadata.PropertyMetadata;
+import javax.persistence.Column;
+import java.sql.JDBCType;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
 /**
  * @author lifang
  * @version 1.0.0
@@ -7,42 +26,168 @@ package org.jetlinks.community.device.entity;
  * @Description 设备影子
  * @createTime 2021年10月14日 09:36:00
  */
+@Data
 public class DeviceShadowEntity {
 
     /**
      * 设备状态
      */
-    private String state;
+    @Column
+    @ColumnType(jdbcType = JDBCType.CLOB)
+    @Schema(description = "影子状态")
+    private ShadowState state;
 
     /**
      * 元数据,仅限设备属性
      */
-    private String metadata;
+//    @Column
+//    @ColumnType(jdbcType = JDBCType.CLOB)
+    @Schema(description = "影子元数据")
+    private ShadowMetadata metadata;
+
+    /**
+     * 设置影子设备时间
+     */
+//    @Column
+//    @ColumnType(jdbcType = JDBCType.DATE)
+    @Schema(defaultValue = "更新时间戳")
+    private long timestamp;
 
+    {
+        timestamp=System.currentTimeMillis();
+    }
+//    @Version
+//    @Column
+//    @Schema(defaultValue = "版本号")
+//    private String version;
 
-    private String timestamp;
     /**
      * 校验数据格式
      * @return
      */
-    public boolean verify(){
-        return true;
+    public void validate(DeviceMetadata metadata){
+        boolean metadataIsNull = ObjectUtil.isNull(this.metadata);
+        boolean stateIsNull = ObjectUtil.isNull(this.state);
+        //状态和元数据只能同时为空或同时不为空
+        if(metadataIsNull!=stateIsNull){
+            throw new BusinessException("state和metadata只能同时为空或同时不为空");
+        }
+        Map<String, Object> stateDesired = this.state.desired;
+        Map<String, Object> stateReport = this.state.reported;
+        if(!metadataIsNull){
+            //数据存在则判断数据
+            Map<String, MetadataUpdateTime> metadataDesired = this.metadata.desired;
+            if (CollectionUtil.isNotEmpty(CollectionUtil.subtract(new ArrayList<>(stateDesired.keySet()),
+                new ArrayList<>(metadataDesired.keySet())))) {
+                throw new BusinessException("state.desired和metadata.desired的key值请保持一致");
+            }
+            Map<String, MetadataUpdateTime> metadataReport= this.metadata.reported;
+            if (CollectionUtil.isNotEmpty(CollectionUtil.subtract(new ArrayList<>(stateReport.keySet()),
+                new ArrayList<>(metadataReport.keySet())))) {
+                throw new BusinessException("state.reported和metadata.reported的key值请保持一致");
+            }
+        }
+
+        Set<String> desiredKeys = stateDesired.keySet();
+        for (String desiredKey : desiredKeys) {
+            PropertyMetadata propertyMetadata = metadata.getPropertyOrNull(desiredKey);
+            if(propertyMetadata==null){
+                throw new BusinessException(String.format("desired 中的 属性{%s}不存在",desiredKey));
+            }
+            DataType valueType = propertyMetadata.getValueType();
+            valueType.validate(stateDesired.get(desiredKey));
+        }
+
+        Set<String> reportedKeys = stateReport.keySet();
+
+        for (String reportedKey : reportedKeys) {
+            PropertyMetadata propertyMetadata = metadata.getPropertyOrNull(reportedKey);
+            if(propertyMetadata==null){
+                throw new BusinessException(String.format("report 中的 属性{%s}不存在",reportedKey));
+            }
+            DataType valueType = propertyMetadata.getValueType();
+            valueType.validate(stateDesired.get(reportedKey));
+        }
     }
 
 
     /**
      * 影子状态
      */
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
     public class ShadowState{
-
+        /**
+         * 平台向设备下发期望值
+         */
+        private Map<String,Object> desired;
+        /**
+         * 设备向平台上报期望值
+         */
+        private Map<String,Object> reported;
 
     }
 
     /**
-     * 影子元数据
+     * 影子元数据,存储更新时间
      */
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
     public class ShadowMetadata{
+        /**
+         * 平台向设备下发期望值
+         */
+        private Map<String,MetadataUpdateTime> desired;
+        /**
+         * 设备向平台上报期望值
+         */
+        private Map<String,MetadataUpdateTime> reported;
 
+    }
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public class MetadataUpdateTime{
+        private long timestamp;
+    }
 
+    public static void main(String[] args) {
+       String s="{\n" +
+           "    \"state\": {\n" +
+           "        \"desired\": {\n" +
+           "            \"color\": \"RED\", \n" +
+           "            \"sequence\": [\n" +
+           "                \"RED\", \n" +
+           "                \"GREEN\", \n" +
+           "                \"BLUE\"\n" +
+           "            ]\n" +
+           "        }, \n" +
+           "        \"reported\": {\n" +
+           "            \"color\": \"GREEN\"\n" +
+           "        }\n" +
+           "    }, \n" +
+           "    \"metadata\": {\n" +
+           "        \"desired\": {\n" +
+           "            \"color\": {\n" +
+           "                \"timestamp\": 1469564492\n" +
+           "            }, \n" +
+           "            \"sequence\": {\n" +
+           "                \"timestamp\": 1469564492\n" +
+           "            }\n" +
+           "        }, \n" +
+           "        \"reported\": {\n" +
+           "            \"color\": {\n" +
+           "                \"timestamp\": 1469564492\n" +
+           "            }\n" +
+           "        }\n" +
+           "    }, \n" +
+           "    \"timestamp\": 1469564492, \n" +
+           "    \"version\": 1\n" +
+           "}";
+        DeviceShadowEntity map = JSONUtil.toBean(s, DeviceShadowEntity.class);
+        System.out.println(map);
     }
 }

+ 31 - 13
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/web/DeviceInstanceController.java

@@ -1,9 +1,9 @@
 package org.jetlinks.community.device.web;
 
+import cn.hutool.json.JSONUtil;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
-import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
@@ -626,24 +626,42 @@ public class DeviceInstanceController implements
     @PutMapping("/{deviceId:.+}/shadow")
     @SaveAction
     @Operation(summary = "设置设备影子")
-    public Mono<String> setDeviceShadow(@PathVariable @Parameter(description = "设备ID") String deviceId,
-                                        @RequestBody Mono<String> shadow) {
-        return Mono
-            .zip(registry.getDevice(deviceId), shadow)
-            .flatMap(tp2 -> tp2.getT1()
-                .setConfig(DeviceConfigKey.shadow, tp2.getT2())
-                .thenReturn(tp2.getT2()));
+    public Mono<DeviceShadowEntity> setDeviceShadow(@PathVariable @Parameter(description = "设备ID") String deviceId,
+                                                    @RequestBody Mono<String> shadowStr) {
+        return shadowStr
+            .map(str->JSONUtil.toBean(str,DeviceShadowEntity.class))
+            .onErrorMap(Exception.class, err -> new BusinessException("JSON格式错误", err))
+            .zipWith(registry.getDevice(deviceId))
+            .flatMap(tp2->{
+                DeviceOperator operator = tp2.getT2();
+                DeviceShadowEntity shadow = tp2.getT1();
+                return operator.getMetadata()
+                    .doOnNext(shadow::validate)
+                    .zipWith(Mono.just(tp2.getT1()));
+            })
+            .flatMap(tp2->{
+                DeviceShadowEntity shadow = tp2.getT2();
+                return service.createUpdate()
+                    .where(DeviceInstanceEntity::getId,deviceId)
+                    .set(DeviceInstanceEntity::getShadow,shadow)
+                    .execute()
+                    .zipWith(Mono.just(tp2.getT2()));
+            })
+            .flatMap(tp2->Mono.just(tp2.getT2()));
     }
 
     //获取设备影子
     @GetMapping("/{deviceId:.+}/shadow")
     @SaveAction
     @Operation(summary = "获取设备影子")
-    public Mono<String> getDeviceShadow(@PathVariable @Parameter(description = "设备ID") String deviceId) {
-        return registry
-            .getDevice(deviceId)
-            .flatMap(operator -> operator.getSelfConfig(DeviceConfigKey.shadow))
-            .defaultIfEmpty("{\n}");
+    public Mono<DeviceShadowEntity> getDeviceShadow(@PathVariable @Parameter(description = "设备ID") String deviceId) {
+        return service.findById(deviceId)
+            .map(DeviceInstanceEntity::getShadow)
+            .defaultIfEmpty(new DeviceShadowEntity());
+//        return registry
+//            .getDevice(deviceId)
+//            .flatMap(operator -> operator.getSelfConfig(DeviceConfigKey.shadow))
+//            .defaultIfEmpty("{\n}");
     }
 
     //设置设备属性