|
|
@@ -36,6 +36,7 @@ import java.time.LocalDateTime;
|
|
|
import java.util.*;
|
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
|
|
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.getBigDecimal;
|
|
|
|
|
|
/**
|
|
|
* IoT 设备【属性】数据 Service 实现类
|
|
|
@@ -131,50 +132,59 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
+ @SuppressWarnings("PatternVariableCanBeUsed")
|
|
|
public void saveDeviceProperty(IotDeviceDO device, IotDeviceMessage message) {
|
|
|
if (!(message.getParams() instanceof Map)) {
|
|
|
log.error("[saveDeviceProperty][消息内容({}) 的 data 类型不正确]", message);
|
|
|
return;
|
|
|
}
|
|
|
+ Map<?, ?> params = (Map<?, ?>) message.getParams();
|
|
|
+ if (CollUtil.isEmpty(params)) {
|
|
|
+ log.error("[saveDeviceProperty][消息内容({}) 的 data 为空]", message);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
// 1. 根据物模型,拼接合法的属性
|
|
|
// TODO @芋艿:【待定 004】赋能后,属性到底以 thingModel 为准(ik),还是 db 的表结构为准(tl)?
|
|
|
List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductIdFromCache(device.getProductId());
|
|
|
Map<String, Object> properties = new HashMap<>();
|
|
|
- ((Map<?, ?>) message.getParams()).forEach((key, value) -> {
|
|
|
+ params.forEach((key, value) -> {
|
|
|
IotThingModelDO thingModel = CollUtil.findOne(thingModels, o -> o.getIdentifier().equals(key));
|
|
|
if (thingModel == null || thingModel.getProperty() == null) {
|
|
|
log.error("[saveDeviceProperty][消息({}) 的属性({}) 不存在]", message, key);
|
|
|
return;
|
|
|
}
|
|
|
- if (ObjectUtils.equalsAny(thingModel.getProperty().getDataType(),
|
|
|
+ String dataType = thingModel.getProperty().getDataType();
|
|
|
+ if (ObjectUtils.equalsAny(dataType,
|
|
|
IotDataSpecsDataTypeEnum.STRUCT.getDataType(), IotDataSpecsDataTypeEnum.ARRAY.getDataType())) {
|
|
|
// 特殊:STRUCT 和 ARRAY 类型,在 TDengine 里,有没对应数据类型,只能通过 JSON 来存储
|
|
|
properties.put((String) key, JsonUtils.toJsonString(value));
|
|
|
- } else if (IotDataSpecsDataTypeEnum.DOUBLE.getDataType().equals(thingModel.getProperty().getDataType())) {
|
|
|
- properties.put((String) key, Convert.toDouble(value));
|
|
|
- } else if (IotDataSpecsDataTypeEnum.FLOAT.getDataType().equals(thingModel.getProperty().getDataType())) {
|
|
|
+ } else if (IotDataSpecsDataTypeEnum.INT.getDataType().equals(dataType)) {
|
|
|
+ properties.put((String) key, Convert.toInt(value));
|
|
|
+ } else if (IotDataSpecsDataTypeEnum.FLOAT.getDataType().equals(dataType)) {
|
|
|
properties.put((String) key, Convert.toFloat(value));
|
|
|
- } else if (IotDataSpecsDataTypeEnum.BOOL.getDataType().equals(thingModel.getProperty().getDataType())) {
|
|
|
+ } else if (IotDataSpecsDataTypeEnum.DOUBLE.getDataType().equals(dataType)) {
|
|
|
+ properties.put((String) key, Convert.toDouble(value));
|
|
|
+ } else if (IotDataSpecsDataTypeEnum.BOOL.getDataType().equals(dataType)) {
|
|
|
properties.put((String) key, Convert.toByte(value));
|
|
|
- } else {
|
|
|
+ } else {
|
|
|
properties.put((String) key, value);
|
|
|
}
|
|
|
});
|
|
|
if (CollUtil.isEmpty(properties)) {
|
|
|
log.error("[saveDeviceProperty][消息({}) 没有合法的属性]", message);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 2.1 保存设备属性【数据】
|
|
|
- devicePropertyMapper.insert(device, properties, LocalDateTimeUtil.toEpochMilli(message.getReportTime()));
|
|
|
+ } else {
|
|
|
+ // 2.1 保存设备属性【数据】
|
|
|
+ devicePropertyMapper.insert(device, properties, LocalDateTimeUtil.toEpochMilli(message.getReportTime()));
|
|
|
|
|
|
- // 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.getId(), properties2);
|
|
|
+ // 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.getId(), properties2);
|
|
|
+ }
|
|
|
|
|
|
// 2.3 提取 GeoLocation 并更新设备定位
|
|
|
+ // 为什么 properties 为空,也要执行定位更新?因为可能上报的属性里,没有合法属性,但是包含 GeoLocation 定位属性
|
|
|
extractAndUpdateDeviceLocation(device, (Map<?, ?>) message.getParams());
|
|
|
}
|
|
|
|
|
|
@@ -231,14 +241,13 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
|
|
|
*/
|
|
|
private void extractAndUpdateDeviceLocation(IotDeviceDO device, Map<?, ?> params) {
|
|
|
// 1. 解析 GeoLocation 经纬度坐标
|
|
|
- Double[] location = parseGeoLocation(params);
|
|
|
+ BigDecimal[] location = parseGeoLocation(params);
|
|
|
if (location == null) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 2. 更新设备定位
|
|
|
- deviceService.updateDeviceLocation(device,
|
|
|
- BigDecimal.valueOf(location[0]), BigDecimal.valueOf(location[1]));
|
|
|
+ deviceService.updateDeviceLocation(device, location[0], location[1]);
|
|
|
log.info("[extractAndUpdateGeoLocation][设备({}) 定位更新: lng={}, lat={}]",
|
|
|
device.getId(), location[0], location[1]);
|
|
|
}
|
|
|
@@ -250,8 +259,7 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
|
|
|
* @return [经度, 纬度],解析失败返回 null
|
|
|
*/
|
|
|
@SuppressWarnings("unchecked")
|
|
|
- // TODO @AI:返回 BigDecimal 数组;
|
|
|
- private Double[] parseGeoLocation(Map<?, ?> params) {
|
|
|
+ private BigDecimal[] parseGeoLocation(Map<?, ?> params) {
|
|
|
if (params == null) {
|
|
|
return null;
|
|
|
}
|
|
|
@@ -276,18 +284,24 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
|
|
|
}
|
|
|
|
|
|
// 3. 提取经纬度(支持阿里云命名规范:首字母大写)
|
|
|
- Double longitude = MapUtil.getDouble(geoLocation, "Longitude");
|
|
|
+ BigDecimal longitude = getBigDecimal(geoLocation, "Longitude");
|
|
|
if (longitude == null) {
|
|
|
- longitude = MapUtil.getDouble(geoLocation, "longitude");
|
|
|
+ longitude = getBigDecimal(geoLocation, "longitude");
|
|
|
}
|
|
|
- Double latitude = MapUtil.getDouble(geoLocation, "Latitude");
|
|
|
+ BigDecimal latitude = getBigDecimal(geoLocation, "Latitude");
|
|
|
if (latitude == null) {
|
|
|
- latitude = MapUtil.getDouble(geoLocation, "latitude");
|
|
|
+ latitude = getBigDecimal(geoLocation, "latitude");
|
|
|
}
|
|
|
if (longitude == null || latitude == null) {
|
|
|
return null;
|
|
|
}
|
|
|
- return new Double[]{longitude, latitude};
|
|
|
+ // 校验经纬度范围:经度 -180 到 180,纬度 -90 到 90
|
|
|
+ if (longitude.compareTo(BigDecimal.valueOf(-180)) < 0 || longitude.compareTo(BigDecimal.valueOf(180)) > 0
|
|
|
+ || latitude.compareTo(BigDecimal.valueOf(-90)) < 0 || latitude.compareTo(BigDecimal.valueOf(90)) > 0) {
|
|
|
+ log.warn("[parseGeoLocation][经纬度超出有效范围: lng={}, lat={}]", longitude, latitude);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return new BigDecimal[]{longitude, latitude};
|
|
|
}
|
|
|
|
|
|
}
|