【新增功能】 设备数据存储和展示

This commit is contained in:
安浩浩 2024-11-03 00:16:46 +08:00
parent 3dafd31da6
commit 624f5283b3
20 changed files with 523 additions and 108 deletions

View File

@ -30,7 +30,7 @@ public enum IotProductFunctionTypeEnum implements IntArrayValuable {
*/
private final String description;
public static IotProductFunctionTypeEnum valueOf(Integer type) {
public static IotProductFunctionTypeEnum valueOfType(Integer type) {
for (IotProductFunctionTypeEnum value : values()) {
if (value.getType().equals(type)) {
return value;

View File

@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import io.swagger.v3.oas.annotations.Operation;

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT 设备数据")
@RestController
@RequestMapping("/iot/device/data")
@Validated
public class IotDeviceDataController {
@Resource
private IotDeviceDataService deviceDataService;
@GetMapping("/latest-data")
@Operation(summary = "获取设备属性最新数据")
public CommonResult<List<IotDeviceDataRespVO>> getDevicePropertiesLatestData(@Valid IotDeviceDataReqVO deviceDataReqVO) {
List<IotDeviceDataDO> list = deviceDataService.getDevicePropertiesLatestData(deviceDataReqVO);
return success(BeanUtils.toBean(list, IotDeviceDataRespVO.class));
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 设备数据 Request VO")
@Data
public class IotDeviceDataReqVO {
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
private Long deviceId;
@Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED)
private String identifier;
@Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 设备数据 Response VO")
@Data
public class IotDeviceDataRespVO {
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
private Long deviceId;
@Schema(description = "物模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21816")
private Long thinkModelFunctionId;
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED)
private String productKey;
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
private String deviceName;
@Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED)
private String identifier;
@Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;
@Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED)
private String dataType;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
@Schema(description = "最新值", requiredMode = Schema.RequiredMode.REQUIRED)
private String value;
}

View File

@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.iot.convert.device;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelEvent;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelProperty;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelService;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@Mapper
public interface IotDeviceDataConvert {
IotDeviceDataConvert INSTANCE = Mappers.getMapper(IotDeviceDataConvert.class);
// default List<IotDeviceDataRespVO> convert(Map<String, Object> deviceData, IotDeviceDO device){
// List<IotDeviceDataRespVO> list = new ArrayList<>();
// deviceData.forEach((identifier, value) -> {
//// ThingModelProperty property = ThingModelService.INSTANCE.getProperty(device.getProductId(), identifier);
//// if (Objects.isNull(property)) {
//// return;
//// }
// IotDeviceDataRespVO vo = new IotDeviceDataRespVO();
// vo.setDeviceId(device.getId());
// vo.setProductKey(device.getProductKey());
// vo.setDeviceName(device.getDeviceName());
// vo.setIdentifier(identifier);
//// vo.setName(property.getName());
//// vo.setDataType(property.getDataType().getType());
// vo.setUpdateTime(device.getUpdateTime());
// vo.setValue(value.toString());
// list.add(vo);
// });
// return list;
// }
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.device;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* IoT 设备数据 DO
*
* @author haohao
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotDeviceDataDO {
/**
* 设备编号
*/
private Long deviceId;
/**
* 物模型编号
*/
private Long thinkModelFunctionId;
/**
* 产品标识
*/
private String productKey;
/**
* 设备名称
*/
private String deviceName;
/**
* 属性标识符
*/
private String identifier;
/**
* 属性名称
*/
private String name;
/**
* 数据类型
*/
private String dataType;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 最新值
*/
private String value;
}

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.dal.mysql.device;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import org.apache.ibatis.annotations.Mapper;

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.iot.dal.redis;
/**
* Iot Redis Key 枚举类
*
* @author 芋道源码
*/
public interface RedisKeyConstants {
/**
* 设备属性数据缓存
* <p>
* KEY 格式device_property_data:{deviceId}
* VALUE 数据类型String 设备属性数据
*/
String DEVICE_PROPERTY_DATA = "device_property_data:%s_%s_%s";
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.iot.dal.redis.deviceData;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants.DEVICE_PROPERTY_DATA;
/**
* {@link IotDeviceDataDO} Redis DAO
*/
@Repository
public class DeviceDataRedisDAO {
@Resource
private StringRedisTemplate stringRedisTemplate;
public IotDeviceDataDO get(String productKey, String deviceName, String identifier) {
String redisKey = formatKey(productKey, deviceName, identifier);
return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), IotDeviceDataDO.class);
}
public void set(IotDeviceDataDO deviceData) {
String redisKey = formatKey(deviceData.getProductKey(), deviceData.getDeviceName(), deviceData.getIdentifier());
stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(deviceData));
}
public void delete(String productKey, String deviceName, String identifier) {
String redisKey = formatKey(productKey, deviceName, identifier);
stringRedisTemplate.delete(redisKey);
}
private static String formatKey(String productKey, String deviceName, String identifier) {
return String.format(DEVICE_PROPERTY_DATA, productKey, deviceName, identifier);
}
}

View File

@ -1,12 +1,11 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import jakarta.validation.Valid;
import java.util.List;
/**
* IoT 设备数据 Service 接口
*
@ -23,4 +22,12 @@ public interface IotDeviceDataService {
* @param message 消息
*/
void saveDeviceData(String productKey, String deviceName, String message);
/**
* 获得设备属性最新数据
*
* @param deviceId 设备编号
* @return 设备属性最新数据
*/
List<IotDeviceDataDO> getDevicePropertiesLatestData(@Valid IotDeviceDataReqVO deviceId);
}

View File

@ -1,13 +1,23 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.hutool.json.JSONObject;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum;
import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
import cn.iocoder.yudao.module.iot.service.thinkmodelfunction.IotThinkModelFunctionService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Service
public class IotDeviceDataServiceImpl implements IotDeviceDataService {
@ -16,6 +26,11 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
private IotDeviceService deviceService;
@Resource
private IotThingModelMessageService thingModelMessageService;
@Resource
private IotThinkModelFunctionService thinkModelFunctionService;
@Resource
private DeviceDataRedisDAO deviceDataRedisDAO;
@Override
public void saveDeviceData(String productKey, String deviceName, String message) {
@ -36,4 +51,44 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
.build();
thingModelMessageService.saveThingModelMessage(device, thingModelMessage);
}
@Override
public List<IotDeviceDataDO> getDevicePropertiesLatestData(@Valid IotDeviceDataReqVO deviceDataReqVO) {
List<IotDeviceDataDO> list = new ArrayList<>();
// 1. 获取设备信息
IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
// 2. 获取设备属性最新数据
List<IotThinkModelFunctionDO> thinkModelFunctionList = thinkModelFunctionService.getThinkModelFunctionListByProductKey(device.getProductKey());
thinkModelFunctionList = thinkModelFunctionList.stream()
.filter(function -> IotProductFunctionTypeEnum.PROPERTY.getType()
.equals(function.getType())).toList();
// 3. 过滤标识符和属性名称
if (deviceDataReqVO.getIdentifier() != null) {
thinkModelFunctionList = thinkModelFunctionList.stream()
.filter(function -> function.getIdentifier().toLowerCase().contains(deviceDataReqVO.getIdentifier().toLowerCase()))
.toList();
}
if (deviceDataReqVO.getName() != null) {
thinkModelFunctionList = thinkModelFunctionList.stream()
.filter(function -> function.getName().toLowerCase().contains(deviceDataReqVO.getName().toLowerCase()))
.toList();
}
// 4. 获取设备属性最新数据
thinkModelFunctionList.forEach(function -> {
IotDeviceDataDO deviceData = deviceDataRedisDAO.get(device.getProductKey(), device.getDeviceName(), function.getIdentifier());
if (deviceData == null) {
deviceData = new IotDeviceDataDO();
deviceData.setProductKey(device.getProductKey());
deviceData.setDeviceName(device.getDeviceName());
deviceData.setIdentifier(function.getIdentifier());
deviceData.setDeviceId(deviceDataReqVO.getDeviceId());
deviceData.setThinkModelFunctionId(function.getId());
deviceData.setName(function.getName());
deviceData.setDataType(function.getProperty().getDataType().getType());
}
list.add(deviceData);
});
return list;
}
}

View File

@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
import jakarta.validation.*;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;

View File

@ -5,9 +5,9 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;

View File

@ -1,12 +1,12 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelProperty;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelRespVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdRestApi;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum;
import jakarta.annotation.Resource;
@ -14,10 +14,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
@Service
@ -27,9 +24,6 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
@Resource
private IotTdEngineService iotTdEngineService;
@Resource
private TdRestApi tdRestApi;
@Value("${spring.datasource.dynamic.datasource.tdengine.url}")
private String url;
@ -37,39 +31,25 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
public void createSuperTable(ThingModelRespVO thingModel, Integer deviceType) {
// 1. 解析物模型获得字段列表
List<TdFieldDO> schemaFields = new ArrayList<>();
schemaFields.add(TdFieldDO.builder().
fieldName("time").
dataType("TIMESTAMP").
build());
schemaFields.add(TdFieldDO.builder()
.fieldName("time")
.dataType("TIMESTAMP")
.build());
schemaFields.addAll(FieldParser.parse(thingModel));
// 3. 设置超级表的标签
List<TdFieldDO> tagsFields = new ArrayList<>();
tagsFields.add(TdFieldDO.builder().
fieldName("product_key").
dataType("NCHAR").
dataLength(64).
build());
tagsFields.add(TdFieldDO.builder().
fieldName("device_key").
dataType("NCHAR").
dataLength(64).
build());
tagsFields.add(TdFieldDO.builder().
fieldName("device_name").
dataType("NCHAR").
dataLength(64).
build());
tagsFields.add(TdFieldDO.builder().
fieldName("device_type").
dataType("INT").
build());
List<TdFieldDO> tagsFields = Arrays.asList(
TdFieldDO.builder().fieldName("product_key").dataType("NCHAR").dataLength(64).build(),
TdFieldDO.builder().fieldName("device_key").dataType("NCHAR").dataLength(64).build(),
TdFieldDO.builder().fieldName("device_name").dataType("NCHAR").dataLength(64).build(),
TdFieldDO.builder().fieldName("device_type").dataType("INT").build()
);
// 4. 获取超级表的名称
String superTableName = getProductPropertySTableName(deviceType, thingModel.getProductKey());
// 5. 创建超级表
String dataBaseName = url.substring(url.lastIndexOf("/") + 1);
String dataBaseName = getDatabaseName();
iotTdEngineService.createSuperTable(schemaFields, tagsFields, dataBaseName, superTableName);
}
@ -81,7 +61,7 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
List<TdFieldDO> newFields = FieldParser.parse(thingModel);
updateTableFields(tbName, oldFields, newFields);
} catch (Throwable e) {
} catch (Exception e) {
log.error("更新物模型超级表失败", e);
}
}
@ -90,14 +70,15 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
private List<TdFieldDO> getTableFields(String tableName) {
List<TdFieldDO> fields = new ArrayList<>();
// 获取超级表的描述信息
List<Map<String, Object>> maps = iotTdEngineService.describeSuperTable(url.substring(url.lastIndexOf("/") + 1), tableName);
List<Map<String, Object>> maps = iotTdEngineService.describeSuperTable(getDatabaseName(), tableName);
if (maps != null) {
// 过滤掉 note 字段为 TAG 的记录
maps = maps.stream().filter(map -> !"TAG".equals(map.get("note"))).toList();
// 过滤掉 time 字段
maps = maps.stream().filter(map -> !"time".equals(map.get("field"))).toList();
// 过滤掉 note 字段为 TAG 的记录和 time 字段
List<Map<String, Object>> filteredMaps = maps.stream()
.filter(map -> !"TAG".equals(map.get("note")))
.filter(map -> !"time".equals(map.get("field")))
.toList();
// 解析字段信息
fields = FieldParser.parse(maps.stream()
fields = FieldParser.parse(filteredMaps.stream()
.map(map -> List.of(map.get("field"), map.get("type"), map.get("length")))
.collect(Collectors.toList()));
}
@ -113,7 +94,7 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
// 获取删除字段
List<TdFieldDO> dropFields = getDropFields(oldFields, newFields);
String dataBaseName = url.substring(url.lastIndexOf("/") + 1);
String dataBaseName = getDatabaseName();
// 添加新增字段
if (CollUtil.isNotEmpty(addFields)) {
iotTdEngineService.addColumnForSuperTable(dataBaseName, tableName, addFields);
@ -131,25 +112,37 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
// 获取新增字段
private List<TdFieldDO> getAddFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
Set<String> oldFieldNames = oldFields.stream()
.map(TdFieldDO::getFieldName)
.collect(Collectors.toSet());
return newFields.stream()
.filter(f -> oldFields.stream().noneMatch(old -> old.getFieldName().equals(f.getFieldName())))
.filter(f -> !oldFieldNames.contains(f.getFieldName()))
.collect(Collectors.toList());
}
// 获取修改字段
private List<TdFieldDO> getModifyFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
Map<String, TdFieldDO> oldFieldMap = oldFields.stream()
.collect(Collectors.toMap(TdFieldDO::getFieldName, f -> f));
return newFields.stream()
.filter(f -> oldFields.stream().anyMatch(old ->
old.getFieldName().equals(f.getFieldName()) &&
(!old.getDataType().equals(f.getDataType()) || !Objects.equals(old.getDataLength(), f.getDataLength()))))
.filter(f -> {
TdFieldDO oldField = oldFieldMap.get(f.getFieldName());
return oldField != null &&
(!oldField.getDataType().equals(f.getDataType()) ||
!Objects.equals(oldField.getDataLength(), f.getDataLength()));
})
.collect(Collectors.toList());
}
// 获取删除字段
private List<TdFieldDO> getDropFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
Set<String> newFieldNames = newFields.stream()
.map(TdFieldDO::getFieldName)
.collect(Collectors.toSet());
return oldFields.stream()
.filter(f -> !"time".equals(f.getFieldName()) && !"device_id".equals(f.getFieldName()) &&
newFields.stream().noneMatch(n -> n.getFieldName().equals(f.getFieldName())))
.filter(f -> !"time".equals(f.getFieldName()) && !"device_id".equals(f.getFieldName()))
.filter(f -> !newFieldNames.contains(f.getFieldName()))
.collect(Collectors.toList());
}
@ -157,13 +150,13 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
public void createSuperTableDataModel(IotProductDO product, List<IotThinkModelFunctionDO> functionList) {
ThingModelRespVO thingModel = buildThingModel(product, functionList);
if (thingModel.getModel().getProperties().isEmpty()) {
if (thingModel.getModel() == null || CollUtil.isEmpty(thingModel.getModel().getProperties())) {
log.warn("物模型属性列表为空,不创建超级表");
return;
}
String superTableName = getProductPropertySTableName(product.getDeviceType(), product.getProductKey());
String dataBaseName = url.substring(url.lastIndexOf("/") + 1);
String dataBaseName = getDatabaseName();
Integer tableExists = iotTdEngineService.checkSuperTableExists(dataBaseName, superTableName);
if (tableExists != null && tableExists > 0) {
@ -180,7 +173,8 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
ThingModelRespVO.Model model = new ThingModelRespVO.Model();
List<ThingModelProperty> properties = functionList.stream()
.filter(function -> IotProductFunctionTypeEnum.PROPERTY.equals(IotProductFunctionTypeEnum.valueOf(function.getType())))
.filter(function -> IotProductFunctionTypeEnum.PROPERTY.equals(
IotProductFunctionTypeEnum.valueOfType(function.getType())))
.map(this::buildThingModelProperty)
.collect(Collectors.toList());
@ -191,14 +185,15 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
}
private ThingModelProperty buildThingModelProperty(IotThinkModelFunctionDO function) {
ThingModelProperty property = new ThingModelProperty();
property.setIdentifier(function.getIdentifier());
property.setName(function.getName());
property.setDescription(function.getDescription());
ThingModelProperty property = BeanUtil.copyProperties(function, ThingModelProperty.class);
property.setDataType(function.getProperty().getDataType());
return property;
}
private String getDatabaseName() {
return url.substring(url.lastIndexOf("/") + 1);
}
static String getProductPropertySTableName(Integer deviceType, String productKey) {
return switch (deviceType) {
case 1 -> String.format("gateway_sub_%s", productKey).toLowerCase();
@ -207,7 +202,4 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
};
}
static String getDevicePropertyTableName(String deviceType, String productKey, String deviceKey) {
return String.format("%s_%s_%s", deviceType, productKey, deviceKey).toLowerCase();
}
}

View File

@ -1,13 +1,16 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.hutool.core.date.DateUtil;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TableDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
@ -17,9 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@ -36,11 +37,14 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
@Resource
private IotTdEngineService iotTdEngineService;
@Resource
private DeviceDataRedisDAO deviceDataRedisDAO;
@Override
@TenantIgnore
public void saveThingModelMessage(IotDeviceDO device, ThingModelMessage thingModelMessage) {
// 判断设备状态如果为未激活状态创建数据表
if (device.getStatus().equals(0)) {
if (IotDeviceStatusEnum.INACTIVE.getStatus().equals(device.getStatus())) {
// 创建设备数据表
createDeviceTable(device.getDeviceType(), device.getProductKey(), device.getDeviceName(), device.getDeviceKey());
// 更新设备状态
@ -50,53 +54,99 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
iotDeviceService.updateDeviceStatus(updateReqVO);
}
// 1. 获取设备属性
// 获取设备属性
Map<String, Object> params = thingModelMessage.dataToMap();
// 2. 物模型校验过滤非物模型属性
List<IotThinkModelFunctionDO> thinkModelFunctionListByProductKey = iotThinkModelFunctionService.getThinkModelFunctionListByProductKey(thingModelMessage.getProductKey());
// 物模型校验过滤非物模型属性
List<IotThinkModelFunctionDO> functionList = iotThinkModelFunctionService
.getThinkModelFunctionListByProductKey(thingModelMessage.getProductKey())
.stream()
.filter(function -> IotProductFunctionTypeEnum.PROPERTY.getType().equals(function.getType()))
.toList();
// 2.1 筛选是属性 IotProductFunctionTypeEnum
thinkModelFunctionListByProductKey.removeIf(iotThinkModelFunctionDO -> !iotThinkModelFunctionDO.getType().equals(IotProductFunctionTypeEnum.PROPERTY.getType()));
if (thinkModelFunctionListByProductKey.isEmpty()) {
if (functionList.isEmpty()) {
return;
}
// 2.2 获取属性名称
Map<String, String> thingModelProperties = thinkModelFunctionListByProductKey.stream().collect(Collectors.toMap(IotThinkModelFunctionDO::getIdentifier, IotThinkModelFunctionDO::getName));
// 4. 保存属性记录
// 获取属性标识符集合
Set<String> propertyIdentifiers = functionList.stream()
.map(IotThinkModelFunctionDO::getIdentifier)
.collect(Collectors.toSet());
Map<String, IotThinkModelFunctionDO> functionMap = functionList.stream()
.collect(Collectors.toMap(IotThinkModelFunctionDO::getIdentifier, function -> function));
// 过滤并收集有效的属性字段
List<TdFieldDO> schemaFieldValues = new ArrayList<>();
// 1. 设置字段名
schemaFieldValues.add(new TdFieldDO("time", thingModelMessage.getTime()));
// 2. 遍历新属性
params.forEach((key, val) -> {
if (thingModelProperties.containsKey(key)) {
if (propertyIdentifiers.contains(key)) {
schemaFieldValues.add(new TdFieldDO(key.toLowerCase(), val));
// 缓存设备属性
setDeviceDataCache(device, functionMap.get(key), val, thingModelMessage.getTime());
}
});
if (schemaFieldValues.size() == 1) {
return;
}
// 3. 保存设备属性
// 构建并保存设备属性
TableDO tableData = new TableDO();
tableData.setDataBaseName(url.substring(url.lastIndexOf("/") + 1));
tableData.setDataBaseName(getDatabaseName());
tableData.setSuperTableName(getProductPropertySTableName(device.getDeviceType(), device.getProductKey()));
tableData.setTableName("device_" + device.getProductKey().toLowerCase() + "_" + device.getDeviceName().toLowerCase());
tableData.setTableName(getDeviceTableName(device.getProductKey(), device.getDeviceName()));
tableData.setSchemaFieldValues(schemaFieldValues);
// 4. 保存数据
iotTdEngineService.insertData(tableData);
}
/**
* 缓存设备属性
*
* @param device 设备信息
* @param iotThinkModelFunctionDO 物模型属性
* @param val 属性值
* @param time 时间
*/
private void setDeviceDataCache(IotDeviceDO device, IotThinkModelFunctionDO iotThinkModelFunctionDO, Object val, Long time) {
IotDeviceDataDO deviceData = IotDeviceDataDO.builder()
.productKey(device.getProductKey())
.deviceName(device.getDeviceName())
.identifier(iotThinkModelFunctionDO.getIdentifier())
.value(val != null ? val.toString() : null)
.updateTime(DateUtil.toLocalDateTime(new Date(time)))
.deviceId(device.getId())
.thinkModelFunctionId(iotThinkModelFunctionDO.getId())
.name(iotThinkModelFunctionDO.getName())
.dataType(iotThinkModelFunctionDO.getProperty().getDataType().getType())
.build();
deviceDataRedisDAO.set(deviceData);
}
/**
* 创建设备数据表
*
* @param deviceType 设备类型
* @param productKey 产品 Key
* @param deviceName 设备名称
* @param deviceKey 设备 Key
*/
private void createDeviceTable(Integer deviceType, String productKey, String deviceName, String deviceKey) {
String superTableName = getProductPropertySTableName(deviceType, productKey);
String dataBaseName = getDatabaseName();
List<Map<String, Object>> maps = iotTdEngineService.describeSuperTable(dataBaseName, superTableName);
List<TdFieldDO> tagsFieldValues = new ArrayList<>();
String SuperTableName = getProductPropertySTableName(deviceType, productKey);
List<Map<String, Object>> maps = iotTdEngineService.describeSuperTable(url.substring(url.lastIndexOf("/") + 1), SuperTableName);
if (maps != null) {
List<Map<String, Object>> taggedNotesList = maps.stream().filter(map -> "TAG".equals(map.get("note"))).toList();
List<Map<String, Object>> taggedNotesList = maps.stream()
.filter(map -> "TAG".equals(map.get("note")))
.toList();
tagsFieldValues = FieldParser.parse(taggedNotesList.stream()
.map(map -> List.of(map.get("field"), map.get("type"), map.get("length")))
.collect(Collectors.toList()));
for (TdFieldDO tagsFieldValue : tagsFieldValues) {
switch (tagsFieldValue.getFieldName()) {
case "product_key" -> tagsFieldValue.setFieldValue(productKey);
@ -107,21 +157,49 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
}
}
// 1. 创建设备数据表
String tableName = "device_" + productKey.toLowerCase() + "_" + deviceName.toLowerCase();
// 创建设备数据表
String tableName = getDeviceTableName(productKey, deviceName);
TableDO tableDto = new TableDO();
tableDto.setDataBaseName(url.substring(url.lastIndexOf("/") + 1));
tableDto.setSuperTableName(SuperTableName);
tableDto.setDataBaseName(dataBaseName);
tableDto.setSuperTableName(superTableName);
tableDto.setTableName(tableName);
tableDto.setTagsFieldValues(tagsFieldValues);
iotTdEngineService.createTable(tableDto);
}
static String getProductPropertySTableName(Integer deviceType, String productKey) {
/**
* 获取数据库名称
*
* @return 数据库名称
*/
private String getDatabaseName() {
return url.substring(url.lastIndexOf("/") + 1);
}
/**
* 获取产品属性表名
*
* @param deviceType 设备类型
* @param productKey 产品 Key
* @return 产品属性表名
*/
private static String getProductPropertySTableName(Integer deviceType, String productKey) {
return switch (deviceType) {
case 1 -> String.format("gateway_sub_%s", productKey).toLowerCase();
case 2 -> String.format("gateway_%s", productKey).toLowerCase();
default -> String.format("device_%s", productKey).toLowerCase();
};
}
/**
* 获取设备表名
*
* @param productKey 产品 Key
* @param deviceName 设备名称
* @return 设备表名
*/
private static String getDeviceTableName(String productKey, String deviceName) {
return String.format("device_%s_%s", productKey.toLowerCase(), deviceName.toLowerCase());
}
}