新增物模型批量新增功能

This commit is contained in:
iteaj
2025-06-02 14:10:33 +08:00
parent 827075d4b6
commit dadf0511ac
13 changed files with 332 additions and 23 deletions
@@ -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<List<IVOption>> resolvers() {
return success(DataValueResolverFactory.options());
}
/**
* 批量模型属性
* @param entity
* @return
*/
@GetMapping("listBatchModel")
public Result<List<BatchModelDto>> listBatchModel(BatchModelDto entity) {
return this.productService.listBatchModel(entity);
}
/**
* 新增或者更新批量模型记录
* @param entity
* @return
*/
@PostMapping("batchModelSaveOrUpdate")
public Result<Boolean> batchModelSaveOrUpdate(@RequestBody BatchModelDto entity) {
return this.productService.batchModelSaveOrUpdate(entity);
}
/**
* 删除批量模型记录
* @param entity id和apiCode必填
* @return
*/
@PostMapping("batchModelDel")
public Result<Boolean> batchModelDel(@RequestBody BatchModelDto entity) {
return this.productService.delBatchModel(entity.getId(), entity.getApiCode());
}
}
@@ -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<Map<String, Collection<ParamMeta>>> listEventApiTableHead() {
Map<String, Collection<ParamMeta>> result = new HashMap<>();
ProtocolSupplierManager.suppliers().forEach(supplier -> {
final ProtocolModel protocolModel = supplier.getProtocol();
if(protocolModel.getCtrlMode() == CtrlMode.POINT) {
final Map<String, ParamMeta> 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);
}
}
@@ -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<String, Object> 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]);
}
}
}
}
@@ -64,4 +64,6 @@ public interface ModelApiMapper extends BaseMapper<ModelApi> {
* @return
*/
boolean hasCtrlStatus(Long productId, String code);
void removeJoinByCode(String code);
}
@@ -96,4 +96,6 @@ public interface ProductMapper extends BaseMapper<Product> {
* @return
*/
List<Product> listOfProtocolCodes(DeviceType deviceType, String[] protocolCodes, TransportProtocol protocol);
List<BatchModelDto> listBatchModel(BatchModelDto entity);
}
@@ -52,4 +52,6 @@ public interface IModelApiService extends IBaseService<ModelApi> {
DetailResult<ModelApi> getAsStatusModelApi(Long productId);
DetailResult<ModelApi> detailByCode(String modelApiCode);
void removeJoinByCode(String code);
}
@@ -131,4 +131,10 @@ public interface IProductService extends IBaseService<Product> {
* @return
*/
ListResult<Product> listOfProtocolCodes(DeviceType deviceType, String[] protocolCodes, TransportProtocol protocol);
ListResult<BatchModelDto> listBatchModel(BatchModelDto entity);
BooleanResult batchModelSaveOrUpdate(BatchModelDto entity);
Result<Boolean> delBatchModel(Long id, String apiCode);
}
@@ -232,4 +232,12 @@ public class ModelApiServiceImpl extends BaseServiceImpl<ModelApiMapper, ModelAp
return new DetailResult<>(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);
}
}
}
@@ -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<ProductMapper, Product>
return new ListResult<>(getBaseMapper().listOfProtocolCodes(deviceType, protocolCodes, protocol));
}
@Override
public ListResult<BatchModelDto> 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<Boolean> 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<ModelApiConfig> 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<ModelApiConfig> 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);
@@ -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();
}
}
@@ -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}
</update>
<delete id="removeJoinByCode">
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>
<select id="listFuncByProductId" resultMap="DetailResultMap">
select * from iot_model_api where product_id = #{productId} and type='func' order by create_time desc
@@ -199,6 +199,23 @@
left join iot_device d on p.id = d.product_id
group by a.type;
</select>
<select id="listBatchModel" resultType="com.iteaj.iboot.module.iot.dto.BatchModelDto">
select a.*, b.direct, b.name apiName, b.code apiCode, group_concat(concat(cc.protocol_attr_field, '->', cc.value) separator ':::') apiDownConfig from iot_model_attr a
left join iot_product p on a.product_id = p.id
left join iot_model_api_config c on a.product_id = c.product_id and a.id = c.model_attr_id
left join iot_model_api_config cc on cc.api_code = c.api_code and cc.id != c.id
left join iot_model_api b on c.api_code = b.code and b.type = 'event'
<where>
p.code=#{productCode}
<if test="name != null and name != ''">
and a.name like '%${name}%'
</if>
<if test="field != null and field != ''">
and a.field like '%${field}%'
</if>
</where>
group by b.code order by a.create_time
</select>
<delete id="removeModel">
delete from iot_model_api where product_id=#{productId};
@@ -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<C extends NetworkConfig, T extends Frame
* @return 如果已经存在直接返回, 不存在则创建
*/
T createComponent(C config);
default Map<String, ParamMeta> resolverMetaConfig(Predicate<AbstractProtocolModelApi> filter) {
final ProtocolModel protocolModel = getProtocol();
Map<String, ParamMeta> fieldMetas = new LinkedHashMap<>();
final Map<String, AbstractProtocolModelApi> apiMap = protocolModel.getApis();
if(apiMap != null && !apiMap.isEmpty()) {
final List<AbstractProtocolModelApi> 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;
}
}