diff --git a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/controller/ProductController.java b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/controller/ProductController.java index 5f610c5c..8104ae2c 100644 --- a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/controller/ProductController.java +++ b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/controller/ProductController.java @@ -12,6 +12,7 @@ import com.iteaj.framework.security.Logical; import com.iteaj.framework.spi.iot.*; import com.iteaj.iboot.module.iot.consts.DeviceType; import com.iteaj.iboot.module.iot.consts.FuncStatus; +import com.iteaj.iboot.module.iot.dto.BatchModelDto; import com.iteaj.iboot.module.iot.dto.ProductDto; import com.iteaj.iboot.module.iot.entity.Product; import com.iteaj.iboot.module.iot.service.IDeviceService; @@ -172,5 +173,35 @@ public class ProductController extends BaseController { public Result> resolvers() { return success(DataValueResolverFactory.options()); } + + /** + * 批量模型属性 + * @param entity + * @return + */ + @GetMapping("listBatchModel") + public Result> listBatchModel(BatchModelDto entity) { + return this.productService.listBatchModel(entity); + } + + /** + * 新增或者更新批量模型记录 + * @param entity + * @return + */ + @PostMapping("batchModelSaveOrUpdate") + public Result batchModelSaveOrUpdate(@RequestBody BatchModelDto entity) { + return this.productService.batchModelSaveOrUpdate(entity); + } + + /** + * 删除批量模型记录 + * @param entity id和apiCode必填 + * @return + */ + @PostMapping("batchModelDel") + public Result batchModelDel(@RequestBody BatchModelDto entity) { + return this.productService.delBatchModel(entity.getId(), entity.getApiCode()); + } } diff --git a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/controller/ProtocolController.java b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/controller/ProtocolController.java index 56c08ebc..0d68395b 100644 --- a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/controller/ProtocolController.java +++ b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/controller/ProtocolController.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.*; import java.util.stream.Collectors; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.iteaj.framework.IVOption; @@ -13,12 +14,16 @@ import com.iteaj.framework.result.Result; import com.iteaj.framework.security.Logical; import com.iteaj.framework.spi.file.UploadResult; import com.iteaj.framework.spi.file.UploadService; +import com.iteaj.framework.spi.iot.consts.ApiConfigDirection; +import com.iteaj.framework.spi.iot.consts.TriggerMode; +import com.iteaj.framework.spi.iot.protocol.AbstractProtocolModelApi; import com.iteaj.framework.spi.iot.protocol.ProtocolApiType; import com.iteaj.framework.spi.iot.protocol.ProtocolModel; import com.iteaj.framework.spi.iot.DeviceProtocolSupplier; import com.iteaj.framework.spi.iot.ProtocolSupplierManager; import com.iteaj.framework.spi.iot.consts.CtrlMode; import com.iteaj.framework.spi.iot.consts.ProtocolImplMode; +import com.iteaj.framework.spi.iot.protocol.ProtocolModelAttr; import com.iteaj.iboot.module.iot.dto.ProtocolDto; import com.iteaj.iboot.module.iot.entity.Product; import com.iteaj.iboot.module.iot.entity.ProtocolApi; @@ -275,5 +280,27 @@ public class ProtocolController extends BaseController { return fail("不支持的类型[type="+type+"]"); } } + + /** + * 返回各个驱动的事件模型的配置项 + * @return + */ + @GetMapping("listEventApiTableHead") + public Result>> listEventApiTableHead() { + Map> result = new HashMap<>(); + ProtocolSupplierManager.suppliers().forEach(supplier -> { + final ProtocolModel protocolModel = supplier.getProtocol(); + if(protocolModel.getCtrlMode() == CtrlMode.POINT) { + final Map map = supplier.resolverMetaConfig(modelApi -> { + final AbstractProtocolModelApi protocolModelApi = (AbstractProtocolModelApi) modelApi; + return protocolModelApi.getTriggerMode() == TriggerMode.passive + && protocolModelApi.getType() == ProtocolApiType.event; + }); + + result.put(protocolModel.getCode(), map.values()); + } + }); + return success(result); + } } diff --git a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/dto/BatchModelDto.java b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/dto/BatchModelDto.java index 649c5356..4738253a 100644 --- a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/dto/BatchModelDto.java +++ b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/dto/BatchModelDto.java @@ -1,17 +1,28 @@ package com.iteaj.iboot.module.iot.dto; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.annotation.SqlCondition; import com.baomidou.mybatisplus.annotation.TableField; import com.iteaj.framework.spi.iot.consts.AttrType; import lombok.Getter; import lombok.Setter; +import org.springframework.data.annotation.Transient; -import javax.validation.constraints.NotBlank; +import java.util.HashMap; +import java.util.Map; @Getter @Setter public class BatchModelDto { + /** + * 模型属性id + */ + private Long id; + + /** + * 产品id + */ private Long productId; /** @@ -70,4 +81,35 @@ public class BatchModelDto { * 协议指令 */ private String direct; + + /** + * 接口名 + */ + private String apiName; + + /** + * 接口代码 + */ + private String apiCode; + + /** + * 产品码 + */ + private String productCode; + + @Transient + private String apiDownConfig; + + private Map config; + + public void setApiDownConfig(String apiDownConfig) { + if(StrUtil.isNotBlank(apiDownConfig)) { + config = new HashMap<>(); + final String[] split = apiDownConfig.split(":::"); + for (int i = 0; i < split.length; i++) { + final String[] item = split[i].split("->"); + config.put(item[0], item[1]); + } + } + } } diff --git a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/mapper/ModelApiMapper.java b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/mapper/ModelApiMapper.java index 05b0399f..44af04b2 100644 --- a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/mapper/ModelApiMapper.java +++ b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/mapper/ModelApiMapper.java @@ -64,4 +64,6 @@ public interface ModelApiMapper extends BaseMapper { * @return */ boolean hasCtrlStatus(Long productId, String code); + + void removeJoinByCode(String code); } diff --git a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/mapper/ProductMapper.java b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/mapper/ProductMapper.java index 5bb15c56..0dd8878d 100644 --- a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/mapper/ProductMapper.java +++ b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/mapper/ProductMapper.java @@ -96,4 +96,6 @@ public interface ProductMapper extends BaseMapper { * @return */ List listOfProtocolCodes(DeviceType deviceType, String[] protocolCodes, TransportProtocol protocol); + + List listBatchModel(BatchModelDto entity); } diff --git a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/IModelApiService.java b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/IModelApiService.java index 31fd594b..63daf384 100644 --- a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/IModelApiService.java +++ b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/IModelApiService.java @@ -52,4 +52,6 @@ public interface IModelApiService extends IBaseService { DetailResult getAsStatusModelApi(Long productId); DetailResult detailByCode(String modelApiCode); + + void removeJoinByCode(String code); } diff --git a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/IProductService.java b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/IProductService.java index 9c5f4be8..5cb3ad99 100644 --- a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/IProductService.java +++ b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/IProductService.java @@ -131,4 +131,10 @@ public interface IProductService extends IBaseService { * @return */ ListResult listOfProtocolCodes(DeviceType deviceType, String[] protocolCodes, TransportProtocol protocol); + + ListResult listBatchModel(BatchModelDto entity); + + BooleanResult batchModelSaveOrUpdate(BatchModelDto entity); + + Result delBatchModel(Long id, String apiCode); } diff --git a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/impl/ModelApiServiceImpl.java b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/impl/ModelApiServiceImpl.java index f6e2e2c3..8f755f97 100644 --- a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/impl/ModelApiServiceImpl.java +++ b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/impl/ModelApiServiceImpl.java @@ -232,4 +232,12 @@ public class ModelApiServiceImpl extends BaseServiceImpl(modelApi); } + + @Override + public void removeJoinByCode(String code) { + if(StrUtil.isNotBlank(code)) { + this.getBaseMapper().removeJoinByCode(code); + cacheManager.getCache(CacheKeys.IBOOT_CACHE_KEY_MODEL_API).evict(code); + } + } } diff --git a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/impl/ProductServiceImpl.java b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/impl/ProductServiceImpl.java index 431aa964..950d611c 100644 --- a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/impl/ProductServiceImpl.java +++ b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/service/impl/ProductServiceImpl.java @@ -1,39 +1,30 @@ package com.iteaj.iboot.module.iot.service.impl; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.iteaj.framework.BaseServiceImpl; import com.iteaj.framework.exception.ServiceException; -import com.iteaj.framework.result.BooleanResult; -import com.iteaj.framework.result.DetailResult; -import com.iteaj.framework.result.ListResult; -import com.iteaj.framework.result.PageResult; +import com.iteaj.framework.result.*; import com.iteaj.framework.spi.iot.ClientProtocolSupplier; import com.iteaj.framework.spi.iot.DeviceProtocolSupplier; import com.iteaj.framework.spi.iot.ProtocolSupplierManager; -import com.iteaj.framework.spi.iot.consts.AttrType; -import com.iteaj.framework.spi.iot.consts.ProtocolImplMode; -import com.iteaj.framework.spi.iot.consts.TransportProtocol; +import com.iteaj.framework.spi.iot.consts.*; import com.iteaj.framework.spi.iot.listener.EntityPayload; import com.iteaj.framework.spi.iot.listener.IotEvenPublisher; import com.iteaj.framework.spi.iot.listener.IotEventType; -import com.iteaj.framework.spi.iot.protocol.ProtocolApiType; -import com.iteaj.framework.spi.iot.protocol.ProtocolModel; -import com.iteaj.framework.spi.iot.protocol.ProtocolModelApiConfig; -import com.iteaj.framework.spi.iot.protocol.ProtocolModelAttr; +import com.iteaj.framework.spi.iot.protocol.*; import com.iteaj.iboot.common.CacheKeys; import com.iteaj.iboot.module.iot.cache.IotCacheManager; import com.iteaj.iboot.module.iot.collect.model.DeviceStatusModelApiManager; import com.iteaj.iboot.module.iot.consts.*; -import com.iteaj.iboot.module.iot.dto.DeviceTypeCountDto; -import com.iteaj.iboot.module.iot.dto.FuncStatusProfileDto; -import com.iteaj.iboot.module.iot.dto.ProductDto; -import com.iteaj.iboot.module.iot.dto.ProtocolToProductModel; +import com.iteaj.iboot.module.iot.dto.*; import com.iteaj.iboot.module.iot.entity.*; import com.iteaj.iboot.module.iot.mapper.ProductMapper; import com.iteaj.iboot.module.iot.service.*; import com.iteaj.iboot.module.iot.utils.IotNetworkUtil; +import com.iteaj.iboot.module.iot.utils.IotUtil; import org.springframework.beans.BeanUtils; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; @@ -43,9 +34,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.io.Serializable; -import java.util.Collection; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; /** @@ -502,6 +491,108 @@ public class ProductServiceImpl extends BaseServiceImpl return new ListResult<>(getBaseMapper().listOfProtocolCodes(deviceType, protocolCodes, protocol)); } + @Override + public ListResult listBatchModel(BatchModelDto entity) { + return new ListResult<>(getBaseMapper().listBatchModel(entity)); + } + + @Override + @Transactional + public BooleanResult batchModelSaveOrUpdate(BatchModelDto entity) { + final Product product = this.getById(entity.getProductId()).getData(); + if(product == null) { + return BooleanResult.buildFalse("产品不存在"); + } + if(product.getStatus() == FuncStatus.enabled) { + return BooleanResult.buildFalse("请先停用产品"); + } + final DeviceProtocolSupplier supplier = ProtocolSupplierManager.get(product.getProtocolCode()); + if(supplier == null) { + return BooleanResult.buildFalse("驱动不存在"); + } + + // 处理模型属性 + final ModelAttr modelAttr = new ModelAttr(); + if(entity.getId() == null) {// 模型属性为null表示新增 + BeanUtils.copyProperties(entity, modelAttr); + modelAttr.setOrigin(AttrOrigin.Model).setCtrlStatus(false).setEnumerate(false); + modelAttrService.save(modelAttr).ifPresent(item -> entity.setId(modelAttr.getId())); + } else { // 更新 + BeanUtils.copyProperties(entity, modelAttr); + modelAttrService.updateById(modelAttr); + } + + // 处理模型api + if(StrUtil.isNotBlank(entity.getApiCode())) { // 更新 + final ModelApi modelApi = modelApiService.detailByCode(entity.getApiCode()) + .ifNotPresentThrow("模型接口不存在[" + entity.getApiCode() + "]") + .getData(); + if(StrUtil.isNotBlank(entity.getDirect())) { // 有选择指令 + resolverModelApi(modelApi, entity, supplier, product); + modelApiService.removeJoinByCode(modelApi.getCode()); + this.modelApiService.save(modelApi); + } else { // 移除了指令 + modelApiService.removeJoinByCode(modelApi.getCode()); + } + } else { // 新增 + if(StrUtil.isNotBlank(entity.getDirect())) { + final ModelApi modelApi = new ModelApi(); + resolverModelApi(modelApi, entity, supplier, product); + this.modelApiService.save(modelApi); + } + } + + return BooleanResult.buildTrue("处理成功"); + } + + @Override + @Transactional + public Result delBatchModel(Long id, String apiCode) { + this.modelApiService.removeJoinByCode(apiCode); + this.modelAttrService.removeByIds(Arrays.asList(id)); + return BooleanResult.buildTrue("删除成功"); + } + + private void resolverModelApi(ModelApi modelApi, BatchModelDto entity, DeviceProtocolSupplier supplier, Product product) { + final ProtocolModel protocol = supplier.getProtocol(); + final AbstractProtocolModelApi protocolApi = protocol.getApi(entity.getDirect()); + String apiCode = modelApi.getCode() != null ? modelApi.getCode() + : IotUtil.getModelCodePrefix(product.getCode()) + "get_" + entity.getField(); + + modelApi.setAsStatus(false).setProductId(entity.getProductId()) + .setTriggerMode(protocolApi.getTriggerMode()).setProtocol(false).setDebug(true) + .setDirect(entity.getDirect()).setFuncType(FuncType.R).setType(protocolApi.getType()) + .setName("获取"+entity.getName()).setCode(apiCode); + List upConfigs = new ArrayList<>(); + protocolApi.getUpConfig().forEach((direct, config) -> { + final ProtocolModelAttr modelAttr = protocol.getAttr(config.getProtocolModelAttrField()); + final ModelApiConfig apiConfig = new ModelApiConfig().setModelAttrId(entity.getId()).setAttrName(entity.getName()) + .setAttrField(entity.getField()).setProductId(entity.getProductId()).setApiCode(apiCode).setDataType(entity.getDataType()) + .setDirection(ApiConfigDirection.UP).setFieldType(ApiFieldType.field).setProtocolAttrName(modelAttr.getName()) + .setProtocolAttrField(config.getProtocolModelAttrField()).setProtocolDataType(modelAttr.getDataType().getValue()) + .setValue("@"+entity.getField()).setSort(config.getSort()).setRemark(config.getRemark()); + upConfigs.add(apiConfig); + }); + + List downConfigs = new ArrayList<>(); + protocolApi.getDownConfig().forEach((direct, config) -> { + final ProtocolModelAttr modelAttr = protocol.getAttr(config.getProtocolModelAttrField()); + final String value = (String) entity.getConfig().get(config.getProtocolModelAttrField()); + if(StrUtil.isBlank(value)) { + throw new ServiceException(config.getProtocolModelAttrField()+"必填"); + } + + final ModelApiConfig apiConfig = new ModelApiConfig().setModelAttrId(entity.getId()).setAttrName(entity.getName()) + .setAttrField(entity.getField()).setProductId(entity.getProductId()).setApiCode(apiCode) + .setDirection(ApiConfigDirection.DOWN).setFieldType(ApiFieldType.field).setProtocolAttrName(modelAttr.getName()) + .setProtocolAttrField(config.getProtocolModelAttrField()).setProtocolDataType(modelAttr.getDataType().getValue()) + .setValue(value).setSort(config.getSort()).setRemark(config.getRemark()).setDataType(modelAttr.getDataType().getValue()); + downConfigs.add(apiConfig); + }); + + modelApi.setUpConfig(upConfigs).setDownConfig(downConfigs); + } + private ModelApi convertProtocolApiToModelApi(ProtocolApi api) { ModelApi modelApi = new ModelApi(); BeanUtils.copyProperties(api, modelApi); diff --git a/bootstrap/src/main/java/com/iteaj/iboot/module/iot/utils/IotUtil.java b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/utils/IotUtil.java new file mode 100644 index 00000000..cba0c3a1 --- /dev/null +++ b/bootstrap/src/main/java/com/iteaj/iboot/module/iot/utils/IotUtil.java @@ -0,0 +1,24 @@ +package com.iteaj.iboot.module.iot.utils; + +public class IotUtil { + + public static String getModelCodePrefix(String productCode) { + if(productCode.length() <= 5) { + return productCode + "_"; + } + + StringBuilder sb = new StringBuilder(); + final char[] chars = productCode.toCharArray(); + sb.append(chars[0]); + int limit = productCode.length() <= 20 ? 4 : 5; + for (int i = 0; i < chars.length; i++) { + if(Character.isLetter(chars[i])) { + if(i % limit == 0) { + sb.append(chars[i]); + } + } + } + + return sb.append('_').toString(); + } +} diff --git a/bootstrap/src/main/resources/mapper/iot/ModelApiMapper.xml b/bootstrap/src/main/resources/mapper/iot/ModelApiMapper.xml index 9634a5d6..a4e97d18 100644 --- a/bootstrap/src/main/resources/mapper/iot/ModelApiMapper.xml +++ b/bootstrap/src/main/resources/mapper/iot/ModelApiMapper.xml @@ -30,6 +30,11 @@ update iot_model_api set as_status = false where product_id=#{productId} and func_type=#{funcType}; update iot_model_api set as_status=true where product_id=#{productId} and func_type=#{funcType} and trigger_mode='passive' and code=#{code} + + delete a, b from iot_model_api a + inner join iot_model_api_config b on a.code = b.api_code + where code=#{code}; + + delete from iot_model_api where product_id=#{productId}; diff --git a/framework/src/main/java/com/iteaj/framework/spi/iot/DeviceProtocolSupplier.java b/framework/src/main/java/com/iteaj/framework/spi/iot/DeviceProtocolSupplier.java index cb0c49fe..a8d7e2ee 100644 --- a/framework/src/main/java/com/iteaj/framework/spi/iot/DeviceProtocolSupplier.java +++ b/framework/src/main/java/com/iteaj/framework/spi/iot/DeviceProtocolSupplier.java @@ -1,16 +1,20 @@ package com.iteaj.framework.spi.iot; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ClassUtil; +import com.iteaj.framework.IVOption; import com.iteaj.framework.ParamMeta; -import com.iteaj.framework.spi.iot.consts.ConnectionType; -import com.iteaj.framework.spi.iot.consts.GatewayType; -import com.iteaj.framework.spi.iot.consts.ProtocolImplMode; -import com.iteaj.framework.spi.iot.protocol.ProtocolModel; +import com.iteaj.framework.spi.iot.consts.*; +import com.iteaj.framework.spi.iot.protocol.*; import com.iteaj.iot.FrameworkComponent; import com.iteaj.iot.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + /** * 协议提供 */ @@ -117,4 +121,52 @@ public interface DeviceProtocolSupplier resolverMetaConfig(Predicate filter) { + final ProtocolModel protocolModel = getProtocol(); + Map fieldMetas = new LinkedHashMap<>(); + final Map apiMap = protocolModel.getApis(); + + if(apiMap != null && !apiMap.isEmpty()) { + + final List modelApis = apiMap + .values().stream().filter(filter).collect(Collectors.toList()); + + if(!modelApis.isEmpty()) { + // 新增指令项 + IVOption[] directOptions = new IVOption[modelApis.size()]; + fieldMetas.put("direct", ParamMeta.buildRequiredSelect("direct", "指令", null, null, directOptions)); + + // 新增下行配置项 + for (int i = 0; i < modelApis.size(); i++) { + AbstractProtocolModelApi modelApi = modelApis.get(i); + final IVOption directOption = new IVOption(modelApi.getName(), modelApi.getCode()); + if(modelApi.getDownConfig() != null) { + modelApi.getDownConfig().values().stream() + .filter(apiConfig -> apiConfig.getDirection() == ApiConfigDirection.DOWN) + .forEach(apiConfig -> { + final ProtocolModelAttr modelAttr = protocolModel.getAttrs().get(apiConfig.getProtocolModelAttrField()); + String attrName = modelAttr != null ? modelAttr.getName() : apiConfig.getProtocolModelAttrField(); + directOption.addChildren(attrName, apiConfig.getProtocolModelAttrField()); + if(!fieldMetas.containsKey(apiConfig.getProtocolModelAttrField())) { + if(CollectionUtil.isNotEmpty(apiConfig.getOptions())) { + final IVOption[] options = apiConfig.getOptions().stream() + .map(item -> new IVOption(item.getLabel(), item.getValue())) + .toArray(size -> new IVOption[size]); + fieldMetas.put(apiConfig.getProtocolModelAttrField(), ParamMeta + .buildSelect(apiConfig.getProtocolModelAttrField(), attrName, null, options)); + } else { + fieldMetas.put(apiConfig.getProtocolModelAttrField(), ParamMeta.build(apiConfig.getProtocolModelAttrField(), attrName)); + } + } + }); + } + + directOptions[i] = directOption; + } + } + } + + return fieldMetas; + } }