Browse Source

fix: 温湿度上下限问题

jackzhou 8 months ago
parent
commit
83ab43b3af
13 changed files with 421 additions and 68 deletions
  1. 65 16
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/bean/influxdb/SensorData.java
  2. 29 10
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/bean/influxdb/SensorDataService.java
  3. 47 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/bean/influxdb/flux/FilterCondition.java
  4. 92 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/bean/influxdb/flux/FluxQueryBuilder.java
  5. 26 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/bean/influxdb/flux/Operator.java
  6. 5 1
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/AppDevice.java
  7. 37 16
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/AppDeviceData.java
  8. 26 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/DoubleNullToDashSerializer.java
  9. 26 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/FloatNullToDashSerializer.java
  10. 19 14
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/service/AppDeviceService.java
  11. 6 2
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitordevice/service/impl/MonitorDeviceServiceImpl.java
  12. 42 8
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortargetregion/entity/MonitorTargetRegion.java
  13. 1 1
      snowy-web-app/src/main/resources/application.properties

+ 65 - 16
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/bean/influxdb/SensorData.java

@@ -20,7 +20,7 @@ import vip.xiaonuo.coldchain.core.config.JfcloudColdChainConstants;
 import vip.xiaonuo.coldchain.core.service.dataprocess.model.PowerEnum;
 import vip.xiaonuo.coldchain.core.service.dataprocess.model.PowerEnum;
 
 
 import java.time.Instant;
 import java.time.Instant;
-import java.time.format.DateTimeFormatter;
+import java.time.ZoneOffset;
 import java.util.Optional;
 import java.util.Optional;
 
 
 @Data
 @Data
@@ -43,19 +43,14 @@ public class SensorData extends JfcloudInFluxEntity {
      * 二氧化碳浓度字段
      * 二氧化碳浓度字段
      */
      */
     @Column(name = "co2")
     @Column(name = "co2")
-    private double co2 = 0;
+    private Float co2;
 
 
-    /**
-     * 路数,默认路数1
-     */
-    @Column(name = "roads")
-    private Integer roads = 1;
 
 
     /**
     /**
      * 电池剩余电量
      * 电池剩余电量
      */
      */
     @Column(name = "battery", tag = true)
     @Column(name = "battery", tag = true)
-    private double battery = 0;
+    private Float battery;
 
 
 
 
     /**
     /**
@@ -74,13 +69,13 @@ public class SensorData extends JfcloudInFluxEntity {
      * 经度字段
      * 经度字段
      */
      */
     @Column(name = "longitude", tag = true)
     @Column(name = "longitude", tag = true)
-    private double lng;
+    private Double lng;
 
 
     /**
     /**
      * 纬度字段
      * 纬度字段
      */
      */
     @Column(name = "latitude", tag = true)
     @Column(name = "latitude", tag = true)
-    private double lat;
+    private Double lat;
 
 
     /**
     /**
      * 设备ID标签
      * 设备ID标签
@@ -88,6 +83,12 @@ public class SensorData extends JfcloudInFluxEntity {
     @Column(name = "device_id", tag = true)
     @Column(name = "device_id", tag = true)
     private String deviceId;
     private String deviceId;
 
 
+    /**
+     * 路数,默认路数1
+     */
+    @Column(name = "roads", tag = true)
+    private Integer roads = 1;
+
     /**
     /**
      * 采集设备型号
      * 采集设备型号
      */
      */
@@ -102,27 +103,75 @@ public class SensorData extends JfcloudInFluxEntity {
         sensorData.setRoads(getIntegerValue(record, "roads"));
         sensorData.setRoads(getIntegerValue(record, "roads"));
         sensorData.setTemperature(getDoubleValue(record, "temperature"));
         sensorData.setTemperature(getDoubleValue(record, "temperature"));
         sensorData.setHumidity(getDoubleValue(record, "humidity"));
         sensorData.setHumidity(getDoubleValue(record, "humidity"));
-        sensorData.setCo2(getDoubleValue(record, "co2"));
-        sensorData.setBattery(getDoubleValue(record, "battery"));
+        sensorData.setCo2(getFloatValue(record, "co2"));
+        sensorData.setBattery(getFloatValue(record, "battery"));
         sensorData.setPlugInStatus(Optional.ofNullable(record.getValueByKey("plugInStatus")).map(Object::toString).orElse(null));
         sensorData.setPlugInStatus(Optional.ofNullable(record.getValueByKey("plugInStatus")).map(Object::toString).orElse(null));
         sensorData.setLocation(Optional.ofNullable(record.getValueByKey("location")).map(Object::toString).orElse(null));
         sensorData.setLocation(Optional.ofNullable(record.getValueByKey("location")).map(Object::toString).orElse(null));
         sensorData.setLng(getDoubleValue(record, "longitude"));
         sensorData.setLng(getDoubleValue(record, "longitude"));
         sensorData.setLat(getDoubleValue(record, "latitude"));
         sensorData.setLat(getDoubleValue(record, "latitude"));
         Instant time = record.getTime();
         Instant time = record.getTime();
         if (time != null) {
         if (time != null) {
-            sensorData.setTime(Instant.parse(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(time)));
+            // 将 LocalDateTime 转换为 ZonedDateTime,附带时区信息
+            ZoneOffset offset = ZoneOffset.UTC;  // 或者使用 ZoneId.systemDefault() 获取系统时区
+            sensorData.setTime(time.atOffset(offset).toInstant());
         }
         }
         return sensorData;
         return sensorData;
     }
     }
 
 
     // Helper method to safely extract Integer values
     // Helper method to safely extract Integer values
     private static Integer getIntegerValue(FluxRecord record, String key) {
     private static Integer getIntegerValue(FluxRecord record, String key) {
-        return Optional.ofNullable(record.getValueByKey(key)).map(value -> (Integer) value).orElse(null);
+        return Optional.ofNullable(record.getValueByKey(key))  // Step 1: Get value by key (might be null)
+                .filter(value -> value instanceof Integer)  // Step 2: Ensure the value is an instance of Integer
+                .map(value -> (Integer) value)  // Step 3: Safely cast to Integer
+                .orElseGet(() -> {
+                    // If not an Integer, try to parse it as a String (fallback mechanism)
+                    Object value = record.getValueByKey(key);
+                    if (value instanceof String) {
+                        try {
+                            return Integer.valueOf((String) value);  // Try to parse String to Integer
+                        } catch (NumberFormatException e) {
+                            // If parsing fails, return null
+                            return null;
+                        }
+                    }
+                    return null;  // Return null if not a String or Integer
+                });
     }
     }
 
 
-    // Helper method to safely extract Double values
+
     private static Double getDoubleValue(FluxRecord record, String key) {
     private static Double getDoubleValue(FluxRecord record, String key) {
-        return Optional.ofNullable(record.getValueByKey(key)).map(value -> (Double) value).orElse(null);
+        return Optional.ofNullable(record.getValueByKey(key))
+                .map(value -> {
+                    if (value instanceof Double) {
+                        return (Double) value;  // 直接返回 Double 类型
+                    } else if (value instanceof String) {
+                        try {
+                            return Double.valueOf((String) value);  // 尝试将 String 转换为 Double
+                        } catch (NumberFormatException e) {
+                            return null;  // 如果转换失败,返回 0.0 或其他默认值
+                        }
+                    }
+                    return null;  // 返回默认值,如果值既不是 Double 也不是 String
+                })
+                .orElse(null);  // 如果没有值,返回 0.0
+    }
+
+
+    private static Float getFloatValue(FluxRecord record, String key) {
+        return Optional.ofNullable(record.getValueByKey(key))
+                .map(value -> {
+                    if (value instanceof Float) {
+                        return (Float) value;  // 直接返回 Double 类型
+                    } else if (value instanceof String) {
+                        try {
+                            return Float.valueOf((String) value);
+                        } catch (NumberFormatException e) {
+                            return null;  // 如果转换失败,返回 0.0 或其他默认值
+                        }
+                    }
+                    return null;  // 返回默认值,如果值既不是 Double 也不是 String
+                })
+                .orElse(null);  // 如果没有值,返回 0.0
     }
     }
 
 
 }
 }

+ 29 - 10
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/bean/influxdb/SensorDataService.java

@@ -63,24 +63,19 @@ public class SensorDataService {
      */
      */
     public String buildFluxQuery(String deviceId, int roads, Instant startTime, Instant endTime, List<SensorQueryCondition> queryConditions, Class<?> sensorDataClass) {
     public String buildFluxQuery(String deviceId, int roads, Instant startTime, Instant endTime, List<SensorQueryCondition> queryConditions, Class<?> sensorDataClass) {
         StringBuilder fluxQuery = new StringBuilder();
         StringBuilder fluxQuery = new StringBuilder();
-
         // 获取 measurement(表名)
         // 获取 measurement(表名)
         String measurement = getMeasurement(sensorDataClass);
         String measurement = getMeasurement(sensorDataClass);
-
         // 获取字段名称列表
         // 获取字段名称列表
         List<String> fieldNames = getFieldNames(sensorDataClass);
         List<String> fieldNames = getFieldNames(sensorDataClass);
-
         fluxQuery.append("from(bucket: \"").append(getBucketName()).append("\") ")  // 使用动态获取的桶名
         fluxQuery.append("from(bucket: \"").append(getBucketName()).append("\") ")  // 使用动态获取的桶名
                 .append("|> range(start: ").append(startTime).append(", stop: ").append(endTime).append(") ")  // 设置时间范围
                 .append("|> range(start: ").append(startTime).append(", stop: ").append(endTime).append(") ")  // 设置时间范围
                 .append("|> filter(fn: (r) => r._measurement == \"").append(measurement).append("\" and r.device_id == \"").append(deviceId).append("\" and r.roads == ").append(roads).append(") ");
                 .append("|> filter(fn: (r) => r._measurement == \"").append(measurement).append("\" and r.device_id == \"").append(deviceId).append("\" and r.roads == ").append(roads).append(") ");
-
         // 添加自定义查询条件
         // 添加自定义查询条件
         if (queryConditions != null) {
         if (queryConditions != null) {
             for (SensorQueryCondition condition : queryConditions) {
             for (SensorQueryCondition condition : queryConditions) {
                 fluxQuery.append("|> filter(fn: (r) => r._field == \"").append(condition.getField()).append("\" and r._value ").append(condition.getOperator().getOperator()).append(" ").append(condition.getValue()).append(") ");
                 fluxQuery.append("|> filter(fn: (r) => r._field == \"").append(condition.getField()).append("\" and r._value ").append(condition.getOperator().getOperator()).append(" ").append(condition.getValue()).append(") ");
             }
             }
         }
         }
-
         fluxQuery.append("|> sort(columns: [\"_time\"], desc: true)");  // 按时间降序排列
         fluxQuery.append("|> sort(columns: [\"_time\"], desc: true)");  // 按时间降序排列
         return fluxQuery.toString();
         return fluxQuery.toString();
     }
     }
@@ -99,7 +94,6 @@ public class SensorDataService {
         String fluxQuery = buildFluxQuery(deviceId, roads, startTime, endTime, queryConditions, SensorData.class);
         String fluxQuery = buildFluxQuery(deviceId, roads, startTime, endTime, queryConditions, SensorData.class);
         QueryApi queryApi = jfcloudInfluxDBService.getInfluxDBClient().getQueryApi();
         QueryApi queryApi = jfcloudInfluxDBService.getInfluxDBClient().getQueryApi();
         List<FluxTable> tables = queryApi.query(fluxQuery);
         List<FluxTable> tables = queryApi.query(fluxQuery);
-
         // 将查询结果映射为 SensorData 实体
         // 将查询结果映射为 SensorData 实体
         return tables.stream().flatMap(table -> table.getRecords().stream()).map(SensorData::mapToSensorData).collect(Collectors.toList());
         return tables.stream().flatMap(table -> table.getRecords().stream()).map(SensorData::mapToSensorData).collect(Collectors.toList());
     }
     }
@@ -113,13 +107,39 @@ public class SensorDataService {
      */
      */
     public SensorData queryLatestDataByDeviceIdAndRoads(String deviceId, Integer roads) {
     public SensorData queryLatestDataByDeviceIdAndRoads(String deviceId, Integer roads) {
         // 构建 Flux 查询,获取最新一条记录
         // 构建 Flux 查询,获取最新一条记录
-        String fluxQuery = String.format("from(bucket: \"%s\") |> range(start: -30d) |> filter(fn: (r) => r._measurement == \"%s\" and r.device_id == \"%s\" and r.roads == %d) |> sort(columns: [\"_time\"], desc: true) |> limit(n: 1)", getBucketName(), getMeasurement(SensorData.class), deviceId, roads);
-
+//        String fluxQuery = String.format(
+//                "from(bucket: \"%s\") " +
+//                        "|> range(start: -30d) " +  // 查询过去 30 天的数据
+//                        "|> filter(fn: (r) => r._measurement == \"%s\" and r[\"device_id\"] == \"%s\" and r[\"roads\"] == \"%s\") " +
+//                        "|> sort(columns: [\"_time\"], desc: true) " +  // 按时间降序排序
+//                        "|> limit(n: 1)",  // 限制查询结果为 1 条
+//                getBucketName(),  // 动态获取桶名
+//                getMeasurement(SensorData.class),  // 动态获取 measurement 名称
+//                deviceId,  // 设备 ID
+//                roads  // 路数
+//        );
+        String fluxQuery = String.format(
+                "from(bucket: \"%s\") " +
+                        "|> range(start: -30d) " +
+                        "|> filter(fn: (r) => r._measurement == \"%s\" and r[\"device_id\"] == \"%s\" and r[\"roads\"] == \"%s\") " +
+                        "|> sort(columns: [\"_time\"], desc: true) " +
+                        "|> limit(n: 1) " +
+                        "|> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\") " +
+                        "|> keep(columns: [\"_time\", \"temperature\", \"humidity\", \"co2\", \"battery\", \"plugInStatus\", \"location\", \"longitude\", \"latitude\", \"device_id\", \"roads\", \"modelName\"])",
+                getBucketName(),
+                getMeasurement(SensorData.class),
+                deviceId,
+                roads
+        );
         QueryApi queryApi = jfcloudInfluxDBService.getInfluxDBClient().getQueryApi();
         QueryApi queryApi = jfcloudInfluxDBService.getInfluxDBClient().getQueryApi();
         List<FluxTable> tables = queryApi.query(fluxQuery);
         List<FluxTable> tables = queryApi.query(fluxQuery);
 
 
         // 将查询结果映射为 SensorData 实体
         // 将查询结果映射为 SensorData 实体
-        return tables.stream().flatMap(table -> table.getRecords().stream()).map(SensorData::mapToSensorData).findFirst().orElse(null);  // 如果没有数据,返回 null
+        return tables.stream()
+                .flatMap(table -> table.getRecords().stream())  // 拉平所有记录
+                .map(SensorData::mapToSensorData)  // 将每个记录映射为 SensorData 实体
+                .findFirst()  // 只取第一条记录(即最新一条记录)
+                .orElse(null);  // 如果没有数据,返回 null
     }
     }
 
 
     /**
     /**
@@ -135,7 +155,6 @@ public class SensorDataService {
         String query = String.format("SELECT * FROM \"%s\" WHERE \"device_id\" = '%s' AND \"roads\" = %d AND time >= '%s' AND time <= '%s' ORDER BY time DESC", getMeasurement(SensorData.class), deviceId, roads, startTime, endTime);
         String query = String.format("SELECT * FROM \"%s\" WHERE \"device_id\" = '%s' AND \"roads\" = %d AND time >= '%s' AND time <= '%s' ORDER BY time DESC", getMeasurement(SensorData.class), deviceId, roads, startTime, endTime);
         QueryApi queryApi = jfcloudInfluxDBService.getInfluxDBClient().getQueryApi();
         QueryApi queryApi = jfcloudInfluxDBService.getInfluxDBClient().getQueryApi();
         List<FluxTable> results = queryApi.query(query);
         List<FluxTable> results = queryApi.query(query);
-
         // 将查询结果转换为 SensorData 实体
         // 将查询结果转换为 SensorData 实体
         return results.stream().flatMap(table -> table.getRecords().stream()).map(SensorData::mapToSensorData).collect(Collectors.toList());
         return results.stream().flatMap(table -> table.getRecords().stream()).map(SensorData::mapToSensorData).collect(Collectors.toList());
     }
     }

+ 47 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/bean/influxdb/flux/FilterCondition.java

@@ -0,0 +1,47 @@
+package vip.xiaonuo.coldchain.core.bean.influxdb.flux;
+
+/**
+ * @author jackzhou
+ * @version 1.0
+ * @project jfcloud-coldchain
+ * @description
+ * @date 2024/11/22 19:48:48
+ */
+public class FilterCondition {
+    private String field;  // 字段名
+    private String value;  // 字段值
+    private Operator operator;  // 操作符(例如:`>`, `<`, `=`, 等)
+
+    public FilterCondition(String field, String value, Operator operator) {
+        this.field = field;
+        this.value = value;
+        this.operator = operator;
+    }
+
+    // Getter & Setter
+
+    public String getField() {
+        return field;
+    }
+
+    public void setField(String field) {
+        this.field = field;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public Operator getOperator() {
+        return operator;
+    }
+
+    public void setOperator(Operator operator) {
+        this.operator = operator;
+    }
+}
+

+ 92 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/bean/influxdb/flux/FluxQueryBuilder.java

@@ -0,0 +1,92 @@
+package vip.xiaonuo.coldchain.core.bean.influxdb.flux;
+
+/**
+ * @author jackzhou
+ * @version 1.0
+ * @project jfcloud-coldchain
+ * @description
+ * @date 2024/11/22 19:48:34
+ */
+
+import com.influxdb.annotations.Column;
+import com.influxdb.annotations.Measurement;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+public class FluxQueryBuilder {
+
+    /**
+     * 构建 Flux 查询语句
+     *
+     * @param clazz 实体类
+     * @param filters 过滤条件(标签和字段)
+     * @param startTime 查询开始时间
+     * @param endTime 查询结束时间
+     * @return 构建的 Flux 查询语句
+     */
+    public static String buildQuery(Class<?> clazz, List<FilterCondition> filters, String startTime, String endTime) {
+        StringBuilder fluxQuery = new StringBuilder();
+
+        // 获取 measurement 名称
+        String measurement = getMeasurement(clazz);
+        fluxQuery.append("from(bucket: \"").append(getBucketName()).append("\") ")
+                .append("|> range(start: ").append(startTime).append(", stop: ").append(endTime).append(") ")
+                .append("|> filter(fn: (r) => r._measurement == \"").append(measurement).append("\") ");
+
+        // 根据字段类型(tag 或 field)添加过滤条件
+        for (FilterCondition filter : filters) {
+            if (isTagField(clazz, filter.getField())) {
+                // 标签字段过滤
+                fluxQuery.append("|> filter(fn: (r) => r[\"").append(filter.getField()).append("\"] == \"").append(filter.getValue()).append("\") ");
+            } else {
+                // 字段过滤
+                fluxQuery.append("|> filter(fn: (r) => r._field == \"").append(filter.getField()).append("\" and r._value ").append(filter.getOperator().getOperator()).append(" ").append(filter.getValue()).append(") ");
+            }
+        }
+
+        // 默认排序和限制
+        fluxQuery.append("|> sort(columns: [\"_time\"], desc: true) ");
+        fluxQuery.append("|> limit(n: 1)");
+
+        return fluxQuery.toString();
+    }
+
+    /**
+     * 判断字段是否为标签字段
+     *
+     * @param clazz 类
+     * @param fieldName 字段名称
+     * @return true 如果是标签字段,false 如果是普通字段
+     */
+    private static boolean isTagField(Class<?> clazz, String fieldName) {
+        try {
+            Field field = clazz.getDeclaredField(fieldName);
+            // 判断字段是否带有 @Column 并且 tag=true
+            Column column = field.getAnnotation(Column.class);
+            return column != null && column.tag();
+        } catch (NoSuchFieldException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取 Measurement 名称
+     *
+     * @param clazz 类类型
+     * @return Measurement 名称
+     */
+    private static String getMeasurement(Class<?> clazz) {
+        Measurement measurement = clazz.getAnnotation(Measurement.class);
+        return (measurement != null) ? measurement.name() : "default_measurement";
+    }
+
+    /**
+     * 获取桶名
+     *
+     * @return 桶名
+     */
+    private static String getBucketName() {
+        return "default_bucket";
+    }
+}

+ 26 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/bean/influxdb/flux/Operator.java

@@ -0,0 +1,26 @@
+package vip.xiaonuo.coldchain.core.bean.influxdb.flux;
+
+/**
+ * @author jackzhou
+ * @version 1.0
+ * @project jfcloud-coldchain
+ * @description
+ * @date 2024/11/22 19:49:13
+ */
+public enum Operator {
+    EQUALS("="),
+    GREATER_THAN(">"),
+    LESS_THAN("<"),
+    GREATER_THAN_OR_EQUAL(">="),
+    LESS_THAN_OR_EQUAL("<=");
+
+    private final String operator;
+
+    Operator(String operator) {
+        this.operator = operator;
+    }
+
+    public String getOperator() {
+        return operator;
+    }
+}

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

@@ -8,6 +8,8 @@ package vip.xiaonuo.coldchain.modular.app.param;
  * @date 2024/11/17 22:30:15
  * @date 2024/11/17 22:30:15
  */
  */
 
 
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.Data;
@@ -34,7 +36,9 @@ public class AppDevice extends MonitorTarget {
      * 电池电量 (%)
      * 电池电量 (%)
      */
      */
     @Schema(description = "电池电量 (%)")
     @Schema(description = "电池电量 (%)")
-    private double batteryPercentage;
+    @JsonSerialize(using = FloatNullToDashSerializer.class)  // 使用自定义序列化器
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#.00")
+    private Float batteryPercentage;
 
 
     /**
     /**
      * 关键的设备探头集合
      * 关键的设备探头集合

+ 37 - 16
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/AppDeviceData.java

@@ -1,28 +1,49 @@
 package vip.xiaonuo.coldchain.modular.app.param;
 package vip.xiaonuo.coldchain.modular.app.param;
 
 
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import vip.xiaonuo.coldchain.core.service.dataprocess.model.PowerEnum;
+import vip.xiaonuo.coldchain.modular.monitortargetregion.entity.MonitorTargetRegion;
+
+import java.util.Date;
+
 /**
 /**
  * @author jackzhou
  * @author jackzhou
  * @version 1.0
  * @version 1.0
  * @project jfcloud-coldchain
  * @project jfcloud-coldchain
- * @description
+ * @description 设备数据类,包含设备的各项监控数据及电源状态
  * @date 2024/11/17 22:39:49
  * @date 2024/11/17 22:39:49
  */
  */
+@Data
+public class AppDeviceData extends MonitorTargetRegion {
 
 
-import lombok.Data;
-import vip.xiaonuo.coldchain.core.service.dataprocess.model.PowerEnum;
-import vip.xiaonuo.coldchain.modular.monitortargetregion.entity.MonitorTargetRegion;
+    @Schema(description = "数据记录的时间")
+    private Date timestamp;  // 数据记录的时间
 
 
-import java.time.LocalDateTime;
+    @Schema(description = "设备温度 (℃)")
+    @JsonSerialize(using = DoubleNullToDashSerializer.class)  // 使用自定义序列化器
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#.00")
+    private Double temperature;  // 温度
 
 
-@Data
-public class AppDeviceData extends MonitorTargetRegion {
-    private LocalDateTime timestamp;    // 数据记录的时间
-    private Double temperature;         // 设备温度 (℃)
-    private Double humidity;            // 设备湿度 (%)
-    private Double co2Level;            // 二氧化碳浓度 (ppm)
-    private Double batteryPercentage;   // 电池电量 (%)
-    private String plugInStatus = PowerEnum.AC.getCode();
-
-    public AppDeviceData() {
-    }
+    @Schema(description = "设备湿度 (%)")
+    @JsonSerialize(using = DoubleNullToDashSerializer.class)  // 使用自定义序列化器
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#.00")
+    private Double humidity;  // 湿度
+
+    @Schema(description = "二氧化碳浓度 (ppm)")
+    @JsonSerialize(using = FloatNullToDashSerializer.class)  // 使用自定义序列化器
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#.00")
+    private Float co2Level;  // 二氧化碳浓度
+
+    @Schema(description = "电池电量 (%)")
+    @JsonSerialize(using = FloatNullToDashSerializer.class)  // 使用自定义序列化器
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#.00")
+    private Float batteryPercentage;  // 电池电量
+
+    @Schema(description = "电源状态,AC电源或DC电池")
+    private String plugInStatus = PowerEnum.AC.getCode();  // 电源状态(默认值为AC电源)
+
+    // 默认构造函数不再需要显式定义,Lombok 会自动生成
 }
 }

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

@@ -0,0 +1,26 @@
+package vip.xiaonuo.coldchain.modular.app.param;
+
+/**
+ * @author jackzhou
+ * @version 1.0
+ * @project jfcloud-coldchain
+ * @description
+ * @date 2024/11/23 19:01:49
+ */
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+
+public class DoubleNullToDashSerializer extends JsonSerializer<Double> {
+    @Override
+    public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        if (value == null) {
+            gen.writeString("--");
+        } else {
+            gen.writeString(String.format("%.2f", value));  // 格式化数字为两位小数
+        }
+    }
+}

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

@@ -0,0 +1,26 @@
+package vip.xiaonuo.coldchain.modular.app.param;
+
+/**
+ * @author jackzhou
+ * @version 1.0
+ * @project jfcloud-coldchain
+ * @description
+ * @date 2024/11/23 19:01:49
+ */
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+
+public class FloatNullToDashSerializer extends JsonSerializer<Float> {
+    @Override
+    public void serialize(Float value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        if (value == null) {
+            gen.writeString("--");
+        } else {
+            gen.writeString(String.format("%.2f", value));  // 格式化数字为两位小数
+        }
+    }
+}

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

@@ -27,6 +27,7 @@ import vip.xiaonuo.coldchain.modular.monitortargetregion.entity.MonitorTargetReg
 import vip.xiaonuo.coldchain.modular.monitortargetregion.service.MonitorTargetRegionService;
 import vip.xiaonuo.coldchain.modular.monitortargetregion.service.MonitorTargetRegionService;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.List;
 import java.util.Objects;
 import java.util.Objects;
 
 
@@ -80,7 +81,7 @@ public class AppDeviceService {
             AppDevice appDevice = new AppDevice();
             AppDevice appDevice = new AppDevice();
             // 将监控目标信息复制到 AppDevice 中
             // 将监控目标信息复制到 AppDevice 中
             BeanUtil.copyProperties(monitorTarget, appDevice);
             BeanUtil.copyProperties(monitorTarget, appDevice);
-
+            appDevice.setMonitorTargetRegionList(null);
             // 获取当前监控目标的 ID,用于查询子设备数据
             // 获取当前监控目标的 ID,用于查询子设备数据
             String monitorTargetId = monitorTarget.getId();
             String monitorTargetId = monitorTarget.getId();
 
 
@@ -102,25 +103,29 @@ public class AppDeviceService {
                     AppDeviceData appDeviceData = new AppDeviceData();
                     AppDeviceData appDeviceData = new AppDeviceData();
                     children.add(appDeviceData);
                     children.add(appDeviceData);
                     BeanUtil.copyProperties(monitorTargetRegion, appDeviceData);
                     BeanUtil.copyProperties(monitorTargetRegion, appDeviceData);
-                    String deviceId = appDeviceData.getDeviceCode();
+                    String deviceCode = appDeviceData.getDeviceCode();
                     // 传感器路数
                     // 传感器路数
                     Integer sensorRoute = appDeviceData.getSensorRoute();
                     Integer sensorRoute = appDeviceData.getSensorRoute();
-                    if (!Objects.isNull(sensorRoute) && StrUtil.isNotBlank(deviceId)) {
-                        String startTime = null, endTime = null;
-                        SensorData sensorData = monitorDeviceService.queryLatestDataByDeviceIdAndRoads(deviceId, sensorRoute);
-                        List<SensorData> sensorDataList = monitorDeviceService.queryDataByDeviceIdAndRoads(deviceId, sensorRoute, startTime, endTime);
+                    if (!Objects.isNull(sensorRoute) && StrUtil.isNotBlank(deviceCode)) {
+                        SensorData sensorData = monitorDeviceService.queryLatestDataByDeviceIdAndRoads(deviceCode, sensorRoute);
+                        if (!Objects.isNull(sensorData)) {
+                            appDeviceData.setBatteryPercentage(sensorData.getBattery());
+                            appDeviceData.setTimestamp(Date.from(sensorData.getTime()));
+                            appDeviceData.setHumidity(sensorData.getHumidity());
+                            appDeviceData.setTemperature(sensorData.getTemperature());
+                            appDeviceData.setCo2Level(sensorData.getCo2());
+                            appDeviceData.setPlugInStatus(sensorData.getPlugInStatus());
+                        }
                     }
                     }
-
-                    // 这里可以调用外部服务(如 InfluxDB)获取实时数据(温湿度、CO2 等),
-                    // 但目前这个逻辑的实际获取部分尚未实现
                 }
                 }
-
-                // 更新设备的电池电量和插电状态等信息
-                // 这里假设我们取第一个子设备的数据来填充主体设备的电池电量等信息
+                // 取第一个子设备的数据来填充主体设备的电池电量和插电状态等信息
                 if (!appDevice.getChildren().isEmpty()) {
                 if (!appDevice.getChildren().isEmpty()) {
                     AppDeviceData firstChild = appDevice.getChildren().get(0);
                     AppDeviceData firstChild = appDevice.getChildren().get(0);
-                    appDevice.setPlugInStatus(firstChild.getPlugInStatus());
-                    appDevice.setBatteryPercentage(firstChild.getBatteryPercentage());
+                    if (Objects.isNull(firstChild)) {
+                        appDevice.setPlugInStatus(firstChild.getPlugInStatus());
+                        appDevice.setBatteryPercentage(firstChild.getBatteryPercentage());
+                        appDevice.setUpdateTime(firstChild.getTimestamp());
+                    }
                 }
                 }
             }
             }
 
 

+ 6 - 2
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitordevice/service/impl/MonitorDeviceServiceImpl.java

@@ -33,7 +33,10 @@ import vip.xiaonuo.coldchain.core.cache.monitordevice.MonitorDeviceCache;
 import vip.xiaonuo.coldchain.core.renke.RenKeService;
 import vip.xiaonuo.coldchain.core.renke.RenKeService;
 import vip.xiaonuo.coldchain.modular.monitordevice.entity.MonitorDevice;
 import vip.xiaonuo.coldchain.modular.monitordevice.entity.MonitorDevice;
 import vip.xiaonuo.coldchain.modular.monitordevice.mapper.MonitorDeviceMapper;
 import vip.xiaonuo.coldchain.modular.monitordevice.mapper.MonitorDeviceMapper;
-import vip.xiaonuo.coldchain.modular.monitordevice.param.*;
+import vip.xiaonuo.coldchain.modular.monitordevice.param.MonitorDeviceAddParam;
+import vip.xiaonuo.coldchain.modular.monitordevice.param.MonitorDeviceEditParam;
+import vip.xiaonuo.coldchain.modular.monitordevice.param.MonitorDeviceIdParam;
+import vip.xiaonuo.coldchain.modular.monitordevice.param.MonitorDevicePageParam;
 import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
 import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
 import vip.xiaonuo.common.enums.CommonSortOrderEnum;
 import vip.xiaonuo.common.enums.CommonSortOrderEnum;
 import vip.xiaonuo.common.exception.CommonException;
 import vip.xiaonuo.common.exception.CommonException;
@@ -213,6 +216,7 @@ public class MonitorDeviceServiceImpl extends ServiceImpl<MonitorDeviceMapper, M
 
 
     @Override
     @Override
     public List<SensorData> querySensorData(String deviceId, int roads, Instant startTime, Instant endTime, List<SensorQueryCondition> queryConditions) {
     public List<SensorData> querySensorData(String deviceId, int roads, Instant startTime, Instant endTime, List<SensorQueryCondition> queryConditions) {
-        return sensorDataService.querySensorData(deviceId, roads, startTime, endTime, queryConditions);
+//        return sensorDataService.querySensorData(deviceId, roads, startTime, endTime, queryConditions);
+        return null;
     }
     }
 }
 }

+ 42 - 8
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortargetregion/entity/MonitorTargetRegion.java

@@ -14,11 +14,12 @@ package vip.xiaonuo.coldchain.modular.monitortargetregion.entity;
 
 
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.influxdb.annotations.Column;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;
 import lombok.Getter;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.Setter;
+import vip.xiaonuo.coldchain.modular.app.param.FloatNullToDashSerializer;
 
 
 import java.util.Date;
 import java.util.Date;
 
 
@@ -119,14 +120,47 @@ public class MonitorTargetRegion {
     private String deviceCode;
     private String deviceCode;
 
 
     /**
     /**
-     * 报警上限
+     * 温度报警上限 temperature humidity co2
      */
      */
-    @Schema(description = "报警上限")
-    private Float limitUp;
+    @Schema(description = "温度报警上限")
+    @JsonSerialize(using = FloatNullToDashSerializer.class)
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#.00")
+    private Float temperatureUp;
+    /**
+     * 温度报警下限
+     */
+    @Schema(description = "温度报警下限")
+    @JsonSerialize(using = FloatNullToDashSerializer.class)
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#.00")
+    private Float temperatureDown;
 
 
     /**
     /**
-     * 报警下限
+     * 湿度报警上限 temperature humidity co2
+     */
+    @Schema(description = "湿度报警上限")
+    @JsonSerialize(using = FloatNullToDashSerializer.class)
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#.00")
+    private Float humidityUp;
+    /**
+     * 湿度报警下限
+     */
+    @Schema(description = "湿度报警下限")
+    @JsonSerialize(using = FloatNullToDashSerializer.class)
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#.00")
+    private Float humidityDown;
+
+    /**
+     * 二氧化碳报警上限 temperature humidity co2
+     */
+    @Schema(description = "二氧化碳报警上限")
+    @JsonSerialize(using = FloatNullToDashSerializer.class)
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#.00")
+    private Float co2Up;
+    /**
+     * 二氧化碳报警下限
      */
      */
-    @Schema(description = "报警下限")
-    private Float limitDown;
+    @Schema(description = "二氧化碳报警下限")
+    @JsonSerialize(using = FloatNullToDashSerializer.class)
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#.00")
+    private Float co2Down;
 }
 }

+ 1 - 1
snowy-web-app/src/main/resources/application.properties

@@ -35,7 +35,7 @@ spring.datasource.dynamic.strict=true
 # influxdb
 # influxdb
 spring.data.influxdb.url=${INFLUXDB_URL:http://192.168.67.228:8086}
 spring.data.influxdb.url=${INFLUXDB_URL:http://192.168.67.228:8086}
 spring.data.influxdb.token=${INFLUXDB_TOKEN:1NdDyN3LCKIEBFkQ1AqAmRSitZGfdCs0nuF11nLQbTcnN_5mp_asnfFnmAXTvDDQXGr83llxuXJu5wNTPtrsIw==}
 spring.data.influxdb.token=${INFLUXDB_TOKEN:1NdDyN3LCKIEBFkQ1AqAmRSitZGfdCs0nuF11nLQbTcnN_5mp_asnfFnmAXTvDDQXGr83llxuXJu5wNTPtrsIw==}
-spring.data.influxdb.org=${INFLUXDB_ORG:jfcloud}
+spring.data.influxdb.org=${INFLUXDB_ORG:coldchain}
 spring.data.influxdb.bucket=${INFLUXDB_BUCKET:coldchain}
 spring.data.influxdb.bucket=${INFLUXDB_BUCKET:coldchain}
 
 
 # postgres
 # postgres