Browse Source

feat(iot):【设备定位:70%】优化设备坐标的解析逻辑代码,基于 hashed-juggling-tome.md

YunaiV 7 months ago
parent
commit
6892571a33

+ 44 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/MapUtils.java

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -65,4 +66,47 @@ public class MapUtils {
         return map;
     }
 
+    /**
+     * 从 Map 中获取 BigDecimal 值
+     *
+     * @param map Map 数据源
+     * @param key 键名
+     * @return BigDecimal 值,解析失败或值为 null 时返回 null
+     */
+    public static BigDecimal getBigDecimal(Map<String, ?> map, String key) {
+        return getBigDecimal(map, key, null);
+    }
+
+    /**
+     * 从 Map 中获取 BigDecimal 值
+     *
+     * @param map          Map 数据源
+     * @param key          键名
+     * @param defaultValue 默认值
+     * @return BigDecimal 值,解析失败或值为 null 时返回默认值
+     */
+    public static BigDecimal getBigDecimal(Map<String, ?> map, String key, BigDecimal defaultValue) {
+        if (map == null) {
+            return defaultValue;
+        }
+        Object value = map.get(key);
+        if (value == null) {
+            return defaultValue;
+        }
+        if (value instanceof BigDecimal) {
+            return (BigDecimal) value;
+        }
+        if (value instanceof Number) {
+            return BigDecimal.valueOf(((Number) value).doubleValue());
+        }
+        if (value instanceof String) {
+            try {
+                return new BigDecimal((String) value);
+            } catch (NumberFormatException e) {
+                return defaultValue;
+            }
+        }
+        return defaultValue;
+    }
+
 }

+ 8 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.DecimalMax;
+import jakarta.validation.constraints.DecimalMin;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -37,10 +39,14 @@ public class IotDeviceSaveReqVO {
     @Schema(description = "设备配置", example = "{\"abc\": \"efg\"}")
     private String config;
 
-    @Schema(description = "设备位置的纬度", example = "16380")
+    @Schema(description = "设备位置的纬度", example = "39.915")
+    @DecimalMin(value = "-90", message = "纬度范围为 -90 到 90")
+    @DecimalMax(value = "90", message = "纬度范围为 -90 到 90")
     private BigDecimal latitude;
 
-    @Schema(description = "设备位置的经度", example = "16380")
+    @Schema(description = "设备位置的经度", example = "116.404")
+    @DecimalMin(value = "-180", message = "经度范围为 -180 到 180")
+    @DecimalMax(value = "180", message = "经度范围为 -180 到 180")
     private BigDecimal longitude;
 
 }

+ 0 - 10
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java

@@ -137,16 +137,6 @@ public class IotDeviceDO extends TenantBaseDO {
      * 设备位置的经度
      */
     private BigDecimal longitude;
-    /**
-     * 地区编码
-     * <p>
-     * 关联 Area 的 id
-     */
-    private Integer areaId;
-    /**
-     * 设备详细地址
-     */
-    private String address;
 
     /**
      * 设备配置

+ 40 - 26
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImpl.java

@@ -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};
     }
 
 }