reactor:【IoT 物联网】调整物模型相关的时序表,通过 productId 和 deviceId 区分

This commit is contained in:
YunaiV 2025-06-12 09:06:10 +08:00
parent c3499af524
commit b37814ec9c
9 changed files with 48 additions and 86 deletions

View File

@ -20,12 +20,6 @@ public class IotDevicePropertyHistoryPageReqVO extends PageParam {
@NotNull(message = "设备编号不能为空")
private Long deviceId;
@Schema(description = "产品 Key", hidden = true)
private String productKey; // 非前端传递后端自己查询设置
@Schema(description = "设备名称", hidden = true)
private String deviceName; // 非前端传递后端自己查询设置
@Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "属性标识符不能为空")
private String identifier;

View File

@ -50,10 +50,6 @@ public interface IotThingModelMapper extends BaseMapperX<IotThingModelDO> {
return selectList(IotThingModelDO::getProductId, productId);
}
default List<IotThingModelDO> selectListByProductKey(String productKey) {
return selectList(IotThingModelDO::getProductKey, productKey);
}
default List<IotThingModelDO> selectListByProductIdAndType(Long productId, Integer type) {
return selectList(IotThingModelDO::getProductId, productId,
IotThingModelDO::getType, type);

View File

@ -9,15 +9,14 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
*/
public interface RedisKeyConstants {
// TODO @芋艿弱化 deviceKey使用 product_key + device_name 替代
/**
* 设备属性的数据缓存采用 HASH 结构
* <p>
* KEY 格式device_property:{deviceKey}
* KEY 格式device_property:{deviceId}
* HASH KEYidentifier 属性标识
* VALUE 数据类型String(JSON) {@link IotDevicePropertyDO}
*/
String DEVICE_PROPERTY = "iot:device_property:%s";
String DEVICE_PROPERTY = "iot:device_property:%d";
/**
* 设备的最后上报时间采用 ZSET 结构
@ -31,7 +30,7 @@ public interface RedisKeyConstants {
* 设备关联的网关 serverId 缓存采用 HASH 结构
*
* KEY 格式device_server_id
* HASH KEY{productKey},{deviceName}
* HASH KEY{deviceId}
* VALUE 数据类型String serverId
*/
String DEVICE_SERVER_ID = "iot:device_server_id";
@ -56,7 +55,7 @@ public interface RedisKeyConstants {
/**
* 物模型的数据缓存使用 Spring Cache 操作忽略租户
*
* KEY 格式thing_model_${productKey}
* KEY 格式thing_model_${productId}
* VALUE 数据类型String 数组(JSON) {@link cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO} 列表
*/
String THING_MODEL_LIST = "iot:thing_model_list";

View File

@ -22,8 +22,8 @@ public class DevicePropertyRedisDAO {
@Resource
private StringRedisTemplate stringRedisTemplate;
public Map<String, IotDevicePropertyDO> get(String productKey, String deviceName) {
String redisKey = formatKey(productKey, deviceName);
public Map<String, IotDevicePropertyDO> get(Long id) {
String redisKey = formatKey(id);
Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(redisKey);
if (CollUtil.isEmpty(entries)) {
return Collections.emptyMap();
@ -33,18 +33,18 @@ public class DevicePropertyRedisDAO {
entry -> JsonUtils.parseObject((String) entry.getValue(), IotDevicePropertyDO.class));
}
public void putAll(String productKey, String deviceName, Map<String, IotDevicePropertyDO> properties) {
public void putAll(Long id, Map<String, IotDevicePropertyDO> properties) {
if (CollUtil.isEmpty(properties)) {
return;
}
String redisKey = formatKey(productKey, deviceName);
String redisKey = formatKey(id);
stringRedisTemplate.opsForHash().putAll(redisKey, convertMap(properties.entrySet(),
Map.Entry::getKey,
entry -> JsonUtils.toJsonString(entry.getValue())));
}
private static String formatKey(String productKey, String deviceName) {
return String.format(DEVICE_PROPERTY, productKey, deviceName);
private static String formatKey(Long id) {
return String.format(DEVICE_PROPERTY, id);
}
}

View File

@ -23,17 +23,17 @@ import java.util.stream.Collectors;
@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析因为 JSqlParser TDengine SQL 解析会报错
public interface IotDevicePropertyMapper {
List<TDengineTableField> getProductPropertySTableFieldList(@Param("productKey") String productKey);
List<TDengineTableField> getProductPropertySTableFieldList(@Param("productId") Long productId);
void createProductPropertySTable(@Param("productKey") String productKey,
void createProductPropertySTable(@Param("productId") Long productId,
@Param("fields") List<TDengineTableField> fields);
@SuppressWarnings("SimplifyStreamApiCallChains") // 保持 JDK8 兼容性
default void alterProductPropertySTable(String productKey,
default void alterProductPropertySTable(Long productId,
List<TDengineTableField> oldFields,
List<TDengineTableField> newFields) {
oldFields.removeIf(field -> StrUtil.equalsAny(field.getField(),
TDengineTableField.FIELD_TS, "report_time", "device_name"));
TDengineTableField.FIELD_TS, "report_time"));
List<TDengineTableField> addFields = newFields.stream().filter( // 新增的字段
newField -> oldFields.stream().noneMatch(oldField -> oldField.getField().equals(newField.getField())))
.collect(Collectors.toList());
@ -62,22 +62,22 @@ public interface IotDevicePropertyMapper {
});
// 执行
addFields.forEach(field -> alterProductPropertySTableAddField(productKey, field));
dropFields.forEach(field -> alterProductPropertySTableDropField(productKey, field));
modifyLengthFields.forEach(field -> alterProductPropertySTableModifyField(productKey, field));
addFields.forEach(field -> alterProductPropertySTableAddField(productId, field));
dropFields.forEach(field -> alterProductPropertySTableDropField(productId, field));
modifyLengthFields.forEach(field -> alterProductPropertySTableModifyField(productId, field));
modifyTypeFields.forEach(field -> {
alterProductPropertySTableDropField(productKey, field);
alterProductPropertySTableAddField(productKey, field);
alterProductPropertySTableDropField(productId, field);
alterProductPropertySTableAddField(productId, field);
});
}
void alterProductPropertySTableAddField(@Param("productKey") String productKey,
void alterProductPropertySTableAddField(@Param("productId") Long productId,
@Param("field") TDengineTableField field);
void alterProductPropertySTableModifyField(@Param("productKey") String productKey,
void alterProductPropertySTableModifyField(@Param("productId") Long productId,
@Param("field") TDengineTableField field);
void alterProductPropertySTableDropField(@Param("productKey") String productKey,
void alterProductPropertySTableDropField(@Param("productId") Long productId,
@Param("field") TDengineTableField field);
void insert(@Param("device") IotDeviceDO device,

View File

@ -20,7 +20,6 @@ import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyMapper;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
import com.baomidou.mybatisplus.core.metadata.IPage;
@ -59,8 +58,6 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
.put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿怎么映射
.build();
@Resource
private IotDeviceService deviceService;
@Resource
private IotThingModelService thingModelService;
@Resource
@ -87,7 +84,7 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
// 1.2 解析 DB 里的字段
List<TDengineTableField> oldFields = new ArrayList<>();
try {
oldFields.addAll(devicePropertyMapper.getProductPropertySTableFieldList(product.getProductKey()));
oldFields.addAll(devicePropertyMapper.getProductPropertySTableFieldList(product.getId()));
} catch (Exception e) {
if (!e.getMessage().contains("Table does not exist")) {
throw e;
@ -101,11 +98,11 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
log.info("[defineDevicePropertyData][productId({}) 没有需要定义的属性]", productId);
return;
}
devicePropertyMapper.createProductPropertySTable(product.getProductKey(), newFields);
devicePropertyMapper.createProductPropertySTable(product.getId(), newFields);
return;
}
// 2.2 情况二如果是修改的时候需要更新表
devicePropertyMapper.alterProductPropertySTable(product.getProductKey(), oldFields, newFields);
devicePropertyMapper.alterProductPropertySTable(product.getId(), oldFields, newFields);
}
private List<TDengineTableField> buildTableFieldList(List<IotThingModelDO> thingModels) {
@ -122,16 +119,17 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
@Override
public void saveDeviceProperty(IotDeviceDO device, IotDeviceMessage message) {
if (!(message.getData() instanceof Map)) {
// TODO @芋艿这里要改下协议
if (!(message.getParams() instanceof Map)) {
log.error("[saveDeviceProperty][消息内容({}) 的 data 类型不正确]", message);
return;
}
// 1. 根据物模型拼接合法的属性
// TODO @芋艿待定 004赋能后属性到底以 thingModel 为准ik还是 db 的表结构为准tl
List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductKeyFromCache(device.getProductKey());
List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductIdFromCache(device.getProductId());
Map<String, Object> properties = new HashMap<>();
((Map<?, ?>) message.getData()).forEach((key, value) -> {
((Map<?, ?>) message.getParams()).forEach((key, value) -> {
if (CollUtil.findOne(thingModels, thingModel -> thingModel.getIdentifier().equals(key)) == null) {
log.error("[saveDeviceProperty][消息({}) 的属性({}) 不存在]", message, key);
return;
@ -150,25 +148,16 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
// 2.2 保存设备属性日志
Map<String, IotDevicePropertyDO> properties2 = convertMap(properties.entrySet(), Map.Entry::getKey, entry ->
IotDevicePropertyDO.builder().value(entry.getValue()).updateTime(message.getReportTime()).build());
deviceDataRedisDAO.putAll(device.getProductKey(), device.getDeviceName(), properties2);
deviceDataRedisDAO.putAll(device.getId(), properties2);
}
@Override
public Map<String, IotDevicePropertyDO> getLatestDeviceProperties(Long deviceId) {
// 获取设备信息
IotDeviceDO device = deviceService.validateDeviceExists(deviceId);
// 获得设备属性
return deviceDataRedisDAO.get(device.getProductKey(), device.getDeviceName());
return deviceDataRedisDAO.get(deviceId);
}
@Override
public PageResult<IotDevicePropertyRespVO> getHistoryDevicePropertyPage(IotDevicePropertyHistoryPageReqVO pageReqVO) {
// 获取设备信息
IotDeviceDO device = deviceService.validateDeviceExists(pageReqVO.getDeviceId());
pageReqVO.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName());
// 分页查询
try {
IPage<IotDevicePropertyRespVO> page = devicePropertyMapper.selectPageByHistory(
new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO);

View File

@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelS
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import jakarta.validation.Valid;
import java.time.LocalDateTime;
import java.util.List;
/**
@ -60,10 +59,10 @@ public interface IotThingModelService {
*
* 注意该方法会忽略租户信息所以调用时需要确认会不会有跨租户访问的风险
*
* @param productKey 产品标识
* @param productId 产品编号
* @return 产品物模型列表
*/
List<IotThingModelDO> getThingModelListByProductKeyFromCache(String productKey);
List<IotThingModelDO> getThingModelListByProductIdFromCache(Long productId);
/**
* 获得产品物模型分页
@ -81,13 +80,4 @@ public interface IotThingModelService {
*/
List<IotThingModelDO> getThingModelList(IotThingModelListReqVO reqVO);
// TODO @super用不到删除下哈
/**
* 获得物模型数量
*
* @param createTime 创建时间如果为空则统计所有物模型数量
* @return 物模型数量
*/
Long getThingModelCount(LocalDateTime createTime);
}

View File

@ -29,7 +29,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -133,10 +132,10 @@ public class IotThingModelServiceImpl implements IotThingModelService {
}
@Override
@Cacheable(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey")
@Cacheable(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productId")
@TenantIgnore // 忽略租户信息跨租户 productKey 是唯一的
public List<IotThingModelDO> getThingModelListByProductKeyFromCache(String productKey) {
return thingModelMapper.selectListByProductKey(productKey);
public List<IotThingModelDO> getThingModelListByProductIdFromCache(Long productId) {
return thingModelMapper.selectListByProductId(productId);
}
@Override
@ -364,10 +363,4 @@ public class IotThingModelServiceImpl implements IotThingModelService {
return SpringUtil.getBean(getClass());
}
// TODO @super用不到删除下
@Override
public Long getThingModelCount(LocalDateTime createTime) {
return thingModelMapper.selectCountByCreateTime(createTime);
}
}

View File

@ -5,11 +5,11 @@
<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyMapper">
<select id="getProductPropertySTableFieldList" resultType="cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField">
DESCRIBE product_property_${productKey}
DESCRIBE product_property_${productId}
</select>
<update id="createProductPropertySTable">
CREATE STABLE product_property_${productKey} (
CREATE STABLE product_property_${productId} (
ts TIMESTAMP,
report_time TIMESTAMP,
<foreach item="field" collection="fields" separator=",">
@ -20,12 +20,12 @@
</foreach>
)
TAGS (
device_name NCHAR(50)
device_id BIGINT
)
</update>
<update id="alterProductPropertySTableAddField">
ALTER STABLE product_property_${productKey}
ALTER STABLE product_property_${productId}
ADD COLUMN ${field.field} ${field.type}
<if test="field.length != null and field.length > 0">
(${field.length})
@ -33,7 +33,7 @@
</update>
<update id="alterProductPropertySTableModifyField">
ALTER STABLE product_property_${productKey}
ALTER STABLE product_property_${productId}
MODIFY COLUMN ${field.field} ${field.type}
<if test="field.length != null and field.length > 0">
(${field.length})
@ -41,14 +41,14 @@
</update>
<update id="alterProductPropertySTableDropField">
ALTER STABLE product_property_${productKey}
ALTER STABLE product_property_${productId}
DROP COLUMN ${field.field}
</update>
<insert id="insert">
INSERT INTO device_property_${device.productKey}_${device.deviceName}
USING product_property_${device.productKey}
TAGS ('${device.deviceName}')
INSERT INTO device_property_${device.id}
USING product_property_${device.productId}
TAGS ('${device.id}')
(ts, report_time,
<foreach item="key" collection="properties.keys" separator=",">
${@cn.hutool.core.util.StrUtil@toUnderlineCase(key)}
@ -63,13 +63,14 @@
</insert>
<select id="describeSuperTable" resultType="java.util.Map">
DESCRIBE product_property_${productKey}
DESCRIBE product_property_${productId}
</select>
<!-- TODO @芋艿:这里查询有点问题 -->
<select id="selectPageByHistory"
resultType="cn.iocoder.yudao.module.iot.controller.admin.device.vo.data.IotDevicePropertyRespVO">
SELECT ${@cn.hutool.core.util.StrUtil@toUnderlineCase(reqVO.identifier)} AS `value`, ts AS update_time
FROM device_property_${reqVO.productKey}_${reqVO.deviceName}
FROM device_property_${reqVO.deviceId}
WHERE ${@cn.hutool.core.util.StrUtil@toUnderlineCase(reqVO.identifier)} IS NOT NULL
AND ts BETWEEN ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[0])}
AND ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[1])}