jackzhou 5 сар өмнө
parent
commit
a87c2880e7

+ 90 - 38
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/util/DateFormatter.java

@@ -8,6 +8,8 @@ package vip.xiaonuo.coldchain.core.util;
  * @date 2024/11/22 14:50:28
  */
 
+import vip.xiaonuo.coldchain.modular.app.param.AggregationWindow;
+
 import java.text.SimpleDateFormat;
 import java.time.Instant;
 import java.time.LocalDateTime;
@@ -102,51 +104,81 @@ public class DateFormatter {
         }
     }
 
-    public static String getFormattedSingleDateHourse(Instant lastUpdated) {
-        // 如果 lastUpdated 为 null,返回默认值
-        if (lastUpdated == null) {
-            return "";
+    // 格式化 Date 类型
+    public static String formatTime(Date date, AggregationWindow aggregationWindow) {
+        if (date == null || aggregationWindow == null) {
+            throw new IllegalArgumentException("日期时间和聚合窗口不能为空");
         }
-        // 将 Instant 转换为 LocalDateTime
-        LocalDateTime lastUpdatedLocal = LocalDateTime.ofInstant(lastUpdated, ZoneId.systemDefault());
-//        // 获取当前时间
-//        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 dayFormatter = DateTimeFormatter.ofPattern("MM-dd");
-        return lastUpdatedLocal.format(dayFormatter);
+        Instant instant = date.toInstant();
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+        return formatTime(localDateTime, aggregationWindow);
     }
 
+    // 格式化 String 类型,增加了 dateFormat 参数
+    public static String formatTime(String dateTimeStr, AggregationWindow aggregationWindow, String dateFormat) {
+        if (dateTimeStr == null || aggregationWindow == null) {
+            throw new IllegalArgumentException("日期时间和聚合窗口不能为空");
+        }
+        if (dateFormat == null) {
+            dateFormat = DateTimeFormatter.ISO_LOCAL_DATE_TIME.toString();  // 默认使用 ISO 格式
+        }
 
-    public static void main(String[] args) {
-        // 当前时间
-        Instant now = Instant.now();
-
-        // 创建一些不同的时间点来进行测试
-        Instant recentTime = now.minus(3, ChronoUnit.DAYS);  // 3 天前
-        Instant olderTime = now.minus(10, ChronoUnit.DAYS);  // 10 天前
-        Instant exactlyNow = now;  // 当前时间
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
+        LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr, formatter);
+        return formatTime(localDateTime, aggregationWindow);
+    }
 
-        // 输出测试结果
-        System.out.println("最近的时间(3天前): " + getFormattedSingleDateHourse(recentTime));
-        System.out.println("较早的时间(10天前): " + getFormattedSingleDateHourse(olderTime));
-        System.out.println("当前时间: " + getFormattedSingleDateHourse(exactlyNow));
+    // 格式化 Instant 类型
+    public static String formatTime(Instant instant, AggregationWindow aggregationWindow) {
+        if (instant == null || aggregationWindow == null) {
+            throw new IllegalArgumentException("日期时间和聚合窗口不能为空");
+        }
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+        return formatTime(localDateTime, aggregationWindow);
     }
 
+    // 格式化 LocalDateTime 类型
+    public static String formatTime(LocalDateTime localDateTime, AggregationWindow aggregationWindow) {
+        if (localDateTime == null || aggregationWindow == null) {
+            throw new IllegalArgumentException("日期时间和聚合窗口不能为空");
+        }
+
+        // 定义日期时间格式
+        String pattern;
+        switch (aggregationWindow) {
+            case MINUTE:
+                pattern = "yyyy-MM-dd HH:mm"; // 精确到分钟
+                break;
+            case HOUR:
+                pattern = "yyyy-MM-dd HH"; // 精确到小时
+                break;
+            case HALF_DAY:
+                pattern = "yyyy-MM-dd a"; // 精确到上午/下午
+                break;
+            case DAY:
+                pattern = "yyyy-MM-dd"; // 精确到天
+                break;
+            case WEEK:
+                pattern = "yyyy年 第w周"; // 中文自然周
+                break;
+            case MONTH:
+                pattern = "yyyy年MM月"; // 精确到月
+                break;
+            case QUARTER:
+                // 计算季度:将月份(1-12)映射为中文的"第一季度"至"第四季度"
+                int month = localDateTime.getMonthValue();
+                String quarter = "第" + ((month - 1) / 3 + 1) + "季度";
+                return localDateTime.getYear() + "年" + quarter; // 格式如 "2024年第一季度"
+            case YEAR:
+                pattern = "yyyy年"; // 精确到年
+                break;
+            default:
+                pattern = "yyyy-MM-dd"; // 默认精确到天
+                break;
+        }
+        // 返回格式化的日期时间
+        return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
+    }
 
     public static String convertToUTCPlus8(String time) {
         // 1. 解析时间为 Instant (UTC)
@@ -181,5 +213,25 @@ public class DateFormatter {
         // 将"YYYY/MM/DD"替换为"YYYY-MM-DD"
         return input.trim().replaceAll("(\\d{4})/(\\d{2})/(\\d{2})", "$1-$2-$3");
     }
+
+//    public static void main(String[] args) {
+//        // 示例:传入 Date 类型
+//        Date now = new Date();
+//        System.out.println(DateFormatter.formatTime(now, AggregationWindow.DAY));  // 2024-12-10
+//
+//        // 示例:传入 String 类型并指定日期格式
+//        String dateStr = "12-10-2024 10:30";
+//        String dateFormat = "MM-dd-yyyy HH:mm"; // 指定自定义的日期格式
+//        System.out.println(DateFormatter.formatTime(dateStr, AggregationWindow.HOUR, dateFormat));  // 2024-12-10 10
+//
+//        // 示例:传入 Instant 类型
+//        Instant instantNow = Instant.now();
+//        System.out.println(DateFormatter.formatTime(instantNow, AggregationWindow.QUARTER));  // 2024年第四季度
+//
+//        // 示例:传入 LocalDateTime 类型
+//        LocalDateTime localDateTimeNow = LocalDateTime.now();
+//        System.out.println(DateFormatter.formatTime(localDateTimeNow, AggregationWindow.MONTH));  // 2024年12月
+//    }
+
 }
 

+ 90 - 33
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/AggregationWindow.java

@@ -7,74 +7,131 @@ import java.time.Duration;
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
 
 /**
+ * 聚合窗口枚举类,定义不同的时间维度聚合规则
+ * <p>
+ * 聚合窗口包括分钟、小时、半天、天、自然周、月度、季度、年度等
+ * </p>
+ *
  * @author jackzhou
- * @version 1.0
+ * @version 1.2
  * @project jfcloud-coldchain
- * @description
+ * @description 提供聚合窗口相关的枚举值和计算逻辑
  * @date 2024/12/10 15:31:56
  */
 public enum AggregationWindow {
-    MINUTE("1m", "分钟"), HOUR("1h", "小时"), HALF_DAY("12h", "半天"), DAY("1d", "天"), WEEK("1w", "自然周"), MONTH("1mo", "月度"), YEAR("1y", "年度"),DEFAULT("1d", "天");
+
+    MINUTE("1m", "分钟"),
+    HOUR("1h", "小时"),
+    HALF_DAY("12h", "半天"),
+    DAY("1d", "天"),
+    WEEK("1w", "自然周"),
+    MONTH("1mo", "月度"),
+    QUARTER("3mo", "季度"), // 新增季度聚合窗口
+    YEAR("1y", "年度"),
+    DEFAULT("1d", "天");
 
     private final String code;
     private final String description;
 
-    // 构造函数
+    /**
+     * 构造函数
+     *
+     * @param code        聚合窗口代码
+     * @param description 聚合窗口描述
+     */
     AggregationWindow(String code, String description) {
         this.code = code;
         this.description = description;
     }
 
-    // 获取聚合窗口的代码
+    /**
+     * 获取聚合窗口的代码
+     *
+     * @return 窗口代码
+     */
     public String getCode() {
         return code;
     }
 
-    // 获取聚合窗口的描述
+    /**
+     * 获取聚合窗口的描述
+     *
+     * @return 窗口描述
+     */
     public String getDescription() {
         return description;
     }
 
-    // 根据字符串获取对应的聚合窗口
+    /**
+     * 根据代码获取对应的聚合窗口
+     *
+     * @param window 聚合窗口代码
+     * @return 匹配的聚合窗口,如果无匹配返回 DEFAULT
+     */
     public static AggregationWindow fromString(String window) {
+        if (StrUtil.isBlank(window)) {
+            return DEFAULT;
+        }
         for (AggregationWindow aggregationWindow : AggregationWindow.values()) {
             if (aggregationWindow.code.equals(window)) {
                 return aggregationWindow;
             }
         }
-        return AggregationWindow.DEFAULT;
+        return DEFAULT;
     }
 
-    // 根据时间范围计算推荐的聚合窗口
+    /**
+     * 根据时间范围自动计算推荐的聚合窗口
+     *
+     * @param startTime 起始时间(支持 yyyy-MM-dd 格式)
+     * @param stopTime  结束时间(支持 yyyy-MM-dd 格式)
+     * @return 推荐的聚合窗口
+     */
     public static AggregationWindow determineAggregationWindow(String startTime, String stopTime) {
+        // 检查输入时间是否为空
         if (StrUtil.isBlank(startTime) || StrUtil.isBlank(stopTime)) {
-            return AggregationWindow.DEFAULT;
+            return DEFAULT;
         }
-        // 替换日期格式,确保格式一致
-        startTime = DateFormatter.replaceDateFormat(startTime);
-        stopTime = DateFormatter.replaceDateFormat(stopTime);
-        LocalDate localDate = LocalDate.parse(startTime);  // Parse as LocalDate
-        Instant start = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant();  // Convert to Instant
-        LocalDate localDate2 = LocalDate.parse(stopTime);  // Parse as LocalDate
-        Instant stop = localDate2.atStartOfDay(ZoneId.systemDefault()).toInstant();  // Convert to Instant
-        Duration duration = Duration.between(start, stop);
-        long days = duration.toDays();
-        if (days >= 365) {
-            return YEAR; // 超过一年,按年度聚合
-        } else if (days >= 30) {
-            return MONTH; // 超过30天,按月度聚合
-        } else if (days >= 7) {
-            return WEEK; // 超过7天,按自然周聚合
-        } else if (days >= 1) {
-            return DAY; // 超过1天,按天聚合
-        } else if (duration.toHours() >= 12) {
-            return HALF_DAY; // 超过半天,按半天聚合
-        } else if (duration.toHours() >= 1) {
-            return HOUR; // 超过1小时,按小时聚合
-        } else {
-            return MINUTE; // 默认按分钟聚合
+
+        try {
+            // 替换日期格式以支持多种输入格式
+            startTime = DateFormatter.replaceDateFormat(startTime);
+            stopTime = DateFormatter.replaceDateFormat(stopTime);
+
+            // 解析时间字符串为 LocalDate,并转为 Instant
+            LocalDate startDate = LocalDate.parse(startTime);
+            LocalDate stopDate = LocalDate.parse(stopTime);
+            Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
+            Instant stop = stopDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
+
+            // 计算时间差
+            Duration duration = Duration.between(start, stop);
+            long days = duration.toDays();
+
+            // 根据时间范围选择适当的聚合窗口
+            if (days >= 365) {
+                return YEAR; // 超过一年,按年度聚合
+            } else if (days >= 90) {
+                return QUARTER; // 超过90天,按季度聚合
+            } else if (days >= 30) {
+                return MONTH; // 超过30天,按月度聚合
+            } else if (days >= 7) {
+                return WEEK; // 超过7天,按自然周聚合
+            } else if (days >= 1) {
+                return DAY; // 超过1天,按天聚合
+            } else if (duration.toHours() >= 12) {
+                return HALF_DAY; // 超过半天,按半天聚合
+            } else if (duration.toHours() >= 1) {
+                return HOUR; // 超过1小时,按小时聚合
+            } else {
+                return MINUTE; // 默认按分钟聚合
+            }
+        } catch (DateTimeParseException e) {
+            // 如果解析时间失败,返回默认聚合窗口
+            return DEFAULT;
         }
     }
 }

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

@@ -4,8 +4,9 @@ import lombok.experimental.UtilityClass;
 import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
 import vip.xiaonuo.coldchain.core.util.DateFormatter;
 
-import java.util.*;
-import java.util.stream.Collectors;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
 
 @UtilityClass
 public class SensorDataTransformer {
@@ -51,81 +52,23 @@ public class SensorDataTransformer {
 //    }
 
 
-    // 通用方法,提取时间和数值
-    private static Map<String, Object> extractData(List<SensorData> sortedData, String dataType) {
-        // 过滤并映射到时间和数值
-        List<String> times = sortedData.stream()
-                .filter(data -> {
-                    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;
-                })
-                .map(data -> DateFormatter.getFormattedSingleDateHourse(data.getTime())) // 获取时间
-                .collect(Collectors.toList());
-
-        List<Float> values = sortedData.stream()
-                .filter(data -> {
-                    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;
-                })
-                .map(data -> {
-                    if ("temperature".equals(dataType)) return formatFloat(data.getTemperature());
-                    if ("humidity".equals(dataType)) return formatFloat(data.getHumidity());
-                    if ("co2".equals(dataType)) return formatFloat(data.getCo2());
-                    return null;
-                })
-                .collect(Collectors.toList());
-
-        Map<String, Object> dataMap = new HashMap<>();
-        dataMap.put("x", times);  // 时间数组
-        dataMap.put("y", values); // 数值数组
-        return dataMap;
-    }
-
     // 格式化浮动值为保留两位小数
     public static Float formatFloat(Float value) {
         if (value == null) return null;
         return Float.parseFloat(String.format("%.2f", value));
     }
 
-    // 主方法,处理传感器数据
-    public static Map<String, Object> transformToResponseFormat2(List<SensorData> sensorDataList) {
-        // 按时间升序排序
-        List<SensorData> sortedData = sensorDataList.stream()
-                .sorted(Comparator.comparing(SensorData::getTime))  // 按时间升序排序
-                .collect(Collectors.toList());
-
-        // 提取温度、湿度和二氧化碳数据
-        Map<String, Object> temperatureData = extractData(sortedData, "temperature");
-        Map<String, Object> humidityData = extractData(sortedData, "humidity");
-        Map<String, Object> co2Data = extractData(sortedData, "co2");
-
-        // 创建最终的返回数据结构
-        Map<String, Object> response = new HashMap<>();
-        response.put("temperature", temperatureData);
-        response.put("humidity", humidityData);
-        response.put("co2", co2Data);
-
-        return response;
-    }
 
-    public SensorEchartData processSensorData(List<SensorData> sensorDataList, String dataType) {
+    public SensorEchartData processSensorData(List<SensorData> sensorDataList, String dataType, AggregationWindow window) {
         // 按时间升序排序
-        List<SensorData> sortedData = sensorDataList.stream()
-                .sorted(Comparator.comparing(SensorData::getTime))
-                .toList();
+        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)));                 // 数值格式化
-                });
+        sortedData.stream().filter(data -> isValidData(data, dataType)).forEach(data -> {
+            times.add(DateFormatter.formatTime(data.getTime(), window)); // 时间格式化
+            values.add(SensorDataTransformer.formatFloat(getDataValue(data, dataType)));                 // 数值格式化
+        });
         return new SensorEchartData(times, values);
     }
 

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

@@ -187,6 +187,7 @@ public class AppDeviceService {
 
         // 遍历传感器类型映射
         AggregationWindow finalWindow = window;
+        AggregationWindow finalWindow1 = window;
         sensorMapping.forEach((key, dataType) -> {
             // 如果传感器类型包含对应的字符
             if (sensorType.toLowerCase().contains(key)) {
@@ -200,7 +201,7 @@ public class AppDeviceService {
                 );
 
                 // 处理数据并设置到对应的字段
-                SensorEchartData echartData = SensorDataTransformer.processSensorData(sensorData, dataType);
+                SensorEchartData echartData = SensorDataTransformer.processSensorData(sensorData, dataType, finalWindow1);
                 if ("temperature".equals(dataType)) {
                     result.setTemperature(echartData);
                 } else if ("humidity".equals(dataType)) {