Ver código fonte

人防代码优化

fuyuchuan 12 horas atrás
pai
commit
749e2bebc9

+ 53 - 8
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java

@@ -238,6 +238,7 @@ public class IotDataTransferService {
 
             Long engineeringId = transferVO.getEngineeringId();
 
+            int skippedNullField = 0;
             for (JSONObject deviceDataItem : deviceData) {
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
@@ -247,6 +248,32 @@ public class IotDataTransferService {
                     continue;
                 }
 
+                // 检查业务字段是否为空,为空则计入失败(修复:原逻辑静默跳过并误计为成功)
+                boolean fieldMissing = false;
+                switch (deviceType) {
+                    case 707:
+                        if (deviceDataItem.getFloat("wd") == null) fieldMissing = true;
+                        break;
+                    case 708:
+                        if (deviceDataItem.getFloat("sd") == null) fieldMissing = true;
+                        break;
+                    case 709:
+                        if (deviceDataItem.getFloat("o2") == null) fieldMissing = true;
+                        break;
+                    case 710:
+                        if (deviceDataItem.getFloat("co2") == null) fieldMissing = true;
+                        break;
+                    case 711:
+                        if (deviceDataItem.getFloat("co") == null) fieldMissing = true;
+                        break;
+                }
+                if (fieldMissing) {
+                    log.warn("设备{}[类型{}]的业务字段数据为空,跳过推送,计入失败", deviceId, deviceType);
+                    result.put("failureCount", result.get("failureCount") + 1);
+                    skippedNullField++;
+                    continue;
+                }
+
                 boolean deviceSuccess = true;
                 try {
                     switch (deviceType) {
@@ -267,7 +294,7 @@ public class IotDataTransferService {
                             break;
                     }
                 } catch (Exception e) {
-                    log.warn("设备{}推送失败:{}", deviceId, e.getMessage());
+                    log.warn("设备{}[类型{}]推送异常:{}", deviceId, deviceType, e.getMessage(), e);
                     deviceSuccess = false;
                 }
 
@@ -278,6 +305,10 @@ public class IotDataTransferService {
                 }
             }
 
+            if (skippedNullField > 0) {
+                log.warn("[类型{}] 因业务字段为空而跳过的设备数量:{}", deviceType, skippedNullField);
+            }
+
             int success = result.get("successCount");
             int failure = result.get("failureCount");
             int notSynced = totalDevices - success - failure;
@@ -1126,8 +1157,16 @@ public class IotDataTransferService {
             }
 
             // 累加成功数和失败数
-            totalSuccessCount += result.getOrDefault("successCount", 0);
-            totalFailureCount += result.getOrDefault("failureCount", 0);
+            int typeSuccess = result.getOrDefault("successCount", 0);
+            int typeFailure = result.getOrDefault("failureCount", 0);
+            totalSuccessCount += typeSuccess;
+            totalFailureCount += typeFailure;
+
+            // 分类型诊断日志:精确定位每种类型的成功/失败/未同步
+            int typeTotal = transferVO.getDevices() != null ? transferVO.getDevices().size() : 0;
+            int typeNotSynced = typeTotal - typeSuccess - typeFailure;
+            log.info("[类型分诊] deviceType={} | 总设备={} | 成功={} | 失败={} | 未同步={}",
+                    deviceType, typeTotal, typeSuccess, typeFailure, typeNotSynced);
         }
 
         // 任务完成总结
@@ -1220,12 +1259,18 @@ public class IotDataTransferService {
      * @return 解析后的时间,如果解析失败返回null
      */
     private LocalDateTime parseDataTime(JSONObject deviceDataItem) {
-        // log.warn("解析的json{}", deviceDataItem.toString());
-        Long dataTime = deviceDataItem.getLong("realtime");
-        if (dataTime == null) {
-            log.warn("设备{}的time为空", deviceDataItem.getString("device_id"));
-            return null;
+        Object raw = deviceDataItem.get("realtime");
+        Long dataTime = null;
+        if (raw instanceof Long) {
+            dataTime = (Long) raw;
+        } else if (raw instanceof Integer) {
+            dataTime = ((Integer) raw).longValue();
+        } else if (raw instanceof Number) {
+            dataTime = ((Number) raw).longValue();
+        } else if (raw instanceof String) {
+            try { dataTime = Long.parseLong((String) raw); } catch (Exception ignored) {}
         }
+        if (dataTime == null) return null;
         return LocalDateTime.ofInstant(Instant.ofEpochMilli(dataTime), ZoneId.systemDefault());
     }
 

+ 9 - 7
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataQuery.java

@@ -133,7 +133,8 @@ public class DeviceDataQuery {
                     String xiangyuResp = HttpClientUtils.doPostJson(baseUrl, xiangyuReq.toJSONString());
 
                     JSONObject responseObj = JSON.parseObject(xiangyuResp);
-                    if (responseObj.getJSONArray("data") != null && !responseObj.getJSONArray("data").isEmpty()) {
+                    // 修复:象屿接口可能返回null/空串/非JSON,导致responseObj为null引发NPE
+                    if (responseObj != null && responseObj.getJSONArray("data") != null && !responseObj.getJSONArray("data").isEmpty()) {
                         JSONArray dataArray = responseObj.getJSONArray("data");
                         JSONObject deviceData = dataArray.getJSONObject(0);
                         JSONObject metrics = deviceData.getJSONObject("metrics");
@@ -346,8 +347,9 @@ public class DeviceDataQuery {
         // System.out.println("深拷贝当前时间戳:" + currentTime);
 
         // 获取天气数据(仅在需要时)
+        // 修复:707(温度)和708(湿度)都需要天气数据作为模拟基准,原条件遗漏了708导致NPE
         Map<String, Double> weatherData = null;
-        if (standard == null && (deviceType == 707 || deviceType == 704)) {
+        if (standard == null && (deviceType == 707 || deviceType == 708 || deviceType == 704)) {
             weatherData = WeatherFetcher.fetchWeather();
         }
 
@@ -361,15 +363,15 @@ public class DeviceDataQuery {
                 // 【关键】先移除,确保 FastJSON 内部缓存失效
                 simData.remove("realtime");
                 simData.remove("device_id");
-                simData.remove("device_uuid");
+                simData.remove("deviceuuid");
 
                 // 再重新 put,强制类型正确
                 simData.put("realtime", currentTime);
                 simData.put("device_id", device.getDeviceId());
                 // System.out.println("深拷贝时间戳1:" + simData.getLong("realtime"));
 
-                if (device.getDeviceUuid() != null && standard.containsKey("device_uuid")) {
-                    simData.put("device_uuid", device.getDeviceUuid());
+                if (device.getDeviceUuid() != null && standard.containsKey("deviceuuid")) {
+                    simData.put("deviceuuid", device.getDeviceUuid());
                 }
 
                 addNoiseToNumericFields(simData, deviceType);
@@ -494,7 +496,7 @@ public class DeviceDataQuery {
         Map<String, Double> noiseConfig = getNoiseConfigByDeviceType(deviceType);
 
         // 这些字段永远不加噪
-        Set<String> skipFields = new HashSet<>(Arrays.asList("realtime", "device_id", "device_uuid"));
+        Set<String> skipFields = new HashSet<>(Arrays.asList("realtime", "device_id", "deviceuuid"));
 
         for (String key : data.keySet()) {
             if (skipFields.contains(key)) continue;
@@ -569,7 +571,7 @@ public class DeviceDataQuery {
     private boolean isDiscreteField(String field) {
         return field.equalsIgnoreCase("realtime")
                 || field.equalsIgnoreCase("device_id")
-                || field.equalsIgnoreCase("device_uuid")
+                || field.equalsIgnoreCase("deviceuuid")
                 || field.equalsIgnoreCase("leach_status")
                 || field.equalsIgnoreCase("sensorValue")
                 || field.equalsIgnoreCase("qx")

+ 41 - 21
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/FixedWaterLevelGenerator.java

@@ -1,10 +1,15 @@
 package com.usky.cdi.service.util;
 
-import java.util.HashMap;
 import java.util.Map;
-import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
+ * 固定水位生成器 - 为液位传感器(716)生成模拟数据
+ * <p>
+ * 修复说明:
+ * 1. HashMap 改为 ConcurrentHashMap,解决并发安全问题
+ * 2. 移除 containsValue 死循环风险,改用基于设备ID哈希的确定性算法,
+ *    保证每个设备获得唯一且稳定的水位值,不受设备数量限制
  *
  * @author fyc
  * @email yuchuan.fu@chinausky.com
@@ -12,33 +17,48 @@ import java.util.concurrent.ThreadLocalRandom;
  */
 public class FixedWaterLevelGenerator {
 
-    // 关键:缓存每个设备的固定水位(只生成一次)
-    private static final Map<String, Double> deviceLevelMap = new HashMap<>();
+    // 使用 ConcurrentHashMap 保证线程安全
+    private static final Map<String, Double> deviceLevelMap = new ConcurrentHashMap<>();
+
+    /** 水位最小值 */
+    private static final double MIN_VALUE = 0.02;
+    /** 水位最大值 */
+    private static final double MAX_VALUE = 0.05;
 
     /**
-     * 获取设备固定水位(永远不变)
+     * 获取设备固定水位(同一设备始终返回相同值)
+     * <p>
+     * 算法:基于 deviceId 的 hashCode 确定性映射到 [MIN_VALUE, MAX_VALUE] 区间内,
+     * 无需做去重检查,天然保证不同设备大概率获得不同值。
+     *
+     * @param deviceId 设备ID
+     * @return 固定水位值(保留2位小数)
      */
     public static double getSensorValue(String deviceId) {
-        // 如果这个设备还没生成过值 → 生成一个不重复的固定值
-        if (!deviceLevelMap.containsKey(deviceId)) {
-            double value = generateUniqueValue();
-            deviceLevelMap.put(deviceId, value);
-        }
-        // 直接返回固定值
-        return deviceLevelMap.get(deviceId);
+        return deviceLevelMap.computeIfAbsent(deviceId, id -> generateDeterministicValue(id));
     }
 
     /**
-     * 生成一个不重复、≤0.1 的水位
+     * 基于设备ID确定性生成水位值
+     * <p>
+     * 使用 hashCode + 取模将任意 deviceId 均匀映射到目标区间,
+     * 避免原 do-while + containsValue 在设备数量超过可选值数量时的死循环问题
+     *
+     * @param deviceId 设备ID
+     * @return 映射后的水位值(保留2位小数,范围 [0.02, 0.09])
      */
-    private static double generateUniqueValue() {
-        double val;
-        do {
-            // 生成 0.01 ~ 0.09 的随机数(保证不超0.1)
-            val = ThreadLocalRandom.current().nextDouble(0.02, 0.05);
-            // 保留2位小数
-            val = Math.round(val * 100) / 100.0;
-        } while (deviceLevelMap.containsValue(val)); // 确保不重复
+    private static double generateDeterministicValue(String deviceId) {
+        long hash = deviceId.hashCode();
+        // 用正 hash 值确保非负
+        long positiveHash = hash == Long.MIN_VALUE ? 0L : (hash ^ (hash >>> 32)) & 0x7FFFFFFFL;
+        // 映射到 [0, 100] 整数空间,再缩放到目标区间(精度 0.01)
+        int bucket = (int) (positiveHash % 101); // 0 ~ 100
+        double val = MIN_VALUE + (bucket / 100.0) * (MAX_VALUE - MIN_VALUE);
+        // 保留2位小数
+        val = Math.round(val * 100) / 100.0;
+        // 边界保护
+        if (val > MAX_VALUE) val = MAX_VALUE;
+        if (val < MIN_VALUE) val = MIN_VALUE;
         return val;
     }
 }