mirror of
https://gitee.com/zhijiantianya/yudao-cloud.git
synced 2026-10-06 07:22:58 +08:00
【同步】BOOT 和 CLOUD 的功能
This commit is contained in:
+14
@@ -1,9 +1,12 @@
|
||||
package cn.iocoder.yudao.module.iot.framework.tdengine.core;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* TDEngine 表字段
|
||||
*/
|
||||
@@ -61,4 +64,15 @@ public class TDengineTableField {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建字段名:TDengine 默认会将字段名转为小写,需要和建表、写入、查询保持一致。
|
||||
* 例如:PT -> pt,PfT -> pf_t。
|
||||
*/
|
||||
public static String buildFieldName(String field) {
|
||||
if (StrUtil.isBlank(field)) {
|
||||
return field;
|
||||
}
|
||||
return StrUtil.toUnderlineCase(field).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
|
||||
private List<TDengineTableField> buildTableFieldList(List<IotThingModelDO> thingModels) {
|
||||
return convertList(thingModels, thingModel -> {
|
||||
TDengineTableField field = new TDengineTableField(
|
||||
StrUtil.toUnderlineCase(thingModel.getIdentifier()), // TDengine 字段默认都是小写
|
||||
TDengineTableField.buildFieldName(thingModel.getIdentifier()),
|
||||
TYPE_MAPPING.get(thingModel.getProperty().getDataType()));
|
||||
String dataType = thingModel.getProperty().getDataType();
|
||||
if (Objects.equals(dataType, IotDataSpecsDataTypeEnum.TEXT.getDataType())) {
|
||||
|
||||
+2
-2
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.text.CharPool;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotCurrentTimeConditionMatcher;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotTimerConditionEvaluator;
|
||||
@@ -11,7 +12,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
@@ -73,7 +73,7 @@ public class IotSceneRuleTimeHelper {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (isDateTimeOperator(operatorEnum)) {
|
||||
// 日期时间匹配(时间戳,秒级)
|
||||
long currentTimestamp = now.atZone(ZoneId.systemDefault()).toEpochSecond();
|
||||
long currentTimestamp = LocalDateTimeUtils.toEpochSecond(now);
|
||||
return matchDateTime(currentTimestamp, operatorEnum, param);
|
||||
} else {
|
||||
// 当日时间匹配(HH:mm:ss)
|
||||
|
||||
+4
-4
@@ -51,7 +51,7 @@
|
||||
TAGS ('${device.id}')
|
||||
(ts, report_time,
|
||||
<foreach item="key" collection="properties.keys" separator=",">
|
||||
${@cn.hutool.core.util.StrUtil@toUnderlineCase(key)}
|
||||
${@cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField@buildFieldName(key)}
|
||||
</foreach>
|
||||
)
|
||||
VALUES
|
||||
@@ -68,12 +68,12 @@
|
||||
|
||||
<select id="selectListByHistory"
|
||||
resultType="cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO">
|
||||
SELECT ${@cn.hutool.core.util.StrUtil@toUnderlineCase(reqVO.identifier)} AS `value`, ts AS update_time
|
||||
SELECT ${@cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField@buildFieldName(reqVO.identifier)} AS `value`, ts AS update_time
|
||||
FROM device_property_${reqVO.deviceId}
|
||||
WHERE ${@cn.hutool.core.util.StrUtil@toUnderlineCase(reqVO.identifier)} IS NOT NULL
|
||||
WHERE ${@cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField@buildFieldName(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])}
|
||||
ORDER BY ts DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
</mapper>
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.yudao.module.iot.framework.tdengine.core;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
public class TDengineTableFieldTest {
|
||||
|
||||
@Test
|
||||
public void testBuildFieldName() {
|
||||
assertEquals("ua", TDengineTableField.buildFieldName("Ua"));
|
||||
assertEquals("pf_t", TDengineTableField.buildFieldName("PfT"));
|
||||
assertEquals("pt", TDengineTableField.buildFieldName("PT"));
|
||||
assertEquals("pa", TDengineTableField.buildFieldName("PA"));
|
||||
assertEquals("geo_location", TDengineTableField.buildFieldName("GeoLocation"));
|
||||
assertEquals("light_status", TDengineTableField.buildFieldName("LightStatus"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildFieldName_blank() {
|
||||
assertNull(TDengineTableField.buildFieldName(null));
|
||||
assertEquals("", TDengineTableField.buildFieldName(""));
|
||||
assertEquals(" ", TDengineTableField.buildFieldName(" "));
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -5,11 +5,15 @@ import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
|
||||
import cn.iocoder.yudao.module.iot.dal.redis.device.DevicePropertyRedisDAO;
|
||||
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.product.IotProductService;
|
||||
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -17,8 +21,12 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
@@ -38,6 +46,8 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
|
||||
@Mock
|
||||
private IotThingModelService thingModelService;
|
||||
@Mock
|
||||
private IotProductService productService;
|
||||
@Mock
|
||||
private IotDevicePropertyMapper devicePropertyMapper;
|
||||
@Mock
|
||||
private DevicePropertyRedisDAO deviceDataRedisDAO;
|
||||
@@ -100,12 +110,15 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
|
||||
params.put("Temperature", "abc");
|
||||
IotDeviceMessage message = buildMessage(params);
|
||||
|
||||
// mock 方法
|
||||
when(thingModelService.getThingModelListByProductIdFromCache(device.getProductId()))
|
||||
.thenReturn(singletonList(temperature));
|
||||
when(thingModelService.convertThingModelPropertyValue(temperature, "abc")).thenReturn(null);
|
||||
|
||||
// 调用,并断言:不会抛出异常
|
||||
assertDoesNotThrow(() -> service.saveDeviceProperty(device, message));
|
||||
|
||||
// 断言:没有合法属性,不会写入 TDengine 与 Redis
|
||||
verify(devicePropertyMapper, never()).insert(any(), any(), anyLong(), anyLong());
|
||||
verify(deviceDataRedisDAO, never()).putAll(anyLong(), any());
|
||||
}
|
||||
@@ -119,11 +132,14 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
|
||||
params.put("Temperature", null);
|
||||
IotDeviceMessage message = buildMessage(params);
|
||||
|
||||
// mock 方法
|
||||
when(thingModelService.getThingModelListByProductIdFromCache(device.getProductId()))
|
||||
.thenReturn(singletonList(thingModel));
|
||||
|
||||
// 调用,并断言:不会抛出异常
|
||||
assertDoesNotThrow(() -> service.saveDeviceProperty(device, message));
|
||||
|
||||
// 断言:跳过空值,不会转换属性值,也不会写入 TDengine 与 Redis
|
||||
verify(thingModelService, never()).convertThingModelPropertyValue(any(), any());
|
||||
verify(devicePropertyMapper, never()).insert(any(), any(), anyLong(), anyLong());
|
||||
verify(deviceDataRedisDAO, never()).putAll(anyLong(), any());
|
||||
@@ -139,17 +155,48 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
|
||||
params.put("PowerSwitch", true);
|
||||
IotDeviceMessage message = buildMessage(params);
|
||||
|
||||
// mock 方法
|
||||
when(thingModelService.getThingModelListByProductIdFromCache(device.getProductId()))
|
||||
.thenReturn(singletonList(thingModel));
|
||||
when(thingModelService.convertThingModelPropertyValue(thingModel, true)).thenReturn((byte) 1);
|
||||
|
||||
// 调用,并断言:非字符串 key 不影响其它合法属性
|
||||
assertDoesNotThrow(() -> service.saveDeviceProperty(device, message));
|
||||
|
||||
// 断言:只写入合法属性
|
||||
Map<String, Object> dbProperties = captureMapperInsertProperties();
|
||||
assertEquals(1, dbProperties.size());
|
||||
assertEquals((byte) 1, dbProperties.get("PowerSwitch"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefineDevicePropertyData_fieldNameToLowerCase() {
|
||||
// 准备参数:全大写缩写和驼峰缩写都需要转换为 TDengine 实际的小写字段名
|
||||
Long productId = 2L;
|
||||
IotProductDO product = IotProductDO.builder().id(productId).build();
|
||||
List<IotThingModelDO> thingModels = Arrays.asList(
|
||||
buildThingModel("Ua", IotDataSpecsDataTypeEnum.FLOAT.getDataType()),
|
||||
buildThingModel("PfT", IotDataSpecsDataTypeEnum.FLOAT.getDataType()),
|
||||
buildThingModel("PT", IotDataSpecsDataTypeEnum.FLOAT.getDataType()),
|
||||
buildThingModel("PA", IotDataSpecsDataTypeEnum.FLOAT.getDataType()));
|
||||
thingModels.forEach(thingModel -> thingModel.setType(IotThingModelTypeEnum.PROPERTY.getType()));
|
||||
|
||||
// mock 方法
|
||||
when(productService.validateProductExists(productId)).thenReturn(product);
|
||||
when(thingModelService.getThingModelListByProductId(productId)).thenReturn(thingModels);
|
||||
when(devicePropertyMapper.getProductPropertySTableFieldList(productId)).thenReturn(Collections.emptyList());
|
||||
|
||||
// 调用
|
||||
service.defineDevicePropertyData(productId);
|
||||
|
||||
// 断言:字段名统一为小写下划线,避免 PT 和数据库中的 pt 被误判为不同字段
|
||||
ArgumentCaptor<List<TDengineTableField>> captor = ArgumentCaptor.forClass(List.class);
|
||||
verify(devicePropertyMapper).createProductPropertySTable(eq(productId), captor.capture());
|
||||
assertEquals(Arrays.asList("ua", "pf_t", "pt", "pa"), captor.getValue().stream()
|
||||
.map(TDengineTableField::getField)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user