Browse Source

增加设备配置定义和物模型操作接口

zhou-hao 4 years ago
parent
commit
b383c075c0

+ 44 - 0
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/service/DefaultDeviceConfigMetadataManager.java

@@ -0,0 +1,44 @@
+package org.jetlinks.community.device.service;
+
+import org.jetlinks.community.device.spi.DeviceConfigMetadataSupplier;
+import org.jetlinks.core.metadata.ConfigMetadata;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+
+import javax.annotation.Nonnull;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+@Component
+public class DefaultDeviceConfigMetadataManager implements DeviceConfigMetadataManager, BeanPostProcessor {
+
+    private final List<DeviceConfigMetadataSupplier> suppliers = new CopyOnWriteArrayList<>();
+
+    protected void register(DeviceConfigMetadataSupplier supplier) {
+        suppliers.add(supplier);
+    }
+
+    @Override
+    public Flux<ConfigMetadata> getDeviceConfigMetadata(String deviceId) {
+        return Flux.fromIterable(suppliers)
+                   .flatMap(supplier -> supplier.getDeviceConfigMetadata(deviceId))
+                   .sort(Comparator.comparing(ConfigMetadata::getName));
+    }
+
+    @Override
+    public Flux<ConfigMetadata> getProductConfigMetadata(String productId) {
+        return Flux.fromIterable(suppliers)
+                   .flatMap(supplier -> supplier.getProductConfigMetadata(productId))
+                   .sort(Comparator.comparing(ConfigMetadata::getName));
+    }
+
+    @Override
+    public Object postProcessAfterInitialization(@Nonnull Object bean, @Nonnull String beanName) {
+        if (bean instanceof DeviceConfigMetadataSupplier) {
+            register(((DeviceConfigMetadataSupplier) bean));
+        }
+        return bean;
+    }
+}

+ 46 - 0
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/service/DefaultDeviceConfigMetadataSupplier.java

@@ -0,0 +1,46 @@
+package org.jetlinks.community.device.service;
+
+import lombok.AllArgsConstructor;
+import org.jetlinks.core.ProtocolSupports;
+import org.jetlinks.core.message.codec.DefaultTransport;
+import org.jetlinks.core.metadata.ConfigMetadata;
+import org.jetlinks.core.metadata.DeviceConfigScope;
+import org.jetlinks.community.device.entity.DeviceInstanceEntity;
+import org.jetlinks.community.device.spi.DeviceConfigMetadataSupplier;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+
+@Component
+@AllArgsConstructor
+public class DefaultDeviceConfigMetadataSupplier implements DeviceConfigMetadataSupplier {
+
+    private final LocalDeviceInstanceService instanceService;
+
+    private final LocalDeviceProductService productService;
+
+    private final ProtocolSupports protocolSupports;
+
+    @Override
+    public Flux<ConfigMetadata> getDeviceConfigMetadata(String deviceId) {
+
+        return instanceService
+            .findById(deviceId)
+            .map(DeviceInstanceEntity::getProductId)
+            .flatMapMany(this::getProductConfigMetadata0)
+            .filter(metadata -> metadata.hasScope(DeviceConfigScope.device));
+    }
+
+    @Override
+    public Flux<ConfigMetadata> getProductConfigMetadata(String productId) {
+        return getProductConfigMetadata0(productId)
+            .filter(metadata -> metadata.hasScope(DeviceConfigScope.product));
+    }
+
+    private Flux<ConfigMetadata> getProductConfigMetadata0(String productId) {
+        return productService
+            .findById(productId)
+            .flatMapMany(product -> protocolSupports
+                .getProtocol(product.getMessageProtocol())
+                .flatMap(support -> support.getConfigMetadata(DefaultTransport.valueOf(product.getTransportProtocol()))));
+    }
+}

+ 31 - 0
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/service/DeviceConfigMetadataManager.java

@@ -0,0 +1,31 @@
+package org.jetlinks.community.device.service;
+
+import org.jetlinks.core.metadata.ConfigMetadata;
+import reactor.core.publisher.Flux;
+
+/**
+ * 设备配置信息管理
+ *
+ * @author zhouhao
+ * @since 1.6
+ */
+public interface DeviceConfigMetadataManager {
+
+    /**
+     * 根据设备ID获取配置信息
+     *
+     * @param deviceId 产品ID
+     * @return 配置信息
+     */
+    Flux<ConfigMetadata> getDeviceConfigMetadata(String deviceId);
+
+    /**
+     * 根据产品ID获取配置信息
+     *
+     * @param productId 产品ID
+     * @return 配置信息
+     */
+    Flux<ConfigMetadata> getProductConfigMetadata(String productId);
+
+
+}

+ 12 - 0
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/spi/DeviceConfigMetadataSupplier.java

@@ -0,0 +1,12 @@
+package org.jetlinks.community.device.spi;
+
+import org.jetlinks.core.metadata.ConfigMetadata;
+import reactor.core.publisher.Flux;
+
+public interface DeviceConfigMetadataSupplier {
+
+    Flux<ConfigMetadata> getDeviceConfigMetadata(String deviceId);
+
+    Flux<ConfigMetadata> getProductConfigMetadata(String productId);
+
+}

+ 91 - 71
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/web/DeviceInstanceController.java

@@ -25,6 +25,7 @@ import org.jetlinks.community.device.enums.DeviceState;
 import org.jetlinks.community.device.response.DeviceDeployResult;
 import org.jetlinks.community.device.response.DeviceDeployResult;
 import org.jetlinks.community.device.response.DeviceDetail;
 import org.jetlinks.community.device.response.DeviceDetail;
 import org.jetlinks.community.device.response.ImportDeviceInstanceResult;
 import org.jetlinks.community.device.response.ImportDeviceInstanceResult;
+import org.jetlinks.community.device.service.DeviceConfigMetadataManager;
 import org.jetlinks.community.device.service.LocalDeviceInstanceService;
 import org.jetlinks.community.device.service.LocalDeviceInstanceService;
 import org.jetlinks.community.device.service.LocalDeviceProductService;
 import org.jetlinks.community.device.service.LocalDeviceProductService;
 import org.jetlinks.community.device.service.data.DeviceDataService;
 import org.jetlinks.community.device.service.data.DeviceDataService;
@@ -87,19 +88,23 @@ public class DeviceInstanceController implements
 
 
     private final DeviceDataService deviceDataService;
     private final DeviceDataService deviceDataService;
 
 
+    private final DeviceConfigMetadataManager metadataManager;
+
     @SuppressWarnings("all")
     @SuppressWarnings("all")
     public DeviceInstanceController(LocalDeviceInstanceService service,
     public DeviceInstanceController(LocalDeviceInstanceService service,
                                     DeviceRegistry registry,
                                     DeviceRegistry registry,
                                     LocalDeviceProductService productService,
                                     LocalDeviceProductService productService,
                                     ImportExportService importExportService,
                                     ImportExportService importExportService,
                                     ReactiveRepository<DeviceTagEntity, String> tagRepository,
                                     ReactiveRepository<DeviceTagEntity, String> tagRepository,
-                                    DeviceDataService deviceDataService) {
+                                    DeviceDataService deviceDataService,
+                                    DeviceConfigMetadataManager metadataManager) {
         this.service = service;
         this.service = service;
         this.registry = registry;
         this.registry = registry;
         this.productService = productService;
         this.productService = productService;
         this.importExportService = importExportService;
         this.importExportService = importExportService;
         this.tagRepository = tagRepository;
         this.tagRepository = tagRepository;
         this.deviceDataService = deviceDataService;
         this.deviceDataService = deviceDataService;
+        this.metadataManager = metadataManager;
     }
     }
 
 
 
 
@@ -111,6 +116,14 @@ public class DeviceInstanceController implements
         return service.getDeviceDetail(id);
         return service.getDeviceDetail(id);
     }
     }
 
 
+    //获取设备详情
+    @GetMapping("/{id:.+}/config-metadata")
+    @QueryAction
+    @Operation(summary = "获取设备需要的配置定义信息")
+    public Flux<ConfigMetadata> getDeviceConfigMetadata(@PathVariable @Parameter(description = "设备ID") String id) {
+        return metadataManager.getDeviceConfigMetadata(id);
+    }
+
     //获取设备运行状态
     //获取设备运行状态
     @GetMapping("/{id:.+}/state")
     @GetMapping("/{id:.+}/state")
     @QueryAction
     @QueryAction
@@ -208,8 +221,8 @@ public class DeviceInstanceController implements
     public Mono<DeviceProperty> getDeviceLatestProperty(@PathVariable @Parameter(description = "设备ID") String deviceId,
     public Mono<DeviceProperty> getDeviceLatestProperty(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                         @PathVariable @Parameter(description = "属性ID") String property) {
                                                         @PathVariable @Parameter(description = "属性ID") String property) {
         return deviceDataService.queryEachOneProperties(deviceId, QueryParamEntity.of(), property)
         return deviceDataService.queryEachOneProperties(deviceId, QueryParamEntity.of(), property)
-            .take(1)
-            .singleOrEmpty()
+                                .take(1)
+                                .singleOrEmpty()
             ;
             ;
     }
     }
 
 
@@ -274,10 +287,10 @@ public class DeviceInstanceController implements
     public Mono<Void> deleteDeviceTag(@PathVariable @Parameter(description = "设备ID") String deviceId,
     public Mono<Void> deleteDeviceTag(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                       @PathVariable @Parameter(description = "标签ID") String tagId) {
                                       @PathVariable @Parameter(description = "标签ID") String tagId) {
         return tagRepository.createDelete()
         return tagRepository.createDelete()
-            .where(DeviceTagEntity::getDeviceId, deviceId)
-            .and(DeviceTagEntity::getId, tagId)
-            .execute()
-            .then();
+                            .where(DeviceTagEntity::getDeviceId, deviceId)
+                            .and(DeviceTagEntity::getId, tagId)
+                            .execute()
+                            .then();
     }
     }
 
 
     /**
     /**
@@ -292,7 +305,7 @@ public class DeviceInstanceController implements
     @Operation(summary = "批量删除设备")
     @Operation(summary = "批量删除设备")
     public Mono<Integer> deleteBatch(@RequestBody Mono<List<String>> idList) {
     public Mono<Integer> deleteBatch(@RequestBody Mono<List<String>> idList) {
         return idList.flatMapMany(Flux::fromIterable)
         return idList.flatMapMany(Flux::fromIterable)
-            .as(service::deleteById);
+                     .as(service::deleteById);
     }
     }
 
 
     /**
     /**
@@ -321,9 +334,9 @@ public class DeviceInstanceController implements
     @Operation(summary = "批量激活设备")
     @Operation(summary = "批量激活设备")
     public Mono<Integer> deployBatch(@RequestBody Mono<List<String>> idList) {
     public Mono<Integer> deployBatch(@RequestBody Mono<List<String>> idList) {
         return idList.flatMapMany(service::findById)
         return idList.flatMapMany(service::findById)
-            .as(service::deploy)
-            .map(DeviceDeployResult::getTotal)
-            .reduce(Math::addExact);
+                     .as(service::deploy)
+                     .map(DeviceDeployResult::getTotal)
+                     .reduce(Math::addExact);
     }
     }
 
 
     /**
     /**
@@ -349,8 +362,8 @@ public class DeviceInstanceController implements
     @Operation(summary = "获取设备全部标签数据")
     @Operation(summary = "获取设备全部标签数据")
     public Flux<DeviceTagEntity> getDeviceTags(@PathVariable @Parameter(description = "设备ID") String deviceId) {
     public Flux<DeviceTagEntity> getDeviceTags(@PathVariable @Parameter(description = "设备ID") String deviceId) {
         return tagRepository.createQuery()
         return tagRepository.createQuery()
-            .where(DeviceTagEntity::getDeviceId, deviceId)
-            .fetch();
+                            .where(DeviceTagEntity::getDeviceId, deviceId)
+                            .fetch();
     }
     }
 
 
     //保存设备标签
     //保存设备标签
@@ -379,21 +392,21 @@ public class DeviceInstanceController implements
                 product.getMetadata(),
                 product.getMetadata(),
                 product.getProtocol(),
                 product.getProtocol(),
                 productService.findById(productId))
                 productService.findById(productId))
-                .flatMap(tp3 -> {
-                    DeviceMetadata metadata = tp3.getT1();
-                    ProtocolSupport protocol = tp3.getT2();
-                    DeviceProductEntity entity = tp3.getT3();
-
-                    return protocol.getSupportedTransport()
-                        .collectList()
-                        .map(entity::getTransportEnum)
-                        .flatMap(Mono::justOrEmpty)
-                        .flatMap(protocol::getConfigMetadata)
-                        .map(ConfigMetadata::getProperties)
-                        .defaultIfEmpty(Collections.emptyList())
-                        .map(configs -> Tuples.of(entity, product, metadata, configs));
-
-                })
+                                    .flatMap(tp3 -> {
+                                        DeviceMetadata metadata = tp3.getT1();
+                                        ProtocolSupport protocol = tp3.getT2();
+                                        DeviceProductEntity entity = tp3.getT3();
+
+                                        return protocol.getSupportedTransport()
+                                                       .collectList()
+                                                       .map(entity::getTransportEnum)
+                                                       .flatMap(Mono::justOrEmpty)
+                                                       .flatMap(protocol::getConfigMetadata)
+                                                       .map(ConfigMetadata::getProperties)
+                                                       .defaultIfEmpty(Collections.emptyList())
+                                                       .map(configs -> Tuples.of(entity, product, metadata, configs));
+
+                                    })
             );
             );
 
 
     }
     }
@@ -423,12 +436,12 @@ public class DeviceInstanceController implements
             .buffer(100)//每100条数据保存一次
             .buffer(100)//每100条数据保存一次
             .publishOn(Schedulers.single())
             .publishOn(Schedulers.single())
             .concatMap(buffer ->
             .concatMap(buffer ->
-                Mono.zip(
-                    service.save(Flux.fromIterable(buffer).map(Tuple2::getT1)),
-                    tagRepository
-                        .save(Flux.fromIterable(buffer).flatMapIterable(Tuple2::getT2))
-                        .defaultIfEmpty(SaveResult.of(0, 0))
-                ))
+                           Mono.zip(
+                               service.save(Flux.fromIterable(buffer).map(Tuple2::getT1)),
+                               tagRepository
+                                   .save(Flux.fromIterable(buffer).flatMapIterable(Tuple2::getT2))
+                                   .defaultIfEmpty(SaveResult.of(0, 0))
+                           ))
             .map(res -> ImportDeviceInstanceResult.success(res.getT1()))
             .map(res -> ImportDeviceInstanceResult.success(res.getT1()))
             .onErrorResume(err -> Mono.just(ImportDeviceInstanceResult.error(err)));
             .onErrorResume(err -> Mono.just(ImportDeviceInstanceResult.error(err)));
     }
     }
@@ -441,15 +454,16 @@ public class DeviceInstanceController implements
                                              ServerHttpResponse response,
                                              ServerHttpResponse response,
                                              @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
                                              @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
         response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
         response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
-            "attachment; filename=".concat(URLEncoder.encode("设备导入模版." + format, StandardCharsets.UTF_8.displayName())));
+                                  "attachment; filename=".concat(URLEncoder.encode("设备导入模版." + format, StandardCharsets.UTF_8
+                                      .displayName())));
         return getDeviceProductDetail(productId)
         return getDeviceProductDetail(productId)
             .map(tp4 -> DeviceExcelInfo.getTemplateHeaderMapping(tp4.getT3().getTags(), tp4.getT4()))
             .map(tp4 -> DeviceExcelInfo.getTemplateHeaderMapping(tp4.getT3().getTags(), tp4.getT4()))
             .defaultIfEmpty(DeviceExcelInfo.getTemplateHeaderMapping(Collections.emptyList(), Collections.emptyList()))
             .defaultIfEmpty(DeviceExcelInfo.getTemplateHeaderMapping(Collections.emptyList(), Collections.emptyList()))
             .flatMapMany(headers ->
             .flatMapMany(headers ->
-                ReactorExcel.<DeviceExcelInfo>writer(format)
-                    .headers(headers)
-                    .converter(DeviceExcelInfo::toMap)
-                    .writeBuffer(Flux.empty()))
+                             ReactorExcel.<DeviceExcelInfo>writer(format)
+                                 .headers(headers)
+                                 .converter(DeviceExcelInfo::toMap)
+                                 .writeBuffer(Flux.empty()))
             .doOnError(err -> log.error(err.getMessage(), err))
             .doOnError(err -> log.error(err.getMessage(), err))
             .map(bufferFactory::wrap)
             .map(bufferFactory::wrap)
             .as(response::writeWith);
             .as(response::writeWith);
@@ -464,38 +478,41 @@ public class DeviceInstanceController implements
                              @Parameter(hidden = true) QueryParamEntity parameter,
                              @Parameter(hidden = true) QueryParamEntity parameter,
                              @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
                              @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
         response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
         response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
-            "attachment; filename=".concat(URLEncoder.encode("设备实例." + format, StandardCharsets.UTF_8.displayName())));
+                                  "attachment; filename=".concat(URLEncoder.encode("设备实例." + format, StandardCharsets.UTF_8
+                                      .displayName())));
         parameter.setPaging(false);
         parameter.setPaging(false);
         parameter.toNestQuery(q -> q.is(DeviceInstanceEntity::getProductId, productId));
         parameter.toNestQuery(q -> q.is(DeviceInstanceEntity::getProductId, productId));
         return getDeviceProductDetail(productId)
         return getDeviceProductDetail(productId)
             .map(tp4 -> DeviceExcelInfo.getExportHeaderMapping(tp4.getT3().getTags(), tp4.getT4()))
             .map(tp4 -> DeviceExcelInfo.getExportHeaderMapping(tp4.getT3().getTags(), tp4.getT4()))
             .defaultIfEmpty(DeviceExcelInfo.getExportHeaderMapping(Collections.emptyList(), Collections.emptyList()))
             .defaultIfEmpty(DeviceExcelInfo.getExportHeaderMapping(Collections.emptyList(), Collections.emptyList()))
             .flatMapMany(headers ->
             .flatMapMany(headers ->
-                ReactorExcel.<DeviceExcelInfo>writer(format)
-                    .headers(headers)
-                    .converter(DeviceExcelInfo::toMap)
-                    .writeBuffer(
-                        service.query(parameter)
-                            .map(entity -> {
-                                DeviceExcelInfo exportEntity = FastBeanCopier.copy(entity, new DeviceExcelInfo(),"state");
-                                exportEntity.setState(entity.getState().getText());
-                                return exportEntity;
-                            })
-                            .buffer(200)
-                            .flatMap(list -> {
-                                Map<String, DeviceExcelInfo> importInfo = list
-                                    .stream()
-                                    .collect(Collectors.toMap(DeviceExcelInfo::getId, Function.identity()));
-                                return tagRepository.createQuery()
-                                    .where()
-                                    .in(DeviceTagEntity::getDeviceId, importInfo.keySet())
-                                    .fetch()
-                                    .collect(Collectors.groupingBy(DeviceTagEntity::getDeviceId))
-                                    .flatMapIterable(Map::entrySet)
-                                    .doOnNext(entry -> importInfo.get(entry.getKey()).setTags(entry.getValue()))
-                                    .thenMany(Flux.fromIterable(list));
-                            })
-                        , 512 * 1024))//缓冲512k
+                             ReactorExcel.<DeviceExcelInfo>writer(format)
+                                 .headers(headers)
+                                 .converter(DeviceExcelInfo::toMap)
+                                 .writeBuffer(
+                                     service.query(parameter)
+                                            .map(entity -> {
+                                                DeviceExcelInfo exportEntity = FastBeanCopier.copy(entity, new DeviceExcelInfo(), "state");
+                                                exportEntity.setState(entity.getState().getText());
+                                                return exportEntity;
+                                            })
+                                            .buffer(200)
+                                            .flatMap(list -> {
+                                                Map<String, DeviceExcelInfo> importInfo = list
+                                                    .stream()
+                                                    .collect(Collectors.toMap(DeviceExcelInfo::getId, Function.identity()));
+                                                return tagRepository.createQuery()
+                                                                    .where()
+                                                                    .in(DeviceTagEntity::getDeviceId, importInfo.keySet())
+                                                                    .fetch()
+                                                                    .collect(Collectors.groupingBy(DeviceTagEntity::getDeviceId))
+                                                                    .flatMapIterable(Map::entrySet)
+                                                                    .doOnNext(entry -> importInfo
+                                                                        .get(entry.getKey())
+                                                                        .setTags(entry.getValue()))
+                                                                    .thenMany(Flux.fromIterable(list));
+                                            })
+                                     , 512 * 1024))//缓冲512k
             .doOnError(err -> log.error(err.getMessage(), err))
             .doOnError(err -> log.error(err.getMessage(), err))
             .map(bufferFactory::wrap)
             .map(bufferFactory::wrap)
             .as(response::writeWith);
             .as(response::writeWith);
@@ -510,7 +527,8 @@ public class DeviceInstanceController implements
                              @Parameter(hidden = true) QueryParamEntity parameter,
                              @Parameter(hidden = true) QueryParamEntity parameter,
                              @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
                              @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
         response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
         response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
-            "attachment; filename=".concat(URLEncoder.encode("设备实例." + format, StandardCharsets.UTF_8.displayName())));
+                                  "attachment; filename=".concat(URLEncoder.encode("设备实例." + format, StandardCharsets.UTF_8
+                                      .displayName())));
         return ReactorExcel.<DeviceExcelInfo>writer(format)
         return ReactorExcel.<DeviceExcelInfo>writer(format)
             .headers(DeviceExcelInfo.getExportHeaderMapping(Collections.emptyList(), Collections.emptyList()))
             .headers(DeviceExcelInfo.getExportHeaderMapping(Collections.emptyList(), Collections.emptyList()))
             .converter(DeviceExcelInfo::toMap)
             .converter(DeviceExcelInfo::toMap)
@@ -518,7 +536,7 @@ public class DeviceInstanceController implements
                 service
                 service
                     .query(parameter)
                     .query(parameter)
                     .map(entity -> {
                     .map(entity -> {
-                        DeviceExcelInfo exportEntity = FastBeanCopier.copy(entity, new DeviceExcelInfo(),"state");
+                        DeviceExcelInfo exportEntity = FastBeanCopier.copy(entity, new DeviceExcelInfo(), "state");
                         exportEntity.setState(entity.getState().getText());
                         exportEntity.setState(entity.getState().getText());
                         return exportEntity;
                         return exportEntity;
                     })
                     })
@@ -537,8 +555,8 @@ public class DeviceInstanceController implements
         return Mono
         return Mono
             .zip(registry.getDevice(deviceId), shadow)
             .zip(registry.getDevice(deviceId), shadow)
             .flatMap(tp2 -> tp2.getT1()
             .flatMap(tp2 -> tp2.getT1()
-                .setConfig(DeviceConfigKey.shadow, tp2.getT2())
-                .thenReturn(tp2.getT2()));
+                               .setConfig(DeviceConfigKey.shadow, tp2.getT2())
+                               .thenReturn(tp2.getT2()));
     }
     }
 
 
     //获取设备影子
     //获取设备影子
@@ -583,8 +601,10 @@ public class DeviceInstanceController implements
         return param
         return param
             .flatMapMany(request -> deviceDataService
             .flatMapMany(request -> deviceDataService
                 .aggregationPropertiesByDevice(deviceId,
                 .aggregationPropertiesByDevice(deviceId,
-                    request.getQuery(),
-                    request.getColumns().toArray(new DeviceDataService.DevicePropertyAggregation[0]))
+                                               request.getQuery(),
+                                               request
+                                                   .getColumns()
+                                                   .toArray(new DeviceDataService.DevicePropertyAggregation[0]))
             )
             )
             .map(AggregationData::values);
             .map(AggregationData::values);
     }
     }

+ 71 - 12
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/web/DeviceProductController.java

@@ -7,23 +7,21 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.Setter;
-import org.hswebframework.ezorm.core.param.QueryParam;
-import org.hswebframework.web.api.crud.entity.PagerResult;
 import org.hswebframework.web.authorization.annotation.QueryAction;
 import org.hswebframework.web.authorization.annotation.QueryAction;
 import org.hswebframework.web.authorization.annotation.Resource;
 import org.hswebframework.web.authorization.annotation.Resource;
 import org.hswebframework.web.authorization.annotation.SaveAction;
 import org.hswebframework.web.authorization.annotation.SaveAction;
 import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController;
 import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController;
 import org.jetlinks.community.device.entity.DeviceProductEntity;
 import org.jetlinks.community.device.entity.DeviceProductEntity;
+import org.jetlinks.community.device.service.DeviceConfigMetadataManager;
 import org.jetlinks.community.device.service.LocalDeviceProductService;
 import org.jetlinks.community.device.service.LocalDeviceProductService;
 import org.jetlinks.community.device.service.data.DeviceDataService;
 import org.jetlinks.community.device.service.data.DeviceDataService;
 import org.jetlinks.community.device.service.data.DeviceDataStoragePolicy;
 import org.jetlinks.community.device.service.data.DeviceDataStoragePolicy;
-import org.jetlinks.community.device.timeseries.DeviceTimeSeriesMetric;
 import org.jetlinks.community.device.web.request.AggRequest;
 import org.jetlinks.community.device.web.request.AggRequest;
-import org.jetlinks.community.timeseries.TimeSeriesData;
-import org.jetlinks.community.timeseries.TimeSeriesManager;
 import org.jetlinks.community.timeseries.query.AggregationData;
 import org.jetlinks.community.timeseries.query.AggregationData;
 import org.jetlinks.core.metadata.ConfigMetadata;
 import org.jetlinks.core.metadata.ConfigMetadata;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.jetlinks.core.metadata.DeviceMetadataCodec;
+import org.jetlinks.supports.official.JetLinksDeviceMetadataCodec;
+import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 import reactor.core.publisher.Mono;
@@ -37,21 +35,82 @@ import java.util.Map;
 @Tag(name = "设备产品接口")
 @Tag(name = "设备产品接口")
 public class DeviceProductController implements ReactiveServiceCrudController<DeviceProductEntity, String> {
 public class DeviceProductController implements ReactiveServiceCrudController<DeviceProductEntity, String> {
 
 
-    @Autowired
-    private LocalDeviceProductService productService;
+    private final LocalDeviceProductService productService;
 
 
-    @Autowired
-    private List<DeviceDataStoragePolicy> policies;
+    private final List<DeviceDataStoragePolicy> policies;
 
 
-    @Autowired
-    private DeviceDataService deviceDataService;
+    private final DeviceDataService deviceDataService;
 
 
+    private final DeviceConfigMetadataManager configMetadataManager;
+
+    private final ObjectProvider<DeviceMetadataCodec> metadataCodecs;
+
+    private final DeviceMetadataCodec defaultCodec = new JetLinksDeviceMetadataCodec();
+
+    public DeviceProductController(LocalDeviceProductService productService,
+                                   List<DeviceDataStoragePolicy> policies,
+                                   DeviceDataService deviceDataService,
+                                   DeviceConfigMetadataManager configMetadataManager,
+                                   ObjectProvider<DeviceMetadataCodec> metadataCodecs) {
+        this.productService = productService;
+        this.policies = policies;
+        this.deviceDataService = deviceDataService;
+        this.configMetadataManager = configMetadataManager;
+        this.metadataCodecs = metadataCodecs;
+    }
 
 
     @Override
     @Override
     public LocalDeviceProductService getService() {
     public LocalDeviceProductService getService() {
         return productService;
         return productService;
     }
     }
 
 
+    @GetMapping("/{id:.+}/config-metadata")
+    @QueryAction
+    @Operation(summary = "获取产品需要的配置定义信息")
+    public Flux<ConfigMetadata> getDeviceConfigMetadata(@PathVariable
+                                                        @Parameter(description = "产品ID") String id) {
+        return configMetadataManager.getProductConfigMetadata(id);
+    }
+
+    @GetMapping("/metadata/codecs")
+    @QueryAction
+    @Operation(summary = "获取支持的物模型格式")
+    public Flux<DeviceMetadataCodec> getMetadataCodec() {
+        return Flux.fromIterable(metadataCodecs);
+    }
+
+    @PostMapping("/metadata/convert-to/{id}")
+    @QueryAction
+    @Operation(summary = "转换平台的物模型为指定的物模型格式")
+    public Mono<String> convertMetadataTo(@RequestBody Mono<String> metadata,
+                                          @PathVariable String id) {
+
+        return metadata
+            .flatMap(str -> Flux
+                .fromIterable(metadataCodecs)
+                .filter(codec -> codec.getId().equals(id))
+                .next()
+                .flatMap(codec -> defaultCodec
+                    .decode(str)
+                    .flatMap(codec::encode)));
+    }
+
+    @PostMapping("/metadata/convert-from/{id}")
+    @QueryAction
+    @Operation(summary = "转换指定的物模型为平台的物模型格式")
+    public Mono<String> convertMetadataFrom(@RequestBody Mono<String> metadata,
+                                            @PathVariable String id) {
+
+        return metadata
+            .flatMap(str -> Flux
+                .fromIterable(metadataCodecs)
+                .filter(codec -> codec.getId().equals(id))
+                .next()
+                .flatMap(codec -> codec
+                    .decode(str)
+                    .flatMap(defaultCodec::encode)));
+    }
+
     @PostMapping("/{productId:.+}/deploy")
     @PostMapping("/{productId:.+}/deploy")
     @SaveAction
     @SaveAction
     @Operation(summary = "激活产品")
     @Operation(summary = "激活产品")