“djh”
2025-11-11 63486c527b01c459110a88930d9cda1ded633aee
修改新增气象数据统计
9 files modified
8 files added
972 ■■■■■ changed files
src/main/java/com/gkhy/fourierSpecialGasMonitor/controller/GasMonitorDataController.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/domain/sysAdmin/service/impl/MenuDomainServiceImpl.java 69 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/entity/SummaryStats.java 255 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/entity/req/SummaryStatsReqDTO.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/entity/req/UploadGasConcentrationReqDTO.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/entity/resp/SummaryStatsPageRespDTO.java 74 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/enums/StatsTypeEnum.java 43 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/repository/GasConcentrationRepository.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/repository/SummaryStatsRepository.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/GasConcentrationService.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/MonitorDataService.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/SummaryStatsService.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/impl/DataReceiveServiceImpl.java 43 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/impl/GasConcentrationServiceImpl.java 67 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/impl/MonitorDataServiceImpl.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/impl/SummaryStatsServiceImpl.java 112 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/utils/SummaryUtils.java 191 ●●●●● patch | view | raw | blame | history
src/main/java/com/gkhy/fourierSpecialGasMonitor/controller/GasMonitorDataController.java
@@ -8,6 +8,7 @@
import com.gkhy.fourierSpecialGasMonitor.entity.query.GasPageQuery;
import com.gkhy.fourierSpecialGasMonitor.entity.req.*;
import com.gkhy.fourierSpecialGasMonitor.service.MonitorDataService;
import com.gkhy.fourierSpecialGasMonitor.service.SummaryStatsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -25,6 +26,9 @@
    @Autowired
    private MonitorDataService monitorDataService;
    @Autowired
    private SummaryStatsService summaryStatsService;
    @PostMapping("/gas/lineChart")
    public Result gasLineChart(@RequestBody GasLineChartReqDTO reqDto){
@@ -74,4 +78,16 @@
        Result result = monitorDataService.gasConcentrationExport(gasConcentrationExportBO);
        return result;
    }
    @PostMapping("/gasAtmosphere/stats")
    public Result gasAtmosphereStats(@RequestBody PageQuery<SummaryStatsReqDTO> pageQuery){
        Result result = summaryStatsService.listSummaryStats(pageQuery);
        return result;
    }
    @PostMapping("/gasAtmosphere/extremum")
    public Result gasAtmosphereExtremum(@RequestBody SummaryStatsReqDTO reqDTO){
        Result result = monitorDataService.gasAtmosphereExtremum(reqDTO);
        return result;
    }
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/domain/sysAdmin/service/impl/MenuDomainServiceImpl.java
@@ -340,33 +340,60 @@
        return parantList;
    }
    /**
     * 递归解析菜单数据,获取菜单树状结构
     * @param parantItem
     * @param childItemList
     * @return
     */
    private List<MenuItemDomainDTO> praseChildMenuItems(MenuItemDomainDTO parantItem, List<MenuItemDomainDTO> childItemList){
        if(childItemList == null || childItemList.size() == 0 || parantItem == null)
            return null;
        List<MenuItemDomainDTO> resultList = new ArrayList<>();
//        childItemList.forEach(child -> {
//    private List<MenuItemDomainDTO> praseChildMenuItems(MenuItemDomainDTO parantItem, List<MenuItemDomainDTO> childItemList){
//        if(childItemList == null || childItemList.size() == 0 || parantItem == null)
//            return null;
//        List<MenuItemDomainDTO> resultList = new ArrayList<>();
////        childItemList.forEach(child -> {
////            if(parantItem.getId().equals(child.getParentId())){
////                resultList.add(child);
////            }
////        });
//        for(MenuItemDomainDTO child : childItemList){
//            if(parantItem.getId().equals(child.getParentId())){
//                resultList.add(child);
//            }
//        });
        for(MenuItemDomainDTO child : childItemList){
            if(parantItem.getId().equals(child.getParentId())){
                resultList.add(child);
//        }
//        if(resultList.size() > 0){
//            for (MenuItemDomainDTO child : childItemList){
//                List<MenuItemDomainDTO> childList = praseChildMenuItems(child,childItemList);
//                if(childList != null && childList.size() > 0)
//                    child.setSubMenuItemList(childList);
//            }
//        }
//        return resultList;
//    }
    /**
     * 递归解析菜单数据,获取菜单树状结构
     * @param parentItem
     * @param allItems
     * @return
     */
    private List<MenuItemDomainDTO> praseChildMenuItems(MenuItemDomainDTO parentItem, List<MenuItemDomainDTO> allItems) {
        if (allItems == null || allItems.isEmpty() || parentItem == null) {
            return null;
        }
        List<MenuItemDomainDTO> directChildren = new ArrayList<>();
        // 1. 先找出当前父菜单的直接子菜单(parentId等于当前父菜单的id)
        for (MenuItemDomainDTO item : allItems) {
            if (parentItem.getId().equals(item.getParentId())) {
                directChildren.add(item);
            }
        }
        if(resultList.size() > 0){
            for (MenuItemDomainDTO child : childItemList){
                List<MenuItemDomainDTO> childList = praseChildMenuItems(child,childItemList);
                if(childList != null && childList.size() > 0)
                    child.setSubMenuItemList(childList);
        // 2. 只对直接子菜单递归,找它们的子菜单(孙菜单)
        if (!directChildren.isEmpty()) {
            for (MenuItemDomainDTO child : directChildren) { // 关键:遍历directChildren而非allItems
                List<MenuItemDomainDTO> grandChildren = praseChildMenuItems(child, allItems);
                if (grandChildren != null && !grandChildren.isEmpty()) {
                    child.setSubMenuItemList(grandChildren);
                }
            }
        }
        return resultList;
        return directChildren;
    }
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/entity/SummaryStats.java
New file
@@ -0,0 +1,255 @@
package com.gkhy.fourierSpecialGasMonitor.entity;
import lombok.Data;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "summary_stats")
@Data
public class SummaryStats {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private LocalDateTime time;
    //温度
    private Double tempMin;
    private Double tempMax;
    private Double tempAvg;
    private Double temp;
    //湿度
    private Double  humidityMin;
    private Double  humidityMax;
    private Double  humidityAvg;
    private Double  humidity;
    //风速
    private Double windSpeedMin;
    private Double windSpeedMax;
    private Double windSpeedAvg;
    private Double windSpeed;
    //风向
    private Integer windDirectionMin;
    private Integer windDirectionMax;
    private Double windDirectionAvg;
    private int windDirection;
    //压力
    private Double pressureMin;
    private Double pressureMax;
    private Double pressureAvg;
    private Double pressure;
    public SummaryStats(Double tempMin, Double tempMax, Double tempAvg,
                        Double humidityMin, Double humidityMax, Double humidityAvg,
                        Double windSpeedMin, Double windSpeedMax, Double windSpeedAvg,
                        Integer windDirectionMin, Integer windDirectionMax, Double windDirectionAvg,
                        Double pressureMin, Double pressureMax, Double pressureAvg) {
        this.tempMin = tempMin;
        this.tempMax = tempMax;
        this.tempAvg = tempAvg;
        this.humidityMin = humidityMin;
        this.humidityMax = humidityMax;
        this.humidityAvg = humidityAvg;
        this.windSpeedMin = windSpeedMin;
        this.windSpeedMax = windSpeedMax;
        this.windSpeedAvg = windSpeedAvg;
        this.windDirectionMin = windDirectionMin;
        this.windDirectionMax = windDirectionMax;
        this.windDirectionAvg = windDirectionAvg;
        this.pressureMin = pressureMin;
        this.pressureMax = pressureMax;
        this.pressureAvg = pressureAvg;
    }
    public SummaryStats() {
    }
    public boolean isEmpty(){
        return tempMin == null&&humidityMin==null&&windSpeedMin==null&&pressureMin==null&& windDirectionMin == null;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public LocalDateTime getTime() {
        return time;
    }
    public void setTime(LocalDateTime time) {
        this.time = time;
    }
    public Double getTempMin() {
        return tempMin;
    }
    public void setTempMin(Double tempMin) {
        this.tempMin = tempMin;
    }
    public Double getTempMax() {
        return tempMax;
    }
    public void setTempMax(Double tempMax) {
        this.tempMax = tempMax;
    }
    public Double getTempAvg() {
        return tempAvg;
    }
    public void setTempAvg(Double tempAvg) {
        this.tempAvg = tempAvg;
    }
    public Double getTemp() {
        return temp;
    }
    public void setTemp(Double temp) {
        this.temp = temp;
    }
    public Double getHumidityMin() {
        return humidityMin;
    }
    public void setHumidityMin(Double humidityMin) {
        this.humidityMin = humidityMin;
    }
    public Double getHumidityMax() {
        return humidityMax;
    }
    public void setHumidityMax(Double humidityMax) {
        this.humidityMax = humidityMax;
    }
    public Double getHumidityAvg() {
        return humidityAvg;
    }
    public void setHumidityAvg(Double humidityAvg) {
        this.humidityAvg = humidityAvg;
    }
    public Double getHumidity() {
        return humidity;
    }
    public void setHumidity(Double humidity) {
        this.humidity = humidity;
    }
    public Double getWindSpeedMin() {
        return windSpeedMin;
    }
    public void setWindSpeedMin(Double windSpeedMin) {
        this.windSpeedMin = windSpeedMin;
    }
    public Double getWindSpeedMax() {
        return windSpeedMax;
    }
    public void setWindSpeedMax(Double windSpeedMax) {
        this.windSpeedMax = windSpeedMax;
    }
    public Double getWindSpeedAvg() {
        return windSpeedAvg;
    }
    public void setWindSpeedAvg(Double windSpeedAvg) {
        this.windSpeedAvg = windSpeedAvg;
    }
    public Double getWindSpeed() {
        return windSpeed;
    }
    public void setWindSpeed(Double windSpeed) {
        this.windSpeed = windSpeed;
    }
    public int getWindDirectionMin() {
        return windDirectionMin;
    }
    public void setWindDirectionMin(int windDirectionMin) {
        this.windDirectionMin = windDirectionMin;
    }
    public int getWindDirectionMax() {
        return windDirectionMax;
    }
    public void setWindDirectionMax(int windDirectionMax) {
        this.windDirectionMax = windDirectionMax;
    }
    public Double getWindDirectionAvg() {
        return windDirectionAvg;
    }
    public void setWindDirectionAvg(Double windDirectionAvg) {
        this.windDirectionAvg = windDirectionAvg;
    }
    public int getWindDirection() {
        return windDirection;
    }
    public void setWindDirection(int windDirection) {
        this.windDirection = windDirection;
    }
    public Double getPressureMin() {
        return pressureMin;
    }
    public void setPressureMin(Double pressureMin) {
        this.pressureMin = pressureMin;
    }
    public Double getPressureMax() {
        return pressureMax;
    }
    public void setPressureMax(Double pressureMax) {
        this.pressureMax = pressureMax;
    }
    public Double getPressureAvg() {
        return pressureAvg;
    }
    public void setPressureAvg(Double pressureAvg) {
        this.pressureAvg = pressureAvg;
    }
    public Double getPressure() {
        return pressure;
    }
    public void setPressure(Double pressure) {
        this.pressure = pressure;
    }
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/entity/req/SummaryStatsReqDTO.java
New file
@@ -0,0 +1,17 @@
package com.gkhy.fourierSpecialGasMonitor.entity.req;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class SummaryStatsReqDTO {
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    private String type;
    private Integer dateType;
    private String date;
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/entity/req/UploadGasConcentrationReqDTO.java
@@ -28,14 +28,15 @@
    private String angle;
    //温度
    private Double temp;
    //湿度
    private Double  humidity;
    //风速
    private Double windSpeed;
    //风向
    private int windDirection;
    //压力
    private Double pressure;
    private int gasName01;
src/main/java/com/gkhy/fourierSpecialGasMonitor/entity/resp/SummaryStatsPageRespDTO.java
New file
@@ -0,0 +1,74 @@
package com.gkhy.fourierSpecialGasMonitor.entity.resp;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class SummaryStatsPageRespDTO {
    private Long id;
    private LocalDateTime time;
    private Object value;
    private Object min;
    private Object max;
    private Object avg;
    public SummaryStatsPageRespDTO( Object max,Object min, Object avg) {
        this.min = min;
        this.max = max;
        this.avg = avg;
    }
    public SummaryStatsPageRespDTO() {
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public LocalDateTime getTime() {
        return time;
    }
    public void setTime(LocalDateTime time) {
        this.time = time;
    }
    public Object getValue() {
        return value;
    }
    public void setValue(Object value) {
        this.value = value;
    }
    public Object getMin() {
        return min;
    }
    public void setMin(Object min) {
        this.min = min;
    }
    public Object getMax() {
        return max;
    }
    public void setMax(Object max) {
        this.max = max;
    }
    public Object getAvg() {
        return avg;
    }
    public void setAvg(Object avg) {
        this.avg = avg;
    }
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/enums/StatsTypeEnum.java
New file
@@ -0,0 +1,43 @@
package com.gkhy.fourierSpecialGasMonitor.enums;
public enum StatsTypeEnum {
    TEMP("temp", "temp", "tempMin", "tempMax", "tempAvg"),
    HUMIDITY("humidity", "humidity", "humidityMin", "humidityMax", "humidityAvg"),
    WIND_SPEED("windSpeed", "windSpeed", "windSpeedMin", "windSpeedMax", "windSpeedAvg"),
    WIND_DIRECTION("windDirection", "windDirection", "windDirectionMin", "windDirectionMax", "windDirectionAvg"),
    PRESSURE("pressure", "pressure", "pressureMin", "pressureMax", "pressureAvg");
    private  String type; // 与传入的 type 字符串对应
    private  String valueField; // 对应 value 的字段名
    private  String minField;   // 最小值字段名
    private  String maxField;   // 最大值字段名
    private  String avgField;   // 平均值字段名
    StatsTypeEnum(String type, String valueField, String minField, String maxField, String avgField) {
        this.type = type;
        this.valueField = valueField;
        this.minField = minField;
        this.maxField = maxField;
        this.avgField = avgField;
    }
    // 根据 type 字符串获取枚举
    public static StatsTypeEnum getByType(String type) {
        if (type == null) {
            return null;
        }
        for (StatsTypeEnum mapper : values()) {
            if (mapper.type.equalsIgnoreCase(type)) { // 忽略大小写(可选,根据需求调整)
                return mapper;
            }
        }
        return null;
    }
    // getter 方法
    public String getType() { return type; }
    public String getValueField() { return valueField; }
    public String getMinField() { return minField; }
    public String getMaxField() { return maxField; }
    public String getAvgField() { return avgField; }
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/repository/GasConcentrationRepository.java
@@ -3,8 +3,14 @@
import com.gkhy.fourierSpecialGasMonitor.entity.GasCategory;
import com.gkhy.fourierSpecialGasMonitor.entity.GasConcentration;
import com.gkhy.fourierSpecialGasMonitor.entity.SummaryStats;
import com.gkhy.fourierSpecialGasMonitor.entity.req.SummaryStatsReqDTO;
import com.gkhy.fourierSpecialGasMonitor.entity.resp.SummaryStatsPageRespDTO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.security.core.parameters.P;
import org.springframework.stereotype.Repository;
import javax.persistence.OrderBy;
@@ -18,4 +24,34 @@
    GasConcentration findTopByOrderByDataReceivingTimeDesc();
    List<GasConcentration> findAllByDataReceivingTimeBetweenOrderByDataReceivingTimeDesc(LocalDateTime startTime, LocalDateTime endTime);
    //分别查询temp,humidity,windSpeed,windDirection,pressure的平均值,最小值,最大值
    @Query("SELECT new com.gkhy.fourierSpecialGasMonitor.entity.SummaryStats(MIN(g.temp), MAX(g.temp), FUNCTION('ROUND', AVG(g.temp), 2)," +
            "MIN(g.humidity), MAX(g.humidity), FUNCTION('ROUND', AVG(g.humidity), 2), " +
            "MIN(g.windSpeed), MAX(g.windSpeed), FUNCTION('ROUND', AVG(g.windSpeed), 2), " +
            "MIN(g.windDirection), MAX(g.windDirection), FUNCTION('ROUND', AVG(g.windDirection), 2), " +
            "MIN(g.pressure), MAX(g.pressure), FUNCTION('ROUND', AVG(g.pressure), 2) " +
            ") FROM GasConcentration g WHERE g.time BETWEEN :startTime AND :endTime")
    SummaryStats findStats(@Param("startTime") LocalDateTime startTime,@Param("endTime") LocalDateTime endTime);
    @Query("SELECT new com.gkhy.fourierSpecialGasMonitor.entity.resp.SummaryStatsPageRespDTO( " +
            "MAX(CASE WHEN :type = 'temp' THEN g.temp " +
            "WHEN :type = 'humidity' THEN g.humidity " +
            "WHEN :type = 'windSpeed' THEN g.windSpeed " +
            "WHEN :type = 'windDirection' THEN g.windDirection " +
            "WHEN :type = 'pressure' THEN g.pressure END), " +
            "MIN(CASE WHEN :type = 'temp' THEN g.temp " +
            "WHEN :type = 'humidity' THEN g.humidity " +
            "WHEN :type = 'windSpeed' THEN g.windSpeed " +
            "WHEN :type = 'windDirection' THEN g.windDirection " +
            "WHEN :type = 'pressure' THEN g.pressure END), " +
            "FUNCTION('ROUND', AVG(CASE WHEN :type = 'temp' THEN g.temp " +
            "WHEN :type = 'humidity' THEN g.humidity " +
            "WHEN :type = 'windSpeed' THEN g.windSpeed " +
            "WHEN :type = 'windDirection' THEN g.windDirection " +
            "WHEN :type = 'pressure' THEN g.pressure END), 2) " +
            ") FROM GasConcentration g " +
            "WHERE g.time BETWEEN :startTime AND :endTime")
    SummaryStatsPageRespDTO findSummaryStatsByTypeAndTimeRange(@Param("type")String type,@Param("startTime") LocalDateTime startTime,@Param("endTime") LocalDateTime endTime);
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/repository/SummaryStatsRepository.java
New file
@@ -0,0 +1,14 @@
package com.gkhy.fourierSpecialGasMonitor.repository;
import com.gkhy.fourierSpecialGasMonitor.entity.SummaryStats;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface SummaryStatsRepository extends JpaRepository<SummaryStats,Long>, JpaSpecificationExecutor<SummaryStats> {
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/GasConcentrationService.java
@@ -1,12 +1,15 @@
package com.gkhy.fourierSpecialGasMonitor.service;
import com.gkhy.fourierSpecialGasMonitor.commons.domain.Result;
import com.gkhy.fourierSpecialGasMonitor.commons.model.PageQuery;
import com.gkhy.fourierSpecialGasMonitor.entity.GasConcentration;
import com.gkhy.fourierSpecialGasMonitor.entity.GasWarnUser;
import com.gkhy.fourierSpecialGasMonitor.entity.SummaryStats;
import com.gkhy.fourierSpecialGasMonitor.entity.query.GasAtmospherePageQuery;
import com.gkhy.fourierSpecialGasMonitor.entity.query.GasPageQuery;
import com.gkhy.fourierSpecialGasMonitor.entity.req.GasConcentrationExportBO;
import com.gkhy.fourierSpecialGasMonitor.entity.req.SummaryStatsReqDTO;
import org.springframework.data.domain.Page;
import java.time.LocalDateTime;
@@ -26,4 +29,8 @@
    List<GasConcentration> listDatabyTimeSlotAndPosition(LocalDateTime startTime, LocalDateTime endTime, Integer position);
    List<GasConcentration> gasConcentrationExport(GasConcentrationExportBO gasConcentrationExportBO);
    SummaryStats findStats(LocalDateTime startTime,LocalDateTime endTime);
    Result gasAtmosphereExtremum(SummaryStatsReqDTO reqDTO);
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/MonitorDataService.java
@@ -28,4 +28,6 @@
    Result gasFluxExport(GasFluxExportBO gasFluxExportBO);
    Result gasConcentrationExport(GasConcentrationExportBO gasConcentrationExportBO);
    Result gasAtmosphereExtremum(SummaryStatsReqDTO reqDTO);
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/SummaryStatsService.java
New file
@@ -0,0 +1,12 @@
package com.gkhy.fourierSpecialGasMonitor.service;
import com.gkhy.fourierSpecialGasMonitor.commons.domain.Result;
import com.gkhy.fourierSpecialGasMonitor.commons.model.PageQuery;
import com.gkhy.fourierSpecialGasMonitor.entity.SummaryStats;
import com.gkhy.fourierSpecialGasMonitor.entity.req.SummaryStatsReqDTO;
public interface SummaryStatsService {
    void save(SummaryStats stats);
    Result listSummaryStats(PageQuery<SummaryStatsReqDTO> pageQuery);
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/impl/DataReceiveServiceImpl.java
@@ -17,6 +17,7 @@
import com.gkhy.fourierSpecialGasMonitor.repository.GasConcentrationRepository;
import com.gkhy.fourierSpecialGasMonitor.service.*;
import com.gkhy.fourierSpecialGasMonitor.utils.SendMessageUtil;
import com.gkhy.fourierSpecialGasMonitor.utils.SummaryUtils;
import com.gkhy.fourierSpecialGasMonitor.websocket.GasConcentrationExcWebsocketServer;
import com.gkhy.fourierSpecialGasMonitor.websocket.GasConcentrationWebsocketServer;
import com.gkhy.fourierSpecialGasMonitor.websocket.GasDeviceExcWebsocketServer;
@@ -39,6 +40,7 @@
import java.io.IOException;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
@@ -90,6 +92,9 @@
    @Autowired
    private GasFluxWebsocketServer gasFluxWebsocketServer;
    @Autowired
    private SummaryStatsService summaryStatsService;
    private static final ReentrantLock lock = new ReentrantLock();
@@ -268,11 +273,49 @@
        GasConcentration save = gasConcentrationService.save(gasConcentration);
        if (save == null)
            throw new DataReceiveException(this.getClass(), ForeignResultCode.SYSTEM_ERROR_DATABASE_FAIL.getCode(),"气体实时数据保存失败");
        //计算平局值,最大值,最小值
        computeAndSaveDailySummaryStats(save);
        dataCacheAndPush(save);
        execDataCountAndPush(reqDto);
        return ForeignResult.success();
    }
    /**
     * 计算并保存当天的气体浓度统计数据(若统计为空则初始化,否则更新)
     * @param newData 新插入的气体浓度数据
     */
    private void computeAndSaveDailySummaryStats(GasConcentration newData) {
        //  定义当天时间范围(00:00:00 至当前时间)
        LocalDateTime todayStart = LocalDate.now().atStartOfDay();
        LocalDateTime now = LocalDateTime.now();
        //  查询当天已有的统计数据
        SummaryStats dailyStats = gasConcentrationService.findStats(todayStart, now);
        // 若统计数据为空,创建新对象并初始化(用新数据作为初始值)
        if (dailyStats == null || dailyStats.isEmpty()) {
            dailyStats = new SummaryStats();
            SummaryUtils.initSummaryStats(dailyStats, newData);
        } else {
            // 若统计数据已存在,更新统计值(根据新数据重新计算 min/max/avg)
            SummaryUtils.updateSummaryStats(dailyStats, newData);
        }
        dailyStats.setTemp(newData.getTemp());
        dailyStats.setHumidity(newData.getHumidity());
        dailyStats.setWindSpeed(newData.getWindSpeed());
        dailyStats.setWindDirection(newData.getWindDirection());
        dailyStats.setPressure(newData.getPressure());
        // 补充通用字段(时间取最新数据的时间)
        dailyStats.setTime(newData.getTime());
        //  保存统计结果
        summaryStatsService.save(dailyStats);
    }
    private  void execDataCountAndPush(UploadGasConcentrationReqDTO reqDto){
        RBucket<List<GasCategory>> bucket = redissonClient.getBucket(SystemCacheKeyEnum.KEY_GAS_CATEGORY.getKey());
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/impl/GasConcentrationServiceImpl.java
@@ -1,22 +1,23 @@
package com.gkhy.fourierSpecialGasMonitor.service.impl;
import com.gkhy.fourierSpecialGasMonitor.commons.domain.Result;
import com.gkhy.fourierSpecialGasMonitor.commons.domain.SearchResult;
import com.gkhy.fourierSpecialGasMonitor.commons.enums.ResultCode;
import com.gkhy.fourierSpecialGasMonitor.commons.exception.BusinessException;
import com.gkhy.fourierSpecialGasMonitor.commons.model.PageQuery;
import com.gkhy.fourierSpecialGasMonitor.entity.GasConcentration;
import com.gkhy.fourierSpecialGasMonitor.entity.GasFlux;
import com.gkhy.fourierSpecialGasMonitor.entity.GasWarnLog;
import com.gkhy.fourierSpecialGasMonitor.entity.GasWarnUser;
import com.gkhy.fourierSpecialGasMonitor.entity.*;
import com.gkhy.fourierSpecialGasMonitor.entity.query.FindGasWarnLogPageQuery;
import com.gkhy.fourierSpecialGasMonitor.entity.query.FindGasWarnUserPageQuery;
import com.gkhy.fourierSpecialGasMonitor.entity.query.GasAtmospherePageQuery;
import com.gkhy.fourierSpecialGasMonitor.entity.query.GasPageQuery;
import com.gkhy.fourierSpecialGasMonitor.entity.req.GasConcentrationExportBO;
import com.gkhy.fourierSpecialGasMonitor.entity.req.SummaryStatsReqDTO;
import com.gkhy.fourierSpecialGasMonitor.entity.resp.FindGasWarnUserPageRespDTO;
import com.gkhy.fourierSpecialGasMonitor.entity.resp.SummaryStatsPageRespDTO;
import com.gkhy.fourierSpecialGasMonitor.enums.DeleteStatusEnum;
import com.gkhy.fourierSpecialGasMonitor.repository.GasConcentrationRepository;
import com.gkhy.fourierSpecialGasMonitor.service.GasConcentrationService;
import com.gkhy.fourierSpecialGasMonitor.utils.SummaryUtils;
import io.micrometer.core.instrument.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
@@ -27,7 +28,11 @@
import org.springframework.stereotype.Service;
import javax.persistence.criteria.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -158,4 +163,58 @@
        List<GasConcentration> result = gasConcentrationRepository.findAll(specification);
        return result;
    }
    @Override
    public SummaryStats findStats(LocalDateTime startTime,LocalDateTime endTime) {
        return gasConcentrationRepository.findStats(startTime,endTime);
    }
    @Override
    public Result gasAtmosphereExtremum(SummaryStatsReqDTO reqDTO) {
        Result success = Result.success();
        // 1. 参数校验
        SummaryUtils summaryUtils = new SummaryUtils();
        summaryUtils.validateParams(reqDTO);
        String type = reqDTO.getType();       // 气体类型(如 "temp"、"humidity")
        Integer dateType = reqDTO.getDateType(); // 时间粒度(1-日、2-月、3-年)
        String date = reqDTO.getDate();       // 日期(如 "2025-11-10"、"2025-11"、"2025")
        // 2. 根据 dateType 解析时间范围
        LocalDateTime startTime = null;
        LocalDateTime endTime = null;
        String formatDate = null; // 格式化后的日期(用于返回)
        switch (dateType) {
            case 1: // 日查询(date格式:yyyy-MM-dd)
                LocalDate day = LocalDate.parse(date);
                startTime = day.atStartOfDay(); // 当天00:00:00
                endTime = day.atTime(23, 59, 59); // 当天23:59:59
                formatDate = day.format(DateTimeFormatter.ISO_LOCAL_DATE);
                break;
            case 2: // 月查询(date格式:yyyy-MM)
                YearMonth month = YearMonth.parse(date);
                startTime = month.atDay(1).atStartOfDay(); // 当月1日00:00:00
                endTime = month.atEndOfMonth().atTime(23, 59, 59); // 当月最后一天23:59:59
                formatDate = month.format(DateTimeFormatter.ofPattern("yyyy-MM"));
                break;
            case 3: // 年查询(date格式:yyyy)
                Year year = Year.parse(date);
                startTime = year.atDay(1).atStartOfDay(); // 当年1月1日00:00:00
                endTime = year.atMonth(12).atEndOfMonth().atTime(23, 59, 59); // 当年12月最后一天23:59:59
                formatDate = year.toString();
                break;
            default:
                throw new BusinessException(this.getClass(), ResultCode.PARAM_ERROR_ILLEGAL.getCode(),"无效的dateType,必须为1(日)、2(月)、3(年)");
        }
        // 3. 动态查询指定类型和时间范围的统计值
        SummaryStatsPageRespDTO summaryStatsByTypeAndTimeRange = gasConcentrationRepository.findSummaryStatsByTypeAndTimeRange(type, startTime, endTime);
        success.setData(summaryStatsByTypeAndTimeRange);
        // 5. 返回结果
        return success;
    }
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/impl/MonitorDataServiceImpl.java
@@ -480,4 +480,9 @@
        result.setMsg("气体浓度数据导出成功");
        return result;
    }
    @Override
    public Result gasAtmosphereExtremum(SummaryStatsReqDTO reqDTO) {
        return gasConcentrationService.gasAtmosphereExtremum(reqDTO);
    }
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/service/impl/SummaryStatsServiceImpl.java
New file
@@ -0,0 +1,112 @@
package com.gkhy.fourierSpecialGasMonitor.service.impl;
import com.gkhy.fourierSpecialGasMonitor.commons.domain.Result;
import com.gkhy.fourierSpecialGasMonitor.commons.domain.SearchResult;
import com.gkhy.fourierSpecialGasMonitor.commons.enums.ResultCode;
import com.gkhy.fourierSpecialGasMonitor.commons.exception.BusinessException;
import com.gkhy.fourierSpecialGasMonitor.commons.model.PageQuery;
import com.gkhy.fourierSpecialGasMonitor.entity.SummaryStats;
import com.gkhy.fourierSpecialGasMonitor.entity.req.SummaryStatsReqDTO;
import com.gkhy.fourierSpecialGasMonitor.entity.resp.GasAtmospherePageRespDTO;
import com.gkhy.fourierSpecialGasMonitor.entity.resp.SummaryStatsPageRespDTO;
import com.gkhy.fourierSpecialGasMonitor.repository.SummaryStatsRepository;
import com.gkhy.fourierSpecialGasMonitor.service.SummaryStatsService;
import com.gkhy.fourierSpecialGasMonitor.utils.SummaryUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class SummaryStatsServiceImpl implements SummaryStatsService {
    @Autowired
    private SummaryStatsRepository summaryStatsRepository;
    @Override
    public void save(SummaryStats stats) {
        summaryStatsRepository.save(stats);
    }
    @Override
    public Result listSummaryStats(PageQuery<SummaryStatsReqDTO> pageQuery) {
        //  分页参数校验(复用已有逻辑,补充非空判断)
        if (pageQuery == null || pageQuery.getPageIndex() == null || pageQuery.getPageSize() == null) {
            throw new BusinessException(this.getClass(), ResultCode.PARAM_ERROR_NULL, "分页参数不能为空");
        }
        // 校验分页索引和大小合法性(避免负数或过大值)
        if (pageQuery.getPageIndex() < 1 || pageQuery.getPageSize() < 1 || pageQuery.getPageSize() > 1000) {
            throw new BusinessException(this.getClass(), ResultCode.PARAM_ERROR, "分页索引必须≥1,分页大小必须在1-1000之间");
        }
        //查询参数校验(处理 searchParams 为 null 的情况)
        SummaryStatsReqDTO searchParams = pageQuery.getSearchParams();
        if (searchParams == null) {
            searchParams = new SummaryStatsReqDTO(); // 避免后续调用 NPE
        }
        LocalDateTime startTime = searchParams.getStartTime();
        LocalDateTime endTime = searchParams.getEndTime();
        String type = searchParams.getType(); // type 改为 String 类型
        // 校验 type 合法性(允许为 null,若不为 null 则必须是指定值)
        List<String> validTypes = Arrays.asList("temp", "humidity", "windSpeed", "windDirection", "pressure");
        if (type != null && !validTypes.contains(type)) {
            throw new BusinessException(this.getClass(), ResultCode.PARAM_ERROR,
                    "type 必须为 temp、humidity、windSpeed、windDirection、pressure 中的一种");
        }
        //  初始化返回结果
        SearchResult<List<SummaryStatsPageRespDTO>> searchResult = new SearchResult<>();
        searchResult.setPageIndex(pageQuery.getPageIndex());
        searchResult.setPageSize(pageQuery.getPageSize());
        searchResult.setSuccess();
        // 构建查询条件(使用 List 而非 Set,逻辑更清晰;补充时间范围非空判断)
        Specification<SummaryStats> specification = (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            // 时间范围条件:仅当 start/endTime 非空时添加(避免 null 导致的查询异常)
            if (startTime != null && endTime != null) {
                predicates.add(criteriaBuilder.between(root.get("time"), startTime, endTime));
            } else if (startTime != null) {
                predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("time"), startTime));
            } else if (endTime != null) {
                predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("time"), endTime));
            }
            // 可扩展其他条件(如按位置、设备筛选)
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
        // 构建分页参数(封装分页索引转换逻辑,避免硬编码)
        Pageable pageable = PageRequest.of(pageQuery.getPageIndex()-1, pageQuery.getPageSize(), Sort.Direction.DESC, "time");
        //  执行查询
        Page<SummaryStats> statsPage = summaryStatsRepository.findAll(specification, pageable);
        if (statsPage.isEmpty()) {
            return searchResult; // 空结果直接返回
        }
        SummaryUtils summaryUtils = new SummaryUtils();
        // 转换结果(使用枚举映射处理 type 逻辑)
        List<SummaryStatsPageRespDTO> respDTOS = statsPage.getContent().stream()
                .map(stats -> summaryUtils.convertToResp(stats, type))
                .collect(Collectors.toList());
        // 填充返回结果
        searchResult.setTotal(statsPage.getTotalElements());
        searchResult.setPages(statsPage.getTotalPages());
        searchResult.setData(respDTOS);
        return searchResult;
    }
}
src/main/java/com/gkhy/fourierSpecialGasMonitor/utils/SummaryUtils.java
New file
@@ -0,0 +1,191 @@
package com.gkhy.fourierSpecialGasMonitor.utils;
import com.alibaba.druid.util.StringUtils;
import com.gkhy.fourierSpecialGasMonitor.commons.enums.ResultCode;
import com.gkhy.fourierSpecialGasMonitor.commons.exception.BusinessException;
import com.gkhy.fourierSpecialGasMonitor.entity.GasConcentration;
import com.gkhy.fourierSpecialGasMonitor.entity.SummaryStats;
import com.gkhy.fourierSpecialGasMonitor.entity.req.SummaryStatsReqDTO;
import com.gkhy.fourierSpecialGasMonitor.entity.resp.SummaryStatsPageRespDTO;
import com.gkhy.fourierSpecialGasMonitor.enums.StatsTypeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.Year;
import java.time.YearMonth;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
public class SummaryUtils {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * 初始化统计数据(当当天无数据时,用新数据作为初始值)
     */
    public static void  initSummaryStats(SummaryStats stats, GasConcentration data) {
        // 初始化 temp 相关统计(min/max/avg 均为当前数据值)
        stats.setTempMin(data.getTemp());
        stats.setTempMax(data.getTemp());
        stats.setTempAvg(data.getTemp());
        // 初始化 humidity 相关统计
        stats.setHumidityMin(data.getHumidity());
        stats.setHumidityMax(data.getHumidity());
        stats.setHumidityAvg(data.getHumidity());
        // 初始化 windSpeed 相关统计
        Double windSpeed = data.getWindSpeed();
        stats.setWindSpeedMin(windSpeed);
        stats.setWindSpeedMax(windSpeed);
        stats.setWindSpeedAvg(windSpeed);
        // 初始化 windDirection 相关统计(处理可能的 null)
        Integer windDirection = data.getWindDirection();
        stats.setWindDirectionMin(windDirection);
        stats.setWindDirectionMax(windDirection);
        stats.setWindDirectionAvg(windDirection != null ? windDirection.doubleValue() : null);
        // 初始化 pressure 相关统计
        Double pressure = data.getPressure();
        stats.setPressureMin(pressure);
        stats.setPressureMax(pressure);
        stats.setPressureAvg(pressure);
    }
    /**
     * 更新统计数据(根据新数据重新计算 min/max/avg)
     */
    public static void updateSummaryStats(SummaryStats stats, GasConcentration newData) {
        // 更新 temp:min 取更小值,max 取更大值,avg 重新计算(需注意:avg 不能直接用当前 avg 计算,需累计总和/数量)
        // 注意:此处简化处理,实际 avg 需基于总样本数计算(建议在数据库查询时直接聚合,而非内存中更新)
        Double newTemp = newData.getTemp();
        stats.setTempMin(min(stats.getTempMin(), newTemp));
        stats.setTempMax(max(stats.getTempMax(), newTemp));
        // 更新 humidity
        Double newHumidity = newData.getHumidity();
        stats.setHumidityMin(min(stats.getHumidityMin(), newHumidity));
        stats.setHumidityMax(max(stats.getHumidityMax(), newHumidity));
        // 更新 windSpeed
        Double newWindSpeed = newData.getWindSpeed();
        stats.setWindSpeedMin(min(stats.getWindSpeedMin(), newWindSpeed));
        stats.setWindSpeedMax(max(stats.getWindSpeedMax(), newWindSpeed));
        // 更新 windDirection
        Integer newWindDir = newData.getWindDirection();
        stats.setWindDirectionMin(min(stats.getWindDirectionMin(), newWindDir));
        stats.setWindDirectionMax(max(stats.getWindDirectionMax(), newWindDir));
        // 更新 pressure
        Double newPressure = newData.getPressure();
        stats.setPressureMin(min(stats.getPressureMin(), newPressure));
        stats.setPressureMax(max(stats.getPressureMax(), newPressure));
    }
    /**
     * 安全计算两个 Double 的最小值(处理 null)
     */
    private static Double min(Double a, Double b) {
        if (a == null) return b;
        if (b == null) return a;
        return Math.min(a, b);
    }
    /**
     * 安全计算两个 Integer 的最小值(处理 null)
     */
    private static Integer min(Integer a, Integer b) {
        if (a == null) return b;
        if (b == null) return a;
        return Math.min(a, b);
    }
    // 同理实现 max 方法(Double 和 Integer 版本)
    private static Double max(Double a, Double b) {
        if (a == null) return b;
        if (b == null) return a;
        return Math.max(a, b);
    }
    private static Integer max(Integer a, Integer b) {
        if (a == null) return b;
        if (b == null) return a;
        return Math.max(a, b);
    }
    public void validateParams(SummaryStatsReqDTO reqDTO) {
        if (reqDTO == null) {
            throw new BusinessException(this.getClass(), ResultCode.PARAM_ERROR_NULL.getCode(),"请求参数不能为空");
        }
        if (StringUtils.isEmpty(reqDTO.getType())) {
            throw new BusinessException(this.getClass(), ResultCode.PARAM_ERROR_NULL.getCode(),"气体类型(type)不能为空");
        }
        if (reqDTO.getDateType() == null || !Arrays.asList(1, 2, 3).contains(reqDTO.getDateType())) {
            throw new BusinessException(this.getClass(), ResultCode.PARAM_ERROR_ILLEGAL.getCode(),"dateType必须为1(日)、2(月)、3(年)");
        }
        if (StringUtils.isEmpty(reqDTO.getDate())) {
            throw new BusinessException(this.getClass(), ResultCode.PARAM_ERROR_NULL.getCode(),"日期(date)不能为空");
        }
        // 校验date格式(根据dateType)
        try {
            switch (reqDTO.getDateType()) {
                case 1:
                    LocalDate.parse(reqDTO.getDate()); // 校验 yyyy-MM-dd
                    break;
                case 2:
                    YearMonth.parse(reqDTO.getDate()); // 校验 yyyy-MM
                    break;
                case 3:
                    Year.parse(reqDTO.getDate()); // 校验 yyyy
                    break;
            }
        } catch (DateTimeParseException e) {
            throw new BusinessException(this.getClass(), ResultCode.PARAM_ERROR_ILLEGAL.getCode(),"日期格式错误,dateType=" + reqDTO.getDateType() + "时,date应为" +
                    (reqDTO.getDateType() == 1 ? "yyyy-MM-dd" : reqDTO.getDateType() == 2 ? "yyyy-MM" : "yyyy"));
        }
    }
    public SummaryStatsPageRespDTO convertToResp(SummaryStats stats, String type) {
        SummaryStatsPageRespDTO respDTO = new SummaryStatsPageRespDTO();
        BeanUtils.copyProperties(stats, respDTO); // 复制公共字段(如 time)
        if (type == null) {
            return respDTO; // 若 type 为 null,不设置指标相关字段
        }
        StatsTypeEnum mapper = StatsTypeEnum.getByType(type);
        if (mapper == null) {
            return respDTO; // 无效 type(理论上已在参数校验时拦截)
        }
        try {
            // 反射获取字段值(通过枚举映射的字段名)
            Field valueField = SummaryStats.class.getDeclaredField(mapper.getValueField());
            Field minField = SummaryStats.class.getDeclaredField(mapper.getMinField());
            Field maxField = SummaryStats.class.getDeclaredField(mapper.getMaxField());
            Field avgField = SummaryStats.class.getDeclaredField(mapper.getAvgField());
            valueField.setAccessible(true);
            minField.setAccessible(true);
            maxField.setAccessible(true);
            avgField.setAccessible(true);
            // 设置到响应 DTO
            respDTO.setValue(valueField.get(stats));
            respDTO.setMin(minField.get(stats));
            respDTO.setMax(maxField.get(stats));
            respDTO.setAvg(avgField.get(stats));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            logger.error("转换 SummaryStats 到 DTO 失败,type={}, stats={}", type, stats, e);
        }
        return respDTO;
    }
}