learshaw 3 месяцев назад
Родитель
Сommit
32cc4aca3e
24 измененных файлов с 1421 добавлено и 624 удалено
  1. 17 149
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/TaskClacService.java
  2. 263 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/TaskColExecutor.java
  3. 0 122
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/TaskExecutor.java
  4. 0 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/config/AnalysisConfig.java
  5. 0 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/config/DateAttrConfig.java
  6. 0 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/config/WeatherConfig.java
  7. 264 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/controller/TestController.java
  8. 34 0
      ems/ems-cloud/ems-dev-adapter/src/main/resources/application-local.yml
  9. 34 0
      ems/ems-cloud/ems-dev-adapter/src/main/resources/application-prod-ct.yml
  10. 31 34
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/CaMeterDController.java
  11. 43 9
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/ElecConsumeForecastController.java
  12. 0 34
      ems/ems-cloud/ems-server/src/main/resources/application-local.yml
  13. 0 34
      ems/ems-cloud/ems-server/src/main/resources/application-prod-ct.yml
  14. 26 24
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/CaMeterD.java
  15. 17 43
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/CaMeterDMapper.java
  16. 36 8
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/ElecConsumeForecastMapper.java
  17. 16 41
      ems/ems-core/src/main/java/com/ruoyi/ems/service/ICaMeterDService.java
  18. 28 3
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IElecConsumeForecastService.java
  19. 59 41
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/CaMeterDServiceImpl.java
  20. 212 2
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/ElecConsumeForecastServiceImpl.java
  21. 145 23
      ems/ems-core/src/main/resources/mapper/ems/CaMeterDMapper.xml
  22. 193 54
      ems/ems-core/src/main/resources/mapper/ems/ElecConsumeForecastMapper.xml
  23. 2 2
      ems/sql/ems_init_data_ctfwq.sql
  24. 1 1
      ems/sql/ems_sys_data.sql

+ 17 - 149
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/task/TaskService.java → ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/TaskClacService.java

@@ -8,44 +8,30 @@
  * 修改单号:  <修改单号>
  * 修改内容:  <修改内容>
  */
-package com.ruoyi.ems.task;
-
-import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONArray;
-import com.alibaba.fastjson2.JSONObject;
-import com.huashe.common.domain.model.DateAttr;
-import com.huashe.common.domain.model.WeatherForecast;
-import com.huashe.common.domain.model.WeatherRt;
-import com.huashe.common.exception.Assert;
-import com.huashe.common.utils.HttpUtils;
+package com.ruoyi.ems;
+
 import com.ruoyi.ems.config.AnalysisConfig;
-import com.ruoyi.ems.config.DateAttrConfig;
-import com.ruoyi.ems.config.WeatherConfig;
 import com.ruoyi.ems.domain.Area;
 import com.ruoyi.ems.domain.EmsFacs;
+import com.ruoyi.ems.handle.BaCtlHandler;
 import com.ruoyi.ems.model.QueryMeter;
 import com.ruoyi.ems.service.IAreaService;
-import com.ruoyi.ems.service.IDateService;
 import com.ruoyi.ems.service.IEmsFacsService;
-import com.ruoyi.ems.service.IWeatherService;
 import com.ruoyi.ems.service.analysis.CarbonCalculationService;
 import com.ruoyi.ems.service.analysis.ElecConsumeForecastService;
 import com.ruoyi.ems.service.analysis.ElecProdForecastService;
 import com.ruoyi.ems.service.analysis.EmsEcoAnalysisService;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.ArrayUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.http.client.utils.URIBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.scheduling.annotation.Async;
-import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.scheduling.annotation.Scheduled;
 
 import javax.annotation.Resource;
 import java.time.LocalDate;
-import java.time.Year;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.TemporalAdjusters;
 import java.util.List;
@@ -60,9 +46,8 @@ import java.util.List;
  * @since [产品/模块版本]
  */
 @Configuration
-@EnableScheduling
-public class TaskService {
-    private static final Logger log = LoggerFactory.getLogger(TaskService.class);
+public class TaskClacService {
+    private static final Logger log = LoggerFactory.getLogger(TaskClacService.class);
 
     @Resource
     private IAreaService areaService;
@@ -70,17 +55,6 @@ public class TaskService {
     @Resource
     private IEmsFacsService facsService;
 
-    @Resource
-    private WeatherConfig weatherConfig;
-
-    @Resource
-    private DateAttrConfig holidayConfig;
-
-    @Resource
-    private IWeatherService weatherService;
-
-    @Resource
-    private IDateService dateService;
 
     @Resource
     private EmsEcoAnalysisService ecoAnalysisService;
@@ -97,123 +71,9 @@ public class TaskService {
     @Resource
     private AnalysisConfig analysisConfig;
 
-    /**
-     * 天气实况采集
-     */
-    @Async
-    @Scheduled(cron = "${general-data.weather.rt.cron}")
-    public void collectRt() {
-        log.debug("start collect weather rt.");
-
-        try {
-            URIBuilder uriBuilder = new URIBuilder(weatherConfig.getWeatherRtAddr());
-            uriBuilder.addParameter("adcode", weatherConfig.getAdcode())
-                .addParameter("apiKey", weatherConfig.getApiKey());
-            String res = HttpUtils.doGet(uriBuilder);
-            log.debug("get res:\r\n{}", res);
-
-            JSONObject jsonObject = JSON.parseObject(res);
-
-            String code = jsonObject.getString("code");
-            Assert.isTrue(StringUtils.equals(code, "0"), -1, jsonObject.getString("message"));
-
-            String data = jsonObject.getString("data");
-
-            if (data != null) {
-                WeatherRt weatherRt = JSONObject.parseObject(data, WeatherRt.class);
-                weatherService.mergeRt(weatherRt);
-            }
-        }
-        catch (Exception e) {
-            log.error("collectRt fail!", e);
-        }
-    }
-
-    /**
-     * 天气预报采集
-     */
-    @Async
-    @Scheduled(cron = "${general-data.weather.forecast.cron}")
-    public void collectForecast() {
-        log.debug("start collect weather forecast.");
-
-        try {
-            URIBuilder uriBuilder = new URIBuilder(weatherConfig.getWeatherForecastAddr());
-            uriBuilder.addParameter("adcode", weatherConfig.getAdcode())
-                .addParameter("apiKey", weatherConfig.getApiKey());
-            String res = HttpUtils.doGet(uriBuilder);
-            log.debug("get res:\r\n{}", res);
-
-            JSONObject jsonObject = JSON.parseObject(res);
-
-            String code = jsonObject.getString("code");
-            Assert.isTrue(StringUtils.equals(code, "0"), -1, jsonObject.getString("message"));
-
-            JSONArray data = jsonObject.getJSONArray("data");
-            Assert.notNull(data, -1, "weather list is null.");
-
-            if (!data.isEmpty()) {
-                List<WeatherForecast> forecastList = JSON.parseArray(data.toString(), WeatherForecast.class);
-
-                if (CollectionUtils.isNotEmpty(forecastList)) {
-                    weatherService.deleteForecastByAdcode(weatherConfig.getAdcode());
-                    weatherService.insertForecastBacth(forecastList);
-                }
-            }
-        }
-        catch (Exception e) {
-            log.error("collectForecast fail!", e);
-        }
-    }
-
-    /**
-     * 节假日采集
-     */
-    @Async
-    @Scheduled(cron = "${general-data.date.cron}")
-    public void collectDateAttr() {
-        log.debug("start collect date attr.");
-
-        try {
-            // 获取当前年份
-            int currentYear = Year.now().getValue();
-
-            // 本年第一天
-            LocalDate firstDay = LocalDate.of(currentYear, 1, 1);
-
-            // 本年最后一天(12月31日)
-            LocalDate lastDay = LocalDate.of(currentYear, 12, 31);
-
-            // 定义日期格式
-            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
-
-            URIBuilder uriBuilder = new URIBuilder(holidayConfig.getApiAddr());
-            uriBuilder.addParameter("startDate", formatter.format(firstDay))
-                .addParameter("endDate", formatter.format(lastDay)).addParameter("apiKey", weatherConfig.getApiKey());
-            String res = HttpUtils.doGet(uriBuilder);
-            log.debug("get res:\r\n{}", res);
-
-            JSONObject jsonObject = JSON.parseObject(res);
-
-            String code = jsonObject.getString("code");
-            Assert.isTrue(StringUtils.equals(code, "0"), -1, jsonObject.getString("message"));
-
-            JSONArray data = jsonObject.getJSONArray("data");
-            Assert.notNull(data, -1, "date attr is null.");
-
-            if (!data.isEmpty()) {
-                List<DateAttr> dateAttrs = JSON.parseArray(data.toString(), DateAttr.class);
-
-                if (CollectionUtils.isNotEmpty(dateAttrs)) {
-                    dateService.deleteByYear(String.valueOf(currentYear));
-                    dateService.insertBatch(dateAttrs);
-                }
-            }
-        }
-        catch (Exception e) {
-            log.error("collect date attr fail!", e);
-        }
-    }
+    @Qualifier("baCtlHandler")
+    @Resource
+    private BaCtlHandler baCtlHandler;
 
     /**
      * 节能分析
@@ -373,6 +233,14 @@ public class TaskService {
         }
     }
 
+    /**
+     * 每小时产出设备计量数据
+     */
+    @Scheduled(cron = "0 0 0/1 * * ?")
+    public void baMeterHourProd() {
+        int cnt = baCtlHandler.meterHourProd();
+        log.debug("产出室内能耗设备计量数据: {} 条", cnt);
+    }
 
     private void areaElecConsumeForecast(String rootArea, LocalDate today) {
         List<Area> areas = areaService.selectAreaTree(rootArea, true);

+ 263 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/TaskColExecutor.java

@@ -0,0 +1,263 @@
+/*
+ * 文 件 名:  TaskHandler
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/3/18
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.ems;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.huashe.common.domain.model.DateAttr;
+import com.huashe.common.domain.model.WeatherForecast;
+import com.huashe.common.domain.model.WeatherRt;
+import com.huashe.common.exception.Assert;
+import com.huashe.common.utils.HttpUtils;
+import com.ruoyi.ems.config.DateAttrConfig;
+import com.ruoyi.ems.config.WeatherConfig;
+import com.ruoyi.ems.core.ObjectCache;
+import com.ruoyi.ems.handle.AcrelElecMonitorHandler;
+import com.ruoyi.ems.handle.BaCtlHandler;
+import com.ruoyi.ems.handle.GeekOpenCbHandler;
+import com.ruoyi.ems.handle.GrowattHandler;
+import com.ruoyi.ems.handle.SquareLightCtlHandler;
+import com.ruoyi.ems.service.IDateService;
+import com.ruoyi.ems.service.IWeatherService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.client.utils.URIBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.time.LocalDate;
+import java.time.Year;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * 任务调度
+ * <功能详细描述>
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/3/18]
+ * @see [相关类/方法]
+ * @since [产品/模块版本]
+ */
+@Service
+public class TaskColExecutor {
+    /**
+     * 日志服务
+     */
+    protected static final Logger log = LoggerFactory.getLogger(TaskColExecutor.class);
+
+    @Resource
+    private WeatherConfig weatherConfig;
+
+    @Resource
+    private DateAttrConfig holidayConfig;
+
+    @Resource
+    private IWeatherService weatherService;
+
+    @Resource
+    private IDateService dateService;
+
+    @Autowired
+    private ObjectCache objectCache;
+
+    @Qualifier("geekOpenCbHandler")
+    @Resource
+    private GeekOpenCbHandler geekOpenCbHandler;
+
+    @Qualifier("baCtlHandler")
+    @Resource
+    private BaCtlHandler baCtlHandler;
+
+    @Qualifier("squareLightCtlHandler")
+    @Resource
+    private SquareLightCtlHandler squareLightCtlHandler;
+
+    @Qualifier("acrelElecMonitorHandler")
+    @Resource
+    private AcrelElecMonitorHandler acrelElecMonitorHandler;
+
+    @Qualifier("growattHandler")
+    @Resource
+    private GrowattHandler growattHandler;
+
+    /**
+     * 定时清理过期设备上报响应
+     */
+    @Scheduled(cron = "0 0/10 * * * ?")
+    public void cleanDevResCache() {
+        int cnt = objectCache.cleanDevResCache();
+        log.debug("清理过期设备上报响应: {} 条", cnt);
+    }
+
+    /**
+     * 定时刷新设备在线状态
+     */
+    @Scheduled(cron = "0 0/5 * * * ?")
+    public void refresh5min() {
+        CompletableFuture.runAsync(() -> squareLightCtlHandler.execSyncDevAttrAll());
+        CompletableFuture.runAsync(() -> squareLightCtlHandler.refreshOnline());
+        CompletableFuture.runAsync(() -> acrelElecMonitorHandler.execSyncDevAttrAll());
+        CompletableFuture.runAsync(() -> growattHandler.refreshOnline());
+    }
+
+//    /**
+//     * 每小时产出设备计量数据
+//     */
+//    @Scheduled(cron = "0 0 0/1 * * ?")
+//    public void geekOpenMeterHourProd() {
+//        int cnt1 = geekOpenCbHandler.meterHourProd();
+//        log.debug("产出GeekOpen设备计量数据: {} 条", cnt1);
+//    }
+
+    /**
+     * 每15min采集能耗数据
+     */
+    @Scheduled(cron = "0 0/15 * * * ?")
+    public void baMeterCollect() {
+        CompletableFuture.runAsync(() -> {
+            baCtlHandler.meterCollect();
+            baCtlHandler.xfCollect();
+            baCtlHandler.ahuCollect();
+            baCtlHandler.wtCollect();
+            baCtlHandler.wpCollect();
+            baCtlHandler.lightCollect();
+        });
+
+        CompletableFuture.runAsync(() -> growattHandler.execCollectInverterData());
+    }
+
+    /**
+     * 天气实况采集
+     */
+    @Async
+    @Scheduled(cron = "${general-data.weather.rt.cron}")
+    public void collectRt() {
+        log.debug("start collect weather rt.");
+
+        try {
+            URIBuilder uriBuilder = new URIBuilder(weatherConfig.getWeatherRtAddr());
+            uriBuilder.addParameter("adcode", weatherConfig.getAdcode())
+                .addParameter("apiKey", weatherConfig.getApiKey());
+            String res = HttpUtils.doGet(uriBuilder);
+            log.debug("get res:\r\n{}", res);
+
+            JSONObject jsonObject = JSON.parseObject(res);
+
+            String code = jsonObject.getString("code");
+            Assert.isTrue(StringUtils.equals(code, "0"), -1, jsonObject.getString("message"));
+
+            String data = jsonObject.getString("data");
+
+            if (data != null) {
+                WeatherRt weatherRt = JSONObject.parseObject(data, WeatherRt.class);
+                weatherService.mergeRt(weatherRt);
+            }
+        }
+        catch (Exception e) {
+            log.error("collectRt fail!", e);
+        }
+    }
+
+    /**
+     * 天气预报采集
+     */
+    @Async
+    @Scheduled(cron = "${general-data.weather.forecast.cron}")
+    public void collectForecast() {
+        log.debug("start collect weather forecast.");
+
+        try {
+            URIBuilder uriBuilder = new URIBuilder(weatherConfig.getWeatherForecastAddr());
+            uriBuilder.addParameter("adcode", weatherConfig.getAdcode())
+                .addParameter("apiKey", weatherConfig.getApiKey());
+            String res = HttpUtils.doGet(uriBuilder);
+            log.debug("get res:\r\n{}", res);
+
+            JSONObject jsonObject = JSON.parseObject(res);
+
+            String code = jsonObject.getString("code");
+            Assert.isTrue(StringUtils.equals(code, "0"), -1, jsonObject.getString("message"));
+
+            JSONArray data = jsonObject.getJSONArray("data");
+            Assert.notNull(data, -1, "weather list is null.");
+
+            if (!data.isEmpty()) {
+                List<WeatherForecast> forecastList = JSON.parseArray(data.toString(), WeatherForecast.class);
+
+                if (CollectionUtils.isNotEmpty(forecastList)) {
+                    weatherService.deleteForecastByAdcode(weatherConfig.getAdcode());
+                    weatherService.insertForecastBacth(forecastList);
+                }
+            }
+        }
+        catch (Exception e) {
+            log.error("collectForecast fail!", e);
+        }
+    }
+
+    /**
+     * 节假日采集
+     */
+    @Async
+    @Scheduled(cron = "${general-data.date.cron}")
+    public void collectDateAttr() {
+        log.debug("start collect date attr.");
+
+        try {
+            // 获取当前年份
+            int currentYear = Year.now().getValue();
+
+            // 本年第一天
+            LocalDate firstDay = LocalDate.of(currentYear, 1, 1);
+
+            // 本年最后一天(12月31日)
+            LocalDate lastDay = LocalDate.of(currentYear, 12, 31);
+
+            // 定义日期格式
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+            URIBuilder uriBuilder = new URIBuilder(holidayConfig.getApiAddr());
+            uriBuilder.addParameter("startDate", formatter.format(firstDay))
+                .addParameter("endDate", formatter.format(lastDay)).addParameter("apiKey", weatherConfig.getApiKey());
+            String res = HttpUtils.doGet(uriBuilder);
+            log.debug("get res:\r\n{}", res);
+
+            JSONObject jsonObject = JSON.parseObject(res);
+
+            String code = jsonObject.getString("code");
+            Assert.isTrue(StringUtils.equals(code, "0"), -1, jsonObject.getString("message"));
+
+            JSONArray data = jsonObject.getJSONArray("data");
+            Assert.notNull(data, -1, "date attr is null.");
+
+            if (!data.isEmpty()) {
+                List<DateAttr> dateAttrs = JSON.parseArray(data.toString(), DateAttr.class);
+
+                if (CollectionUtils.isNotEmpty(dateAttrs)) {
+                    dateService.deleteByYear(String.valueOf(currentYear));
+                    dateService.insertBatch(dateAttrs);
+                }
+            }
+        }
+        catch (Exception e) {
+            log.error("collect date attr fail!", e);
+        }
+    }
+}

+ 0 - 122
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/TaskExecutor.java

@@ -1,122 +0,0 @@
-/*
- * 文 件 名:  TaskHandler
- * 版    权:  华设设计集团股份有限公司
- * 描    述:  <描述>
- * 修 改 人:  lvwenbin
- * 修改时间:  2025/3/18
- * 跟踪单号:  <跟踪单号>
- * 修改单号:  <修改单号>
- * 修改内容:  <修改内容>
- */
-package com.ruoyi.ems;
-
-import com.ruoyi.ems.core.ObjectCache;
-import com.ruoyi.ems.handle.AcrelElecMonitorHandler;
-import com.ruoyi.ems.handle.BaCtlHandler;
-import com.ruoyi.ems.handle.GeekOpenCbHandler;
-import com.ruoyi.ems.handle.GrowattHandler;
-import com.ruoyi.ems.handle.SquareLightCtlHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.util.concurrent.CompletableFuture;
-
-/**
- * 任务调度
- * <功能详细描述>
- *
- * @author lvwenbin
- * @version [版本号, 2025/3/18]
- * @see [相关类/方法]
- * @since [产品/模块版本]
- */
-@Service
-public class TaskExecutor {
-    /**
-     * 日志服务
-     */
-    protected static final Logger log = LoggerFactory.getLogger(TaskExecutor.class);
-
-    @Autowired
-    private ObjectCache objectCache;
-
-    @Qualifier("geekOpenCbHandler")
-    @Resource
-    private GeekOpenCbHandler geekOpenCbHandler;
-
-    @Qualifier("baCtlHandler")
-    @Resource
-    private BaCtlHandler baCtlHandler;
-
-    @Qualifier("squareLightCtlHandler")
-    @Resource
-    private SquareLightCtlHandler squareLightCtlHandler;
-
-    @Qualifier("acrelElecMonitorHandler")
-    @Resource
-    private AcrelElecMonitorHandler acrelElecMonitorHandler;
-
-    @Qualifier("growattHandler")
-    @Resource
-    private GrowattHandler growattHandler;
-
-    /**
-     * 定时清理过期设备上报响应
-     */
-    @Scheduled(cron = "0 0/10 * * * ?")
-    public void cleanDevResCache() {
-        int cnt = objectCache.cleanDevResCache();
-        log.debug("清理过期设备上报响应: {} 条", cnt);
-    }
-
-    /**
-     * 定时刷新设备在线状态
-     */
-    @Scheduled(cron = "0 0/5 * * * ?")
-    public void refresh5min() {
-        CompletableFuture.runAsync(() -> squareLightCtlHandler.execSyncDevAttrAll());
-        CompletableFuture.runAsync(() -> squareLightCtlHandler.refreshOnline());
-        CompletableFuture.runAsync(() -> acrelElecMonitorHandler.execSyncDevAttrAll());
-        CompletableFuture.runAsync(() -> growattHandler.refreshOnline());
-    }
-
-//    /**
-//     * 每小时产出设备计量数据
-//     */
-//    @Scheduled(cron = "0 0 0/1 * * ?")
-//    public void geekOpenMeterHourProd() {
-//        int cnt1 = geekOpenCbHandler.meterHourProd();
-//        log.debug("产出GeekOpen设备计量数据: {} 条", cnt1);
-//    }
-
-    /**
-     * 每15min采集能耗数据
-     */
-    @Scheduled(cron = "0 0/15 * * * ?")
-    public void baMeterCollect() {
-        CompletableFuture.runAsync(() -> {
-            baCtlHandler.meterCollect();
-            baCtlHandler.xfCollect();
-            baCtlHandler.ahuCollect();
-            baCtlHandler.wtCollect();
-            baCtlHandler.wpCollect();
-            baCtlHandler.lightCollect();
-        });
-
-        CompletableFuture.runAsync(() -> growattHandler.execCollectInverterData());
-    }
-
-    /**
-     * 每小时产出设备计量数据
-     */
-    @Scheduled(cron = "0 0 0/1 * * ?")
-    public void baMeterHourProd() {
-        int cnt = baCtlHandler.meterHourProd();
-        log.debug("产出室内能耗设备计量数据: {} 条", cnt);
-    }
-}

+ 0 - 0
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/config/AnalysisConfig.java → ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/config/AnalysisConfig.java


+ 0 - 0
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/config/DateAttrConfig.java → ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/config/DateAttrConfig.java


+ 0 - 0
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/config/WeatherConfig.java → ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/config/WeatherConfig.java


+ 264 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/controller/TestController.java

@@ -0,0 +1,264 @@
+/*
+ * 文 件 名:  GeekOpenCbController
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/2/27
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.ems.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.huashe.common.exception.BusinessException;
+import com.ruoyi.ems.config.BaCtlConfig;
+import com.ruoyi.ems.core.BaCtlEnergyTemplate;
+import com.ruoyi.ems.handle.BaCtlHandler;
+import com.ruoyi.ems.handle.GeekOpenCbHandler;
+import com.ruoyi.ems.handle.Keka86BsHandler;
+import com.ruoyi.ems.model.CallData;
+import com.ruoyi.ems.model.CallResponse;
+import com.ruoyi.ems.model.ModbusCommand;
+import com.ruoyi.ems.model.idenergy.CodesValReq;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * Test
+ * <功能详细描述>
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/2/27]
+ * @see [相关类/方法]
+ * @since [产品/模块版本]
+ */
+@RestController
+@CrossOrigin(allowedHeaders = "*", allowCredentials = "false")
+@RequestMapping("/test")
+@Api(value = "TestController", description = "测试Api")
+public class TestController {
+    /**
+     * 日志
+     */
+    private static final Logger log = LoggerFactory.getLogger(TestController.class);
+
+    @Qualifier("geekOpenCbHandler")
+    @Resource
+    private GeekOpenCbHandler geekOpenCbHandler;
+
+    @Qualifier("baCtlHandler")
+    @Resource
+    private BaCtlHandler baCtlHandler;
+
+    @Qualifier("keka86BsHandler")
+    @Resource
+    private Keka86BsHandler keka86BsHandler;
+
+    @Resource
+    private BaCtlConfig baCtlConfig;
+
+    /**
+     * msgHandle 消息处理
+     *
+     * @return 数据列表
+     */
+    @RequestMapping(value = "/msgHandle", method = RequestMethod.POST)
+    @ApiOperation(value = "/msgHandle", notes = "mqtt消息处理")
+    @ApiResponses({ @ApiResponse(code = 200, message = "success"),
+        @ApiResponse(code = 400, message = "{code:****,message:'fail'}")
+    })
+    public CallResponse<Void> msgHandle(@RequestParam(name = "deviceCode") String deviceCode,
+        @RequestBody String jsonObject) {
+        CallResponse<Void> res = null;
+
+        try {
+            geekOpenCbHandler.msgHandle(deviceCode, jsonObject);
+            res = new CallResponse<>(0, "success");
+        }
+        catch (BusinessException e) {
+            res = new CallResponse<>(500, e.getMessage());
+        }
+        catch (Exception e) {
+            log.error("geekOpenCbAbilityCall fail!", e);
+            res = new CallResponse<>(501, "内部错误:" + e.getMessage());
+        }
+
+        return res;
+    }
+
+    /**
+     * 小时产出
+     *
+     * @return 数据列表
+     */
+    @RequestMapping(value = "/collect", method = RequestMethod.GET)
+    @ApiOperation(value = "/collect", notes = "采集触发")
+    @ApiResponses({ @ApiResponse(code = 200, message = "success"),
+        @ApiResponse(code = 400, message = "{code:****,message:'fail'}")
+    })
+    public CallResponse<Void> collect() {
+        CallResponse<Void> res = null;
+
+        try {
+            baCtlHandler.meterCollect();
+            res = new CallResponse<>(0, "success");
+        }
+        catch (BusinessException e) {
+            res = new CallResponse<>(500, e.getMessage());
+        }
+        catch (Exception e) {
+            log.error("collect fail!", e);
+            res = new CallResponse<>(501, "内部错误:" + e.getMessage());
+        }
+
+        return res;
+    }
+
+    /**
+     * 小时产出
+     *
+     * @return 数据列表
+     */
+    @RequestMapping(value = "/meterHourProd", method = RequestMethod.GET)
+    @ApiOperation(value = "/meterHourProd", notes = "小时计量触发")
+    @ApiResponses({ @ApiResponse(code = 200, message = "success"),
+        @ApiResponse(code = 400, message = "{code:****,message:'fail'}")
+    })
+    public CallResponse<Void> meterHourProd() {
+        CallResponse<Void> res = null;
+
+        try {
+            baCtlHandler.meterHourProd();
+            res = new CallResponse<>(0, "success");
+        }
+        catch (BusinessException e) {
+            res = new CallResponse<>(500, e.getMessage());
+        }
+        catch (Exception e) {
+            log.error("meterHourProd fail!", e);
+            res = new CallResponse<>(501, "内部错误:" + e.getMessage());
+        }
+
+        return res;
+    }
+
+    /**
+     * 小时产出
+     *
+     * @return 数据列表
+     */
+    @RequestMapping(value = "/baGetCodesVal", method = RequestMethod.GET)
+    @ApiOperation(value = "/baGetCodesVal", notes = "BA采集")
+    @ApiResponses({ @ApiResponse(code = 200, message = "success"),
+        @ApiResponse(code = 400, message = "{code:****,message:'fail'}")
+    })
+    public String baGetCodesVal(@RequestParam("pointId") String pointId) {
+        String ret = null;
+
+        try {
+            BaCtlEnergyTemplate template = new BaCtlEnergyTemplate(baCtlConfig.getUrl());
+            CallData<String> callData = template.getCodesVal(new CodesValReq(StringUtils.split(pointId, ",")));
+            ret = callData.getResPayload();
+        }
+        catch (Exception e) {
+            log.error("baGetCodesVal fail!", e);
+            ret = JSON.toJSONString(new CallResponse<>(500, e.getMessage()));
+        }
+
+        return ret;
+    }
+
+    /**
+     * 小时产出
+     *
+     * @return 数据列表
+     */
+    @RequestMapping(value = "/baXfCollect", method = RequestMethod.GET)
+    @ApiOperation(value = "/baXfCollect", notes = "BA新风采集")
+    @ApiResponses({ @ApiResponse(code = 200, message = "success"),
+        @ApiResponse(code = 400, message = "{code:****,message:'fail'}")
+    })
+    public String xfCollect() {
+        String ret = null;
+
+        try {
+            baCtlHandler.wtCollect();
+            baCtlHandler.wpCollect();
+            baCtlHandler.lightCollect();
+        }
+        catch (Exception e) {
+            log.error("xfCollect fail!", e);
+            ret = JSON.toJSONString(new CallResponse<>(500, e.getMessage()));
+        }
+
+        return ret;
+    }
+
+    /**
+     * keka86BsSet
+     *
+     * @return 数据列表
+     */
+    @RequestMapping(value = "/keka86BsSet", method = RequestMethod.GET)
+    @ApiOperation(value = "/keka86BsSet", notes = "keka86BsSet")
+    @ApiResponses({ @ApiResponse(code = 200, message = "success"),
+        @ApiResponse(code = 400, message = "{code:****,message:'fail'}")
+    })
+    public String keka86BsSet(@RequestParam("gatewayId") String gatewayId, @RequestParam("lightId") Integer lightId,
+        @RequestParam("state") Integer state) {
+        String ret = null;
+
+        try {
+            ModbusCommand payload = keka86BsHandler.buildControlCommand(lightId, state);
+            keka86BsHandler.sendMqttHex("/sc/dtu/ctl/" + gatewayId, payload);
+            ret = JSON.toJSONString(new CallResponse<>(0, "success"));
+        }
+        catch (Exception e) {
+            log.error("keka86BsSet fail!", e);
+            ret = JSON.toJSONString(new CallResponse<>(500, e.getMessage()));
+        }
+
+        return ret;
+    }
+
+    /**
+     * keka86BsSet
+     *
+     * @return 数据列表
+     */
+    @RequestMapping(value = "/keka86BsGet", method = RequestMethod.GET)
+    @ApiOperation(value = "/keka86BsGet", notes = "keka86BsGet")
+    @ApiResponses({ @ApiResponse(code = 200, message = "success"),
+        @ApiResponse(code = 400, message = "{code:****,message:'fail'}")
+    })
+    public String keka86BsGet(@RequestParam("gatewayId") String gatewayId, @RequestParam("lightId") Integer lightId) {
+        String ret = null;
+
+        try {
+            ModbusCommand payload = keka86BsHandler.buildReadCommand(lightId);
+            keka86BsHandler.sendMqttHex("/sc/dtu/ctl/" + gatewayId, payload);
+            ret = JSON.toJSONString(new CallResponse<>(0, "success"));
+        }
+        catch (Exception e) {
+            log.error("keka86BsGet fail!", e);
+            ret = JSON.toJSONString(new CallResponse<>(500, e.getMessage()));
+        }
+
+        return ret;
+    }
+}

+ 34 - 0
ems/ems-cloud/ems-dev-adapter/src/main/resources/application-local.yml

@@ -54,6 +54,40 @@ mqtt:
       queueCapacity: 2000
       namePrefix: 'mqttHandle-'
 
+general-data:
+  api-key: 4988803c28e1461b999054255a0b311fga37G3RPI1Bc2uR1
+  server: 101.133.108.113:9000
+  weather:
+    adcode: 321200
+    rt:
+      cron: 0 0 0/1 * * ?
+      api-addr: http://${general-data.server}/general-data/weather/getWeather
+    forecast:
+      cron: 0 15 8 * * ?
+      api-addr: http://${general-data.server}/general-data/weather/getWeatherForecast
+  date:
+    cron: 0 0 12 1 * ?
+    api-addr: http://${general-data.server}/general-data/date/getAttrs
+
+analysis-task:
+  eco-analysis:
+    area-codes: 321283124S3001,321283124S3002
+    cron: 0 0 3 * * ?
+  pv-forecast:
+    area-codes: 321283124S3001,321283124S3002,321283124S3003
+    forecastDays: 5
+    cron: 0 30 8 * * ?
+  elec-consume-forecast:
+    cron: 0 30 8 * * ?
+    forecastDays: 5
+    area-codes: 321283124S3001,321283124S3002
+  carbon-calculation:
+    cron: 0 0 2 * * ?
+    area-codes: 321283124S3001,321283124S3002
+  carbon-forecast:
+    cron: 0 10 2 1 * ?
+    area-codes: 321283124S3001,321283124S3002
+
 adapter:
   ems:
     url: http://127.0.0.1:9202

+ 34 - 0
ems/ems-cloud/ems-dev-adapter/src/main/resources/application-prod-ct.yml

@@ -54,6 +54,40 @@ mqtt:
       queueCapacity: 2000
       namePrefix: 'mqttHandle-'
 
+general-data:
+  api-key: 4988803c28e1461b999054255a0b311fga37G3RPI1Bc2uR1
+  server: 101.133.108.113:9000
+  weather:
+    adcode: 321200
+    rt:
+      cron: 0 0 0/1 * * ?
+      api-addr: http://${general-data.server}/general-data/weather/getWeather
+    forecast:
+      cron: 0 15 8 * * ?
+      api-addr: http://${general-data.server}/general-data/weather/getWeatherForecast
+  date:
+    cron: 0 0 12 1 * ?
+    api-addr: http://${general-data.server}/general-data/date/getAttrs
+
+analysis-task:
+  eco-analysis:
+    area-codes: 321283124S3001,321283124S3002
+    cron: 0 0 3 * * ?
+  pv-forecast:
+    area-codes: 321283124S3001,321283124S3002,321283124S3003
+    forecastDays: 5
+    cron: 0 30 8 * * ?
+  elec-consume-forecast:
+    cron: 0 30 8 * * ?
+    forecastDays: 5
+    area-codes: 321283124S3001,321283124S3002
+  carbon-calculation:
+    cron: 0 0 2 * * ?
+    area-codes: 321283124S3001,321283124S3002
+  carbon-forecast:
+    cron: 0 10 2 1 * ?
+    area-codes: 321283124S3001,321283124S3002
+
 adapter:
   ems:
     url: http://172.17.60.27:9202

+ 31 - 34
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/CaMeterDController.java

@@ -1,18 +1,10 @@
 package com.ruoyi.ems.controller;
 
 import java.util.List;
-
 import javax.servlet.http.HttpServletResponse;
 
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import com.huashe.common.domain.AjaxResult;
 import com.ruoyi.common.core.utils.poi.ExcelUtil;
@@ -25,68 +17,79 @@ import com.ruoyi.ems.domain.CaMeterD;
 import com.ruoyi.ems.service.ICaMeterDService;
 
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
 
 /**
  * 碳计量日Controller
- *
- * @author ruoyi
- * @date 2024-08-12
  */
 @RestController
 @RequestMapping("/caMeterD")
 @Api(value = "CaMeterDController", description = "碳计量日数据访问接口")
 public class CaMeterDController extends BaseController {
+
     @Autowired
     private ICaMeterDService caMeterDService;
 
     /**
-     * 查询碳计量列表
+     * 查询碳计量列表(支持按日/月/年,分页)
      */
     @RequiresPermissions("ca-analysis:emission:list")
     @GetMapping("/list")
-    public TableDataInfo list(CaMeterD caMeterD) {
+    @ApiOperation("查询碳计量列表(支持按日/月/年)")
+    public TableDataInfo list(
+        CaMeterD caMeterD,
+        @ApiParam(value = "时间类型:day-按日, month-按月, year-按年", defaultValue = "year")
+        @RequestParam(value = "timeType", required = false, defaultValue = "year") String timeType) {
         startPage();
-        List<CaMeterD> list = caMeterDService.selectCaMeterDList(caMeterD);
+        List<CaMeterD> list = caMeterDService.selectCaMeterDListByTimeType(caMeterD, timeType);
         return getDataTable(list);
     }
 
+    /**
+     * 查询全部数据(不分页,用于统计)
+     */
     @RequiresPermissions("ca-analysis:emission:list")
     @GetMapping("/list/all")
-    public AjaxResult listAll(CaMeterD caMeterD) {
-        List<CaMeterD> list = caMeterDService.selectSumCaMeterDList(caMeterD);
+    @ApiOperation("查询碳计量全部数据(用于统计,支持按日/月/年)")
+    public AjaxResult listAll(
+        CaMeterD caMeterD,
+        @ApiParam(value = "时间类型:day-按日, month-按月, year-按年", defaultValue = "year")
+        @RequestParam(value = "timeType", required = false, defaultValue = "year") String timeType) {
+        List<CaMeterD> list = caMeterDService.selectSumCaMeterDListByTimeType(caMeterD, timeType);
         return success(list);
     }
 
+    /**
+     * 查询平均值
+     */
     @RequiresPermissions("ca-analysis:emission:list")
     @GetMapping("/area/avg/ca")
+    @ApiOperation("查询碳计量平均值")
     public AjaxResult qryAvgCa(CaMeterD caMeterD) {
         List<CaMeterD> list = caMeterDService.selectAvgCaMeterDList(caMeterD);
         return success(list);
     }
+
     /**
-     * 导出碳计量列表
+     * 导出碳计量列表
      */
     @RequiresPermissions("ca-analysis:emission:export")
     @Log(title = "碳计量日", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
-    public void export(HttpServletResponse response, CaMeterD caMeterD) {
-        List<CaMeterD> list = caMeterDService.selectCaMeterDList(caMeterD);
+    public void export(HttpServletResponse response, CaMeterD caMeterD,
+        @RequestParam(value = "timeType", required = false, defaultValue = "year") String timeType) {
+        List<CaMeterD> list = caMeterDService.selectCaMeterDListByTimeType(caMeterD, timeType);
         ExcelUtil<CaMeterD> util = new ExcelUtil<CaMeterD>(CaMeterD.class);
-        util.exportExcel(response, list, "碳计量数据");
+        util.exportExcel(response, list, "碳计量数据");
     }
 
-    /**
-     * 获取碳计量日详细信息
-     */
     @RequiresPermissions("ca-analysis:emission:query")
     @GetMapping(value = "/{id}")
     public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(caMeterDService.selectCaMeterDById(id));
     }
 
-    /**
-     * 新增碳计量日
-     */
     @RequiresPermissions("ca-analysis:emission:add")
     @Log(title = "碳计量日", businessType = BusinessType.INSERT)
     @PostMapping
@@ -94,9 +97,6 @@ public class CaMeterDController extends BaseController {
         return toAjax(caMeterDService.insertCaMeterD(caMeterD));
     }
 
-    /**
-     * 修改碳计量日
-     */
     @RequiresPermissions("ca-analysis:emission:edit")
     @Log(title = "碳计量日", businessType = BusinessType.UPDATE)
     @PutMapping
@@ -104,13 +104,10 @@ public class CaMeterDController extends BaseController {
         return toAjax(caMeterDService.updateCaMeterD(caMeterD));
     }
 
-    /**
-     * 删除碳计量日
-     */
     @RequiresPermissions("ca-analysis:emission:remove")
     @Log(title = "碳计量日", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(caMeterDService.deleteCaMeterDByIds(ids));
     }
-}
+}

+ 43 - 9
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/ElecConsumeForecastController.java

@@ -10,6 +10,7 @@ import com.ruoyi.common.security.annotation.RequiresPermissions;
 import com.ruoyi.ems.domain.ElecConsumeForecast;
 import com.ruoyi.ems.service.IElecConsumeForecastService;
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -22,6 +23,7 @@ import org.springframework.web.bind.annotation.RestController;
 
 import javax.servlet.http.HttpServletResponse;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 电力消耗预测Controller
@@ -31,8 +33,9 @@ import java.util.List;
  */
 @RestController
 @RequestMapping("/forecastConsume")
-@Api(value = "ElecExpendForecastController", description = "电力消耗预测数据访问接口")
+@Api(value = "ElecConsumeForecastController", description = "电力消耗预测数据访问接口")
 public class ElecConsumeForecastController extends BaseController {
+
     @Autowired
     private IElecConsumeForecastService forecastService;
 
@@ -41,6 +44,7 @@ public class ElecConsumeForecastController extends BaseController {
      */
     @RequiresPermissions("prediction:consume:list")
     @GetMapping("/list")
+    @ApiOperation("查询电力消耗预测列表")
     public TableDataInfo list(ElecConsumeForecast forecast) {
         startPage();
         List<ElecConsumeForecast> list = forecastService.selectForecastList(forecast);
@@ -53,8 +57,9 @@ public class ElecConsumeForecastController extends BaseController {
     @RequiresPermissions("prediction:consume:export")
     @Log(title = "电力消耗预测", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
-    public void export(HttpServletResponse response, ElecConsumeForecast elecExpendForecast) {
-        List<ElecConsumeForecast> list = forecastService.selectForecastList(elecExpendForecast);
+    @ApiOperation("导出电力消耗预测列表")
+    public void export(HttpServletResponse response, ElecConsumeForecast forecast) {
+        List<ElecConsumeForecast> list = forecastService.selectForecastList(forecast);
         ExcelUtil<ElecConsumeForecast> util = new ExcelUtil<ElecConsumeForecast>(ElecConsumeForecast.class);
         util.exportExcel(response, list, "电力消耗预测数据");
     }
@@ -64,6 +69,7 @@ public class ElecConsumeForecastController extends BaseController {
      */
     @RequiresPermissions("prediction:consume:query")
     @GetMapping(value = "/{id}")
+    @ApiOperation("获取电力消耗预测详细信息")
     public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(forecastService.selectForecastById(id));
     }
@@ -74,8 +80,9 @@ public class ElecConsumeForecastController extends BaseController {
     @RequiresPermissions("prediction:consume:add")
     @Log(title = "电力消耗预测", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody ElecConsumeForecast elecExpendForecast) {
-        return toAjax(forecastService.insertForecast(elecExpendForecast));
+    @ApiOperation("新增电力消耗预测")
+    public AjaxResult add(@RequestBody ElecConsumeForecast forecast) {
+        return toAjax(forecastService.insertForecast(forecast));
     }
 
     /**
@@ -84,8 +91,9 @@ public class ElecConsumeForecastController extends BaseController {
     @RequiresPermissions("prediction:consume:edit")
     @Log(title = "电力消耗预测", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody ElecConsumeForecast elecExpendForecast) {
-        return toAjax(forecastService.updateForecast(elecExpendForecast));
+    @ApiOperation("修改电力消耗预测")
+    public AjaxResult edit(@RequestBody ElecConsumeForecast forecast) {
+        return toAjax(forecastService.updateForecast(forecast));
     }
 
     /**
@@ -94,12 +102,38 @@ public class ElecConsumeForecastController extends BaseController {
     @RequiresPermissions("prediction:consume:remove")
     @Log(title = "电力消耗预测", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
+    @ApiOperation("删除电力消耗预测")
     public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(forecastService.deleteForecastByIds(ids));
     }
 
+    /**
+     * 查询每日汇总趋势数据(支持层级汇总)
+     * 当选择父节点时,自动汇总所有子节点数据
+     */
     @GetMapping(value = "/cal/dateRange")
-    public AjaxResult calcElecExpendForecastDateRange(ElecConsumeForecast param) {
+    @ApiOperation("查询每日汇总趋势数据")
+    public AjaxResult calcForecastDateRange(ElecConsumeForecast param) {
+        return success(forecastService.calcForecastDateRange(param));
+    }
+
+    /**
+     * 获取预测汇总统计(支持层级汇总)
+     * 返回:总用电量、日均用电量、子项数量、环比趋势等
+     */
+    @GetMapping(value = "/summary")
+    @ApiOperation("获取预测汇总统计")
+    public AjaxResult getSummary(ElecConsumeForecast param) {
+        return success(forecastService.selectForecastSummary(param));
+    }
+
+    /**
+     * 获取每日趋势数据(支持层级汇总)
+     * 用于图表展示
+     */
+    @GetMapping(value = "/dailyTrend")
+    @ApiOperation("获取每日趋势数据")
+    public AjaxResult getDailyTrend(ElecConsumeForecast param) {
         return success(forecastService.calcForecastDateRange(param));
     }
-}
+}

+ 0 - 34
ems/ems-cloud/ems-server/src/main/resources/application-local.yml

@@ -43,40 +43,6 @@ spring:
           # url:
           # driver-class-name:
 
-general-data:
-  api-key: 4988803c28e1461b999054255a0b311fga37G3RPI1Bc2uR1
-  server: 101.133.108.113:9000
-  weather:
-    adcode: 321200
-    rt:
-      cron: 0 0 0/1 * * ?
-      api-addr: http://${general-data.server}/general-data/weather/getWeather
-    forecast:
-      cron: 0 15 8 * * ?
-      api-addr: http://${general-data.server}/general-data/weather/getWeatherForecast
-  date:
-    cron: 0 0 12 1 * ?
-    api-addr: http://${general-data.server}/general-data/date/getAttrs
-
-analysis-task:
-  eco-analysis:
-    area-codes: 321283124S3001,321283124S3002
-    cron: 0 0 3 * * ?
-  pv-forecast:
-    area-codes: 321283124S3001,321283124S3002,321283124S3003
-    forecastDays: 5
-    cron: 0 30 8 * * ?
-  elec-consume-forecast:
-    cron: 0 0 2 * * ?
-    forecastDays: 5
-    area-codes: 321283124S3001,321283124S3002
-  carbon-calculation:
-    cron: 0 0 2 * * ?
-    area-codes: 321283124S3001,321283124S3002
-  carbon-forecast:
-    cron: 0 10 2 1 * ?
-    area-codes: 321283124S3001,321283124S3002
-
 # mybatis配置
 mybatis:
   # 搜索指定包别名

+ 0 - 34
ems/ems-cloud/ems-server/src/main/resources/application-prod-ct.yml

@@ -43,40 +43,6 @@ spring:
           username: ems_ct
           password: Cxndix7rd2EdtrAd
 
-general-data:
-  api-key: 4988803c28e1461b999054255a0b311fga37G3RPI1Bc2uR1
-  server: 101.133.108.113:9000
-  weather:
-    adcode: 321200
-    rt:
-      cron: 0 0 0/1 * * ?
-      api-addr: http://${general-data.server}/general-data/weather/getWeather
-    forecast:
-      cron: 0 15 8 * * ?
-      api-addr: http://${general-data.server}/general-data/weather/getWeatherForecast
-  date:
-    cron: 0 0 12 1 * ?
-    api-addr: http://${general-data.server}/general-data/date/getAttrs
-
-analysis-task:
-  eco-analysis:
-    area-codes: 321283124S3001,321283124S3002
-    cron: 0 0 3 * * ?
-  pv-forecast:
-    area-codes: 321283124S3001,321283124S3002,321283124S3003
-    forecastDays: 5
-    cron: 0 30 8 * * ?
-  elec-consume-forecast:
-    cron: 0 0 2 * * ?
-    forecastDays: 5
-    area-codes: 321283124S3001,321283124S3002
-  carbon-calculation:
-    cron: 0 0 2 * * ?
-    area-codes: 321283124S3001,321283124S3002
-  carbon-forecast:
-    cron: 0 10 2 1 * ?
-    area-codes: 321283124S3001,321283124S3002
-
 # mybatis配置
 mybatis:
   # 搜索指定包别名

+ 26 - 24
ems/ems-core/src/main/java/com/ruoyi/ems/domain/CaMeterD.java

@@ -18,51 +18,53 @@ import java.util.Date;
  */
 @EqualsAndHashCode(callSuper = true)
 @Data
-
 public class CaMeterD extends BaseEntity {
     private static final long serialVersionUID = 1L;
 
-    /**
-     * 序号
-     */
+    /** 序号 */
     private Long id;
 
-    /**
-     * 园区代码
-     */
+    /** 园区代码 */
     @Excel(name = "园区代码")
     private String areaCode;
 
+    /** 园区名称 */
     @Excel(name = "园区名称")
     private String areaName;
 
-    /**
-     * 日期
-     */
+    /** 日期 */
     @JsonFormat(pattern = "yyyy-MM-dd")
     @Excel(name = "日期", width = 30, dateFormat = "yyyy-MM-dd")
     private Date date;
 
-    /**
-     * 碳排放量 (千克)
-     */
-    @Excel(name = "碳排放量 ", readConverterExp = "千=克")
+    /** 碳排放量(千克) */
+    @Excel(name = "碳排放量", readConverterExp = "千=克")
     private Double caEmissionQuantity;
 
-    /**
-     * 碳汇量(千克)
-     */
+    /** 碳汇量(千克) */
     @Excel(name = "碳汇量", readConverterExp = "千=克")
     private Double caSinkQuantity;
 
+    /** 年份(用于按年/月聚合查询) */
+    private Integer year;
+
+    /** 月份(用于按月聚合查询) */
+    private Integer month;
+
+    /** 时间维度类型:day-按日, month-按月, year-按年 */
+    private String timeType;
+
     @Override
     public String toString() {
         return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
-                .append("id", getId())
-                .append("areaCode", getAreaCode())
-                .append("date", getDate())
-                .append("caEmissionQuantity", getCaEmissionQuantity())
-                .append("caSinkQuantity", getCaSinkQuantity())
-                .toString();
+            .append("id", getId())
+            .append("areaCode", getAreaCode())
+            .append("areaName", getAreaName())
+            .append("date", getDate())
+            .append("caEmissionQuantity", getCaEmissionQuantity())
+            .append("caSinkQuantity", getCaSinkQuantity())
+            .append("year", getYear())
+            .append("month", getMonth())
+            .toString();
     }
-}
+}

+ 17 - 43
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/CaMeterDMapper.java

@@ -1,70 +1,44 @@
 package com.ruoyi.ems.mapper;
 
 import java.util.List;
-
 import org.apache.ibatis.annotations.Param;
-
 import com.ruoyi.ems.domain.CaMeterD;
 
 /**
  * 碳计量日Mapper接口
- *
- * @author ruoyi
- * @date 2024-08-12
  */
 public interface CaMeterDMapper {
-    /**
-     * 查询碳计量日
-     *
-     * @param id 碳计量日主键
-     * @return 碳计量日
-     */
+
     CaMeterD selectCaMeterDById(Long id);
 
-    /**
-     * 查询碳计量日列表
-     *
-     * @param caMeterD 碳计量日
-     * @return 碳计量日集合
-     */
+    /** 按日查询列表 */
     List<CaMeterD> selectCaMeterDList(CaMeterD caMeterD);
 
+    /** 按月聚合查询列表 */
+    List<CaMeterD> selectCaMeterDListByMonth(CaMeterD caMeterD);
+
+    /** 按年聚合查询列表 */
+    List<CaMeterD> selectCaMeterDListByYear(CaMeterD caMeterD);
+
+    /** 按日汇总(统计用) */
     List<CaMeterD> selectSumCaMeterDList(CaMeterD caMeterD);
 
-    List<CaMeterD> selectAvgCaMeterDList(CaMeterD caMeterD);
+    /** 按月汇总(统计用) */
+    List<CaMeterD> selectSumCaMeterDListByMonth(CaMeterD caMeterD);
 
+    /** 按年汇总(统计用) */
+    List<CaMeterD> selectSumCaMeterDListByYear(CaMeterD caMeterD);
+
+    /** 平均值查询 */
+    List<CaMeterD> selectAvgCaMeterDList(CaMeterD caMeterD);
 
-    /**
-     * 新增碳计量日
-     *
-     * @param caMeterD 碳计量日
-     * @return 结果
-     */
     int insertCaMeterD(CaMeterD caMeterD);
 
-    /**
-     * 修改碳计量日
-     *
-     * @param caMeterD 碳计量日
-     * @return 结果
-     */
     int updateCaMeterD(CaMeterD caMeterD);
 
-    /**
-     * 删除碳计量日
-     *
-     * @param id 碳计量日主键
-     * @return 结果
-     */
     int deleteCaMeterDById(Long id);
 
     int deleteCaMeterDByArea(@Param("areaCode") String areaCode, @Param("date") String date);
 
-    /**
-     * 批量删除碳计量日
-     *
-     * @param ids 需要删除的数据主键集合
-     * @return 结果
-     */
     int deleteCaMeterDByIds(Long[] ids);
-}
+}

+ 36 - 8
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/ElecConsumeForecastMapper.java

@@ -4,6 +4,7 @@ import com.ruoyi.ems.domain.ElecConsumeForecast;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 电力消耗预测Mapper接口
@@ -12,6 +13,7 @@ import java.util.List;
  * @date 2024-08-12
  */
 public interface ElecConsumeForecastMapper {
+
     /**
      * 查询电力消耗预测
      *
@@ -21,9 +23,10 @@ public interface ElecConsumeForecastMapper {
     ElecConsumeForecast selectForecastById(Long id);
 
     /**
-     * 查询电力消耗预测列表
+     * 查询电力消耗预测列表(支持层级汇总)
+     * 当选择父节点时,自动包含所有子节点数据
      *
-     * @param forecast 电力消耗预测
+     * @param forecast 电力消耗预测查询条件
      * @return 电力消耗预测集合
      */
     List<ElecConsumeForecast> selectForecastList(ElecConsumeForecast forecast);
@@ -37,9 +40,9 @@ public interface ElecConsumeForecastMapper {
     int insertForecast(ElecConsumeForecast forecast);
 
     /**
-     * 新增电力产能预测
+     * 批量新增电力消耗预测
      *
-     * @param list 电力产能预测
+     * @param list 电力消耗预测列表
      * @return 结果
      */
     int insertBatch(List<ElecConsumeForecast> list);
@@ -61,7 +64,7 @@ public interface ElecConsumeForecastMapper {
     int deleteForecastById(Long id);
 
     /**
-     * 删除电力消耗预测
+     * 按条件删除电力消耗预测
      *
      * @param areaCode  区域代码
      * @param objType   对象类型
@@ -70,8 +73,11 @@ public interface ElecConsumeForecastMapper {
      * @param endTime   结束时间
      * @return 结果
      */
-    int deleteByCondition(@Param("areaCode") String areaCode, @Param("objType") int objType,
-        @Param("objCode") String objCode, @Param("startTime") String startTime, @Param("endTime") String endTime);
+    int deleteByCondition(@Param("areaCode") String areaCode,
+        @Param("objType") int objType,
+        @Param("objCode") String objCode,
+        @Param("startTime") String startTime,
+        @Param("endTime") String endTime);
 
     /**
      * 批量删除电力消耗预测
@@ -81,5 +87,27 @@ public interface ElecConsumeForecastMapper {
      */
     int deleteForecastByIds(Long[] ids);
 
+    /**
+     * 查询每日汇总趋势数据(支持层级汇总)
+     * 当选择父节点时,自动汇总所有子节点数据
+     *
+     * @param forecast 查询条件
+     * @return 每日汇总数据列表,包含date和elecUseQuantity
+     */
     List<ElecConsumeForecast> calcForecastDateRange(ElecConsumeForecast forecast);
-}
+
+    /**
+     * 查询预测汇总统计(支持层级汇总)
+     * 返回:
+     * - totalElec: 总用电量
+     * - avgElec: 平均用电量
+     * - itemCount: 子项数量(去重)
+     * - dayCount: 天数(去重)
+     * - startDate: 最小日期
+     * - endDate: 最大日期
+     *
+     * @param forecast 查询条件
+     * @return 汇总统计结果Map
+     */
+    Map<String, Object> selectForecastSummary(ElecConsumeForecast forecast);
+}

+ 16 - 41
ems/ems-core/src/main/java/com/ruoyi/ems/service/ICaMeterDService.java

@@ -1,65 +1,40 @@
 package com.ruoyi.ems.service;
 
 import com.ruoyi.ems.domain.CaMeterD;
-
 import java.util.List;
 
 /**
  * 碳计量日Service接口
- *
- * @author ruoyi
- * @date 2024-08-12
  */
 public interface ICaMeterDService {
-    /**
-     * 查询碳计量日
-     *
-     * @param id 碳计量日主键
-     * @return 碳计量日
-     */
+
     CaMeterD selectCaMeterDById(Long id);
 
-    /**
-     * 查询碳计量日列表
-     *
-     * @param caMeterD 碳计量日
-     * @return 碳计量日集合
-     */
     List<CaMeterD> selectCaMeterDList(CaMeterD caMeterD);
 
+    List<CaMeterD> selectCaMeterDListByMonth(CaMeterD caMeterD);
+
+    List<CaMeterD> selectCaMeterDListByYear(CaMeterD caMeterD);
+
+    /** 根据时间类型查询列表(分页) */
+    List<CaMeterD> selectCaMeterDListByTimeType(CaMeterD caMeterD, String timeType);
+
     List<CaMeterD> selectSumCaMeterDList(CaMeterD caMeterD);
 
+    List<CaMeterD> selectSumCaMeterDListByMonth(CaMeterD caMeterD);
+
+    List<CaMeterD> selectSumCaMeterDListByYear(CaMeterD caMeterD);
+
+    /** 根据时间类型查询汇总(统计用) */
+    List<CaMeterD> selectSumCaMeterDListByTimeType(CaMeterD caMeterD, String timeType);
+
     List<CaMeterD> selectAvgCaMeterDList(CaMeterD caMeterD);
 
-    /**
-     * 新增碳计量日
-     *
-     * @param caMeterD 碳计量日
-     * @return 结果
-     */
     int insertCaMeterD(CaMeterD caMeterD);
 
-    /**
-     * 修改碳计量日
-     *
-     * @param caMeterD 碳计量日
-     * @return 结果
-     */
     int updateCaMeterD(CaMeterD caMeterD);
 
-    /**
-     * 批量删除碳计量日
-     *
-     * @param ids 需要删除的碳计量日主键集合
-     * @return 结果
-     */
     int deleteCaMeterDByIds(Long[] ids);
 
-    /**
-     * 删除碳计量日信息
-     *
-     * @param id 碳计量日主键
-     * @return 结果
-     */
     int deleteCaMeterDById(Long id);
-}
+}

+ 28 - 3
ems/ems-core/src/main/java/com/ruoyi/ems/service/IElecConsumeForecastService.java

@@ -3,6 +3,7 @@ package com.ruoyi.ems.service;
 import com.ruoyi.ems.domain.ElecConsumeForecast;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 电力消耗预测Service接口
@@ -11,6 +12,7 @@ import java.util.List;
  * @date 2024-08-12
  */
 public interface IElecConsumeForecastService {
+
     /**
      * 查询电力消耗预测
      *
@@ -20,9 +22,10 @@ public interface IElecConsumeForecastService {
     ElecConsumeForecast selectForecastById(Long id);
 
     /**
-     * 查询电力消耗预测列表
+     * 查询电力消耗预测列表(支持层级汇总)
+     * 当选择父节点时,自动包含所有子节点数据
      *
-     * @param forecast 电力消耗预测
+     * @param forecast 电力消耗预测查询条件
      * @return 电力消耗预测集合
      */
     List<ElecConsumeForecast> selectForecastList(ElecConsumeForecast forecast);
@@ -59,5 +62,27 @@ public interface IElecConsumeForecastService {
      */
     int deleteForecastById(Long id);
 
+    /**
+     * 查询每日汇总趋势数据(支持层级汇总)
+     * 当选择父节点时,自动汇总所有子节点数据
+     *
+     * @param forecast 查询条件
+     * @return 每日汇总数据列表
+     */
     List<ElecConsumeForecast> calcForecastDateRange(ElecConsumeForecast forecast);
-}
+
+    /**
+     * 获取预测汇总统计(支持层级汇总)
+     * 返回:
+     * - totalElec: 总用电量
+     * - avgElec: 日均用电量
+     * - itemCount: 子项数量
+     * - dayCount: 天数
+     * - dateRange: 日期范围
+     * - trend: 环比趋势(百分比)
+     *
+     * @param forecast 查询条件
+     * @return 汇总统计结果
+     */
+    Map<String, Object> selectForecastSummary(ElecConsumeForecast forecast);
+}

+ 59 - 41
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/CaMeterDServiceImpl.java

@@ -5,94 +5,112 @@ import com.ruoyi.ems.mapper.CaMeterDMapper;
 import com.ruoyi.ems.service.ICaMeterDService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-
 import java.util.List;
 
 /**
  * 碳计量日Service业务层处理
- *
- * @author ruoyi
- * @date 2024-08-12
  */
 @Service
 public class CaMeterDServiceImpl implements ICaMeterDService {
+
     @Autowired
     private CaMeterDMapper caMeterDMapper;
 
-    /**
-     * 查询碳计量日
-     *
-     * @param id 碳计量日主键
-     * @return 碳计量日
-     */
     @Override
     public CaMeterD selectCaMeterDById(Long id) {
         return caMeterDMapper.selectCaMeterDById(id);
     }
 
-    /**
-     * 查询碳计量日列表
-     *
-     * @param caMeterD 碳计量日
-     * @return 碳计量日
-     */
     @Override
     public List<CaMeterD> selectCaMeterDList(CaMeterD caMeterD) {
         return caMeterDMapper.selectCaMeterDList(caMeterD);
     }
 
     @Override
+    public List<CaMeterD> selectCaMeterDListByMonth(CaMeterD caMeterD) {
+        return caMeterDMapper.selectCaMeterDListByMonth(caMeterD);
+    }
+
+    @Override
+    public List<CaMeterD> selectCaMeterDListByYear(CaMeterD caMeterD) {
+        return caMeterDMapper.selectCaMeterDListByYear(caMeterD);
+    }
+
+    /**
+     * 根据时间类型查询列表(分页用)
+     */
+    @Override
+    public List<CaMeterD> selectCaMeterDListByTimeType(CaMeterD caMeterD, String timeType) {
+        if (timeType == null) {
+            timeType = "year";
+        }
+        switch (timeType) {
+            case "month":
+                return caMeterDMapper.selectCaMeterDListByMonth(caMeterD);
+            case "year":
+                return caMeterDMapper.selectCaMeterDListByYear(caMeterD);
+            case "day":
+            default:
+                return caMeterDMapper.selectCaMeterDList(caMeterD);
+        }
+    }
+
+    @Override
     public List<CaMeterD> selectSumCaMeterDList(CaMeterD caMeterD) {
         return caMeterDMapper.selectSumCaMeterDList(caMeterD);
     }
 
     @Override
-    public List<CaMeterD> selectAvgCaMeterDList(CaMeterD caMeterD) {
-        return caMeterDMapper.selectAvgCaMeterDList(caMeterD);
+    public List<CaMeterD> selectSumCaMeterDListByMonth(CaMeterD caMeterD) {
+        return caMeterDMapper.selectSumCaMeterDListByMonth(caMeterD);
     }
 
+    @Override
+    public List<CaMeterD> selectSumCaMeterDListByYear(CaMeterD caMeterD) {
+        return caMeterDMapper.selectSumCaMeterDListByYear(caMeterD);
+    }
 
     /**
-     * 新增碳计量日
-     *
-     * @param caMeterD 碳计量日
-     * @return 结果
+     * 根据时间类型查询汇总(统计用)
      */
     @Override
+    public List<CaMeterD> selectSumCaMeterDListByTimeType(CaMeterD caMeterD, String timeType) {
+        if (timeType == null) {
+            timeType = "year";
+        }
+        switch (timeType) {
+            case "month":
+                return caMeterDMapper.selectSumCaMeterDListByMonth(caMeterD);
+            case "year":
+                return caMeterDMapper.selectSumCaMeterDListByYear(caMeterD);
+            case "day":
+            default:
+                return caMeterDMapper.selectSumCaMeterDList(caMeterD);
+        }
+    }
+
+    @Override
+    public List<CaMeterD> selectAvgCaMeterDList(CaMeterD caMeterD) {
+        return caMeterDMapper.selectAvgCaMeterDList(caMeterD);
+    }
+
+    @Override
     public int insertCaMeterD(CaMeterD caMeterD) {
         return caMeterDMapper.insertCaMeterD(caMeterD);
     }
 
-    /**
-     * 修改碳计量日
-     *
-     * @param caMeterD 碳计量日
-     * @return 结果
-     */
     @Override
     public int updateCaMeterD(CaMeterD caMeterD) {
         return caMeterDMapper.updateCaMeterD(caMeterD);
     }
 
-    /**
-     * 批量删除碳计量日
-     *
-     * @param ids 需要删除的碳计量日主键
-     * @return 结果
-     */
     @Override
     public int deleteCaMeterDByIds(Long[] ids) {
         return caMeterDMapper.deleteCaMeterDByIds(ids);
     }
 
-    /**
-     * 删除碳计量日信息
-     *
-     * @param id 碳计量日主键
-     * @return 结果
-     */
     @Override
     public int deleteCaMeterDById(Long id) {
         return caMeterDMapper.deleteCaMeterDById(id);
     }
-}
+}

+ 212 - 2
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/ElecConsumeForecastServiceImpl.java

@@ -3,10 +3,17 @@ package com.ruoyi.ems.service.impl;
 import com.ruoyi.ems.domain.ElecConsumeForecast;
 import com.ruoyi.ems.mapper.ElecConsumeForecastMapper;
 import com.ruoyi.ems.service.IElecConsumeForecastService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 电力消耗预测Service业务层处理
@@ -16,9 +23,12 @@ import java.util.List;
  */
 @Service
 public class ElecConsumeForecastServiceImpl implements IElecConsumeForecastService {
+
     @Autowired
     private ElecConsumeForecastMapper forecastMapper;
 
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
     /**
      * 查询电力消耗预测
      *
@@ -31,13 +41,16 @@ public class ElecConsumeForecastServiceImpl implements IElecConsumeForecastServi
     }
 
     /**
-     * 查询电力消耗预测列表
+     * 查询电力消耗预测列表(支持层级汇总)
+     * 注意:预测数据只查询明天及之后的数据
      *
      * @param forecast 电力消耗预测
      * @return 电力消耗预测
      */
     @Override
     public List<ElecConsumeForecast> selectForecastList(ElecConsumeForecast forecast) {
+        // 确保预测数据从明天开始
+        adjustForecastDateRange(forecast);
         return forecastMapper.selectForecastList(forecast);
     }
 
@@ -85,8 +98,205 @@ public class ElecConsumeForecastServiceImpl implements IElecConsumeForecastServi
         return forecastMapper.deleteForecastById(id);
     }
 
+    /**
+     * 查询每日汇总趋势数据(支持层级汇总)
+     * 注意:只返回明天及之后的预测数据
+     */
     @Override
     public List<ElecConsumeForecast> calcForecastDateRange(ElecConsumeForecast forecast) {
+        // 确保预测数据从明天开始
+        adjustForecastDateRange(forecast);
         return forecastMapper.calcForecastDateRange(forecast);
     }
-}
+
+    /**
+     * 获取预测汇总统计(支持层级汇总)
+     * 包含环比趋势计算
+     * 注意:只统计明天及之后的预测数据
+     */
+    @Override
+    public Map<String, Object> selectForecastSummary(ElecConsumeForecast forecast) {
+        // 确保预测数据从明天开始
+        adjustForecastDateRange(forecast);
+
+        // 查询当前周期汇总数据
+        Map<String, Object> result = forecastMapper.selectForecastSummary(forecast);
+
+        if (result == null) {
+            result = new HashMap<>();
+            result.put("totalElec", 0.0);
+            result.put("avgElec", 0.0);
+            result.put("itemCount", 0);
+            result.put("dayCount", 0);
+            result.put("dateRange", "-");
+            result.put("trend", null);
+            return result;
+        }
+
+        // 处理日期范围显示(显示实际的预测周期)
+        String startDate = forecast.getStartRecTime();
+        String endDate = forecast.getEndRecTime();
+        if (StringUtils.isNotEmpty(startDate) && StringUtils.isNotEmpty(endDate)) {
+            result.put("dateRange", startDate + " ~ " + endDate);
+
+            // 计算实际天数
+            LocalDate start = LocalDate.parse(startDate, DATE_FORMATTER);
+            LocalDate end = LocalDate.parse(endDate, DATE_FORMATTER);
+            long days = ChronoUnit.DAYS.between(start, end) + 1;
+            result.put("dayCount", days);
+        }
+        else {
+            Object minDate = result.get("startDate");
+            Object maxDate = result.get("endDate");
+            if (minDate != null && maxDate != null) {
+                result.put("dateRange", minDate.toString() + " ~ " + maxDate.toString());
+            }
+            else {
+                result.put("dateRange", "-");
+            }
+        }
+
+        // 重新计算日均值(基于实际预测天数)
+        Object totalElecObj = result.get("totalElec");
+        Object dayCountObj = result.get("dayCount");
+
+        if (totalElecObj != null && dayCountObj != null) {
+            double totalElec = ((Number) totalElecObj).doubleValue();
+            long dayCount = ((Number) dayCountObj).longValue();
+            if (dayCount > 0) {
+                double avgElec = totalElec / dayCount;
+                result.put("avgElec", Math.round(avgElec * 100) / 100.0);
+            }
+        }
+
+        // 计算环比趋势(与上一周期对比)
+        Double trend = calculateTrend(forecast, result);
+        result.put("trend", trend);
+
+        return result;
+    }
+
+    /**
+     * 调整预测日期范围,确保从明天开始
+     * 预测数据不应包含今天及之前的历史数据
+     *
+     * @param forecast 查询条件
+     */
+    private void adjustForecastDateRange(ElecConsumeForecast forecast) {
+        // 计算明天的日期
+        LocalDate tomorrow = LocalDate.now().plusDays(1);
+        String tomorrowStr = tomorrow.format(DATE_FORMATTER);
+
+        String startRecTime = forecast.getStartRecTime();
+
+        // 如果没有设置开始日期,默认从明天开始
+        if (StringUtils.isEmpty(startRecTime)) {
+            forecast.setStartRecTime(tomorrowStr);
+        }
+        else {
+            // 如果设置的开始日期早于明天,强制调整为明天
+            try {
+                LocalDate startDate = LocalDate.parse(startRecTime, DATE_FORMATTER);
+                if (startDate.isBefore(tomorrow)) {
+                    forecast.setStartRecTime(tomorrowStr);
+                }
+            }
+            catch (Exception e) {
+                // 日期解析失败,使用明天
+                forecast.setStartRecTime(tomorrowStr);
+            }
+        }
+
+        // 如果没有设置结束日期,可以设置一个默认值(比如未来7天)
+        // 或者保持为空,让SQL查询所有未来数据
+        if (StringUtils.isEmpty(forecast.getEndRecTime())) {
+            // 默认查询未来7天
+            LocalDate defaultEnd = tomorrow.plusDays(6);
+            forecast.setEndRecTime(defaultEnd.format(DATE_FORMATTER));
+        }
+        else {
+            // 确保结束日期不早于开始日期
+            try {
+                LocalDate endDate = LocalDate.parse(forecast.getEndRecTime(), DATE_FORMATTER);
+                LocalDate startDate = LocalDate.parse(forecast.getStartRecTime(), DATE_FORMATTER);
+                if (endDate.isBefore(startDate)) {
+                    forecast.setEndRecTime(forecast.getStartRecTime());
+                }
+            }
+            catch (Exception e) {
+                // 忽略解析错误
+            }
+        }
+    }
+
+    /**
+     * 计算环比趋势
+     * 根据当前查询周期,计算上一个同等长度周期的数据,进行对比
+     * 注意:上一周期也需要是预测数据(即上一周期的"明天"数据)
+     *
+     * @param forecast      当前查询条件
+     * @param currentResult 当前周期的汇总结果
+     * @return 环比趋势百分比,正数表示上涨,负数表示下降
+     */
+    private Double calculateTrend(ElecConsumeForecast forecast, Map<String, Object> currentResult) {
+        try {
+            String startRecTime = forecast.getStartRecTime();
+            String endRecTime = forecast.getEndRecTime();
+
+            // 如果没有指定时间范围,无法计算环比
+            if (StringUtils.isEmpty(startRecTime) || StringUtils.isEmpty(endRecTime)) {
+                return null;
+            }
+
+            // 解析日期
+            LocalDate startDate = LocalDate.parse(startRecTime, DATE_FORMATTER);
+            LocalDate endDate = LocalDate.parse(endRecTime, DATE_FORMATTER);
+
+            // 计算当前周期天数
+            long dayDiff = ChronoUnit.DAYS.between(startDate, endDate);
+
+            // 计算上一周期的时间范围
+            LocalDate prevEndDate = startDate.minusDays(1);
+            LocalDate prevStartDate = prevEndDate.minusDays(dayDiff);
+
+            // 构建上一周期的查询条件
+            ElecConsumeForecast prevForecast = new ElecConsumeForecast();
+            BeanUtils.copyProperties(forecast, prevForecast);
+            prevForecast.setStartRecTime(prevStartDate.format(DATE_FORMATTER));
+            prevForecast.setEndRecTime(prevEndDate.format(DATE_FORMATTER));
+
+            // 查询上一周期的汇总数据(不调整日期范围,因为这是历史对比)
+            Map<String, Object> prevResult = forecastMapper.selectForecastSummary(prevForecast);
+
+            if (prevResult == null) {
+                return null;
+            }
+
+            // 获取两个周期的总用电量
+            Object prevTotalObj = prevResult.get("totalElec");
+            Object currTotalObj = currentResult.get("totalElec");
+
+            if (prevTotalObj == null || currTotalObj == null) {
+                return null;
+            }
+
+            double prevTotal = ((Number) prevTotalObj).doubleValue();
+            double currTotal = ((Number) currTotalObj).doubleValue();
+
+            // 避免除零错误
+            if (prevTotal == 0) {
+                return null;
+            }
+
+            // 计算环比变化百分比
+            double trend = ((currTotal - prevTotal) / prevTotal) * 100;
+
+            // 保留一位小数
+            return Math.round(trend * 10) / 10.0;
+        }
+        catch (Exception e) {
+            // 日期解析或计算出错,返回null
+            return null;
+        }
+    }
+}

+ 145 - 23
ems/ems-core/src/main/resources/mapper/ems/CaMeterDMapper.xml

@@ -1,28 +1,33 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.ruoyi.ems.mapper.CaMeterDMapper">
 
     <resultMap type="com.ruoyi.ems.domain.CaMeterD" id="CaMeterDResult">
         <result property="id"    column="id"    />
         <result property="areaCode"    column="area_code"    />
+        <result property="areaName"    column="area_name"    />
         <result property="date"    column="date"    />
         <result property="caEmissionQuantity"    column="ca_emission_quantity"    />
         <result property="caSinkQuantity"    column="ca_sink_quantity"    />
+        <result property="year"    column="year"    />
+        <result property="month"    column="month"    />
     </resultMap>
 
     <sql id="selectCaMeterDVo">
         select id, area_code, date, ca_emission_quantity, ca_sink_quantity from adm_ems_ca_meter_d
     </sql>
 
+    <!-- 按日查询(原有逻辑) -->
     <select id="selectCaMeterDList" parameterType="com.ruoyi.ems.domain.CaMeterD" resultMap="CaMeterDResult">
-        select d.id, d.area_code,area.area_name, date, ca_emission_quantity, ca_sink_quantity from adm_ems_ca_meter_d d
+        select d.id, d.area_code, area.area_name, d.date, d.ca_emission_quantity, d.ca_sink_quantity
+        from adm_ems_ca_meter_d d
         inner join adm_area area on area.area_code = d.area_code
         <where>
             <if test="areaName != null  and areaName != ''">and area.area_name like concat('%', #{areaName}, '%')</if>
             <if test="areaCode != null  and areaCode != '' and areaCode !='-1'"> and d.area_code = #{areaCode}</if>
-            <if test="date != null "> and date = #{date}</if>
+            <if test="date != null "> and d.date = #{date}</if>
             <if test="startRecTime != null and startRecTime != '' ">
                 and d.`date` &gt;= #{startRecTime}
             </if>
@@ -30,39 +35,156 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and d.`date` &lt;= #{endRecTime}
             </if>
         </where>
-        order by date desc
+        order by d.date desc, d.area_code
     </select>
 
+    <!-- ===================== 按月聚合查询(分页用) ===================== -->
+    <select id="selectCaMeterDListByMonth" parameterType="com.ruoyi.ems.domain.CaMeterD" resultMap="CaMeterDResult">
+        select
+        MIN(d.id) as id,
+        d.area_code,
+        area.area_name,
+        DATE_FORMAT(d.date, '%Y-%m-01') as date,
+        YEAR(d.date) as year,
+        MONTH(d.date) as month,
+        SUM(d.ca_emission_quantity) as ca_emission_quantity,
+        SUM(d.ca_sink_quantity) as ca_sink_quantity
+        from adm_ems_ca_meter_d d
+        inner join adm_area area on area.area_code = d.area_code
+        <where>
+            <if test="areaName != null and areaName != ''">and area.area_name like concat('%', #{areaName}, '%')</if>
+            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and d.area_code = #{areaCode}</if>
+            <if test="startRecTime != null and startRecTime != ''">
+                and d.`date` &gt;= #{startRecTime}
+            </if>
+            <if test="endRecTime != null and endRecTime != ''">
+                and d.`date` &lt;= #{endRecTime}
+            </if>
+        </where>
+        group by d.area_code, area.area_name, YEAR(d.date), MONTH(d.date)
+        order by YEAR(d.date) desc, MONTH(d.date) desc, d.area_code
+    </select>
+
+    <!-- ===================== 按年聚合查询(分页用) ===================== -->
+    <select id="selectCaMeterDListByYear" parameterType="com.ruoyi.ems.domain.CaMeterD" resultMap="CaMeterDResult">
+        select
+        MIN(d.id) as id,
+        d.area_code,
+        area.area_name,
+        DATE_FORMAT(d.date, '%Y-01-01') as date,
+        YEAR(d.date) as year,
+        SUM(d.ca_emission_quantity) as ca_emission_quantity,
+        SUM(d.ca_sink_quantity) as ca_sink_quantity
+        from adm_ems_ca_meter_d d
+        inner join adm_area area on area.area_code = d.area_code
+        <where>
+            <if test="areaName != null and areaName != ''">and area.area_name like concat('%', #{areaName}, '%')</if>
+            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and d.area_code = #{areaCode}</if>
+            <if test="startRecTime != null and startRecTime != ''">
+                and d.`date` &gt;= #{startRecTime}
+            </if>
+            <if test="endRecTime != null and endRecTime != ''">
+                and d.`date` &lt;= #{endRecTime}
+            </if>
+        </where>
+        group by d.area_code, area.area_name, YEAR(d.date)
+        order by YEAR(d.date) desc, d.area_code
+    </select>
+
+    <!-- ===================== 统计汇总查询(不分页) ===================== -->
+
+    <!-- 按日汇总 - 修复ca_sink_quantity别名问题 -->
     <select id="selectSumCaMeterDList" parameterType="com.ruoyi.ems.domain.CaMeterD" resultMap="CaMeterDResult">
-        select d.id, d.area_code,area.area_name, date, sum(ca_emission_quantity) ca_emission_quantity,
-        sum(ca_sink_quantity) from adm_ems_ca_meter_d d
+        select
+        MIN(d.id) as id,
+        d.area_code,
+        area.area_name,
+        d.date,
+        SUM(d.ca_emission_quantity) as ca_emission_quantity,
+        SUM(d.ca_sink_quantity) as ca_sink_quantity
+        from adm_ems_ca_meter_d d
         inner join adm_area area on area.area_code = d.area_code
         <where>
-            <if test="areaName != null  and areaName != ''">and area.area_name like concat('%', #{areaName}, '%')</if>
-            <if test="areaCode != null  and areaCode != '' and areaCode !='-1'">and d.area_code = #{areaCode}</if>
-            <if test="date != null ">and date = #{date}</if>
-            <if test="startRecTime != null and startRecTime != '' ">
+            <if test="areaName != null and areaName != ''">and area.area_name like concat('%', #{areaName}, '%')</if>
+            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and d.area_code = #{areaCode}</if>
+            <if test="date != null">and d.date = #{date}</if>
+            <if test="startRecTime != null and startRecTime != ''">
                 and d.`date` &gt;= #{startRecTime}
             </if>
-            <if test="endRecTime != null and endRecTime !=''">
+            <if test="endRecTime != null and endRecTime != ''">
                 and d.`date` &lt;= #{endRecTime}
             </if>
         </where>
-        group by d.date
-        order by date desc
+        group by d.date, d.area_code, area.area_name
+        order by d.date desc
     </select>
 
+    <!-- 按月汇总(统计卡片用) -->
+    <select id="selectSumCaMeterDListByMonth" parameterType="com.ruoyi.ems.domain.CaMeterD" resultMap="CaMeterDResult">
+        select
+        MIN(d.id) as id,
+        d.area_code,
+        area.area_name,
+        DATE_FORMAT(d.date, '%Y-%m-01') as date,
+        YEAR(d.date) as year,
+        MONTH(d.date) as month,
+        SUM(d.ca_emission_quantity) as ca_emission_quantity,
+        SUM(d.ca_sink_quantity) as ca_sink_quantity
+        from adm_ems_ca_meter_d d
+        inner join adm_area area on area.area_code = d.area_code
+        <where>
+            <if test="areaName != null and areaName != ''">and area.area_name like concat('%', #{areaName}, '%')</if>
+            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and d.area_code = #{areaCode}</if>
+            <if test="startRecTime != null and startRecTime != ''">
+                and d.`date` &gt;= #{startRecTime}
+            </if>
+            <if test="endRecTime != null and endRecTime != ''">
+                and d.`date` &lt;= #{endRecTime}
+            </if>
+        </where>
+        group by d.area_code, area.area_name, YEAR(d.date), MONTH(d.date)
+        order by YEAR(d.date) desc, MONTH(d.date) desc
+    </select>
+
+    <!-- 按年汇总(统计卡片用) -->
+    <select id="selectSumCaMeterDListByYear" parameterType="com.ruoyi.ems.domain.CaMeterD" resultMap="CaMeterDResult">
+        select
+        MIN(d.id) as id,
+        d.area_code,
+        area.area_name,
+        DATE_FORMAT(d.date, '%Y-01-01') as date,
+        YEAR(d.date) as year,
+        SUM(d.ca_emission_quantity) as ca_emission_quantity,
+        SUM(d.ca_sink_quantity) as ca_sink_quantity
+        from adm_ems_ca_meter_d d
+        inner join adm_area area on area.area_code = d.area_code
+        <where>
+            <if test="areaName != null and areaName != ''">and area.area_name like concat('%', #{areaName}, '%')</if>
+            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and d.area_code = #{areaCode}</if>
+            <if test="startRecTime != null and startRecTime != ''">
+                and d.`date` &gt;= #{startRecTime}
+            </if>
+            <if test="endRecTime != null and endRecTime != ''">
+                and d.`date` &lt;= #{endRecTime}
+            </if>
+        </where>
+        group by d.area_code, area.area_name, YEAR(d.date)
+        order by YEAR(d.date) desc
+    </select>
+
+    <!-- 平均值查询 -->
     <select id="selectAvgCaMeterDList" parameterType="com.ruoyi.ems.domain.CaMeterD" resultMap="CaMeterDResult">
-        select area_code, avg(ca_emission_quantity) ca_emission_quantity,
-        avg(ca_sink_quantity) from adm_ems_ca_meter_d
+        select
+        area_code,
+        AVG(ca_emission_quantity) as ca_emission_quantity,
+        AVG(ca_sink_quantity) as ca_sink_quantity
+        from adm_ems_ca_meter_d
         <where>
-            <if test="areaCode != null  and areaCode != '' and areaCode !='-1'">and area_code = #{areaCode}</if>
+            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and area_code = #{areaCode}</if>
         </where>
         group by area_code
-        order by date desc
     </select>
 
-
     <select id="selectCaMeterDById" parameterType="Long" resultMap="CaMeterDResult">
         <include refid="selectCaMeterDVo"/>
         where id = #{id}
@@ -75,13 +197,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="date != null">date,</if>
             <if test="caEmissionQuantity != null">ca_emission_quantity,</if>
             <if test="caSinkQuantity != null">ca_sink_quantity,</if>
-         </trim>
+        </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="areaCode != null and areaCode != ''">#{areaCode},</if>
             <if test="date != null">#{date},</if>
             <if test="caEmissionQuantity != null">#{caEmissionQuantity},</if>
             <if test="caSinkQuantity != null">#{caSinkQuantity},</if>
-         </trim>
+        </trim>
     </insert>
 
     <update id="updateCaMeterD" parameterType="com.ruoyi.ems.domain.CaMeterD">
@@ -99,7 +221,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         delete from adm_ems_ca_meter_d where id = #{id}
     </delete>
 
-    <delete id="deleteCaMeterDByArea" >
+    <delete id="deleteCaMeterDByArea">
         delete from adm_ems_ca_meter_d where area_code = #{areaCode} and `date` = #{date}
     </delete>
 
@@ -109,4 +231,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{id}
         </foreach>
     </delete>
-</mapper>
+</mapper>

+ 193 - 54
ems/ems-core/src/main/resources/mapper/ems/ElecConsumeForecastMapper.xml

@@ -1,78 +1,228 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.ruoyi.ems.mapper.ElecConsumeForecastMapper">
 
     <resultMap type="com.ruoyi.ems.domain.ElecConsumeForecast" id="ElecConsumeForecastResult">
-        <result property="id"    column="id"    />
-        <result property="areaCode"    column="area_code"    />
-        <result property="objCode"    column="obj_code"    />
-        <result property="objType"    column="obj_type"    />
-        <result property="date"    column="date"    />
-        <result property="elecUseQuantity"    column="elec_use_quantity"    />
+        <result property="id"               column="id"                 />
+        <result property="areaCode"         column="area_code"          />
+        <result property="objCode"          column="obj_code"           />
+        <result property="objType"          column="obj_type"           />
+        <result property="date"             column="date"               />
+        <result property="elecUseQuantity"  column="elec_use_quantity"  />
+        <result property="objName"          column="obj_name"           />
     </resultMap>
 
     <sql id="selectForecastVo">
-        select id, area_code, obj_code, obj_type, date, elec_use_quantity from adm_ems_elec_consume_forecast
+        select id, area_code, obj_code, obj_type, date, elec_use_quantity
+        from adm_ems_elec_consume_forecast
     </sql>
 
+    <!--
+        层级汇总条件片段
+        核心逻辑:使用 FIND_IN_SET 函数基于 ancestors 字段查找所有子节点
+        当选择父节点时,自动包含该节点及其所有子节点的数据
+    -->
+    <sql id="hierarchyCondition">
+        <choose>
+            <!-- 选择特定对象时,查询该对象及所有子对象 -->
+            <when test="objCode != null and objCode != '' and objCode != '-1'">
+                AND (
+                f.obj_code = #{objCode}
+                OR f.obj_code IN (
+                SELECT area_code FROM adm_area
+                WHERE FIND_IN_SET(#{objCode}, ancestors) > 0
+                )
+                )
+            </when>
+            <!-- 选择特定区域时,查询该区域及其下所有子区域 -->
+            <when test="areaCode != null and areaCode != '' and areaCode != '-1'">
+                AND (
+                f.obj_code = #{areaCode}
+                OR f.obj_code IN (
+                SELECT area_code FROM adm_area
+                WHERE area_code = #{areaCode}
+                OR FIND_IN_SET(#{areaCode}, ancestors) > 0
+                )
+                )
+            </when>
+        </choose>
+    </sql>
+
+    <!--
+        查询预测列表(支持层级汇总)
+        点击父节点时显示所有子节点数据
+    -->
     <select id="selectForecastList" parameterType="com.ruoyi.ems.domain.ElecConsumeForecast" resultMap="ElecConsumeForecastResult">
-        select
+        SELECT
         f.id,
         f.area_code,
         f.obj_code,
         f.obj_type,
-        date,
-        elec_use_quantity,
+        f.date,
+        f.elec_use_quantity,
         COALESCE(a.area_name, fc.facs_name, d.device_name) AS obj_name
-        FROM
-        adm_ems_elec_consume_forecast f
-          LEFT JOIN adm_area a ON f.obj_code = a.area_code AND f.obj_type = 1
-          LEFT JOIN adm_ems_facs fc ON f.obj_code = fc.facs_code AND f.obj_type = 2
-          LEFT JOIN adm_ems_device d ON f.obj_code = d.device_code AND f.obj_type = 3
+        FROM adm_ems_elec_consume_forecast f
+        LEFT JOIN adm_area a ON f.obj_code = a.area_code AND f.obj_type = 1
+        LEFT JOIN adm_ems_facs fc ON f.obj_code = fc.facs_code AND f.obj_type = 2
+        LEFT JOIN adm_ems_device d ON f.obj_code = d.device_code AND f.obj_type = 3
         <where>
-            <if test="areaCode != null  and areaCode != '' and areaCode !='-1'">and f.area_code = #{areaCode}</if>
-            <if test="objCode != null  and objCode != ''">and f.obj_code = #{objCode}</if>
-            <if test="objType != null ">and f.obj_type = #{objType}</if>
-            <if test="startRecTime != null and startRecTime != ''">and f.date &gt;= #{startRecTime}</if>
-            <if test="endRecTime != null and endRecTime != ''">and DATE &lt;=#{endRecTime}</if>
+            <if test="objType != null">
+                AND f.obj_type = #{objType}
+            </if>
+            <if test="startRecTime != null and startRecTime != ''">
+                AND f.date &gt;= #{startRecTime}
+            </if>
+            <if test="endRecTime != null and endRecTime != ''">
+                AND f.date &lt;= #{endRecTime}
+            </if>
+            <!-- 层级汇总条件 -->
+            <include refid="hierarchyCondition"/>
         </where>
+        ORDER BY f.date DESC, f.obj_code
     </select>
 
     <select id="selectForecastById" parameterType="Long" resultMap="ElecConsumeForecastResult">
         <include refid="selectForecastVo"/>
-        where id = #{id}
+        WHERE id = #{id}
+    </select>
+
+    <!--
+        每日汇总趋势查询(支持层级汇总)
+        用于图表展示,按日期分组汇总用电量
+    -->
+    <select id="calcForecastDateRange" parameterType="com.ruoyi.ems.domain.ElecConsumeForecast" resultMap="ElecConsumeForecastResult">
+        SELECT
+        SUM(COALESCE(CAST(f.elec_use_quantity AS DECIMAL(10, 2)), 0)) AS elec_use_quantity,
+        f.date
+        FROM adm_ems_elec_consume_forecast f
+        <where>
+            <if test="objType != null">
+                AND f.obj_type = #{objType}
+            </if>
+            <if test="objType == null">
+                AND f.obj_type = 1
+            </if>
+            <if test="startRecTime != null and startRecTime != ''">
+                AND f.date &gt;= #{startRecTime}
+            </if>
+            <if test="endRecTime != null and endRecTime != ''">
+                AND f.date &lt;= #{endRecTime}
+            </if>
+            <!-- 层级汇总条件 -->
+            <include refid="hierarchyCondition"/>
+        </where>
+        GROUP BY f.date
+        ORDER BY f.date ASC
+    </select>
+
+    <!--
+        汇总统计查询(支持层级汇总)
+        返回:总用电量、平均用电量、子项数量、天数、日期范围
+    -->
+    <select id="selectForecastSummary" parameterType="com.ruoyi.ems.domain.ElecConsumeForecast" resultType="java.util.Map">
+        SELECT
+        COUNT(DISTINCT f.obj_code) AS itemCount,
+        COALESCE(SUM(CAST(f.elec_use_quantity AS DECIMAL(10, 2))), 0) AS totalElec,
+        COALESCE(AVG(daily_sum.daily_elec), 0) AS avgElec,
+        MIN(f.date) AS startDate,
+        MAX(f.date) AS endDate,
+        COUNT(DISTINCT f.date) AS dayCount
+        FROM adm_ems_elec_consume_forecast f
+        LEFT JOIN (
+        -- 子查询:计算每日汇总,用于计算日均值
+        SELECT
+        f2.date,
+        SUM(CAST(f2.elec_use_quantity AS DECIMAL(10, 2))) AS daily_elec
+        FROM adm_ems_elec_consume_forecast f2
+        <where>
+            <if test="objType != null">
+                AND f2.obj_type = #{objType}
+            </if>
+            <if test="objType == null">
+                AND f2.obj_type = 1
+            </if>
+            <if test="startRecTime != null and startRecTime != ''">
+                AND f2.date &gt;= #{startRecTime}
+            </if>
+            <if test="endRecTime != null and endRecTime != ''">
+                AND f2.date &lt;= #{endRecTime}
+            </if>
+            <choose>
+                <when test="objCode != null and objCode != '' and objCode != '-1'">
+                    AND (
+                    f2.obj_code = #{objCode}
+                    OR f2.obj_code IN (
+                    SELECT area_code FROM adm_area
+                    WHERE FIND_IN_SET(#{objCode}, ancestors) > 0
+                    )
+                    )
+                </when>
+                <when test="areaCode != null and areaCode != '' and areaCode != '-1'">
+                    AND (
+                    f2.obj_code = #{areaCode}
+                    OR f2.obj_code IN (
+                    SELECT area_code FROM adm_area
+                    WHERE area_code = #{areaCode}
+                    OR FIND_IN_SET(#{areaCode}, ancestors) > 0
+                    )
+                    )
+                </when>
+            </choose>
+        </where>
+        GROUP BY f2.date
+        ) daily_sum ON f.date = daily_sum.date
+        <where>
+            <if test="objType != null">
+                AND f.obj_type = #{objType}
+            </if>
+            <if test="objType == null">
+                AND f.obj_type = 1
+            </if>
+            <if test="startRecTime != null and startRecTime != ''">
+                AND f.date &gt;= #{startRecTime}
+            </if>
+            <if test="endRecTime != null and endRecTime != ''">
+                AND f.date &lt;= #{endRecTime}
+            </if>
+            <!-- 层级汇总条件 -->
+            <include refid="hierarchyCondition"/>
+        </where>
     </select>
 
+    <!-- 新增单条记录 -->
     <insert id="insertForecast" parameterType="com.ruoyi.ems.domain.ElecConsumeForecast" useGeneratedKeys="true" keyProperty="id">
-        insert into adm_ems_elec_consume_forecast
+        INSERT INTO adm_ems_elec_consume_forecast
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="areaCode != null and areaCode != ''">area_code,</if>
             <if test="objCode != null and objCode != ''">obj_code,</if>
             <if test="objType != null">obj_type,</if>
             <if test="date != null">date,</if>
             <if test="elecUseQuantity != null">elec_use_quantity,</if>
-         </trim>
-        <trim prefix="values (" suffix=")" suffixOverrides=",">
+        </trim>
+        <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
             <if test="areaCode != null and areaCode != ''">#{areaCode},</if>
             <if test="objCode != null and objCode != ''">#{objCode},</if>
             <if test="objType != null">#{objType},</if>
             <if test="date != null">#{date},</if>
             <if test="elecUseQuantity != null">#{elecUseQuantity},</if>
-         </trim>
+        </trim>
     </insert>
 
+    <!-- 批量新增 -->
     <insert id="insertBatch" parameterType="java.util.List">
-        insert into adm_ems_elec_consume_forecast (area_code, obj_code, obj_type, date, elec_use_quantity)
-        values
+        INSERT INTO adm_ems_elec_consume_forecast
+        (area_code, obj_code, obj_type, date, elec_use_quantity)
+        VALUES
         <foreach collection="list" item="item" index="index" separator=",">
             (#{item.areaCode}, #{item.objCode}, #{item.objType}, #{item.date}, #{item.elecUseQuantity})
         </foreach>
     </insert>
 
+    <!-- 更新记录 -->
     <update id="updateForecast" parameterType="com.ruoyi.ems.domain.ElecConsumeForecast">
-        update adm_ems_elec_consume_forecast
+        UPDATE adm_ems_elec_consume_forecast
         <trim prefix="SET" suffixOverrides=",">
             <if test="areaCode != null and areaCode != ''">area_code = #{areaCode},</if>
             <if test="objCode != null and objCode != ''">obj_code = #{objCode},</if>
@@ -80,40 +230,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="date != null">date = #{date},</if>
             <if test="elecUseQuantity != null">elec_use_quantity = #{elecUseQuantity},</if>
         </trim>
-        where id = #{id}
+        WHERE id = #{id}
     </update>
 
+    <!-- 删除单条记录 -->
     <delete id="deleteForecastById" parameterType="Long">
-        delete from adm_ems_elec_consume_forecast where id = #{id}
+        DELETE FROM adm_ems_elec_consume_forecast WHERE id = #{id}
     </delete>
 
+    <!-- 按条件删除 -->
     <delete id="deleteByCondition">
-        delete from adm_ems_elec_consume_forecast
-               where area_code = #{areaCode}
-                 and obj_type = #{objType}
-                 and obj_code =#{objCode}
-                 and `date` between #{startTime} and #{endTime}
+        DELETE FROM adm_ems_elec_consume_forecast
+        WHERE area_code = #{areaCode}
+          AND obj_type = #{objType}
+          AND obj_code = #{objCode}
+          AND date BETWEEN #{startTime} AND #{endTime}
     </delete>
 
+    <!-- 批量删除 -->
     <delete id="deleteForecastByIds" parameterType="String">
-        delete from adm_ems_elec_consume_forecast where id in
+        DELETE FROM adm_ems_elec_consume_forecast WHERE id IN
         <foreach item="id" collection="array" open="(" separator="," close=")">
             #{id}
         </foreach>
     </delete>
-    <select id="calcForecastDateRange" parameterType="ElecConsumeForecast" resultMap="ElecConsumeForecastResult">
-        select sum(
-        COALESCE(CAST(elec_use_quantity AS DECIMAL(10, 2)), 0)) elec_use_quantity,
-        `date`
-        from adm_ems_elec_consume_forecast d
-        <where>
-            obj_type = 1
-            <if test="startRecTime != null and startRecTime != ''">and DATE >=#{startRecTime}</if>
-            <if test="endRecTime != null and endRecTime != ''">and DATE <![CDATA[ <= ]]>#{endRecTime}</if>
-            <if test="areaCode != null and areaCode != '' and areaCode != '-1'">
-                and obj_code = #{areaCode}
-            </if>
-        </where>
-        group by `date`
-    </select>
-</mapper>
+
+</mapper>

+ 2 - 2
ems/sql/ems_init_data_ctfwq.sql

@@ -1786,8 +1786,8 @@ INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `dev
 INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `device_name`, `device_enable`, `location_ref`, `location`, `meter_cls`, `col_cycle`, `col_mode`, `magnification`, `spec_desc`) VALUES ('321283124S3001', 'C_2004_AV_0200','M_W4_DEV_BA_METER_W', '二楼', 1,       '321283124S30010102', '二楼',     70, 300, 0, 1, null);
 -- 司机之家水
 INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `device_name`, `device_enable`, `location_ref`, `location`, `meter_cls`, `col_cycle`, `col_mode`, `magnification`, `spec_desc`) VALUES ('321283124S3001', 'C_2005_AV_0600','M_W4_DEV_BA_METER_W', '北区司机之家维修间ALSS', 1, '321283124S300102', '司机之家',   70, 300, 0, 1, null);
--- 南区电
--- 综合
+-- 南区电
+-- 综合
 INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `device_name`, `device_enable`, `location_ref`, `location`, `meter_cls`, `col_cycle`, `col_mode`, `magnification`, `spec_desc`) VALUES ('321283124S3002', 'C_1003_AV_0000','M_W4_DEV_BA_METER_E', '南区综合楼ALZ', 1,       '321283124S30020101', '一楼强电间', 45, 300, 0, 1, null);
 INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `device_name`, `device_enable`, `location_ref`, `location`, `meter_cls`, `col_cycle`, `col_mode`, `magnification`, `spec_desc`) VALUES ('321283124S3002', 'C_1003_AV_0001','M_W4_DEV_BA_METER_E', '南区综合楼AKZ', 1,       '321283124S30020101', '一楼强电间', 45, 300, 0, 1, null);
 INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `device_name`, `device_enable`, `location_ref`, `location`, `meter_cls`, `col_cycle`, `col_mode`, `magnification`, `spec_desc`) VALUES ('321283124S3002', 'C_1003_AV_0002','M_W4_DEV_BA_METER_E', '南区综合楼APXF2', 1,     '321283124S30020101', '一楼强电间', 45, 300, 0, 1, null);

+ 1 - 1
ems/sql/ems_sys_data.sql

@@ -622,7 +622,7 @@ INSERT INTO `sys_dict_data` VALUES (113, 0, '谷电计量', '2', 'meter_type', N
 INSERT INTO `sys_dict_data` VALUES (114, 0, '区域', '1', 'obj_type', NULL, 'default', 'N', '0', 'admin', '2024-08-12 14:23:19', 'admin', '2024-08-28 15:33:44', '');
 INSERT INTO `sys_dict_data` VALUES (115, 0, '设施', '2', 'obj_type', NULL, 'default', 'N', '0', 'admin', '2024-08-12 14:23:30', '', NULL, ' 2-设施 ');
 INSERT INTO `sys_dict_data` VALUES (116, 0, '设备', '3', 'obj_type', NULL, 'default', 'N', '0', 'admin', '2024-08-12 14:23:40', 'admin', '2024-08-12 14:23:47', '3-设备');
-INSERT INTO `sys_dict_data` VALUES (117, 1, ' photovoltaic', '100', 'basecfg_lable', NULL, 'default', 'N', '0', 'admin', '2024-08-19 10:50:16', 'admin', '2024-08-19 14:33:00', '光伏');
+INSERT INTO `sys_dict_data` VALUES (117, 1, 'photovoltaic', '100', 'basecfg_lable', NULL, 'default', 'N', '0', 'admin', '2024-08-19 10:50:16', 'admin', '2024-08-19 14:33:00', '光伏');
 INSERT INTO `sys_dict_data` VALUES (118, 2, 'energy', '200', 'basecfg_lable', NULL, 'default', 'N', '0', 'admin', '2024-08-19 10:50:59', '', NULL, '能源');
 INSERT INTO `sys_dict_data` VALUES (119, 3, 'Energy storage', '300', 'basecfg_lable', NULL, 'default', 'N', '0', 'admin', '2024-08-19 10:51:30', '', NULL, '储能');
 INSERT INTO `sys_dict_data` VALUES (120, 0, '大于', '1', 'alarm_thre_type', NULL, 'default', 'N', '0', 'admin', '2024-08-26 10:27:12', '', NULL, NULL);