Sfoglia il codice sorgente

fix: 时间段查询

jackzhou 6 mesi fa
parent
commit
7d09b98d4f

+ 18 - 14
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/FluxAggregationUtils.java

@@ -7,10 +7,12 @@ package vip.xiaonuo.coldchain.core.service;
  * @description
  * @date 2024/12/10 12:00:28
  */
+
 import lombok.experimental.UtilityClass;
 
 import java.time.Duration;
 import java.time.Instant;
+
 @UtilityClass
 
 public class FluxAggregationUtils {
@@ -25,28 +27,30 @@ public class FluxAggregationUtils {
     public static String determineAggregationWindow(String startTime, String stopTime) {
         Instant start = Instant.parse(startTime);
         Instant stop = Instant.parse(stopTime);
-
         // 计算时间间隔
         Duration duration = Duration.between(start, stop);
+        // 获取时、分、秒等单位的差异
         long days = duration.toDays();
-
-        // 根据间隔选择聚合窗口
-        if (days > 90) {
+        long hours = duration.toHours() % 24; // 剩余的小时
+        long minutes = duration.toMinutes() % 60; // 剩余的分钟
+        // 判断时间间隔并选择合适的聚合窗口
+        if (days > 365) {
+            return "1y"; // 超过 1 年按年度聚合
+        } else if (days > 90) {
             return "1mo"; // 超过 90 天按月度聚合
         } else if (days > 30) {
             return "1w"; // 超过 30 天按每周聚合
-        } else if (days > 1) {
-            return "1d"; // 超过 1 天但少于 30 天按每日聚合
+        } else if (days > 1 || hours >= 12) {
+            return "1d"; // 超过 1 天或接近一天(12小时以上)按每日聚合
+        } else if (hours > 1) {
+            return "1h"; // 超过 1 小时但小于 12 小时按每小时聚合
+        } else if (minutes >= 30) {
+            return "30m"; // 超过 30 分钟但小于 1 小时按每 30 分钟聚合
+        } else if (minutes > 5) {
+            return "5m"; // 超过 5 分钟按每 5 分钟聚合
         } else {
-            return "1h"; // 小于或等于 1 天按每小时聚合
+            return "1m"; // 小于 5 分钟按每分钟聚合
         }
     }
 
-//    public static void main(String[] args) {
-//        // 示例测试
-//        String startTime = "2024-01-01T00:00:00Z";
-//        String stopTime = "2024-04-01T00:00:00Z";
-//        String aggregationWindow = determineAggregationWindow(startTime, stopTime);
-//        System.out.println("聚合窗口: " + aggregationWindow); // 输出: 1mo
-//    }
 }

+ 1 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/FluxQueryBuilder.java

@@ -54,6 +54,7 @@ public class FluxQueryBuilder {
         }
         // 按时间升序排序
         flux.append("  |> sort(columns: [\"_time\"], desc: false)\n");
+        flux.append("  |> map(fn: (r) => ({_time: r._time, create_time: r._time, "+field+": r._value}))\n");
         return flux.toString();
     }
 

+ 44 - 7
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/JfcloudSensorDataService.java

@@ -1,12 +1,12 @@
 package vip.xiaonuo.coldchain.core.service;
 
-import cn.hutool.core.lang.Assert;
 import com.github.jfcloud.influxdb.config.JfcloudInfluxDB2Properties;
 import com.github.jfcloud.influxdb.flux.JfcloudFluxDataService;
 import com.github.jfcloud.influxdb.service.JfcloudInfluxDBService;
 import com.influxdb.client.QueryApi;
 import com.influxdb.query.FluxTable;
 import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
 import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
 import vip.xiaonuo.coldchain.core.util.DateFormatter;
 
@@ -67,35 +67,72 @@ public class JfcloudSensorDataService extends JfcloudFluxDataService<SensorData>
      * @return 查询到的传感器数据列表
      */
 
-    public List<SensorData> queryDataByDeviceIdAndRoads(String deviceId, Integer roads, String startTimeStr, String endTimeStr,String field) {
+//    public List<SensorData> queryDataByDeviceIdAndRoads(String deviceId, Integer roads, String startTimeStr, String endTimeStr,String field) {
+//        Assert.notNull(deviceId, "deviceId cannot be null");
+//        Assert.notNull(roads, "roads cannot be null");
+//        Assert.notNull(startTimeStr, "startTime cannot be null");
+//        Assert.notNull(endTimeStr, "endTime cannot be null");
+//        Assert.notNull(field, "field cannot be null");
+//        //替换起始时间和结束时间2024/09/09 这种格式为2024-09-09
+//        startTimeStr = DateFormatter.replaceDateFormat(startTimeStr);
+//        endTimeStr = DateFormatter.replaceDateFormat(endTimeStr);
+//        // 如果只有日期部分,则手动补充时间部分(00:00:00)
+//        if (startTimeStr.length() == 10) {
+//            startTimeStr += " 00:00:00";
+//        }
+//        if (endTimeStr.length() == 10) {
+//            endTimeStr += " 23:59:59";
+//        }
+//        String startTimeFormatted = convertToISO8601(startTimeStr);
+//        String endTimeFormatted = convertToISO8601(endTimeStr);
+//        String aggregationWindow = FluxAggregationUtils.determineAggregationWindow(startTimeFormatted, endTimeFormatted);
+//        String measurement = "sensor_data";
+//        Map<String, String> filters = Map.of(
+//                "device_id", deviceId,
+//                "roads", roads.toString()
+//        );
+//        String query = FluxQueryBuilder.buildRangeQuery(getBucketName(), startTimeFormatted, endTimeFormatted, measurement, field, filters, aggregationWindow);
+//        QueryApi queryApi = jfcloudInfluxDBService.getInfluxDBClient().getQueryApi();
+//        List<FluxTable> results = queryApi.query(query);
+//        return results.stream().flatMap(table -> table.getRecords().stream()).map(fluxRecord -> mapRecordToEntity(fluxRecord, getEntityClass()))  // 转换为 SensorData 实体
+//                .collect(Collectors.toList());
+//    }
+
+    public List<SensorData> queryDataByDeviceIdAndRoads(String deviceId, Integer roads, String startTimeStr, String endTimeStr, String field) {
         Assert.notNull(deviceId, "deviceId cannot be null");
         Assert.notNull(roads, "roads cannot be null");
         Assert.notNull(startTimeStr, "startTime cannot be null");
         Assert.notNull(endTimeStr, "endTime cannot be null");
         Assert.notNull(field, "field cannot be null");
-        //替换起始时间和结束时间2024/09/09 这种格式为2024-09-09
+
+        // 替换日期格式,确保格式一致
         startTimeStr = DateFormatter.replaceDateFormat(startTimeStr);
         endTimeStr = DateFormatter.replaceDateFormat(endTimeStr);
-        // 如果只有日期部分,则手动补充时间部分(00:00:00)
+
+        // 如果只有日期部分,补充时间部分为00:00:00和23:59:59
         if (startTimeStr.length() == 10) {
             startTimeStr += " 00:00:00";
         }
         if (endTimeStr.length() == 10) {
             endTimeStr += " 23:59:59";
         }
+        // 转换为ISO8601格式
         String startTimeFormatted = convertToISO8601(startTimeStr);
         String endTimeFormatted = convertToISO8601(endTimeStr);
         String aggregationWindow = FluxAggregationUtils.determineAggregationWindow(startTimeFormatted, endTimeFormatted);
-        String measurement = "sensor_data";
+        String measurement = "sensor_data"; // 数据表名称
         Map<String, String> filters = Map.of(
                 "device_id", deviceId,
                 "roads", roads.toString()
         );
+        // 构建查询语句
         String query = FluxQueryBuilder.buildRangeQuery(getBucketName(), startTimeFormatted, endTimeFormatted, measurement, field, filters, aggregationWindow);
         QueryApi queryApi = jfcloudInfluxDBService.getInfluxDBClient().getQueryApi();
         List<FluxTable> results = queryApi.query(query);
-        return results.stream().flatMap(table -> table.getRecords().stream()).map(fluxRecord -> mapRecordToEntity(fluxRecord, getEntityClass()))  // 转换为 SensorData 实体
-                .collect(Collectors.toList());  // 返回多条数据
+        return results.stream()
+                .flatMap(table -> table.getRecords().stream())
+                .map(fluxRecord -> mapRecordToEntity(fluxRecord, getEntityClass()))
+                .collect(Collectors.toList());
     }
 
 

+ 19 - 16
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/util/DateFormatter.java

@@ -109,23 +109,26 @@ public class DateFormatter {
         }
         // 将 Instant 转换为 LocalDateTime
         LocalDateTime lastUpdatedLocal = LocalDateTime.ofInstant(lastUpdated, ZoneId.systemDefault());
-        // 获取当前时间
-        LocalDateTime now = LocalDateTime.now();
-        // 计算两个日期的差值(以天为单位)
-        long daysDiff = ChronoUnit.DAYS.between(lastUpdatedLocal, now);
-
+//        // 获取当前时间
+//        LocalDateTime now = LocalDateTime.now();
+//        // 计算两个日期的差值(以天为单位)
+//        long daysDiff = ChronoUnit.DAYS.between(lastUpdatedLocal, now);
+//        // 始终使用日期和小时分钟秒的格式
+//        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd HH:mm");
+//        if (daysDiff == 0) {
+//            // 如果是今天,返回 "HH:mm" 格式
+//            return lastUpdatedLocal.format(DateTimeFormatter.ofPattern("HH:mm"));
+//        } else if (daysDiff > 0 && daysDiff <= 7) {
+//            // 如果是过去一周以内,使用中文星期几 + 小时
+//            DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("EEEE", Locale.SIMPLIFIED_CHINESE);
+//            return lastUpdatedLocal.format(dayFormatter) + " " + lastUpdatedLocal.format(DateTimeFormatter.ofPattern("HH:mm"));
+//        } else {
+//            // 超过 7 天,显示完整的日期时间格式
+//            return lastUpdatedLocal.format(formatter);
+//        }
         // 始终使用日期和小时分钟秒的格式
-        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd HH:mm");
-
-        if (daysDiff <= 7) {
-            // 使用 Locale.CHINESE 进行格式化,显示中文星期几并加小时
-            DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("EEEE", Locale.SIMPLIFIED_CHINESE);
-            // 返回中文星期几 + 小时
-            return lastUpdatedLocal.format(dayFormatter) + " " + lastUpdatedLocal.format(DateTimeFormatter.ofPattern("HH:mm"));
-        } else {
-            // 超过 7 天,显示完整的日期时间格式
-            return lastUpdatedLocal.format(formatter);  // 返回完整的日期时间
-        }
+        DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("MM-dd");
+        return lastUpdatedLocal.format(dayFormatter);
     }
 
 

+ 2 - 5
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/controller/AppController.java

@@ -9,7 +9,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
-import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
 import vip.xiaonuo.coldchain.modular.app.param.*;
 import vip.xiaonuo.coldchain.modular.app.service.AppDeviceService;
 import vip.xiaonuo.coldchain.modular.app.service.MessageService;
@@ -65,10 +64,8 @@ public class AppController {
     // 查询设备数据接口(POST)
     @PostMapping("/device/view")
     @Operation(summary = "查询采集设备数据")
-    public CommonResult<Map<String, Object>> queryDeviceData(@RequestBody @Valid AppDeviceQueryParams appDeviceQueryParams) {
-        List<SensorData> sensorDataList = appDeviceService.queryDataByDeviceIdAndRoads(appDeviceQueryParams);
-        Map<String, Object> stringObjectMap = SensorDataTransformer.transformToResponseFormat2(sensorDataList);
-        return CommonResult.data((stringObjectMap));
+    public CommonResult<SensorEchartDataResult> queryDeviceData(@RequestBody @Valid AppDeviceQueryParams appDeviceQueryParams) {
+        return CommonResult.data(appDeviceService.queryDataByDeviceIdAndRoads(appDeviceQueryParams));
     }
 
     @PostMapping("/device/alarm")

+ 35 - 5
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/SensorDataTransformer.java

@@ -4,10 +4,7 @@ import lombok.experimental.UtilityClass;
 import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
 import vip.xiaonuo.coldchain.core.util.DateFormatter;
 
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @UtilityClass
@@ -89,7 +86,7 @@ public class SensorDataTransformer {
     }
 
     // 格式化浮动值为保留两位小数
-    private static Float formatFloat(Float value) {
+    public static Float formatFloat(Float value) {
         if (value == null) return null;
         return Float.parseFloat(String.format("%.2f", value));
     }
@@ -114,4 +111,37 @@ public class SensorDataTransformer {
 
         return response;
     }
+
+    public SensorEchartData processSensorData(List<SensorData> sensorDataList, String dataType) {
+        // 按时间升序排序
+        List<SensorData> sortedData = sensorDataList.stream()
+                .sorted(Comparator.comparing(SensorData::getTime))
+                .toList();
+        // 提取时间和数值
+        List<String> times = new ArrayList<>();
+        List<Float> values = new ArrayList<>();
+        sortedData.stream()
+                .filter(data -> isValidData(data, dataType))
+                .forEach(data -> {
+                    times.add(DateFormatter.getFormattedSingleDateHourse(data.getTime())); // 时间格式化
+                    values.add(SensorDataTransformer.formatFloat(getDataValue(data, dataType)));                 // 数值格式化
+                });
+        return new SensorEchartData(times, values);
+    }
+
+    // 工具方法:判断是否为有效数据
+    private boolean isValidData(SensorData data, String dataType) {
+        if ("temperature".equals(dataType)) return data.getTemperature() != null;
+        if ("humidity".equals(dataType)) return data.getHumidity() != null;
+        if ("co2".equals(dataType)) return data.getCo2() != null;
+        return false;
+    }
+
+    // 工具方法:获取指定类型的数值
+    private Float getDataValue(SensorData data, String dataType) {
+        if ("temperature".equals(dataType)) return data.getTemperature();
+        if ("humidity".equals(dataType)) return data.getHumidity();
+        if ("co2".equals(dataType)) return data.getCo2();
+        return null;
+    }
 }

+ 24 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/SensorEchartData.java

@@ -0,0 +1,24 @@
+package vip.xiaonuo.coldchain.modular.app.param;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author jackzhou
+ * @version 1.0
+ * @project jfcloud-coldchain
+ * @description
+ * @date 2024/12/10 14:00:53
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class SensorEchartData {
+    private List<String> x = new ArrayList<>(); // 时间列表
+    private List<Float> y = new ArrayList<>();  // 数值列表
+}
+

+ 17 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/SensorEchartDataResult.java

@@ -0,0 +1,17 @@
+package vip.xiaonuo.coldchain.modular.app.param;
+
+import lombok.Data;
+
+/**
+ * @author jackzhou
+ * @version 1.0
+ * @project jfcloud-coldchain
+ * @description
+ * @date 2024/12/10 15:03:00
+ */
+@Data
+public class SensorEchartDataResult {
+    private SensorEchartData temperature = new SensorEchartData();
+    private SensorEchartData humidity = new SensorEchartData();
+    private SensorEchartData co2 = new SensorEchartData();
+}

+ 58 - 16
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/service/AppDeviceService.java

@@ -147,26 +147,68 @@ public class AppDeviceService {
         return deviceStatus;
     }
 
-    public List<SensorData> queryDataByDeviceIdAndRoads(AppDeviceQueryParams appDeviceQueryParams) {
+    public SensorEchartDataResult queryDataByDeviceIdAndRoads(AppDeviceQueryParams appDeviceQueryParams) {
         Assert.notNull(appDeviceQueryParams, "appDeviceQueryParams cannot be null");
-        MonitorTargetRegion oneByDeviceCodeAndSensorNo = monitorTargetRegionService.findOneByDeviceCodeAndSensorNo(appDeviceQueryParams.getSensorCode(), appDeviceQueryParams.getSensorRoute());
-        if (Objects.nonNull(oneByDeviceCodeAndSensorNo)) {
-            //探头类型
-            String sensorType = oneByDeviceCodeAndSensorNo.getSensorType();
-            if(StrUtil.isNotBlank(sensorType) && sensorType.toLowerCase().contains("w")){
-                List<SensorData> temperatures = monitorDeviceService.queryDataByDeviceIdAndRoads(appDeviceQueryParams.getSensorCode(), appDeviceQueryParams.getSensorRoute(), appDeviceQueryParams.getStartTime(), appDeviceQueryParams.getEndTime(), "temperature");
-                return temperatures;
-            }
-            if(StrUtil.isNotBlank(sensorType) && sensorType.toLowerCase().contains("s")){
-                List<SensorData> humiditys = monitorDeviceService.queryDataByDeviceIdAndRoads(appDeviceQueryParams.getSensorCode(), appDeviceQueryParams.getSensorRoute(), appDeviceQueryParams.getStartTime(), appDeviceQueryParams.getEndTime(), "humidity");
-            }
-            if(StrUtil.isNotBlank(sensorType) && sensorType.toLowerCase().contains("c")){
-                List<SensorData> co2s = monitorDeviceService.queryDataByDeviceIdAndRoads(appDeviceQueryParams.getSensorCode(), appDeviceQueryParams.getSensorRoute(), appDeviceQueryParams.getStartTime(), appDeviceQueryParams.getEndTime(), "co2");
-            }
+
+        // 查找监控目标区域
+        MonitorTargetRegion monitorTarget = monitorTargetRegionService.findOneByDeviceCodeAndSensorNo(
+                appDeviceQueryParams.getSensorCode(),
+                appDeviceQueryParams.getSensorRoute()
+        );
+        // 如果监控目标区域为空,直接返回空的结果
+        if (Objects.isNull(monitorTarget)) {
+            return getEmptyResult();
         }
-        return Collections.emptyList();
+        String sensorType = monitorTarget.getSensorType();
+
+        // 如果传感器类型为空,直接返回空的结果
+        if (StrUtil.isBlank(sensorType)) {
+            return getEmptyResult();
+        }
+
+        // 定义传感器类型与数据类型的映射关系
+        Map<String, String> sensorMapping = Map.of(
+                "w", "temperature", // 温度
+                "s", "humidity",    // 湿度
+                "c", "co2"          // 二氧化碳
+        );
+
+        // 创建结果对象
+        SensorEchartDataResult result = new SensorEchartDataResult();
+
+        // 遍历传感器类型映射
+        sensorMapping.forEach((key, dataType) -> {
+            // 如果传感器类型包含对应的字符
+            if (sensorType.toLowerCase().contains(key)) {
+                // 获取相应的数据
+                List<SensorData> sensorData = monitorDeviceService.queryDataByDeviceIdAndRoads(
+                        appDeviceQueryParams.getSensorCode(),
+                        appDeviceQueryParams.getSensorRoute(),
+                        appDeviceQueryParams.getStartTime(),
+                        appDeviceQueryParams.getEndTime(),
+                        dataType
+                );
+
+                // 处理数据并设置到对应的字段
+                SensorEchartData echartData = SensorDataTransformer.processSensorData(sensorData, dataType);
+                if ("temperature".equals(dataType)) {
+                    result.setTemperature(echartData);
+                } else if ("humidity".equals(dataType)) {
+                    result.setHumidity(echartData);
+                } else if ("co2".equals(dataType)) {
+                    result.setCo2(echartData);
+                }
+            }
+        });
+        return result;
     }
 
+    // 辅助方法:返回空的结果对象
+    private SensorEchartDataResult getEmptyResult() {
+        return new SensorEchartDataResult(); // 默认构造一个空的结果对象
+    }
+
+
 
     public Boolean updateDeviceAlarm(AppDeviceAlarmParam appDeviceAlarmParam) {
         Assert.notNull(appDeviceAlarmParam, "appDeviceAlarmParam cannot be null");