Explorar o código

单体版改造

learshaw hai 1 día
pai
achega
7bd17eb5b7
Modificáronse 63 ficheiros con 5218 adicións e 1602 borrados
  1. 0 5
      ems/ems-application/ems-admin/pom.xml
  2. 29 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/ems/config/SchedulerConfig.java
  3. 288 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/ems/task/InspectionScheduler.java
  4. 435 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/ems/task/StrategyScheduler.java
  5. 304 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/ems/task/StrategyTriggerListener.java
  6. 0 107
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AdmEmsElecPgIndexController.java
  7. 0 107
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AdmEmsIndexRangeController.java
  8. 0 144
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AdmOpAlarmController.java
  9. 0 107
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AdmOpAlarmPolicyController.java
  10. 0 100
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AdmOpInspectionReportController.java
  11. 252 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AlarmController.java
  12. 184 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AlarmRuleController.java
  13. 49 38
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AreaController.java
  14. 12 19
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/CaConfigController.java
  15. 11 18
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/CaEmissionForecastController.java
  16. 52 51
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/CaMeterDController.java
  17. 114 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ChargingBillController.java
  18. 24 18
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/CoChargingConfigController.java
  19. 420 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/CustomReportController.java
  20. 31 23
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/DeviceController.java
  21. 29 36
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/DeviceLedgerController.java
  22. 4 4
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecAttrController.java
  23. 55 36
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecConsumeForecastController.java
  24. 0 93
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecLoadIndexController.java
  25. 37 12
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecMeterHController.java
  26. 33 32
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecPgSupplyHController.java
  27. 10 15
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecPriceController.java
  28. 3 3
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecPriceStrategyController.java
  29. 3 3
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecPriceStrategyHourController.java
  30. 16 18
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecProdForecastController.java
  31. 125 22
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecPvSupplyHController.java
  32. 8 3
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecStoreController.java
  33. 89 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecStoreIndexController.java
  34. 3 5
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsCommonController.java
  35. 8 13
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsDisStaCoalController.java
  36. 103 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsEcoDController.java
  37. 77 18
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsFacsController.java
  38. 27 2
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjAbilityController.java
  39. 84 29
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjAttrController.java
  40. 14 16
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjAttrValueController.java
  41. 10 17
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjEventController.java
  42. 79 25
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjFlowRelController.java
  43. 76 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjLogController.java
  44. 1 1
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjModelController.java
  45. 15 32
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsSubsystemController.java
  46. 0 108
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsTagController.java
  47. 564 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EnergyConsumptionController.java
  48. 3 3
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/FacsCategoryController.java
  49. 70 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/FdEnergyPriceConfigController.java
  50. 236 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/InspectionPlanController.java
  51. 128 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/InspectionReportController.java
  52. 151 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/InspectionSchedulerController.java
  53. 28 15
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/MeterBoundaryController.java
  54. 139 3
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/MeterDeviceController.java
  55. 16 28
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/MeterReadingController.java
  56. 105 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ObjTagController.java
  57. 451 141
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/OpEnergyStrategyController.java
  58. 0 102
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/OpInspectionPlanController.java
  59. 106 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/PlantCarbonSinkController.java
  60. 37 7
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/WaterMeterHController.java
  61. 69 0
      ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/WeatherController.java
  62. 0 22
      ems/ems-application/ems-admin/src/main/resources/application.yml
  63. 1 1
      ems/ems-application/pom.xml

+ 0 - 5
ems/ems-application/ems-admin/pom.xml

@@ -61,11 +61,6 @@
             <artifactId>ruoyi-generator</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>com.huashe.application</groupId>
-            <artifactId>ruoyi-generator</artifactId>
-        </dependency>
-
         <!-- 通用工具-->
         <dependency>
             <groupId>com.huashe.application</groupId>

+ 29 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/ems/config/SchedulerConfig.java

@@ -0,0 +1,29 @@
+package com.ruoyi.ems.config;
+
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+@Configuration
+@EnableScheduling
+public class SchedulerConfig {
+
+    @Bean
+    public TaskScheduler taskScheduler() {
+        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+        scheduler.setPoolSize(10);
+        scheduler.setThreadNamePrefix("strategy-scheduler-");
+        scheduler.setAwaitTerminationSeconds(60);
+        scheduler.setWaitForTasksToCompleteOnShutdown(true);
+        scheduler.setRemoveOnCancelPolicy(true);
+        scheduler.setErrorHandler(t -> {
+            LoggerFactory.getLogger("StrategyScheduler")
+                .error("定时任务执行异常", t);
+        });
+        scheduler.initialize();
+        return scheduler;
+    }
+}

+ 288 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/ems/task/InspectionScheduler.java

@@ -0,0 +1,288 @@
+package com.ruoyi.ems.task;
+
+import com.ruoyi.ems.domain.InspectionPlan;
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.mapper.InspectionPlanMapper;
+import com.ruoyi.ems.service.IInspectionPlanService;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.scheduling.support.CronExpression;
+import org.springframework.scheduling.support.CronTrigger;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+@Component
+public class InspectionScheduler {
+
+    private static final Logger log = LoggerFactory.getLogger(InspectionScheduler.class);
+
+    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    @Value("${schedulerCfg.inspection-scheduler:disable}")
+    private String schedulerEnable;
+
+    @Autowired
+    private InspectionPlanMapper planMapper;
+
+    @Autowired
+    private IInspectionPlanService planService;
+
+    private ThreadPoolTaskScheduler taskScheduler;
+
+    private final ConcurrentHashMap<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
+
+    private final ConcurrentHashMap<String, InspectionPlan> registeredPlans = new ConcurrentHashMap<>();
+
+    private final Set<String> executingPlans = ConcurrentHashMap.newKeySet();
+
+    private LocalDateTime startTime;
+
+    @PostConstruct
+    public void init() {
+        if ("enable".equals(schedulerEnable)) {
+            log.info("========== 初始化巡检调度器 ==========");
+
+            taskScheduler = new ThreadPoolTaskScheduler();
+            taskScheduler.setPoolSize(5);
+            taskScheduler.setThreadNamePrefix("inspection-scheduler-");
+            taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
+            taskScheduler.setAwaitTerminationSeconds(60);
+            taskScheduler.initialize();
+
+            startTime = LocalDateTime.now();
+
+            loadEnabledPlans();
+
+            log.info("========== 巡检调度器初始化完成,已注册 {} 个计划 ==========", scheduledTasks.size());
+        }
+    }
+
+    @PreDestroy
+    public void destroy() {
+        log.info("========== 销毁巡检调度器 ==========");
+
+        scheduledTasks.values().forEach(future -> future.cancel(false));
+        scheduledTasks.clear();
+        registeredPlans.clear();
+
+        if (taskScheduler != null) {
+            taskScheduler.shutdown();
+        }
+
+        log.info("========== 巡检调度器已销毁 ==========");
+    }
+
+    public void loadEnabledPlans() {
+        scheduledTasks.values().forEach(future -> future.cancel(false));
+        scheduledTasks.clear();
+        registeredPlans.clear();
+
+        List<InspectionPlan> plans = planMapper.selectEnabledSchedulePlans();
+
+        if (plans == null || plans.isEmpty()) {
+            log.info("没有需要加载的巡检计划");
+            return;
+        }
+
+        for (InspectionPlan plan : plans) {
+            try {
+                doRegisterPlan(plan);
+            } catch (Exception e) {
+                log.error("注册巡检计划失败: planCode={}, error={}", plan.getPlanCode(), e.getMessage());
+            }
+        }
+    }
+
+    public void registerPlan(String planCode) {
+        InspectionPlan plan = planMapper.selectByPlanCode(planCode);
+        if (plan == null) {
+            throw new RuntimeException("计划不存在: " + planCode);
+        }
+        registerPlan(plan);
+    }
+
+    public void registerPlan(InspectionPlan plan) {
+        if (plan == null || StringUtils.isBlank(plan.getPlanCode())) {
+            throw new RuntimeException("计划信息不完整");
+        }
+
+        unregisterPlan(plan.getPlanCode());
+
+        if (plan.getPlanType() != 2) {
+            log.warn("计划[{}]不是自动巡检类型,跳过注册", plan.getPlanCode());
+            return;
+        }
+
+        if (StringUtils.isBlank(plan.getCronExpression())) {
+            log.warn("计划[{}]未配置Cron表达式,跳过注册", plan.getPlanCode());
+            return;
+        }
+
+        doRegisterPlan(plan);
+
+        planMapper.updateScheduleEnabled(plan.getPlanCode(), 1);
+    }
+
+    private void doRegisterPlan(InspectionPlan plan) {
+        String planCode = plan.getPlanCode();
+        String cron = plan.getCronExpression();
+
+        if (!CronExpression.isValidExpression(cron)) {
+            throw new RuntimeException("无效的Cron表达式: " + cron);
+        }
+
+        Runnable task = () -> executeInspection(planCode);
+
+        ScheduledFuture<?> future = taskScheduler.schedule(task, new CronTrigger(cron));
+
+        scheduledTasks.put(planCode, future);
+        registeredPlans.put(planCode, plan);
+
+        Date nextExecTime = calculateNextExecTime(cron);
+        planMapper.updateExecInfo(planCode, null, nextExecTime);
+
+        log.info("巡检计划[{}]已注册到调度器,Cron: {}, 下次执行: {}",
+            planCode, cron, nextExecTime != null ? DATETIME_FORMATTER.format(
+                LocalDateTime.ofInstant(nextExecTime.toInstant(), ZoneId.systemDefault())) : "未知");
+    }
+
+    public void unregisterPlan(String planCode) {
+        ScheduledFuture<?> future = scheduledTasks.remove(planCode);
+        if (future != null) {
+            future.cancel(false);
+            log.info("巡检计划[{}]已从调度器注销", planCode);
+        }
+        registeredPlans.remove(planCode);
+
+        planMapper.updateScheduleEnabled(planCode, 0);
+    }
+
+    public void refreshPlan(String planCode) {
+        InspectionPlan plan = planMapper.selectByPlanCode(planCode);
+        if (plan == null) {
+            unregisterPlan(planCode);
+            return;
+        }
+
+        if (plan.getPlanType() == 2
+            && plan.getScheduleEnabled() != null
+            && plan.getScheduleEnabled() == 1
+            && StringUtils.isNotBlank(plan.getCronExpression())) {
+            registerPlan(plan);
+        } else {
+            unregisterPlan(planCode);
+        }
+    }
+
+    public InspectionReport triggerInspection(String planCode) {
+        return executeInspection(planCode);
+    }
+
+    private InspectionReport executeInspection(String planCode) {
+        if (executingPlans.contains(planCode)) {
+            log.warn("计划[{}]正在执行中,跳过本次触发", planCode);
+            return null;
+        }
+
+        executingPlans.add(planCode);
+        log.info("========== 开始执行巡检任务: {} ==========", planCode);
+
+        try {
+            InspectionReport report = planService.executeAutoInspection(planCode, "system");
+
+            InspectionPlan plan = registeredPlans.get(planCode);
+            if (plan != null) {
+                Date nextExecTime = calculateNextExecTime(plan.getCronExpression());
+                planMapper.updateExecInfo(planCode, new Date(), nextExecTime);
+            }
+
+            log.info("========== 巡检任务执行完成: {}, 报告: {} ==========",
+                planCode, report != null ? report.getReportCode() : "null");
+
+            return report;
+        } catch (Exception e) {
+            log.error("巡检任务执行异常: planCode={}, error={}", planCode, e.getMessage(), e);
+            throw new RuntimeException("巡检执行失败: " + e.getMessage(), e);
+        } finally {
+            executingPlans.remove(planCode);
+        }
+    }
+
+    private Date calculateNextExecTime(String cron) {
+        if (StringUtils.isBlank(cron)) {
+            return null;
+        }
+        try {
+            CronExpression expression = CronExpression.parse(cron);
+            LocalDateTime next = expression.next(LocalDateTime.now());
+            return next != null ? Date.from(next.atZone(ZoneId.systemDefault()).toInstant()) : null;
+        } catch (Exception e) {
+            log.warn("计算下次执行时间失败: cron={}, error={}", cron, e.getMessage());
+            return null;
+        }
+    }
+
+    public Map<String, Object> getStatus() {
+        Map<String, Object> status = new HashMap<>();
+
+        status.put("running", taskScheduler != null && !taskScheduler.getScheduledThreadPoolExecutor().isShutdown());
+        status.put("registeredCount", scheduledTasks.size());
+        status.put("executingCount", executingPlans.size());
+        status.put("startTime", startTime != null ? DATETIME_FORMATTER.format(startTime) : null);
+
+        if (taskScheduler != null) {
+            ScheduledThreadPoolExecutor executor = taskScheduler.getScheduledThreadPoolExecutor();
+            status.put("threadPoolStatus", String.format("活跃线程: %d, 池大小: %d, 队列任务: %d",
+                executor.getActiveCount(), executor.getPoolSize(), executor.getQueue().size()));
+        }
+
+        List<Map<String, Object>> planList = new ArrayList<>();
+        for (Map.Entry<String, InspectionPlan> entry : registeredPlans.entrySet()) {
+            InspectionPlan plan = entry.getValue();
+            Map<String, Object> planInfo = new HashMap<>();
+            planInfo.put("planCode", plan.getPlanCode());
+            planInfo.put("planName", plan.getPlanName());
+            planInfo.put("cronExpression", plan.getCronExpression());
+            planInfo.put("executing", executingPlans.contains(plan.getPlanCode()));
+
+            Date nextExec = calculateNextExecTime(plan.getCronExpression());
+            planInfo.put("nextExecTime", nextExec != null ?
+                DATETIME_FORMATTER.format(LocalDateTime.ofInstant(nextExec.toInstant(), ZoneId.systemDefault())) : null);
+
+            planList.add(planInfo);
+        }
+        status.put("registeredPlans", planList);
+
+        return status;
+    }
+
+    public int getRegisteredCount() {
+        return scheduledTasks.size();
+    }
+
+    public boolean isRegistered(String planCode) {
+        return scheduledTasks.containsKey(planCode);
+    }
+
+    public boolean isExecuting(String planCode) {
+        return executingPlans.contains(planCode);
+    }
+}

+ 435 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/ems/task/StrategyScheduler.java

@@ -0,0 +1,435 @@
+package com.ruoyi.ems.task;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.ems.domain.OpEnergyStrategy;
+import com.ruoyi.ems.domain.OpEnergyStrategyTrigger;
+import com.ruoyi.ems.service.IOpEnergyStrategyService;
+import com.ruoyi.ems.service.IOpEnergyStrategyTriggerService;
+import com.ruoyi.ems.strategy.PollingMonitorService;
+import com.ruoyi.ems.strategy.executor.StrategyExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationContext;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.support.CronTrigger;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+
+/**
+ * 策略调度器(重构版)
+ *
+ * 核心改动:所有触发配置统一从触发器表读取
+ *
+ * 触发类型说明:
+ * - EVENT: 事件触发,由 StrategyTriggerListener 处理
+ * - TIME: 定时触发,从触发器的 condition_expr 读取 CRON
+ * - ATTR: 属性变化触发,由 StrategyTriggerListener 处理
+ * - POLLING: 轮询触发,由 PollingMonitorService 处理
+ *
+ * @author lvwenbin
+ */
+@Component
+public class StrategyScheduler {
+    private static final Logger log = LoggerFactory.getLogger(StrategyScheduler.class);
+
+    @Autowired
+    private IOpEnergyStrategyService strategyService;
+
+    @Autowired
+    private IOpEnergyStrategyTriggerService triggerService;
+
+    @Autowired
+    private TaskScheduler taskScheduler;
+
+    @Autowired
+    private ApplicationContext applicationContext;
+
+    @Autowired(required = false)
+    private PollingMonitorService pollingMonitorService;
+
+    @Autowired(required = false)
+    private StrategyTriggerListener triggerListener;
+
+    private StrategyExecutor strategyExecutor;
+
+    @Value("${schedulerCfg.strategy-scheduler:disable}")
+    private String schedulerEnable;
+
+    /**
+     * 定时任务缓存
+     * key: strategyCode:triggerId
+     */
+    private final Map<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
+
+    /**
+     * 已注册策略
+     */
+    private final Map<String, OpEnergyStrategy> registeredStrategies = new ConcurrentHashMap<>();
+
+    /**
+     * 统计计数
+     */
+    private int timeTriggerCount = 0;
+    private int eventTriggerCount = 0;
+    private int attrTriggerCount = 0;
+    private int pollingTriggerCount = 0;
+
+    private StrategyExecutor getStrategyExecutor() {
+        if (strategyExecutor == null) {
+            synchronized (this) {
+                if (strategyExecutor == null) {
+                    strategyExecutor = applicationContext.getBean(StrategyExecutor.class);
+                }
+            }
+        }
+        return strategyExecutor;
+    }
+
+    @PostConstruct
+    public void init() {
+        if ("enable".equals(schedulerEnable)) {
+            log.info("====== 策略调度器初始化 ======");
+            loadEnabledStrategies();
+        }
+    }
+
+    @PreDestroy
+    public void destroy() {
+        log.info("====== 策略调度器销毁 ======");
+        scheduledTasks.values().forEach(future -> future.cancel(true));
+        scheduledTasks.clear();
+        registeredStrategies.clear();
+    }
+
+    /**
+     * 加载所有已启用的策略
+     */
+    public void loadEnabledStrategies() {
+        log.info("开始加载已启用的策略...");
+
+        // 清理现有任务
+        clearAllTasks();
+
+        // 查询所有已启用的策略
+        OpEnergyStrategy query = new OpEnergyStrategy();
+        query.setStrategyState(1);
+        List<OpEnergyStrategy> strategies = strategyService.selectStrategyList(query);
+
+        log.info("共查询到 {} 个已启用的策略", strategies.size());
+
+        for (OpEnergyStrategy strategy : strategies) {
+            try {
+                registerStrategy(strategy);
+            } catch (Exception e) {
+                log.error("注册策略[{}]失败", strategy.getStrategyCode(), e);
+            }
+        }
+
+        log.info("====== 策略加载完成 ======");
+        log.info("  定时触发: {} 个", timeTriggerCount);
+        log.info("  事件触发: {} 个", eventTriggerCount);
+        log.info("  属性触发: {} 个", attrTriggerCount);
+        log.info("  轮询触发: {} 个", pollingTriggerCount);
+    }
+
+    /**
+     * 清理所有任务
+     */
+    private void clearAllTasks() {
+        scheduledTasks.values().forEach(future -> future.cancel(true));
+        scheduledTasks.clear();
+        registeredStrategies.clear();
+        timeTriggerCount = 0;
+        eventTriggerCount = 0;
+        attrTriggerCount = 0;
+        pollingTriggerCount = 0;
+
+        // 清理触发监听器
+        if (triggerListener != null) {
+            triggerListener.clearAll();
+        }
+    }
+
+    /**
+     * 注册策略
+     * 核心改动:遍历触发器表,根据每个触发器的类型进行注册
+     */
+    public void registerStrategy(OpEnergyStrategy strategy) {
+        String strategyCode = strategy.getStrategyCode();
+        log.info("注册策略[{}]: {}", strategyCode, strategy.getStrategyName());
+
+        registeredStrategies.put(strategyCode, strategy);
+
+        // 查询该策略的所有触发器
+        List<OpEnergyStrategyTrigger> triggers = triggerService.selectByStrategyCode(strategyCode);
+
+        if (triggers == null || triggers.isEmpty()) {
+            log.warn("策略[{}]没有配置触发器", strategyCode);
+            return;
+        }
+
+        // 遍历触发器,根据类型进行注册
+        for (OpEnergyStrategyTrigger trigger : triggers) {
+            if (trigger.getEnable() != 1) {
+                log.debug("触发器[{}]已禁用,跳过", trigger.getTriggerName());
+                continue;
+            }
+
+            registerTrigger(strategyCode, trigger);
+        }
+    }
+
+    /**
+     * 注册单个触发器
+     */
+    private void registerTrigger(String strategyCode, OpEnergyStrategyTrigger trigger) {
+        String triggerType = trigger.getTriggerType();
+        String triggerName = trigger.getTriggerName();
+
+        log.debug("注册触发器[{}]: type={}, name={}", strategyCode, triggerType, triggerName);
+
+        switch (triggerType) {
+            case "TIME":
+                registerTimeTrigger(strategyCode, trigger);
+                break;
+
+            case "EVENT":
+                registerEventTrigger(strategyCode, trigger);
+                break;
+
+            case "ATTR":
+                registerAttrTrigger(strategyCode, trigger);
+                break;
+
+            case "POLLING":
+                registerPollingTrigger(strategyCode, trigger);
+                break;
+
+            default:
+                log.warn("未知的触发器类型: {}", triggerType);
+        }
+    }
+
+    /**
+     * 注册定时触发器
+     * 从 condition_expr 中读取 CRON 表达式
+     */
+    private void registerTimeTrigger(String strategyCode, OpEnergyStrategyTrigger trigger) {
+        String cronExpression = parseCronFromCondition(trigger.getConditionExpr());
+
+        if (cronExpression == null || cronExpression.isEmpty()) {
+            log.warn("触发器[{}:{}]未配置CRON表达式", strategyCode, trigger.getTriggerName());
+            return;
+        }
+
+        // 验证 CRON 表达式
+        try {
+            new CronTrigger(cronExpression);
+        } catch (IllegalArgumentException e) {
+            log.error("触发器[{}:{}]的CRON表达式[{}]格式错误",
+                strategyCode, trigger.getTriggerName(), cronExpression, e);
+            return;
+        }
+
+        // 构建任务key
+        String taskKey = strategyCode + ":TIME:" + trigger.getId();
+
+        // 取消已有任务
+        cancelTask(taskKey);
+
+        // 创建定时任务
+        final Long triggerId = trigger.getId();
+        final String triggerName = trigger.getTriggerName();
+
+        ScheduledFuture<?> future = taskScheduler.schedule(
+            () -> executeTimeTrigger(strategyCode, triggerId, triggerName),
+            new CronTrigger(cronExpression)
+        );
+
+        scheduledTasks.put(taskKey, future);
+        timeTriggerCount++;
+
+        log.info("✓ 定时触发器注册成功: strategy={}, trigger={}, cron={}",
+            strategyCode, triggerName, cronExpression);
+    }
+
+    /**
+     * 从 condition_expr 解析 CRON 表达式
+     * 支持格式: {"cron":"0 0 8 * * ?"}
+     */
+    private String parseCronFromCondition(String conditionExpr) {
+        if (conditionExpr == null || conditionExpr.isEmpty()) {
+            return null;
+        }
+
+        try {
+            JSONObject condition = JSON.parseObject(conditionExpr);
+            return condition.getString("cron");
+        } catch (Exception e) {
+            log.warn("解析CRON表达式失败: {}", conditionExpr);
+            return null;
+        }
+    }
+
+    /**
+     * 执行定时触发
+     */
+    private void executeTimeTrigger(String strategyCode, Long triggerId, String triggerName) {
+        log.info(">>> 定时触发执行: strategy={}, trigger={}", strategyCode, triggerName);
+
+        try {
+            Map<String, Object> params = new HashMap<>();
+            params.put("trigger_type", "TIME");
+            params.put("trigger_source", "SCHEDULER:" + triggerName);
+            params.put("trigger_id", triggerId);
+            params.put("exec_by", "SYSTEM_SCHEDULER");
+
+            getStrategyExecutor().executeStrategy(strategyCode, params);
+
+        } catch (Exception e) {
+            log.error("定时触发策略[{}]执行失败", strategyCode, e);
+        }
+    }
+
+    /**
+     * 注册事件触发器
+     */
+    private void registerEventTrigger(String strategyCode, OpEnergyStrategyTrigger trigger) {
+        if (triggerListener == null) {
+            log.warn("TriggerListener未注入,无法注册事件触发器");
+            return;
+        }
+
+        triggerListener.registerEventTrigger(strategyCode, trigger);
+        eventTriggerCount++;
+
+        log.info("✓ 事件触发器注册成功: strategy={}, device={}, event={}",
+            strategyCode, trigger.getSourceObjCode(), trigger.getEventKey());
+    }
+
+    /**
+     * 注册属性变化触发器
+     */
+    private void registerAttrTrigger(String strategyCode, OpEnergyStrategyTrigger trigger) {
+        if (triggerListener == null) {
+            log.warn("TriggerListener未注入,无法注册属性触发器");
+            return;
+        }
+
+        triggerListener.registerAttrTrigger(strategyCode, trigger);
+        attrTriggerCount++;
+
+        log.info("✓ 属性触发器注册成功: strategy={}, device={}, attr={}",
+            strategyCode, trigger.getSourceObjCode(), trigger.getAttrKey());
+    }
+
+    /**
+     * 注册轮询触发器
+     */
+    private void registerPollingTrigger(String strategyCode, OpEnergyStrategyTrigger trigger) {
+        if (pollingMonitorService == null) {
+            log.warn("PollingMonitorService未注入,无法注册轮询触发器");
+            return;
+        }
+
+        // 轮询服务需要策略对象
+        OpEnergyStrategy strategy = registeredStrategies.get(strategyCode);
+        if (strategy != null) {
+            pollingMonitorService.registerPollingStrategy(strategy);
+            pollingTriggerCount++;
+
+            log.info("✓ 轮询触发器注册成功: strategy={}, device={}, attr={}",
+                strategyCode, trigger.getSourceObjCode(), trigger.getAttrKey());
+        }
+    }
+
+    /**
+     * 注销策略
+     */
+    public void unregisterStrategy(String strategyCode) {
+        log.info("注销策略[{}]", strategyCode);
+
+        // 取消所有相关定时任务
+        scheduledTasks.entrySet().removeIf(entry -> {
+            if (entry.getKey().startsWith(strategyCode + ":")) {
+                entry.getValue().cancel(true);
+                return true;
+            }
+            return false;
+        });
+
+        // 移除策略信息
+        registeredStrategies.remove(strategyCode);
+
+        // 注销轮询监控
+        if (pollingMonitorService != null) {
+            pollingMonitorService.unregisterPollingStrategy(strategyCode);
+        }
+
+        // 注销事件/属性触发
+        if (triggerListener != null) {
+            triggerListener.unregisterTriggers(strategyCode);
+        }
+    }
+
+    /**
+     * 刷新策略配置
+     */
+    public void refreshStrategy(String strategyCode) {
+        log.info("刷新策略[{}]", strategyCode);
+
+        // 先注销
+        unregisterStrategy(strategyCode);
+
+        // 重新查询并注册
+        OpEnergyStrategy strategy = strategyService.selectStrategyByCode(strategyCode);
+        if (strategy != null && strategy.getStrategyState() == 1) {
+            registerStrategy(strategy);
+        }
+    }
+
+    /**
+     * 取消指定任务
+     */
+    private void cancelTask(String taskKey) {
+        ScheduledFuture<?> future = scheduledTasks.remove(taskKey);
+        if (future != null) {
+            future.cancel(true);
+        }
+    }
+
+    // ==================== 统计方法 ====================
+
+    public int getRegisteredCount() {
+        return registeredStrategies.size();
+    }
+
+    public int getScheduledTaskCount() {
+        return scheduledTasks.size();
+    }
+
+    public int getAttrTriggerCount() {
+        return attrTriggerCount;
+    }
+
+    public Map<String, Object> getStatus() {
+        Map<String, Object> status = new HashMap<>();
+        status.put("registeredStrategies", registeredStrategies.size());
+        status.put("timeTriggers", timeTriggerCount);
+        status.put("eventTriggers", eventTriggerCount);
+        status.put("attrTriggers", attrTriggerCount);
+        status.put("pollingTriggers", pollingTriggerCount);
+        status.put("scheduledTasks", scheduledTasks.keySet());
+        return status;
+    }
+}

+ 304 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/ems/task/StrategyTriggerListener.java

@@ -0,0 +1,304 @@
+package com.ruoyi.ems.task;
+
+import com.ruoyi.ems.domain.OpEnergyStrategyTrigger;
+import com.ruoyi.ems.service.IOpEnergyStrategyTriggerService;
+import com.ruoyi.ems.strategy.evaluator.ConditionEvaluator;
+import com.ruoyi.ems.strategy.executor.StrategyExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 策略触发监听器 (事件 / 属性)
+ *
+ * 针对 StrategyScheduler 的新版 registerTrigger() 逻辑进行了修复:
+ * - registerEventTrigger()
+ * - registerAttrTrigger()
+ * - unregisterTriggers()
+ * - clearAll()
+ * - 分发入口 onEventReceived() / onAttrChanged()
+ *
+ * @author
+ */
+@Component
+public class StrategyTriggerListener {
+    private static final Logger log = LoggerFactory.getLogger(StrategyTriggerListener.class);
+
+    @Autowired
+    private IOpEnergyStrategyTriggerService triggerService;
+
+    @Autowired
+    private ConditionEvaluator conditionEvaluator;
+
+    @Autowired
+    private ApplicationContext applicationContext;
+
+    private StrategyExecutor strategyExecutor;
+
+    /** key = objCode:eventKey  */
+    private final Map<String, List<TriggerInfo>> eventTriggers = new ConcurrentHashMap<>();
+
+    /** key = objCode:attrKey */
+    private final Map<String, List<TriggerInfo>> attrTriggers = new ConcurrentHashMap<>();
+
+    /** 缓存触发器 → 用于注销 */
+    private final Map<String, List<TriggerInfo>> strategyTriggerIndex = new ConcurrentHashMap<>();
+
+
+    // 延迟加载
+    private StrategyExecutor getStrategyExecutor() {
+        if (strategyExecutor == null) {
+            synchronized (this) {
+                if (strategyExecutor == null) {
+                    strategyExecutor = applicationContext.getBean(StrategyExecutor.class);
+                }
+            }
+        }
+        return strategyExecutor;
+    }
+
+    /* ============================================================
+     * 注册触发器
+     * ============================================================ */
+
+    /** 单个事件触发器 */
+    public void registerEventTrigger(String strategyCode, OpEnergyStrategyTrigger trigger) {
+        String key = trigger.getSourceObjCode() + ":" + trigger.getEventKey();
+
+        TriggerInfo info = new TriggerInfo(strategyCode, trigger);
+
+        eventTriggers.computeIfAbsent(key, k -> new ArrayList<>()).add(info);
+
+        // 建立反向索引,注销时用
+        strategyTriggerIndex.computeIfAbsent(strategyCode, k -> new ArrayList<>()).add(info);
+
+        log.info("事件触发器已注册: key={}, strategy={}", key, strategyCode);
+    }
+
+    /** 单个属性触发器 */
+    public void registerAttrTrigger(String strategyCode, OpEnergyStrategyTrigger trigger) {
+        String key = trigger.getSourceObjCode() + ":" + trigger.getAttrKey();
+
+        TriggerInfo info = new TriggerInfo(strategyCode, trigger);
+
+        attrTriggers.computeIfAbsent(key, k -> new ArrayList<>()).add(info);
+        strategyTriggerIndex.computeIfAbsent(strategyCode, k -> new ArrayList<>()).add(info);
+
+        log.info("属性触发器已注册: key={}, strategy={}", key, strategyCode);
+    }
+
+
+    /* ============================================================
+     * 注销 & 清空
+     * ============================================================ */
+
+    /** 注销某个策略的所有事件/属性触发器 */
+    public void unregisterTriggers(String strategyCode) {
+        List<TriggerInfo> list = strategyTriggerIndex.remove(strategyCode);
+        if (list == null || list.isEmpty()) return;
+
+        for (TriggerInfo info : list) {
+            OpEnergyStrategyTrigger t = info.getTrigger();
+            String key;
+
+            if ("EVENT".equals(t.getTriggerType())) {
+                key = t.getSourceObjCode() + ":" + t.getEventKey();
+                removeTrigger(eventTriggers, key, info);
+            } else if ("ATTR".equals(t.getTriggerType())) {
+                key = t.getSourceObjCode() + ":" + t.getAttrKey();
+                removeTrigger(attrTriggers, key, info);
+            }
+        }
+
+        log.info("触发器已注销: strategy={}", strategyCode);
+    }
+
+    /** 完全清空所有触发器 */
+    public void clearAll() {
+        eventTriggers.clear();
+        attrTriggers.clear();
+        strategyTriggerIndex.clear();
+        log.info("TriggerListener: 所有触发器已清空");
+    }
+
+    private void removeTrigger(Map<String, List<TriggerInfo>> map, String key, TriggerInfo info) {
+        List<TriggerInfo> list = map.get(key);
+        if (list != null) {
+            list.remove(info);
+            if (list.isEmpty()) map.remove(key);
+        }
+    }
+
+
+    /* ============================================================
+     * 触发入口(外部系统调用)
+     * ============================================================ */
+
+    /** 外部系统事件上报 → 事件触发 */
+    public void onEventReceived(String objCode, String eventKey, Map<String, Object> eventData) {
+        String key = objCode + ":" + eventKey;
+
+        List<TriggerInfo> list = eventTriggers.get(key);
+        if (list == null || list.isEmpty()) return;
+
+        for (TriggerInfo info : list) {
+            OpEnergyStrategyTrigger trigger = info.getTrigger();
+
+            // condition_expr 判断
+            if (!checkCondition(trigger, eventData)) continue;
+
+            executeTrigger(info, "EVENT", eventData, "EVENT:" + eventKey);
+        }
+    }
+
+    /** 外部系统属性变化上报 → 属性触发 */
+    public void onAttrChanged(String objCode, String attrKey, Object newVal) {
+        String key = objCode + ":" + attrKey;
+
+        List<TriggerInfo> list = attrTriggers.get(key);
+        if (list == null || list.isEmpty()) return;
+
+        Map<String, Object> data = new HashMap<>();
+        data.put(attrKey, newVal);
+
+        for (TriggerInfo info : list) {
+            OpEnergyStrategyTrigger trigger = info.getTrigger();
+
+            if (!checkCondition(trigger, data)) continue;
+
+            executeTrigger(info, "ATTR", data, "ATTR:" + attrKey);
+        }
+    }
+
+    /* ============================================================
+     * 公共方法
+     * ============================================================ */
+
+    private boolean checkCondition(OpEnergyStrategyTrigger trigger, Map<String, Object> data) {
+        if (trigger.getConditionExpr() == null || trigger.getConditionExpr().isEmpty()) {
+            return true;
+        }
+
+        try {
+            return conditionEvaluator.evaluate(trigger.getConditionExpr(), data);
+        } catch (Exception e) {
+            log.error("condition_expr 解析失败: {}", trigger.getConditionExpr(), e);
+            return false;
+        }
+    }
+
+    /** 执行策略 */
+    private void executeTrigger(TriggerInfo info, String type, Map<String, Object> data, String source) {
+        String strategyCode = info.getStrategyCode();
+
+        log.info(">>> 触发策略: strategy={}, type={}, source={}", strategyCode, type, source);
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("trigger_type", type);
+        params.put("trigger_source", source);
+        params.put("trigger_id", info.getTrigger().getId());
+        params.put("event_data", data);
+
+        try {
+            getStrategyExecutor().executeStrategy(strategyCode, params);
+        } catch (Exception e) {
+            log.error("触发策略失败: {}", strategyCode, e);
+        }
+    }
+
+    /* ============================================================
+     * Controller 兼容方法(你的控制器中正在使用)
+     * ============================================================ */
+
+    /**
+     * 属性变化入口(兼容 Controller)
+     */
+    public int handleAttrChange(String objCode, String attrKey, Object oldValue, Object newValue) {
+        String key = objCode + ":" + attrKey;
+
+        List<TriggerInfo> list = attrTriggers.get(key);
+        if (list == null || list.isEmpty()) return 0;
+
+        Map<String, Object> eventData = new HashMap<>();
+        eventData.put("oldValue", oldValue);
+        eventData.put("newValue", newValue);
+
+        int count = 0;
+        for (TriggerInfo info : list) {
+            OpEnergyStrategyTrigger trigger = info.getTrigger();
+
+            // 条件判断
+            if (!checkCondition(trigger, eventData)) continue;
+
+            executeTrigger(info, "ATTR", eventData, "ATTR:" + attrKey);
+            count++;
+        }
+
+        return count;
+    }
+
+    /**
+     * 设备事件入口(兼容 Controller)
+     */
+    public int handleDeviceEvent(String objCode, String eventKey, Map<String, Object> eventData) {
+        String key = objCode + ":" + eventKey;
+        List<TriggerInfo> list = eventTriggers.get(key);
+        if (list == null || list.isEmpty()) return 0;
+
+        int count = 0;
+        for (TriggerInfo info : list) {
+            OpEnergyStrategyTrigger trigger = info.getTrigger();
+
+            if (!checkCondition(trigger, eventData)) continue;
+
+            executeTrigger(info, "EVENT", eventData, "EVENT:" + eventKey);
+            count++;
+        }
+
+        return count;
+    }
+
+    /**
+     * 获取已注册触发器(用于 /scheduler/status 接口)
+     */
+    public Map<String, Object> getRegisteredTriggers() {
+        Map<String, Object> map = new HashMap<>();
+
+        map.put("eventTriggers", eventTriggers.keySet());
+        map.put("attrTriggers", attrTriggers.keySet());
+        map.put("strategyIndex", strategyTriggerIndex.keySet());
+
+        return map;
+    }
+
+
+    /* ============================================================
+     * 内部结构
+     * ============================================================ */
+
+    private static class TriggerInfo {
+        private final String strategyCode;
+        private final OpEnergyStrategyTrigger trigger;
+
+        public TriggerInfo(String strategyCode, OpEnergyStrategyTrigger trigger) {
+            this.strategyCode = strategyCode;
+            this.trigger = trigger;
+        }
+
+        public String getStrategyCode() {
+            return strategyCode;
+        }
+
+        public OpEnergyStrategyTrigger getTrigger() {
+            return trigger;
+        }
+    }
+}

+ 0 - 107
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AdmEmsElecPgIndexController.java

@@ -1,107 +0,0 @@
-package com.ruoyi.web.controller.ems;
-
-import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.ems.domain.AdmEmsElecPgIndex;
-import com.ruoyi.ems.service.IAdmEmsElecPgIndexService;
-import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-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 javax.servlet.http.HttpServletResponse;
-import java.util.List;
-
-/**
- * 电网设施指标Controller
- * 
- * @author ruoyi
- * @date 2024-08-30
- */
-@RestController
-@RequestMapping("/ems/elecPgIndex")
-@Api(value = "AdmEmsElecPgIndexController", description = "电网设施指标")
-public class AdmEmsElecPgIndexController extends BaseController
-{
-    @Autowired
-    private IAdmEmsElecPgIndexService elecPgIndexService;
-
-    /**
-     * 查询电网设施指标列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:elecPgIndex:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(AdmEmsElecPgIndex elecPgIndex)
-    {
-        startPage();
-        List<AdmEmsElecPgIndex> list = elecPgIndexService.selectAdmEmsElecPgIndexList(elecPgIndex);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出电网设施指标列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:elecPgIndex:export')")
-    @Log(title = "电网设施指标", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(HttpServletResponse response, AdmEmsElecPgIndex elecPgIndex)
-    {
-        List<AdmEmsElecPgIndex> list = elecPgIndexService.selectAdmEmsElecPgIndexList(elecPgIndex);
-        ExcelUtil<AdmEmsElecPgIndex> util = new ExcelUtil<AdmEmsElecPgIndex>(AdmEmsElecPgIndex.class);
-        util.exportExcel(response, list, "电网设施指标数据");
-    }
-
-    /**
-     * 获取电网设施指标详细信息
-     */
-    @PreAuthorize("@ss.hasPermi('ems:elecPgIndex:query')")
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
-        return success(elecPgIndexService.selectAdmEmsElecPgIndexById(id));
-    }
-
-    /**
-     * 新增电网设施指标
-     */
-    @PreAuthorize("@ss.hasPermi('ems:elecPgIndex:add')")
-    @Log(title = "电网设施指标", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody AdmEmsElecPgIndex elecPgIndex)
-    {
-        return toAjax(elecPgIndexService.insertAdmEmsElecPgIndex(elecPgIndex));
-    }
-
-    /**
-     * 修改电网设施指标
-     */
-    @PreAuthorize("@ss.hasPermi('ems:elecPgIndex:edit')")
-    @Log(title = "电网设施指标", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody AdmEmsElecPgIndex elecPgIndex)
-    {
-        return toAjax(elecPgIndexService.updateAdmEmsElecPgIndex(elecPgIndex));
-    }
-
-    /**
-     * 删除电网设施指标
-     */
-    @PreAuthorize("@ss.hasPermi('ems:elecPgIndex:remove')")
-    @Log(title = "电网设施指标", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
-        return toAjax(elecPgIndexService.deleteAdmEmsElecPgIndexByIds(ids));
-    }
-}

+ 0 - 107
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AdmEmsIndexRangeController.java

@@ -1,107 +0,0 @@
-package com.ruoyi.web.controller.ems;
-
-import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.ems.domain.AdmEmsIndexRange;
-import com.ruoyi.ems.service.IAdmEmsIndexRangeService;
-import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-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 javax.servlet.http.HttpServletResponse;
-import java.util.List;
-
-/**
- * 能源指标范围Controller
- * 
- * @author ruoyi
- * @date 2024-08-30
- */
-@RestController
-@RequestMapping("/ems/indexRange")
-@Api(value = "AdmEmsIndexRangeController", description = "能源指标范围")
-public class AdmEmsIndexRangeController extends BaseController
-{
-    @Autowired
-    private IAdmEmsIndexRangeService indexRangeService;
-
-    /**
-     * 查询能源指标范围列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:indexRange:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(AdmEmsIndexRange indexRange)
-    {
-        startPage();
-        List<AdmEmsIndexRange> list = indexRangeService.selectAdmEmsIndexRangeList(indexRange);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出能源指标范围列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:indexRange:export')")
-    @Log(title = "能源指标范围", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(HttpServletResponse response, AdmEmsIndexRange indexRange)
-    {
-        List<AdmEmsIndexRange> list = indexRangeService.selectAdmEmsIndexRangeList(indexRange);
-        ExcelUtil<AdmEmsIndexRange> util = new ExcelUtil<AdmEmsIndexRange>(AdmEmsIndexRange.class);
-        util.exportExcel(response, list, "能源指标范围数据");
-    }
-
-    /**
-     * 获取能源指标范围详细信息
-     */
-    @PreAuthorize("@ss.hasPermi('ems:indexRange:query')")
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
-        return success(indexRangeService.selectAdmEmsIndexRangeById(id));
-    }
-
-    /**
-     * 新增能源指标范围
-     */
-    @PreAuthorize("@ss.hasPermi('ems:indexRange:add')")
-    @Log(title = "能源指标范围", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody AdmEmsIndexRange indexRange)
-    {
-        return toAjax(indexRangeService.insertAdmEmsIndexRange(indexRange));
-    }
-
-    /**
-     * 修改能源指标范围
-     */
-    @PreAuthorize("@ss.hasPermi('ems:indexRange:edit')")
-    @Log(title = "能源指标范围", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody AdmEmsIndexRange indexRange)
-    {
-        return toAjax(indexRangeService.updateAdmEmsIndexRange(indexRange));
-    }
-
-    /**
-     * 删除能源指标范围
-     */
-    @PreAuthorize("@ss.hasPermi('ems:indexRange:remove')")
-    @Log(title = "能源指标范围", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
-        return toAjax(indexRangeService.deleteAdmEmsIndexRangeByIds(ids));
-    }
-}

+ 0 - 144
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AdmOpAlarmController.java

@@ -1,144 +0,0 @@
-package com.ruoyi.web.controller.ems;
-
-import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.ems.domain.OpAlarm;
-import com.ruoyi.ems.service.IOpAlarmService;
-import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-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.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.servlet.http.HttpServletResponse;
-import java.util.List;
-
-/**
- * 能源设施告警Controller
- *
- * @author ruoyi
- * @date 2024-08-26
- */
-@RestController
-@RequestMapping("/ems/alarm-info")
-@Api(value = "AdmOpAlarmController", description = "能源设施告警")
-public class AdmOpAlarmController extends BaseController {
-    @Autowired
-    private IOpAlarmService opAlarmService;
-
-    /**
-     * 查询能源设施告警列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(OpAlarm admOpAlarm) {
-        startPage();
-        List<OpAlarm> list = opAlarmService.selectOpAlarmList(admOpAlarm);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出能源设施告警列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:export')")
-    @Log(title = "能源设施告警", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(HttpServletResponse response, OpAlarm admOpAlarm) {
-        List<OpAlarm> list = opAlarmService.selectOpAlarmList(admOpAlarm);
-        ExcelUtil<OpAlarm> util = new ExcelUtil<>(OpAlarm.class);
-        util.exportExcel(response, list, "能源设施告警数据");
-    }
-
-    /**
-     * 获取能源设施告警详细信息
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:query')")
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id) {
-        return success(opAlarmService.selectOpAlarmById(id));
-    }
-
-    /**
-     * 新增能源设施告警
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:add')")
-    @Log(title = "能源设施告警", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody OpAlarm admOpAlarm) {
-        return toAjax(opAlarmService.insertOpAlarm(admOpAlarm));
-    }
-
-    /**
-     * 修改能源设施告警
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:edit')")
-    @Log(title = "能源设施告警", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody OpAlarm admOpAlarm) {
-        return toAjax(opAlarmService.updateOpAlarm(admOpAlarm));
-    }
-
-    /**
-     * 删除能源设施告警
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:remove')")
-    @Log(title = "能源设施告警", businessType = BusinessType.DELETE)
-    @DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids) {
-        return toAjax(opAlarmService.deleteOpAlarmByIds(ids));
-    }
-
-    @GetMapping("/alarm/type/index")
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:query')")
-    public AjaxResult qryAlarmTypeIndex(@RequestParam("areaCode") String areaCode,
-        @RequestParam("startRecTime") String startTime, @RequestParam("endRecTime") String endTime) {
-        return success(opAlarmService.qryAlarmTypeIndex(areaCode, startTime, endTime));
-    }
-
-    @GetMapping("/alarm/type/index/day")
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:query')")
-    public AjaxResult qryAlarmTypeIndexDay(@RequestParam("areaCode") String areaCode) {
-        return success(opAlarmService.qryAlarmTypeIndexDay(areaCode));
-    }
-
-    @GetMapping("/alarm/type/index/month")
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:query')")
-    public AjaxResult qryAlarmTypeIndexMonth(@RequestParam("areaCode") String areaCode) {
-        return success(opAlarmService.qryAlarmTypeIndexMonth(areaCode));
-    }
-
-    @GetMapping("/alarm/type/index/year")
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:query')")
-    public AjaxResult qryAlarmTypeIndexYear(@RequestParam("areaCode") String areaCode) {
-        return success(opAlarmService.qryAlarmTypeIndexYear(areaCode));
-    }
-
-    @GetMapping("/subsys/index/day")
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:query')")
-    public AjaxResult qrySubSysIndexDay(@RequestParam("areaCode") String areaCode) {
-        return success(opAlarmService.qrySubSysIndexDay(areaCode));
-    }
-
-    @GetMapping("/subsys/index/month")
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:query')")
-    public AjaxResult qrySubSysIndexMonth(@RequestParam("areaCode") String areaCode) {
-        return success(opAlarmService.qrySubSysIndexMonth(areaCode));
-    }
-
-    @GetMapping("/subsys/index/year")
-    @PreAuthorize("@ss.hasPermi('ems:alarm-info:query')")
-    public AjaxResult qrySubSysIndexYear(@RequestParam("areaCode") String areaCode) {
-        return success(opAlarmService.qrySubSysIndexYear(areaCode));
-    }
-}

+ 0 - 107
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AdmOpAlarmPolicyController.java

@@ -1,107 +0,0 @@
-package com.ruoyi.web.controller.ems;
-
-import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.ems.domain.AdmOpAlarmPolicy;
-import com.ruoyi.ems.service.IAdmOpAlarmPolicyService;
-import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-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 javax.servlet.http.HttpServletResponse;
-import java.util.List;
-
-/**
- * 能源设施告警策略Controller
- *
- * @author ruoyi
- * @date 2024-08-26
- */
-@RestController
-@RequestMapping("/ems/alarm")
-@Api(value = "AdmOpAlarmPolicyController", description = "能源设施告警策略")
-public class AdmOpAlarmPolicyController extends BaseController
-{
-    @Autowired
-    private IAdmOpAlarmPolicyService opAlarmPolicyService;
-
-    /**
-     * 查询能源设施告警策略列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-strategy:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(AdmOpAlarmPolicy opAlarmPolicy)
-    {
-        startPage();
-        List<AdmOpAlarmPolicy> list = opAlarmPolicyService.selectAdmOpAlarmPolicyList(opAlarmPolicy);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出能源设施告警策略列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-strategy:export')")
-    @Log(title = "能源设施告警策略", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(HttpServletResponse response, AdmOpAlarmPolicy opAlarmPolicy)
-    {
-        List<AdmOpAlarmPolicy> list = opAlarmPolicyService.selectAdmOpAlarmPolicyList(opAlarmPolicy);
-        ExcelUtil<AdmOpAlarmPolicy> util = new ExcelUtil<AdmOpAlarmPolicy>(AdmOpAlarmPolicy.class);
-        util.exportExcel(response, list, "能源设施告警策略数据");
-    }
-
-    /**
-     * 获取能源设施告警策略详细信息
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-strategy:query')")
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
-        return success(opAlarmPolicyService.selectAdmOpAlarmPolicyById(id));
-    }
-
-    /**
-     * 新增能源设施告警策略
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-strategy:add')")
-    @Log(title = "能源设施告警策略", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody AdmOpAlarmPolicy opAlarmPolicy)
-    {
-        return toAjax(opAlarmPolicyService.insertAdmOpAlarmPolicy(opAlarmPolicy));
-    }
-
-    /**
-     * 修改能源设施告警策略
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-strategy:edit')")
-    @Log(title = "能源设施告警策略", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody AdmOpAlarmPolicy opAlarmPolicy)
-    {
-        return toAjax(opAlarmPolicyService.updateAdmOpAlarmPolicy(opAlarmPolicy));
-    }
-
-    /**
-     * 删除能源设施告警策略
-     */
-    @PreAuthorize("@ss.hasPermi('ems:alarm-strategy:remove')")
-    @Log(title = "能源设施告警策略", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
-        return toAjax(opAlarmPolicyService.deleteAdmOpAlarmPolicyByIds(ids));
-    }
-}

+ 0 - 100
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AdmOpInspectionReportController.java

@@ -1,100 +0,0 @@
-package com.ruoyi.web.controller.ems;
-
-import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.ems.domain.AdmOpInspectionReport;
-import com.ruoyi.ems.service.IAdmOpInspectionReportService;
-import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-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 javax.servlet.http.HttpServletResponse;
-import java.util.List;
-
-/**
- * 巡检报告Controller
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-@RestController
-@RequestMapping("/ems/inspectionReport")
-@Api(value = "AdmOpInspectionReportController", description = "巡检报告")
-public class AdmOpInspectionReportController extends BaseController {
-    @Autowired
-    private IAdmOpInspectionReportService inspectionReportService;
-
-    /**
-     * 查询巡检报告列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-report:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(AdmOpInspectionReport inspectionReport) {
-        startPage();
-        List<AdmOpInspectionReport> list = inspectionReportService.selectAdmOpInspectionReportList(inspectionReport);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出巡检报告列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-report:export')")
-    @Log(title = "巡检报告", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(HttpServletResponse response, AdmOpInspectionReport inspectionReport) {
-        List<AdmOpInspectionReport> list = inspectionReportService.selectAdmOpInspectionReportList(inspectionReport);
-        ExcelUtil<AdmOpInspectionReport> util = new ExcelUtil<AdmOpInspectionReport>(AdmOpInspectionReport.class);
-        util.exportExcel(response, list, "巡检报告数据");
-    }
-
-    /**
-     * 获取巡检报告详细信息
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-report:query')")
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id) {
-        return success(inspectionReportService.selectAdmOpInspectionReportById(id));
-    }
-
-    /**
-     * 新增巡检报告
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-report:add')")
-    @Log(title = "巡检报告", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody AdmOpInspectionReport inspectionReport) {
-        return toAjax(inspectionReportService.insertAdmOpInspectionReport(inspectionReport));
-    }
-
-    /**
-     * 修改巡检报告
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-report:edit')")
-    @Log(title = "巡检报告", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody AdmOpInspectionReport inspectionReport) {
-        return toAjax(inspectionReportService.updateAdmOpInspectionReport(inspectionReport));
-    }
-
-    /**
-     * 删除巡检报告
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-report:remove')")
-    @Log(title = "巡检报告", businessType = BusinessType.DELETE)
-    @DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids) {
-        return toAjax(inspectionReportService.deleteAdmOpInspectionReportByIds(ids));
-    }
-}

+ 252 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AlarmController.java

@@ -0,0 +1,252 @@
+/*
+ * 文 件 名:  AlarmController
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2026/2/3
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.ems.domain.Alarm;
+import com.ruoyi.ems.domain.AlarmHandle;
+import com.ruoyi.ems.service.IAlarmService;
+import com.ruoyi.ems.service.alarm.AlarmProcessService;
+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;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 告警管理Controller
+ */
+@RestController
+@RequestMapping("/ems/alarm")
+@Api(value = "AlarmController", description = "告警管理")
+public class AlarmController extends BaseController {
+    @Autowired
+    private IAlarmService alarmService;
+    @Autowired
+    private AlarmProcessService processService;
+    /**
+     * 查询告警列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:list')")
+    @GetMapping("/list")
+    @ApiOperation("查询告警列表")
+    public TableDataInfo list(Alarm alarm) {
+        startPage();
+        List<Alarm> list = alarmService.selectList(alarm);
+        return getDataTable(list);
+    }
+    /**
+     * 查询活动告警
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:list')")
+    @GetMapping("/active")
+    @ApiOperation("查询活动告警")
+    public AjaxResult listActive(@RequestParam(value = "areaCode", required = false) String areaCode) {
+        return success(alarmService.selectActiveAlarms(areaCode));
+    }
+    /**
+     * 导出告警列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:export')")
+    @Log(title = "告警管理", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    @ApiOperation("导出告警")
+    public void export(HttpServletResponse response, Alarm alarm) {
+        List<Alarm> list = alarmService.selectList(alarm);
+        ExcelUtil<Alarm> util = new ExcelUtil<>(Alarm.class);
+        util.exportExcel(response, list, "告警数据");
+    }
+    /**
+     * 获取告警详情
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:query')")
+    @GetMapping("/{id}")
+    @ApiOperation("获取告警详情")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        Alarm alarm = alarmService.selectById(id);
+        if (alarm != null) {
+            // 加载处置记录
+            alarm.setHandleList(alarmService.selectHandlesByAlarmId(alarm.getAlarmId()));
+        }
+        return success(alarm);
+    }
+    /**
+     * 根据告警ID获取详情
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:query')")
+    @GetMapping("/detail/{alarmId}")
+    @ApiOperation("根据告警ID获取详情")
+    public AjaxResult getByAlarmId(@PathVariable("alarmId") String alarmId) {
+        Alarm alarm = alarmService.selectByAlarmId(alarmId);
+        if (alarm != null) {
+            alarm.setHandleList(alarmService.selectHandlesByAlarmId(alarmId));
+        }
+        return success(alarm);
+    }
+    /**
+     * 手动上报告警
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:add')")
+    @Log(title = "告警管理", businessType = BusinessType.INSERT)
+    @PostMapping("/report")
+    @ApiOperation("手动上报告警")
+    public AjaxResult report(@RequestBody Alarm alarm) {
+        String reporter = SecurityUtils.getUsername();
+        Alarm result = processService.reportAlarm(alarm, reporter);
+        return success(result);
+    }
+    /**
+     * 确认告警
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:handle')")
+    @Log(title = "告警管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/confirm/{alarmId}")
+    @ApiOperation("确认告警")
+    public AjaxResult confirm(@PathVariable("alarmId") String alarmId,
+        @RequestBody AlarmHandle handle) {
+        String operator = SecurityUtils.getUsername();
+        processService.confirmAlarm(alarmId, operator, handle.getRemark());
+        return success();
+    }
+    /**
+     * 处置告警
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:handle')")
+    @Log(title = "告警管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/handle/{alarmId}")
+    @ApiOperation("处置告警")
+    public AjaxResult handle(@PathVariable("alarmId") String alarmId,
+        @RequestBody AlarmHandle handle) {
+        String operator = SecurityUtils.getUsername();
+        processService.handleAlarm(alarmId, handle.getHandleContent(), handle.getHandleResult(), operator);
+        return success();
+    }
+    /**
+     * 解决告警
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:handle')")
+    @Log(title = "告警管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/resolve/{alarmId}")
+    @ApiOperation("解决告警")
+    public AjaxResult resolve(@PathVariable("alarmId") String alarmId,
+        @RequestBody Alarm alarm) {
+        String operator = SecurityUtils.getUsername();
+        processService.resolveAlarm(alarmId, alarm.getResolveRemark(), operator);
+        return success();
+    }
+    /**
+     * 关闭告警
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:handle')")
+    @Log(title = "告警管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/close/{alarmId}")
+    @ApiOperation("关闭告警")
+    public AjaxResult close(@PathVariable("alarmId") String alarmId, @RequestBody Alarm alarm) {
+        String operator = SecurityUtils.getUsername();
+        // 从实体中获取 reason
+        processService.closeAlarm(alarmId, alarm.getResolveRemark(), operator);
+        return success();
+    }
+    /**
+     * 批量确认告警
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:handle')")
+    @Log(title = "告警管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/batchConfirm")
+    @ApiOperation("批量确认告警")
+    public AjaxResult batchConfirm(@RequestBody List<String> alarmIds) {
+        String operator = SecurityUtils.getUsername();
+        int count = processService.batchConfirm(alarmIds, operator);
+        return success("成功确认 " + count + " 条告警");
+    }
+    /**
+     * 删除告警
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:remove')")
+    @Log(title = "告警管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    @ApiOperation("删除告警")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(alarmService.deleteByIds(ids));
+    }
+    // ==================== 统计接口 ====================
+    /**
+     * 告警统计概览
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:query')")
+    @GetMapping("/stats/overview")
+    @ApiOperation("告警统计概览")
+    public AjaxResult statsOverview(Alarm query) {
+        return success(alarmService.selectAlarmStats(query));
+    }
+    /**
+     * 按级别统计
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:query')")
+    @GetMapping("/stats/byLevel")
+    @ApiOperation("按级别统计")
+    public AjaxResult countByLevel(Alarm query) {
+        return success(alarmService.countByLevel(query));
+    }
+    /**
+     * 按状态统计
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:query')")
+    @GetMapping("/stats/byStatus")
+    @ApiOperation("按状态统计")
+    public AjaxResult countByStatus(Alarm query) {
+        return success(alarmService.countByStatus(query));
+    }
+    /**
+     * 按时间趋势统计
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:query')")
+    @GetMapping("/stats/trend")
+    @ApiOperation("按时间趋势统计")
+    public AjaxResult countByTrend(Alarm query,
+        @RequestParam(value = "granularity", defaultValue = "day") String granularity) {
+        return success(alarmService.countByTimeTrend(query, granularity));
+    }
+    /**
+     * 按子系统统计
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:query')")
+    @GetMapping("/stats/bySubsystem")
+    @ApiOperation("按子系统统计")
+    public AjaxResult countBySubsystem(Alarm query) {
+        return success(alarmService.countBySubsystem(query));
+    }
+    /**
+     * 处理率统计
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm:query')")
+    @GetMapping("/stats/handleRate")
+    @ApiOperation("处理率统计")
+    public AjaxResult handleRate(Alarm query) {
+        return success(alarmService.countHandleRate(query));
+    }
+}

+ 184 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AlarmRuleController.java

@@ -0,0 +1,184 @@
+/*
+ * 文 件 名:  AlarmRuleController
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2026/2/3
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.ems.domain.AlarmRule;
+import com.ruoyi.ems.service.IAlarmRuleService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 告警规则Controller
+ */
+@RestController
+@RequestMapping("/ems/alarm/rule")
+@Api(value = "AlarmRuleController", description = "告警规则管理")
+public class AlarmRuleController extends BaseController {
+    @Autowired
+    private IAlarmRuleService ruleService;
+    /**
+     * 查询告警规则列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:list')")
+    @GetMapping("/list")
+    @ApiOperation("查询告警规则列表")
+    public TableDataInfo list(AlarmRule rule) {
+        startPage();
+        List<AlarmRule> list = ruleService.selectList(rule);
+        return getDataTable(list);
+    }
+    /**
+     * 查询全部规则(不分页)
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:list')")
+    @GetMapping("/listAll")
+    @ApiOperation("查询全部告警规则")
+    public AjaxResult listAll(AlarmRule rule) {
+        List<AlarmRule> list = ruleService.selectList(rule);
+        return success(list);
+    }
+    /**
+     * 导出告警规则列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:export')")
+    @Log(title = "告警规则", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    @ApiOperation("导出告警规则")
+    public void export(HttpServletResponse response, AlarmRule rule) {
+        List<AlarmRule> list = ruleService.selectList(rule);
+        ExcelUtil<AlarmRule> util = new ExcelUtil<>(AlarmRule.class);
+        util.exportExcel(response, list, "告警规则数据");
+    }
+    /**
+     * 获取告警规则详情
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:query')")
+    @GetMapping("/{id}")
+    @ApiOperation("获取告警规则详情")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(ruleService.selectById(id));
+    }
+    /**
+     * 根据规则代码获取详情
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:query')")
+    @GetMapping("/code/{ruleCode}")
+    @ApiOperation("根据规则代码获取详情")
+    public AjaxResult getByCode(@PathVariable("ruleCode") String ruleCode) {
+        return success(ruleService.selectByRuleCode(ruleCode));
+    }
+    /**
+     * 新增告警规则
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:add')")
+    @Log(title = "告警规则", businessType = BusinessType.INSERT)
+    @PostMapping
+    @ApiOperation("新增告警规则")
+    public AjaxResult add(@RequestBody AlarmRule rule) {
+        rule.setCreateBy(SecurityUtils.getUsername());
+        return toAjax(ruleService.insert(rule));
+    }
+    /**
+     * 修改告警规则
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:edit')")
+    @Log(title = "告警规则", businessType = BusinessType.UPDATE)
+    @PutMapping
+    @ApiOperation("修改告警规则")
+    public AjaxResult edit(@RequestBody AlarmRule rule) {
+        rule.setUpdateBy(SecurityUtils.getUsername());
+        return toAjax(ruleService.update(rule));
+    }
+    /**
+     * 删除告警规则
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:remove')")
+    @Log(title = "告警规则", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    @ApiOperation("删除告警规则")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(ruleService.deleteByIds(ids));
+    }
+    /**
+     * 启用/禁用规则
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:edit')")
+    @Log(title = "告警规则", businessType = BusinessType.UPDATE)
+    @PutMapping("/enable/{ruleCode}/{enabled}")
+    @ApiOperation("启用/禁用规则")
+    public AjaxResult updateEnabled(@PathVariable("ruleCode") String ruleCode,
+        @PathVariable("enabled") Integer enabled) {
+        return toAjax(ruleService.updateEnabled(ruleCode, enabled));
+    }
+    /**
+     * 批量启用/禁用
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:edit')")
+    @Log(title = "告警规则", businessType = BusinessType.UPDATE)
+    @PutMapping("/batchEnable")
+    @ApiOperation("批量启用/禁用")
+    public AjaxResult batchUpdateEnabled(@RequestBody List<String> ruleCodes,
+        @RequestParam("enabled") Integer enabled) {
+        return toAjax(ruleService.batchUpdateEnabled(ruleCodes, enabled));
+    }
+    /**
+     * 复制规则
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:add')")
+    @Log(title = "告警规则", businessType = BusinessType.INSERT)
+    @PostMapping("/copy")
+    @ApiOperation("复制规则")
+    public AjaxResult copyRule(@RequestParam("sourceRuleCode") String sourceRuleCode,
+        @RequestParam("newRuleName") String newRuleName) {
+        AlarmRule newRule = ruleService.copyRule(sourceRuleCode, newRuleName);
+        return success(newRule);
+    }
+    /**
+     * 查询指定分组的规则
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:list')")
+    @GetMapping("/group/{groupCode}")
+    @ApiOperation("查询指定分组的规则")
+    public AjaxResult listByGroup(@PathVariable("groupCode") String groupCode) {
+        return success(ruleService.selectByGroupCode(groupCode));
+    }
+    /**
+     * 查询适用于巡检的规则
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:list')")
+    @GetMapping("/forInspection")
+    @ApiOperation("查询适用于巡检的规则")
+    public AjaxResult listForInspection(@RequestParam(value = "deviceModel", required = false) String deviceModel) {
+        return success(ruleService.selectRulesForInspection(deviceModel));
+    }
+    /**
+     * 查询适用于实时监测的规则
+     */
+    @PreAuthorize("@ss.hasPermi('ems:alarm-rule:list')")
+    @GetMapping("/forRealtime")
+    @ApiOperation("查询适用于实时监测的规则")
+    public AjaxResult listForRealtime(@RequestParam(value = "deviceModel", required = false) String deviceModel,
+        @RequestParam(value = "attrKey", required = false) String attrKey) {
+        return success(ruleService.selectRulesForRealtime(deviceModel, attrKey));
+    }
+}

+ 49 - 38
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/AreaController.java

@@ -1,11 +1,13 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.Area;
 import com.ruoyi.ems.domain.EmsFacs;
+import com.ruoyi.ems.domain.MeterDevice;
 import com.ruoyi.ems.domain.ObjTagRel;
 import com.ruoyi.ems.enums.BusObjType;
 import com.ruoyi.ems.model.TreeEntity;
@@ -14,8 +16,8 @@ import com.ruoyi.ems.service.IEmsFacsService;
 import com.ruoyi.ems.service.IObjTagRelService;
 import com.ruoyi.ems.util.AreaUtils;
 import io.swagger.annotations.Api;
+import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -25,7 +27,7 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
-
+import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -43,13 +45,10 @@ import java.util.stream.Collectors;
 public class AreaController extends BaseController {
     @Autowired
     private IAreaService areaService;
-
     @Autowired
     private IEmsFacsService facsService;
-
     @Autowired
     private IObjTagRelService objTagRelService;
-
     /**
      * 查询区域对象列表
      */
@@ -59,7 +58,6 @@ public class AreaController extends BaseController {
         List<Area> list = areaService.selectArea(area);
         return success(list);
     }
-
     /**
      * 查询区域对象列表
      */
@@ -69,35 +67,30 @@ public class AreaController extends BaseController {
         List<Area> list = areaService.selectAreaDetail(area);
         return success(list);
     }
-
     /**
      * 查询区域对象列表
      */
     @PreAuthorize("@ss.hasPermi('ems:area:list')")
     @GetMapping("/getAreaListByTag")
-    public AjaxResult getAreaListByTag(@RequestParam(name = "parentCode", required = false, defaultValue = "0") String parentCode,
+    public AjaxResult getAreaListByTag(
+        @RequestParam(name = "parentCode", required = false, defaultValue = "0") String parentCode,
         @RequestParam(name = "recursion", required = false, defaultValue = "true") boolean recursion,
         @RequestParam(name = "tagCode") String tagCode) {
         // 查询区域树
         List<Area> areaTree = areaService.selectAreaTree(parentCode, recursion);
-
         // 查询列表
         List<ObjTagRel> tagRels = objTagRelService.selectListByTagCode(BusObjType.AREA.getValue(), tagCode);
-        Set<String> facsAreaCodes = tagRels.stream().map(ObjTagRel::getObjCode).collect(Collectors.toSet());
-
+        Set<String> tagAreaCodes = tagRels.stream().map(ObjTagRel::getObjCode).collect(Collectors.toSet());
         // 过滤树节点
         List<Area> areaTargets = AreaUtils.compressTree(areaTree);
-
         Iterator<Area> it = areaTargets.iterator();
         while (it.hasNext()) {
-            if (!facsAreaCodes.contains(it.next().getAreaCode())) {
+            if (!tagAreaCodes.contains(it.next().getAreaCode())) {
                 it.remove();
             }
         }
-
         return success(areaTargets);
     }
-
     /**
      * 获取服务区树
      *
@@ -106,12 +99,18 @@ public class AreaController extends BaseController {
      */
     @GetMapping(value = "/getAreaTree")
     public AjaxResult getAreaTree(@RequestParam(name = "rootCode") String rootCode,
-        @RequestParam(name = "recursion", required = false) boolean recursion) {
-        List<Area> areas = areaService.selectAreaTree(rootCode, recursion);
-        List<TreeEntity> ret = AreaUtils.convertAreaTree(areas);
+        @RequestParam(name = "layer", required = false) Integer layer) {
+        List<TreeEntity> ret = null;
+        if (null == layer) {
+            List<Area> areas = areaService.selectAreaTree(rootCode, true);
+            ret = AreaUtils.convertAreaTree(areas);
+        }
+        else {
+            List<Area> areas = areaService.selectAreaTree(rootCode, layer > 1);
+            ret = AreaUtils.convertAreaTree(areas, layer);
+        }
         return success(ret);
     }
-
     /**
      * 查询区域树 (根据设施类型过滤)
      *
@@ -124,23 +123,19 @@ public class AreaController extends BaseController {
         @RequestParam(name = "facsCategory") String facsCategory,
         @RequestParam(name = "facsSubCategory", required = false) String facsSubCategory,
         @RequestParam(name = "recursion", required = false) boolean recursion) {
-
         // 查询区域树
         List<Area> areaTree = areaService.selectAreaTree(parentCode, recursion);
-
         // 查询设施列表
         EmsFacs facsParam = new EmsFacs();
         facsParam.setFacsCategory(facsCategory);
         facsParam.setFacsSubCategory(facsSubCategory);
         List<EmsFacs> facsList = facsService.selectEmsFacsList(facsParam);
         Set<String> facsAreaCodes = facsList.stream().map(EmsFacs::getRefArea).collect(Collectors.toSet());
-
         // 过滤树节点
         areaTree = AreaUtils.filterTree(areaTree, facsAreaCodes);
         List<TreeEntity> retList = AreaUtils.convertAreaTree(areaTree);
         return success(retList);
     }
-
     /**
      * 查询区域树 (根据对象标签过滤)
      *
@@ -150,23 +145,20 @@ public class AreaController extends BaseController {
      * @return 区域位置树
      */
     @GetMapping(value = "/getAreaTreeByTag")
-    public AjaxResult getAreaTreeByTag(@RequestParam(name = "parentCode", required = false, defaultValue = "0") String parentCode,
+    public AjaxResult getAreaTreeByTag(
+        @RequestParam(name = "parentCode", required = false, defaultValue = "0") String parentCode,
         @RequestParam(name = "recursion", required = false, defaultValue = "true") boolean recursion,
         @RequestParam(name = "tagCode") String tagCode) {
         // 查询区域树
         List<Area> areaTree = areaService.selectAreaTree(parentCode, recursion);
-
         // 查询列表
-        List<ObjTagRel> tagRels = objTagRelService.selectListByTagCode(BusObjType.AREA.getValue(), tagCode);
-        Set<String> facsAreaCodes = tagRels.stream().map(ObjTagRel::getObjCode).collect(Collectors.toSet());
-
+        List<Area> list = areaService.selectAreaByTag(tagCode);
+        Set<String> tagAreaCodes = list.stream().map(Area::getAreaCode).collect(Collectors.toSet());
         // 过滤树节点
-        areaTree = AreaUtils.filterTree(areaTree, facsAreaCodes);
-        List<TreeEntity> retList = AreaUtils.convertAreaTree(areaTree);
-
+        areaTree = AreaUtils.filterTree(areaTree, tagAreaCodes);
+        List<TreeEntity> retList = convertAreaTreeHelper(areaTree, tagAreaCodes, 1, Integer.MAX_VALUE);
         return success(retList);
     }
-
     /**
      * 获取区域对象详细信息
      */
@@ -175,13 +167,11 @@ public class AreaController extends BaseController {
     public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(areaService.selectAreaById(id));
     }
-
     @PreAuthorize("@ss.hasPermi('ems:area:query')")
     @GetMapping(value = "/getByCode")
     public AjaxResult getByCode(@RequestParam("areaCode") String areaCode) {
         return success(areaService.selectAreaByCode(areaCode));
     }
-
     /**
      * 新增区域对象
      */
@@ -191,7 +181,6 @@ public class AreaController extends BaseController {
     public AjaxResult add(@RequestBody Area area) {
         return toAjax(areaService.insertArea(area));
     }
-
     /**
      * 修改区域对象
      */
@@ -201,7 +190,6 @@ public class AreaController extends BaseController {
     public AjaxResult edit(@RequestBody Area area) {
         return toAjax(areaService.updateArea(area));
     }
-
     /**
      * 删除区域对象
      */
@@ -211,5 +199,28 @@ public class AreaController extends BaseController {
     public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(areaService.deleteAreaByIds(ids));
     }
-
+    private static List<TreeEntity> convertAreaTreeHelper(List<?> areas, Set<String> tagAreaCodes, int currentLevel,
+        int maxLevel) {
+        List<TreeEntity> retList = new ArrayList<>();
+        if (CollectionUtils.isEmpty(areas)) {
+            return retList;
+        }
+        for (Object obj : areas) {
+            Area area = (Area) obj;
+            TreeEntity tree = new TreeEntity();
+            tree.setId(area.getAreaCode());
+            tree.setLabel(area.getAreaName());
+            if (tagAreaCodes.contains(area.getAreaCode())) {
+                tree.setType("seller");
+            } else {
+                tree.setType("area");
+            }
+            // 当当前层级小于最大层级时,递归处理子节点
+            if (currentLevel < maxLevel && CollectionUtils.isNotEmpty(area.getChildren())) {
+                tree.setChildren(convertAreaTreeHelper(area.getChildren(), tagAreaCodes, currentLevel + 1, maxLevel));
+            }
+            retList.add(tree);
+        }
+        return retList;
+    }
 }

+ 12 - 19
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/CaConfigController.java

@@ -1,16 +1,16 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.EmissionFactor;
 import com.ruoyi.ems.service.IEmissionFactorService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -25,15 +25,14 @@ import java.util.List;
 
 /**
  * 排放因子Controller
- * 
+ *
  * @author ruoyi
  * @date 2024-07-11
  */
 @RestController
 @RequestMapping("/ems/basecfg/cacfg")
 @Api(value = "CaConfigController", description = "排放因子管理")
-public class CaConfigController extends BaseController
-{
+public class CaConfigController extends BaseController {
     @Autowired
     private IEmissionFactorService emissionFactorService;
 
@@ -42,8 +41,7 @@ public class CaConfigController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('basecfg:cacfg:list')")
     @GetMapping("/emissionFactor/list")
-    public TableDataInfo listEmissionFactor(EmissionFactor emissionFactor)
-    {
+    public TableDataInfo listEmissionFactor(EmissionFactor emissionFactor) {
         startPage();
         List<EmissionFactor> list = emissionFactorService.selectEmissionFactorList(emissionFactor);
         return getDataTable(list);
@@ -55,8 +53,7 @@ public class CaConfigController extends BaseController
     @PreAuthorize("@ss.hasPermi('basecfg:cacfg:export')")
     @Log(title = "排放因子", businessType = BusinessType.EXPORT)
     @PostMapping("/emissionFactor/export")
-    public void export(HttpServletResponse response, EmissionFactor emissionFactor)
-    {
+    public void export(HttpServletResponse response, EmissionFactor emissionFactor) {
         List<EmissionFactor> list = emissionFactorService.selectEmissionFactorList(emissionFactor);
         ExcelUtil<EmissionFactor> util = new ExcelUtil<EmissionFactor>(EmissionFactor.class);
         util.exportExcel(response, list, "排放因子维数据");
@@ -67,8 +64,7 @@ public class CaConfigController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('basecfg:cacfg:query')")
     @GetMapping(value = "/emissionFactor/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(emissionFactorService.selectEmissionFactorById(id));
     }
 
@@ -78,8 +74,7 @@ public class CaConfigController extends BaseController
     @PreAuthorize("@ss.hasPermi('basecfg:cacfg:add')")
     @Log(title = "排放因子", businessType = BusinessType.INSERT)
     @PostMapping(value = "/emissionFactor")
-    public AjaxResult add(@RequestBody EmissionFactor emissionFactor)
-    {
+    public AjaxResult add(@RequestBody EmissionFactor emissionFactor) {
         return toAjax(emissionFactorService.insertEmissionFactor(emissionFactor));
     }
 
@@ -89,8 +84,7 @@ public class CaConfigController extends BaseController
     @PreAuthorize("@ss.hasPermi('basecfg:cacfg:edit')")
     @Log(title = "排放因子", businessType = BusinessType.UPDATE)
     @PutMapping(value = "/emissionFactor")
-    public AjaxResult edit(@RequestBody EmissionFactor emissionFactor)
-    {
+    public AjaxResult edit(@RequestBody EmissionFactor emissionFactor) {
         return toAjax(emissionFactorService.updateEmissionFactor(emissionFactor));
     }
 
@@ -99,9 +93,8 @@ public class CaConfigController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('basecfg:cacfg:remove')")
     @Log(title = "排放因子", businessType = BusinessType.DELETE)
-	@DeleteMapping("/emissionFactor/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
+    @DeleteMapping("/emissionFactor/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(emissionFactorService.deleteEmissionFactorByIds(ids));
     }
 }

+ 11 - 18
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/CaEmissionForecastController.java

@@ -1,16 +1,16 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.CaEmissionForecast;
 import com.ruoyi.ems.service.ICaEmissionForecastService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -32,8 +32,7 @@ import java.util.List;
 @RestController
 @RequestMapping("/ems/forecastCa")
 @Api(value = "CaEmissionForecastController", description = "碳排放预测数据接口")
-public class CaEmissionForecastController extends BaseController
-{
+public class CaEmissionForecastController extends BaseController {
     @Autowired
     private ICaEmissionForecastService caEmissionForecastService;
 
@@ -42,8 +41,7 @@ public class CaEmissionForecastController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('prediction:ca:list')")
     @GetMapping("/list")
-    public TableDataInfo list(CaEmissionForecast emissionForecast)
-    {
+    public TableDataInfo list(CaEmissionForecast emissionForecast) {
         startPage();
         List<CaEmissionForecast> list = caEmissionForecastService.selectCaEmissionForecastList(emissionForecast);
         return getDataTable(list);
@@ -55,8 +53,7 @@ public class CaEmissionForecastController extends BaseController
     @PreAuthorize("@ss.hasPermi('prediction:ca:export')")
     @Log(title = "碳排放预测", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
-    public void export(HttpServletResponse response, CaEmissionForecast emissionForecast)
-    {
+    public void export(HttpServletResponse response, CaEmissionForecast emissionForecast) {
         List<CaEmissionForecast> list = caEmissionForecastService.selectCaEmissionForecastList(emissionForecast);
         ExcelUtil<CaEmissionForecast> util = new ExcelUtil<CaEmissionForecast>(CaEmissionForecast.class);
         util.exportExcel(response, list, "碳排放预测数据");
@@ -67,8 +64,7 @@ public class CaEmissionForecastController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('prediction:ca:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(caEmissionForecastService.selectCaEmissionForecastById(id));
     }
 
@@ -78,8 +74,7 @@ public class CaEmissionForecastController extends BaseController
     @PreAuthorize("@ss.hasPermi('prediction:ca:add')")
     @Log(title = "碳排放预测", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody CaEmissionForecast emissionForecast)
-    {
+    public AjaxResult add(@RequestBody CaEmissionForecast emissionForecast) {
         return toAjax(caEmissionForecastService.insertCaEmissionForecast(emissionForecast));
     }
 
@@ -89,8 +84,7 @@ public class CaEmissionForecastController extends BaseController
     @PreAuthorize("@ss.hasPermi('prediction:ca:edit')")
     @Log(title = "碳排放预测", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody CaEmissionForecast CaEmissionForecast)
-    {
+    public AjaxResult edit(@RequestBody CaEmissionForecast CaEmissionForecast) {
         return toAjax(caEmissionForecastService.updateCaEmissionForecast(CaEmissionForecast));
     }
 
@@ -99,9 +93,8 @@ public class CaEmissionForecastController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('prediction:ca:remove')")
     @Log(title = "碳排放预测", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(caEmissionForecastService.deleteCaEmissionForecastByIds(ids));
     }
 }

+ 52 - 51
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/CaMeterDController.java

@@ -1,107 +1,108 @@
 package com.ruoyi.web.controller.ems;
 
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.CaMeterD;
 import com.ruoyi.ems.service.ICaMeterDService;
 import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-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 javax.servlet.http.HttpServletResponse;
-import java.util.List;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
 
 /**
  * 碳计量日Controller
- *
- * @author ruoyi
- * @date 2024-08-12
  */
 @RestController
 @RequestMapping("/ems/caMeterD")
 @Api(value = "CaMeterDController", description = "碳计量日数据访问接口")
-public class CaMeterDController extends BaseController
-{
+public class CaMeterDController extends BaseController {
     @Autowired
     private ICaMeterDService caMeterDService;
 
     /**
-     * 查询碳计量列表
+     * 查询碳计量列表(支持按日/月/年,分页)
      */
     @PreAuthorize("@ss.hasPermi('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);
     }
 
     /**
-     * 导出碳计量日列表
+     * 查询全部数据(不分页,用于统计)
+     */
+    @PreAuthorize("@ss.hasPermi('ca-analysis:emission:list')")
+    @GetMapping("/list/all")
+    @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);
+    }
+
+    /**
+     * 查询平均值
+     */
+    @PreAuthorize("@ss.hasPermi('ca-analysis:emission:list')")
+    @GetMapping("/area/avg/ca")
+    @ApiOperation("查询碳计量平均值")
+    public AjaxResult qryAvgCa(CaMeterD caMeterD) {
+        List<CaMeterD> list = caMeterDService.selectAvgCaMeterDList(caMeterD);
+        return success(list);
+    }
+
+    /**
+     * 导出碳计量列表
      */
     @PreAuthorize("@ss.hasPermi('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, "碳计量数据");
     }
 
-    /**
-     * 获取碳计量日详细信息
-     */
     @PreAuthorize("@ss.hasPermi('ca-analysis:emission:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(caMeterDService.selectCaMeterDById(id));
     }
 
-    /**
-     * 新增碳计量日
-     */
     @PreAuthorize("@ss.hasPermi('ca-analysis:emission:add')")
     @Log(title = "碳计量日", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody CaMeterD caMeterD)
-    {
+    public AjaxResult add(@RequestBody CaMeterD caMeterD) {
         return toAjax(caMeterDService.insertCaMeterD(caMeterD));
     }
 
-    /**
-     * 修改碳计量日
-     */
     @PreAuthorize("@ss.hasPermi('ca-analysis:emission:edit')")
     @Log(title = "碳计量日", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody CaMeterD caMeterD)
-    {
+    public AjaxResult edit(@RequestBody CaMeterD caMeterD) {
         return toAjax(caMeterDService.updateCaMeterD(caMeterD));
     }
 
-    /**
-     * 删除碳计量日
-     */
     @PreAuthorize("@ss.hasPermi('ca-analysis:emission:remove')")
     @Log(title = "碳计量日", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(caMeterDService.deleteCaMeterDByIds(ids));
     }
-}
+}

+ 114 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ChargingBillController.java

@@ -0,0 +1,114 @@
+// com/ruoyi/ems/controller/ChargingBillController.java
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.model.ChargingBillVO;
+import com.ruoyi.ems.model.QueryChargingBill;
+import com.ruoyi.ems.service.IChargingBillService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 商户能源账单Controller
+ *
+ * @author ruoyi
+ * @date 2024-12-20
+ */
+@RestController
+@RequestMapping("/ems/charging/bill")
+@Api(value = "ChargingBillController", description = "计费账单服务")
+public class ChargingBillController extends BaseController {
+    @Autowired
+    private IChargingBillService chargingBillService;
+
+    /**
+     * 查询总览数据
+     */
+    @PreAuthorize("@ss.hasPermi('ems:charging:list')")
+    @GetMapping("/overview")
+    @ApiOperation("查询总览数据")
+    public AjaxResult getOverview(QueryChargingBill queryParam) {
+        try {
+            ChargingBillVO bill = chargingBillService.selectOverviewTotal(queryParam);
+            return success(bill);
+        }
+        catch (Exception e) {
+            logger.error("查询总览数据异常", e);
+            return error("系统异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 查询总览数据
+     */
+    @PreAuthorize("@ss.hasPermi('ems:charging:list')")
+    @GetMapping("/overview/list")
+    @ApiOperation("查询总览数据")
+    public AjaxResult getOverviewList(QueryChargingBill queryParam) {
+        try {
+            List<ChargingBillVO> list = chargingBillService.selectOverviewList(queryParam);
+            return success(list);
+        }
+        catch (Exception e) {
+            logger.error("查询总览数据异常", e);
+            return error("系统异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 查询个户综合账单
+     */
+    @PreAuthorize("@ss.hasPermi('ems:charging:list')")
+    @GetMapping("/seller")
+    @ApiOperation("查询商户用能账单")
+    public AjaxResult getIndividualBill(QueryChargingBill queryParam) {
+        try {
+            ChargingBillVO bill = chargingBillService.selectSellerEmsBill(queryParam);
+            return success(bill);
+        }
+        catch (Exception e) {
+            logger.error("查询个户账单异常", e);
+            return error("系统异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 查询历史账单列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:charging:list')")
+    @GetMapping("/seller/history")
+    @ApiOperation("查询历史账单列表")
+    public TableDataInfo getSellerHistoryBills(QueryChargingBill queryParam) {
+        startPage();
+        List<ChargingBillVO> list = chargingBillService.selectSellerHistoryBills(queryParam);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出账单
+     */
+    @PreAuthorize("@ss.hasPermi('ems:charging:export')")
+    @Log(title = "计费账单", businessType = BusinessType.EXPORT)
+    @PostMapping("/seller/export")
+    @ApiOperation("导出账单")
+    public void exportSellerBill(HttpServletResponse response, QueryChargingBill queryParam) {
+        List<ChargingBillVO> list = chargingBillService.selectSellerHistoryBills(queryParam);
+        ExcelUtil<ChargingBillVO> util = new ExcelUtil<>(ChargingBillVO.class);
+        String fileName = String.format("计费账单_%s.xlsx", System.currentTimeMillis());
+        util.exportExcel(response, list, fileName);
+    }
+}

+ 24 - 18
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/CoChargingConfigController.java

@@ -1,15 +1,15 @@
 package com.ruoyi.web.controller.ems;
 
-import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
+import com.huashe.common.domain.AjaxResult;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.CoChargingConfig;
 import com.ruoyi.ems.service.ICoChargingConfigService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -17,43 +17,52 @@ 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.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
 
 /**
  * 商户能源计费配置Controller
- * 
+ *
  * @author ruoyi
  * @date 2024-08-26
  */
 @RestController
 @RequestMapping("/ems/basecfg/elecPrice/coCharging")
 @Api(value = "CoChargingConfigController", description = "商户能源计费配置")
-public class CoChargingConfigController extends BaseController
-{
+public class CoChargingConfigController extends BaseController {
     @Autowired
     private ICoChargingConfigService coChargingConfigService;
 
     /**
      * 查询商户能源计费配置列表
      */
-    @PreAuthorize("@ss.hasPermi('basecfg:price:list')")
     @GetMapping("/list")
-    public TableDataInfo list(CoChargingConfig coChargingConfig)
-    {
+    public TableDataInfo list(CoChargingConfig coChargingConfig) {
         startPage();
         List<CoChargingConfig> list = coChargingConfigService.selectCoChargingConfigList(coChargingConfig);
         return getDataTable(list);
     }
 
     /**
+     * 根据区域查询商户能源计费配置
+     *
+     * @param areaCode areaCode
+     * @return
+     */
+    @GetMapping("/getByArea")
+    public AjaxResult getByArea(@RequestParam(name = "areaCode") String areaCode) {
+        CoChargingConfig config = coChargingConfigService.selectByArea(areaCode);
+        return success(config);
+    }
+
+    /**
      * 获取商户能源计费配置详细信息
      */
     @PreAuthorize("@ss.hasPermi('basecfg:price:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(coChargingConfigService.selectCoChargingConfigById(id));
     }
 
@@ -63,8 +72,7 @@ public class CoChargingConfigController extends BaseController
     @PreAuthorize("@ss.hasPermi('basecfg:price:add')")
     @Log(title = "商户能源计费配置", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody CoChargingConfig coChargingConfig)
-    {
+    public AjaxResult add(@RequestBody CoChargingConfig coChargingConfig) {
         return toAjax(coChargingConfigService.insertCoChargingConfig(coChargingConfig));
     }
 
@@ -74,8 +82,7 @@ public class CoChargingConfigController extends BaseController
     @PreAuthorize("@ss.hasPermi('ems:config:edit')")
     @Log(title = "商户能源计费配置", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody CoChargingConfig coChargingConfig)
-    {
+    public AjaxResult edit(@RequestBody CoChargingConfig coChargingConfig) {
         return toAjax(coChargingConfigService.updateCoChargingConfig(coChargingConfig));
     }
 
@@ -84,9 +91,8 @@ public class CoChargingConfigController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('ems:config:remove')")
     @Log(title = "商户能源计费配置", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(coChargingConfigService.deleteCoChargingConfigByIds(ids));
     }
 }

+ 420 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/CustomReportController.java

@@ -0,0 +1,420 @@
+/*
+ * 文 件 名:  CustomReportController
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2026/1/30
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.ems.domain.ReportDatasource;
+import com.ruoyi.ems.domain.ReportField;
+import com.ruoyi.ems.domain.ReportFieldCondition;
+import com.ruoyi.ems.domain.ReportQueryConfig;
+import com.ruoyi.ems.domain.ReportRelation;
+import com.ruoyi.ems.domain.ReportTemplate;
+import com.ruoyi.ems.model.ReportQueryResult;
+import com.ruoyi.ems.service.ICustomReportService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 自定义报表Controller
+ * 提供动态SQL查询能力,支持:
+ * 1. 数据源管理(配置可查询的表)
+ * 2. 字段配置(配置可选字段和条件)
+ * 3. 报表模板管理(保存/加载用户配置)
+ * 4. 动态查询执行(根据配置生成SQL并执行)
+ * 5. 数据导出(Excel导出)
+ *
+ * @author ruoyi
+ * @date 2026-01-30
+ */
+@RestController
+@RequestMapping("/ems/report/custom")
+@Api(value = "CustomReportController", description = "自定义报表接口")
+public class CustomReportController extends BaseController {
+    @Autowired
+    private ICustomReportService customReportService;
+    // ==================== 数据源管理 ====================
+
+    /**
+     * 查询所有可用的数据源
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:list')")
+    @GetMapping("/datasource/list")
+    @ApiOperation("查询数据源列表")
+    public AjaxResult listDatasources(@ApiParam("分类筛选") @RequestParam(required = false) String category) {
+        List<ReportDatasource> list = customReportService.selectDatasourceList(category);
+        return success(list);
+    }
+
+    /**
+     * 获取数据源详情(包含字段、条件、关联配置)
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:list')")
+    @GetMapping("/datasource/{dsCode}")
+    @ApiOperation("获取数据源详情")
+    public AjaxResult getDatasourceDetail(@ApiParam("数据源编码") @PathVariable String dsCode) {
+        ReportDatasource datasource = customReportService.selectDatasourceDetail(dsCode);
+        if (datasource == null) {
+            return error("数据源不存在");
+        }
+        return success(datasource);
+    }
+
+    /**
+     * 获取数据源的字段列表
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:list')")
+    @GetMapping("/datasource/{dsCode}/fields")
+    @ApiOperation("获取数据源字段列表")
+    public AjaxResult getDatasourceFields(@ApiParam("数据源编码") @PathVariable String dsCode) {
+        List<ReportField> fields = customReportService.selectFieldsByDsCode(dsCode);
+        return success(fields);
+    }
+
+    /**
+     * 获取字段的可用条件
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:list')")
+    @GetMapping("/datasource/{dsCode}/field/{fieldCode}/conditions")
+    @ApiOperation("获取字段条件列表")
+    public AjaxResult getFieldConditions(@ApiParam("数据源编码") @PathVariable String dsCode,
+        @ApiParam("字段编码") @PathVariable String fieldCode) {
+        List<ReportFieldCondition> conditions = customReportService.selectFieldConditions(dsCode, fieldCode);
+        return success(conditions);
+    }
+    // ==================== 报表模板管理 ====================
+
+    /**
+     * 查询模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:list')")
+    @GetMapping("/template/list")
+    @ApiOperation("查询报表模板列表")
+    public TableDataInfo listTemplates(ReportTemplate query) {
+        startPage();
+        // 设置当前用户ID,用于查询私有模板
+        Long userId = SecurityUtils.getUserId();
+        query.setUserId(userId);
+        List<ReportTemplate> list = customReportService.selectTemplateList(query);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取模板详情
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:list')")
+    @GetMapping("/template/{templateCode}")
+    @ApiOperation("获取报表模板详情")
+    public AjaxResult getTemplate(@ApiParam("模板编码") @PathVariable String templateCode) {
+        ReportTemplate template = customReportService.selectTemplateByCode(templateCode);
+        if (template == null) {
+            return error("模板不存在");
+        }
+        return success(template);
+    }
+
+    /**
+     * 保存模板
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:add')")
+    @Log(title = "自定义报表模板", businessType = BusinessType.INSERT)
+    @PostMapping("/template")
+    @ApiOperation("保存报表模板")
+    public AjaxResult saveTemplate(@RequestBody ReportTemplate template) {
+        // 设置当前用户信息
+        Long userId = SecurityUtils.getUserId();
+        String userName = SecurityUtils.getUsername();
+        template.setUserId(userId);
+        template.setUserName(userName);
+        template.setCreateBy(userName);
+        int result = customReportService.saveTemplate(template);
+        return result > 0 ? success("保存成功") : error("保存失败");
+    }
+
+    /**
+     * 更新模板
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:edit')")
+    @Log(title = "自定义报表模板", businessType = BusinessType.UPDATE)
+    @PutMapping("/template")
+    @ApiOperation("更新报表模板")
+    public AjaxResult updateTemplate(@RequestBody ReportTemplate template) {
+        // 检查权限(只能修改自己的模板或系统管理员)
+        ReportTemplate existing = customReportService.selectTemplateByCode(template.getTemplateCode());
+        if (existing == null) {
+            return error("模板不存在");
+        }
+        Long userId = SecurityUtils.getUserId();
+        if (existing.getIsSystem() != null && existing.getIsSystem() == 1) {
+            return error("系统模板不可修改");
+        }
+        if (!userId.equals(existing.getUserId())) {
+            return error("无权修改此模板");
+        }
+        template.setUpdateBy(SecurityUtils.getUsername());
+        int result = customReportService.updateTemplate(template);
+        return result > 0 ? success("更新成功") : error("更新失败");
+    }
+
+    /**
+     * 删除模板
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:remove')")
+    @Log(title = "自定义报表模板", businessType = BusinessType.DELETE)
+    @DeleteMapping("/template/{templateCode}")
+    @ApiOperation("删除报表模板")
+    public AjaxResult deleteTemplate(@ApiParam("模板编码") @PathVariable String templateCode) {
+        // 检查权限
+        ReportTemplate existing = customReportService.selectTemplateByCode(templateCode);
+        if (existing == null) {
+            return error("模板不存在");
+        }
+        if (existing.getIsSystem() != null && existing.getIsSystem() == 1) {
+            return error("系统模板不可删除");
+        }
+        Long userId = SecurityUtils.getUserId();
+        if (!userId.equals(existing.getUserId())) {
+            return error("无权删除此模板");
+        }
+        int result = customReportService.deleteTemplate(templateCode);
+        return result > 0 ? success("删除成功") : error("删除失败");
+    }
+    // ==================== 动态查询执行 ====================
+
+    /**
+     * 执行报表查询
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:query')")
+    @PostMapping("/query")
+    @ApiOperation("执行报表查询")
+    public AjaxResult executeQuery(@RequestBody ReportQueryConfig queryConfig) {
+        try {
+            // 参数校验
+            if (queryConfig.getDsCode() == null && queryConfig.getTemplateCode() == null) {
+                return error("请指定数据源或模板");
+            }
+            queryConfig.initDefaults();
+            ReportQueryResult result = customReportService.executeQuery(queryConfig);
+            return success(result);
+        }
+        catch (Exception e) {
+            logger.error("执行报表查询异常", e);
+            return error("查询失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 执行报表查询(基于模板)
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:query')")
+    @PostMapping("/query/template/{templateCode}")
+    @ApiOperation("基于模板执行查询")
+    public AjaxResult executeQueryByTemplate(@ApiParam("模板编码") @PathVariable String templateCode,
+        @ApiParam("参数值") @RequestBody(required = false) Map<String, Object> params) {
+        try {
+            ReportQueryConfig queryConfig = new ReportQueryConfig();
+            queryConfig.setTemplateCode(templateCode);
+            queryConfig.setParams(params);
+            queryConfig.initDefaults();
+            // 更新模板使用次数
+            customReportService.incrementTemplateUseCount(templateCode);
+            ReportQueryResult result = customReportService.executeQuery(queryConfig);
+            return success(result);
+        }
+        catch (Exception e) {
+            logger.error("执行模板查询异常", e);
+            return error("查询失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取报表汇总数据
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:query')")
+    @PostMapping("/summary")
+    @ApiOperation("获取报表汇总数据")
+    public AjaxResult getSummary(@RequestBody ReportQueryConfig queryConfig) {
+        try {
+            if (queryConfig.getDsCode() == null && queryConfig.getTemplateCode() == null) {
+                return error("请指定数据源或模板");
+            }
+            Map<String, Object> summary = customReportService.executeSummaryQuery(queryConfig);
+            return success(summary);
+        }
+        catch (Exception e) {
+            logger.error("获取报表汇总异常", e);
+            return error("查询失败: " + e.getMessage());
+        }
+    }
+    // ==================== 数据导出 ====================
+
+    /**
+     * 导出报表数据
+     */
+    @PreAuthorize("@ss.hasPermi('report:custom:export')")
+    @Log(title = "自定义报表", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    @ApiOperation("导出报表数据")
+    public void exportReport(HttpServletResponse response, @RequestBody ReportQueryConfig queryConfig) {
+        try {
+            queryConfig.setExportAll(true);
+            queryConfig.initDefaults();
+            // 执行查询获取全部数据
+            ReportQueryResult result = customReportService.executeQuery(queryConfig);
+            if (result.getCode() != 200) {
+                response.setContentType("application/json;charset=UTF-8");
+                response.getWriter().write("{\"code\":500,\"msg\":\"" + result.getMsg() + "\"}");
+                return;
+            }
+            // 获取数据源信息用于文件名
+            String dsCode = queryConfig.getDsCode();
+            if (dsCode == null && queryConfig.getTemplateCode() != null) {
+                ReportTemplate template = customReportService.selectTemplateByCode(queryConfig.getTemplateCode());
+                if (template != null) {
+                    dsCode = template.getDsCode();
+                }
+            }
+            ReportDatasource datasource = customReportService.selectDatasourceByCode(dsCode);
+            String dsName = datasource != null ? datasource.getDsName() : "报表";
+            // 导出Excel
+            customReportService.exportToExcel(response, result, dsName);
+        }
+        catch (Exception e) {
+            logger.error("导出报表异常", e);
+            try {
+                response.setContentType("application/json;charset=UTF-8");
+                response.getWriter().write("{\"code\":500,\"msg\":\"导出失败: " + e.getMessage() + "\"}");
+            }
+            catch (Exception ex) {
+                logger.error("响应异常信息失败", ex);
+            }
+        }
+    }
+    // ==================== 配置管理(管理员) ====================
+
+    /**
+     * 新增数据源配置
+     */
+    @PreAuthorize("@ss.hasPermi('report:config:add')")
+    @Log(title = "数据源配置", businessType = BusinessType.INSERT)
+    @PostMapping("/datasource")
+    @ApiOperation("新增数据源配置")
+    public AjaxResult addDatasource(@RequestBody ReportDatasource datasource) {
+        datasource.setCreateBy(SecurityUtils.getUsername());
+        int result = customReportService.insertDatasource(datasource);
+        return result > 0 ? success("新增成功") : error("新增失败");
+    }
+
+    /**
+     * 修改数据源配置
+     */
+    @PreAuthorize("@ss.hasPermi('report:config:edit')")
+    @Log(title = "数据源配置", businessType = BusinessType.UPDATE)
+    @PutMapping("/datasource")
+    @ApiOperation("修改数据源配置")
+    public AjaxResult updateDatasource(@RequestBody ReportDatasource datasource) {
+        datasource.setUpdateBy(SecurityUtils.getUsername());
+        int result = customReportService.updateDatasource(datasource);
+        return result > 0 ? success("修改成功") : error("修改失败");
+    }
+
+    /**
+     * 新增字段配置
+     */
+    @PreAuthorize("@ss.hasPermi('report:config:add')")
+    @Log(title = "字段配置", businessType = BusinessType.INSERT)
+    @PostMapping("/field")
+    @ApiOperation("新增字段配置")
+    public AjaxResult addField(@RequestBody ReportField field) {
+        int result = customReportService.insertField(field);
+        return result > 0 ? success("新增成功") : error("新增失败");
+    }
+
+    /**
+     * 批量新增字段配置
+     */
+    @PreAuthorize("@ss.hasPermi('report:config:add')")
+    @Log(title = "字段配置", businessType = BusinessType.INSERT)
+    @PostMapping("/field/batch")
+    @ApiOperation("批量新增字段配置")
+    public AjaxResult addFieldBatch(@RequestBody List<ReportField> fields) {
+        int result = customReportService.insertFieldBatch(fields);
+        return result > 0 ? success("新增成功") : error("新增失败");
+    }
+
+    /**
+     * 修改字段配置
+     */
+    @PreAuthorize("@ss.hasPermi('report:config:edit')")
+    @Log(title = "字段配置", businessType = BusinessType.UPDATE)
+    @PutMapping("/field")
+    @ApiOperation("修改字段配置")
+    public AjaxResult updateField(@RequestBody ReportField field) {
+        int result = customReportService.updateField(field);
+        return result > 0 ? success("修改成功") : error("修改失败");
+    }
+
+    /**
+     * 删除字段配置
+     */
+    @PreAuthorize("@ss.hasPermi('report:config:remove')")
+    @Log(title = "字段配置", businessType = BusinessType.DELETE)
+    @DeleteMapping("/field/{dsCode}/{fieldCode}")
+    @ApiOperation("删除字段配置")
+    public AjaxResult deleteField(@PathVariable String dsCode, @PathVariable String fieldCode) {
+        int result = customReportService.deleteField(dsCode, fieldCode);
+        return result > 0 ? success("删除成功") : error("删除失败");
+    }
+
+    /**
+     * 新增关联配置
+     */
+    @PreAuthorize("@ss.hasPermi('report:config:add')")
+    @Log(title = "关联配置", businessType = BusinessType.INSERT)
+    @PostMapping("/relation")
+    @ApiOperation("新增关联配置")
+    public AjaxResult addRelation(@RequestBody ReportRelation relation) {
+        int result = customReportService.insertRelation(relation);
+        return result > 0 ? success("新增成功") : error("新增失败");
+    }
+
+    /**
+     * 修改关联配置
+     */
+    @PreAuthorize("@ss.hasPermi('report:config:edit')")
+    @Log(title = "关联配置", businessType = BusinessType.UPDATE)
+    @PutMapping("/relation")
+    @ApiOperation("修改关联配置")
+    public AjaxResult updateRelation(@RequestBody ReportRelation relation) {
+        int result = customReportService.updateRelation(relation);
+        return result > 0 ? success("修改成功") : error("修改失败");
+    }
+}

+ 31 - 23
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/DeviceController.java

@@ -1,36 +1,37 @@
 package com.ruoyi.web.controller.ems;
 
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.huashe.common.exception.BusinessException;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.exception.BusinessException;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.Area;
 import com.ruoyi.ems.domain.EmsDevice;
+import com.ruoyi.ems.domain.EmsDeviceModel;
 import com.ruoyi.ems.model.QueryDevice;
 import com.ruoyi.ems.service.IAreaService;
 import com.ruoyi.ems.service.IEmsDeviceService;
 import com.ruoyi.ems.service.IEmsFacsService;
 import com.ruoyi.ems.util.AreaUtils;
 import io.swagger.annotations.Api;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-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.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.servlet.http.HttpServletResponse;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * 能源设备Controller
@@ -96,7 +97,6 @@ public class DeviceController extends BaseController {
     @GetMapping("/listRecursionByArea")
     public TableDataInfo listRecursionByArea(QueryDevice queryDevice) {
         TableDataInfo tabInfo = null;
-
         try {
             if (StringUtils.isNotEmpty(queryDevice.getLocationRef())) {
                 List<Area> areaTree = areaService.selectAreaTree(queryDevice.getLocationRef(), true);
@@ -106,7 +106,6 @@ public class DeviceController extends BaseController {
                 AreaUtils.getCodeRecursion(areaTree, areaCodes);
                 queryDevice.setAreaCodes(areaCodes);
             }
-
             startPage();
             List<EmsDevice> list = deviceService.selectByAreaTree(queryDevice);
             tabInfo = getDataTable(list);
@@ -116,7 +115,6 @@ public class DeviceController extends BaseController {
             tabInfo.setCode(e.getCode());
             tabInfo.setMsg(e.getMessage());
         }
-
         return tabInfo;
     }
 
@@ -194,7 +192,17 @@ public class DeviceController extends BaseController {
      * @return
      */
     @GetMapping(value = "/type/online")
-    public AjaxResult getDeviceOnlineSummary(@RequestParam("areaCode") String areaCode) {
+    public AjaxResult getDeviceOnlineSummary(@RequestParam(value = "areaCode", required = false) String areaCode) {
         return success(deviceService.calcDeviceOnlineSummary(areaCode));
     }
+
+    @GetMapping(value = "/total/status")
+    public AjaxResult cntTotalDevice(@RequestParam(value = "areaCode", required = false) String areaCode) {
+        return success(deviceService.cntTotalDevice(areaCode));
+    }
+
+    @GetMapping(value = "/device/model")
+    public AjaxResult getDeviceModel(EmsDeviceModel params) {
+        return success(deviceService.selectSubSystemDeviceState(params));
+    }
 }

+ 29 - 36
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsDeviceRbookController.java → ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/DeviceLedgerController.java

@@ -1,16 +1,16 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.ems.domain.EmsDeviceRbook;
-import com.ruoyi.ems.service.IEmsDeviceRbookService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.domain.DeviceLedger;
+import com.ruoyi.ems.service.IDeviceLedgerService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -25,83 +25,76 @@ import java.util.List;
 
 /**
  * 设备台账Controller
- * 
+ *
  * @author ruoyi
  * @date 2024-09-10
  */
 @RestController
-@RequestMapping("/ems/device/rbook")
-@Api(value = "EmsDeviceRbookController", description = "设备台账")
-public class EmsDeviceRbookController extends BaseController
-{
+@RequestMapping("/ems/device/ledger")
+@Api(value = "EmsDeviceLedgerController", description = "设备台账")
+public class DeviceLedgerController extends BaseController {
     @Autowired
-    private IEmsDeviceRbookService admEmsDeviceRbookService;
+    private IDeviceLedgerService deviceLedgerService;
 
     /**
      * 查询设备台账列表
      */
-    @PreAuthorize("@ss.hasPermi('ems:rbook:list')")
+    @PreAuthorize("@ss.hasPermi('ems:ledger:list')")
     @GetMapping("/list")
-    public TableDataInfo list(EmsDeviceRbook rbook)
-    {
+    public TableDataInfo list(DeviceLedger Ledger) {
         startPage();
-        List<EmsDeviceRbook> list = admEmsDeviceRbookService.selectDeviceRbookList(rbook);
+        List<DeviceLedger> list = deviceLedgerService.selectDeviceLedgerList(Ledger);
         return getDataTable(list);
     }
 
     /**
      * 导出设备台账列表
      */
-    @PreAuthorize("@ss.hasPermi('ems:rbook:export')")
+    @PreAuthorize("@ss.hasPermi('ems:ledger:export')")
     @Log(title = "设备台账", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
-    public void export(HttpServletResponse response, EmsDeviceRbook rbook)
-    {
-        List<EmsDeviceRbook> list = admEmsDeviceRbookService.selectDeviceRbookList(rbook);
-        ExcelUtil<EmsDeviceRbook> util = new ExcelUtil<EmsDeviceRbook>(EmsDeviceRbook.class);
+    public void export(HttpServletResponse response, DeviceLedger Ledger) {
+        List<DeviceLedger> list = deviceLedgerService.selectDeviceLedgerList(Ledger);
+        ExcelUtil<DeviceLedger> util = new ExcelUtil<DeviceLedger>(DeviceLedger.class);
         util.exportExcel(response, list, "设备台账数据");
     }
 
     /**
      * 获取设备台账详细信息
      */
-    @PreAuthorize("@ss.hasPermi('ems:rbook:query')")
+    @PreAuthorize("@ss.hasPermi('ems:ledger:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
-        return success(admEmsDeviceRbookService.selectDeviceRbookById(id));
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(deviceLedgerService.selectDeviceLedgerById(id));
     }
 
     /**
      * 新增设备台账
      */
-    @PreAuthorize("@ss.hasPermi('ems:rbook:add')")
+    @PreAuthorize("@ss.hasPermi('ems:ledger:add')")
     @Log(title = "设备台账", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody EmsDeviceRbook rbook)
-    {
-        return toAjax(admEmsDeviceRbookService.insertDeviceRbook(rbook));
+    public AjaxResult add(@RequestBody DeviceLedger Ledger) {
+        return toAjax(deviceLedgerService.insertDeviceLedger(Ledger));
     }
 
     /**
      * 修改设备台账
      */
-    @PreAuthorize("@ss.hasPermi('ems:rbook:edit')")
+    @PreAuthorize("@ss.hasPermi('ems:ledger:edit')")
     @Log(title = "设备台账", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody EmsDeviceRbook rbook)
-    {
-        return toAjax(admEmsDeviceRbookService.updateDeviceRbook(rbook));
+    public AjaxResult edit(@RequestBody DeviceLedger Ledger) {
+        return toAjax(deviceLedgerService.updateDeviceLedger(Ledger));
     }
 
     /**
      * 删除设备台账
      */
-    @PreAuthorize("@ss.hasPermi('ems:rbook:remove')")
+    @PreAuthorize("@ss.hasPermi('ems:ledger:remove')")
     @Log(title = "设备台账", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
-        return toAjax(admEmsDeviceRbookService.deleteDeviceRbookByIds(ids));
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(deviceLedgerService.deleteDeviceLedgerByIds(ids));
     }
 }

+ 4 - 4
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecAttrController.java

@@ -1,14 +1,14 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.ElecAttr;
 import com.ruoyi.ems.service.IElecAttrService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -61,7 +61,7 @@ public class ElecAttrController extends BaseController {
      */
     @PreAuthorize("@ss.hasPermi('basecfg:price:query')")
     @GetMapping(value = "/getEffectiveListByArea")
-    public AjaxResult getEffectiveListByArea(@RequestParam(name="areaCode") String areaCode) {
+    public AjaxResult getEffectiveListByArea(@RequestParam(name = "areaCode") String areaCode) {
         return success(attrService.getEffectiveListByArea(areaCode));
     }
 
@@ -70,7 +70,7 @@ public class ElecAttrController extends BaseController {
      */
     @PreAuthorize("@ss.hasPermi('basecfg:price:query')")
     @GetMapping(value = "/getEffectiveByArea")
-    public AjaxResult getEffectiveByArea(@RequestParam(name="areaCode") String areaCode) {
+    public AjaxResult getEffectiveByArea(@RequestParam(name = "areaCode") String areaCode) {
         return success(attrService.getEffectiveByArea(areaCode));
     }
 

+ 55 - 36
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecConsumeForecastController.java

@@ -1,16 +1,16 @@
 package com.ruoyi.web.controller.ems;
-
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 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.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -19,10 +19,9 @@ 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 javax.servlet.http.HttpServletResponse;
 import java.util.List;
-
+import java.util.Map;
 /**
  * 电力消耗预测Controller
  *
@@ -31,77 +30,97 @@ import java.util.List;
  */
 @RestController
 @RequestMapping("/ems/forecastConsume")
-@Api(value = "ElecExpendForecastController", description = "电力消耗预测数据访问接口")
-public class ElecConsumeForecastController extends BaseController
-{
+@Api(value = "ElecConsumeForecastController", description = "电力消耗预测数据访问接口")
+public class ElecConsumeForecastController extends BaseController {
     @Autowired
-    private IElecConsumeForecastService elecExpendForecastService;
-
+    private IElecConsumeForecastService forecastService;
     /**
      * 查询电力消耗预测列表
      */
     @PreAuthorize("@ss.hasPermi('prediction:consume:list')")
     @GetMapping("/list")
-    public TableDataInfo list(ElecConsumeForecast forecast)
-    {
+    @ApiOperation("查询电力消耗预测列表")
+    public TableDataInfo list(ElecConsumeForecast forecast) {
         startPage();
-        List<ElecConsumeForecast> list = elecExpendForecastService.selectForecastList(forecast);
+        List<ElecConsumeForecast> list = forecastService.selectForecastList(forecast);
         return getDataTable(list);
     }
-
     /**
      * 导出电力消耗预测列表
      */
     @PreAuthorize("@ss.hasPermi('prediction:consume:export')")
     @Log(title = "电力消耗预测", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
-    public void export(HttpServletResponse response, ElecConsumeForecast forecast)
-    {
-        List<ElecConsumeForecast> list = elecExpendForecastService.selectForecastList(forecast);
+    @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, "电力消耗预测数据");
     }
-
     /**
      * 获取电力消耗预测详细信息
      */
     @PreAuthorize("@ss.hasPermi('prediction:consume:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
-        return success(elecExpendForecastService.selectForecastById(id));
+    @ApiOperation("获取电力消耗预测详细信息")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(forecastService.selectForecastById(id));
     }
-
     /**
      * 新增电力消耗预测
      */
     @PreAuthorize("@ss.hasPermi('prediction:consume:add')")
     @Log(title = "电力消耗预测", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody ElecConsumeForecast forecast)
-    {
-        return toAjax(elecExpendForecastService.insertForecast(forecast));
+    @ApiOperation("新增电力消耗预测")
+    public AjaxResult add(@RequestBody ElecConsumeForecast forecast) {
+        return toAjax(forecastService.insertForecast(forecast));
     }
-
     /**
      * 修改电力消耗预测
      */
     @PreAuthorize("@ss.hasPermi('prediction:consume:edit')")
     @Log(title = "电力消耗预测", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody ElecConsumeForecast forecast)
-    {
-        return toAjax(elecExpendForecastService.updateForecast(forecast));
+    @ApiOperation("修改电力消耗预测")
+    public AjaxResult edit(@RequestBody ElecConsumeForecast forecast) {
+        return toAjax(forecastService.updateForecast(forecast));
     }
-
     /**
      * 删除电力消耗预测
      */
     @PreAuthorize("@ss.hasPermi('prediction:consume:remove')")
     @Log(title = "电力消耗预测", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
-        return toAjax(elecExpendForecastService.deleteForecastByIds(ids));
+    @DeleteMapping("/{ids}")
+    @ApiOperation("删除电力消耗预测")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(forecastService.deleteForecastByIds(ids));
+    }
+    /**
+     * 查询每日汇总趋势数据(支持层级汇总)
+     * 当选择父节点时,自动汇总所有子节点数据
+     */
+    @GetMapping(value = "/cal/dateRange")
+    @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 - 93
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecLoadIndexController.java

@@ -1,93 +0,0 @@
-package com.ruoyi.web.controller.ems;
-
-import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.ems.domain.ElecLoadIndex;
-import com.ruoyi.ems.model.QueryMeter;
-import com.ruoyi.ems.service.IElecLoadIndexService;
-import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.servlet.http.HttpServletResponse;
-import java.util.List;
-
-/**
- * 电力负荷设施指标Controller
- *
- * @author ruoyi
- * @date 2024-08-09
- */
-@RestController
-@RequestMapping("/ems/object/loadIndex")
-@Api(value = "ElecLoadIndexController", description = "电力负荷指标数据接口")
-public class ElecLoadIndexController extends BaseController {
-    @Autowired
-    private IElecLoadIndexService indexService;
-
-    /**
-     * 查询电力负荷设施指标列表
-     */
-    @GetMapping("/min/15/list")
-    public TableDataInfo min15List(QueryMeter param) {
-        startPage();
-        List<ElecLoadIndex> list = indexService.selectMin15IndexList(param);
-        return getDataTable(list);
-    }
-
-    @GetMapping("/min/15/getMaxLoad")
-    public AjaxResult getMin15MaxLoad(QueryMeter param) {
-        ElecLoadIndex maxLoad = indexService.selectMin15IndexMaxLoad(param);
-        return success(maxLoad);
-    }
-
-    @GetMapping("/day/getMaxLoad")
-    public TableDataInfo getMaxLoad(QueryMeter param) {
-        startPage();
-        List<ElecLoadIndex> list = indexService.selectDayIndexMaxLoad(param);
-        return getDataTable(list);
-    }
-
-    /**
-     * 查询最新电力负荷设施指标
-     *
-     * @param areaCode 区域代码
-     * @param objType  对象类型
-     * @param objCode  对象代码
-     * @return 电力负荷设施指标
-     */
-    @GetMapping("/getNewIndex")
-    public AjaxResult getNewIndex(@RequestParam(name = "areaCode") String areaCode,
-        @RequestParam(name = "objType") String objType, @RequestParam(name = "objCode") String objCode) {
-        return success(indexService.selectNewIndex(areaCode, objType, objCode));
-    }
-
-    /**
-     * 导出电力负荷设施指标列表
-     */
-    @Log(title = "电力负荷设施指标", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(HttpServletResponse response, QueryMeter param) {
-        List<ElecLoadIndex> list = indexService.selectMin15IndexList(param);
-        ExcelUtil<ElecLoadIndex> util = new ExcelUtil<ElecLoadIndex>(ElecLoadIndex.class);
-        util.exportExcel(response, list, "电力负荷设施指标数据");
-    }
-
-    /**
-     * 新增电力负荷设施指标
-     */
-    @Log(title = "电力负荷设施指标", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody ElecLoadIndex index) {
-        return toAjax(indexService.insertMin15Index(index));
-    }
-}

+ 37 - 12
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecMeterHController.java

@@ -1,29 +1,24 @@
 package com.ruoyi.web.controller.ems;
-
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.ElecMeterH;
+import com.ruoyi.ems.domain.EnergyMeter;
 import com.ruoyi.ems.model.QueryMeter;
 import com.ruoyi.ems.service.IElecMeterHService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 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.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
-
-import javax.servlet.http.HttpServletResponse;
-import java.util.List;
-
 /**
  * 用电计量-小时Controller
  *
@@ -36,7 +31,17 @@ import java.util.List;
 public class ElecMeterHController extends BaseController {
     @Autowired
     private IElecMeterHService elecMeterHService;
-
+    /**
+     * 计算时间范围内日平均
+     *
+     * @param queryMeter 支持areaCode、deviceCode、date范围筛选
+     * @return ElecMeter
+     */
+    @GetMapping("/day/avg")
+    public AjaxResult selectElecDayAvg(QueryMeter queryMeter) {
+        EnergyMeter ElecMeter = elecMeterHService.selectElecDayAvg(queryMeter);
+        return success(ElecMeter);
+    }
     /**
      * 新增用电计量-小时
      */
@@ -46,7 +51,6 @@ public class ElecMeterHController extends BaseController {
     public AjaxResult add(@RequestBody ElecMeterH elecMeterH) {
         return toAjax(elecMeterHService.insertElecMeterH(elecMeterH));
     }
-
     /**
      * 修改用电计量-小时
      */
@@ -56,7 +60,6 @@ public class ElecMeterHController extends BaseController {
     public AjaxResult edit(@RequestBody ElecMeterH elecMeterH) {
         return toAjax(elecMeterHService.updateElecMeterH(elecMeterH));
     }
-
     /**
      * 删除用电计量-小时
      */
@@ -66,4 +69,26 @@ public class ElecMeterHController extends BaseController {
     public AjaxResult removeByDate(@RequestParam(name = "date") String date) {
         return toAjax(elecMeterHService.deleteElecMeterHByDate(date));
     }
+    @GetMapping("/sum/byDate/{date}")
+    public AjaxResult qryElecMeterByDate(@PathVariable("date") String date,
+        @RequestParam(name = "areaCode", required = false) String areaCode) {
+        return success(elecMeterHService.qryElecMeterByDate(date, areaCode));
+    }
+    @GetMapping("/sum/timeIndex/byDate/{date}/{timeIndex}")
+    public AjaxResult qryTimeIndexElecMeterByDay(@PathVariable("date") String date,
+        @PathVariable("timeIndex") Integer timeIndex) {
+        return success(elecMeterHService.qryTimeIndexElecMeterByDay(date, timeIndex));
+    }
+    @GetMapping("/sum/date/byDate/{date}")
+    public AjaxResult qryDateElecMeterByDate(@PathVariable("date") String date) {
+        return success(elecMeterHService.qryDateElecMeterByDate(date));
+    }
+    @GetMapping("/sum/date/byYear/{date}")
+    public AjaxResult qryDateElecMeterByYear(@PathVariable("date") String date) {
+        return success(elecMeterHService.qryDateElecMeterByYear(date));
+    }
+    @GetMapping("/sum/device/day")
+    public AjaxResult qryDeviceDay(ElecMeterH elecMeterH) {
+        return success(elecMeterHService.qryDeviceTypeElecMeterByDay(elecMeterH));
+    }
 }

+ 33 - 32
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecPgSupplyHController.java

@@ -1,17 +1,17 @@
 package com.ruoyi.web.controller.ems;
-
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.ElecPgSupplyH;
 import com.ruoyi.ems.model.QueryMeter;
 import com.ruoyi.ems.service.IElecPgSupplyHService;
+import com.ruoyi.ems.service.IElecPvSupplyHService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -20,10 +20,8 @@ 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 javax.servlet.http.HttpServletResponse;
 import java.util.List;
-
 /**
  * 电网供应计量-小时Controller
  *
@@ -33,97 +31,100 @@ import java.util.List;
 @RestController
 @RequestMapping("/ems/pg/supply/hour")
 @Api(value = "ElecPgSupplyHController", description = "电网供应计量")
-public class ElecPgSupplyHController extends BaseController
-{
+public class ElecPgSupplyHController extends BaseController {
     @Autowired
     private IElecPgSupplyHService pgSupplyHService;
-
+    @Autowired
+    private IElecPvSupplyHService pvSupplyHService;
     /**
      * 查询电网供应计量-小时列表
      */
     @PreAuthorize("@ss.hasPermi('power-mgr:pg:list')")
     @GetMapping("/list")
-    public TableDataInfo list(ElecPgSupplyH admEmsPgSupplyH)
-    {
+    public TableDataInfo list(ElecPgSupplyH admEmsPgSupplyH) {
         startPage();
         List<ElecPgSupplyH> list = pgSupplyHService.selectPgSupplyHList(admEmsPgSupplyH);
         return getDataTable(list);
     }
-
     /**
      * 查询电网供应计量-小时列表
      */
     @PreAuthorize("@ss.hasPermi('power-mgr:pg:list')")
     @GetMapping("/listByDate")
-    public AjaxResult listByDate(QueryMeter queryMeter)
-    {
+    public AjaxResult listByDate(QueryMeter queryMeter) {
         List<ElecPgSupplyH> list = pgSupplyHService.selectPgSupplyH(queryMeter);
         return success(list);
     }
-
     /**
      * 导出电网供应计量-小时列表
      */
     @PreAuthorize("@ss.hasPermi('power-mgr:pg:export')")
     @Log(title = "电网供应计量-小时", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
-    public void export(HttpServletResponse response, ElecPgSupplyH admEmsPgSupplyH)
-    {
+    public void export(HttpServletResponse response, ElecPgSupplyH admEmsPgSupplyH) {
         List<ElecPgSupplyH> list = pgSupplyHService.selectPgSupplyHList(admEmsPgSupplyH);
         ExcelUtil<ElecPgSupplyH> util = new ExcelUtil<ElecPgSupplyH>(ElecPgSupplyH.class);
         util.exportExcel(response, list, "电网供应计量-小时数据");
     }
-
     /**
      * 获取电网供应计量-小时详细信息
      */
     @PreAuthorize("@ss.hasPermi('power-mgr:pg:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(pgSupplyHService.selectPgSupplyHById(id));
     }
-
     @GetMapping(value = "/summery/h")
     public AjaxResult getSummeryByH() {
         return success(pgSupplyHService.selectSupplyByH());
     }
-
     @GetMapping(value = "/summery/this/day")
     public AjaxResult getSummeryByThisDay() {
         return success(pgSupplyHService.selectSupplyByDay());
     }
-
     /**
      * 新增电网供应计量-小时
      */
     @PreAuthorize("@ss.hasPermi('power-mgr:pg:add')")
     @Log(title = "电网供应计量-小时", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody ElecPgSupplyH admEmsPgSupplyH)
-    {
+    public AjaxResult add(@RequestBody ElecPgSupplyH admEmsPgSupplyH) {
         return toAjax(pgSupplyHService.insertPgSupplyH(admEmsPgSupplyH));
     }
-
     /**
      * 修改电网供应计量-小时
      */
     @PreAuthorize("@ss.hasPermi('power-mgr:pg:edit')")
     @Log(title = "电网供应计量-小时", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody ElecPgSupplyH admEmsPgSupplyH)
-    {
+    public AjaxResult edit(@RequestBody ElecPgSupplyH admEmsPgSupplyH) {
         return toAjax(pgSupplyHService.updatePgSupplyH(admEmsPgSupplyH));
     }
-
     /**
      * 删除电网供应计量-小时
      */
     @PreAuthorize("@ss.hasPermi('power-mgr:pg:remove')")
     @Log(title = "电网供应计量-小时", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(pgSupplyHService.deletePgSupplyHByIds(ids));
     }
+    @GetMapping(value = "/total/pv")
+    public AjaxResult calPvByDateRange(QueryMeter param) {
+        return success(pvSupplyHService.selectPvSupplySummary(param));
+    }
+    @GetMapping(value = "/day/pv")
+    public AjaxResult calDayPvRange(QueryMeter param) {
+        param.setTimeDimension("day");
+        return success(pvSupplyHService.selectPvSupplyList(param));
+    }
+    @GetMapping(value = "/month/pv")
+    public AjaxResult calMonthPvByDateRange(QueryMeter param) {
+        param.setTimeDimension("month");
+        return success(pvSupplyHService.selectPvSupplyList(param));
+    }
+    @GetMapping(value = "/month/pg")
+    public AjaxResult calMonthSupplyByH(ElecPgSupplyH param) {
+        return success(pgSupplyHService.calMonthSupplyByH(param));
+    }
 }

+ 10 - 15
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecPriceController.java

@@ -1,17 +1,17 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.ElecGwPriceConfig;
 import com.ruoyi.ems.domain.ElecPvPriceConfig;
 import com.ruoyi.ems.service.IElecPriceConfigService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -101,6 +101,7 @@ public class ElecPriceController extends BaseController {
 
     /**
      * 列出价格列表
+     *
      * @return 价格列表
      */
     @GetMapping("/gw/listall")
@@ -114,8 +115,7 @@ public class ElecPriceController extends BaseController {
      */
     @PreAuthorize("@ss.hasPermi('basecfg:price:list')")
     @GetMapping("/pv/list")
-    public TableDataInfo listPv(ElecPvPriceConfig pvPriceConfig)
-    {
+    public TableDataInfo listPv(ElecPvPriceConfig pvPriceConfig) {
         startPage();
         List<ElecPvPriceConfig> list = configService.selectPvPriceCfgList(pvPriceConfig);
         return getDataTable(list);
@@ -127,8 +127,7 @@ public class ElecPriceController extends BaseController {
     @PreAuthorize("@ss.hasPermi('basecfg:price:export')")
     @Log(title = "光伏电价配置", businessType = BusinessType.EXPORT)
     @PostMapping("/pv/export")
-    public void exportPv(HttpServletResponse response, ElecPvPriceConfig pvPriceConfig)
-    {
+    public void exportPv(HttpServletResponse response, ElecPvPriceConfig pvPriceConfig) {
         List<ElecPvPriceConfig> list = configService.selectPvPriceCfgList(pvPriceConfig);
         ExcelUtil<ElecPvPriceConfig> util = new ExcelUtil<>(ElecPvPriceConfig.class);
         util.exportExcel(response, list, "光伏电价配置数据");
@@ -139,8 +138,7 @@ public class ElecPriceController extends BaseController {
      */
     @PreAuthorize("@ss.hasPermi('basecfg:price:query')")
     @GetMapping(value = "/pv/{id}")
-    public AjaxResult getPvInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getPvInfo(@PathVariable("id") Long id) {
         return success(configService.selectPvPriceCfgById(id));
     }
 
@@ -150,8 +148,7 @@ public class ElecPriceController extends BaseController {
     @PreAuthorize("@ss.hasPermi('basecfg:price:add')")
     @Log(title = "光伏电价配置", businessType = BusinessType.INSERT)
     @PostMapping(value = "/pv")
-    public AjaxResult addPv(@RequestBody ElecPvPriceConfig pvPriceConfig)
-    {
+    public AjaxResult addPv(@RequestBody ElecPvPriceConfig pvPriceConfig) {
         return toAjax(configService.insertPvPriceCfg(pvPriceConfig));
     }
 
@@ -161,8 +158,7 @@ public class ElecPriceController extends BaseController {
     @PreAuthorize("@ss.hasPermi('basecfg:price:edit')")
     @Log(title = "光伏电价配置", businessType = BusinessType.UPDATE)
     @PutMapping(value = "/pv")
-    public AjaxResult editPv(@RequestBody ElecPvPriceConfig pvPriceConfig)
-    {
+    public AjaxResult editPv(@RequestBody ElecPvPriceConfig pvPriceConfig) {
         return toAjax(configService.updatePvPriceCfg(pvPriceConfig));
     }
 
@@ -172,8 +168,7 @@ public class ElecPriceController extends BaseController {
     @PreAuthorize("@ss.hasPermi('basecfg:price:remove')")
     @Log(title = "光伏电价配置", businessType = BusinessType.DELETE)
     @DeleteMapping("/pv/{ids}")
-    public AjaxResult removePv(@PathVariable Long[] ids)
-    {
+    public AjaxResult removePv(@PathVariable Long[] ids) {
         return toAjax(configService.deletePvPriceCfgByIds(ids));
     }
 }

+ 3 - 3
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecPriceStrategyController.java

@@ -1,16 +1,16 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.ElecPriceStrategy;
 import com.ruoyi.ems.service.IElecPriceStrategyService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;

+ 3 - 3
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecPriceStrategyHourController.java

@@ -1,16 +1,16 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.ElecPriceStrategyHour;
 import com.ruoyi.ems.service.IElecPriceStrategyHourService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;

+ 16 - 18
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecProdForecastController.java

@@ -1,16 +1,16 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.ElecProdForecast;
 import com.ruoyi.ems.service.IElecProdForecastService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -32,8 +32,7 @@ import java.util.List;
 @RestController
 @RequestMapping("/ems/predictionProd")
 @Api(value = "ElecProdForecastController", description = "电力产能预测数据接口")
-public class ElecProdForecastController extends BaseController
-{
+public class ElecProdForecastController extends BaseController {
     @Autowired
     private IElecProdForecastService elecProdForecastService;
 
@@ -42,8 +41,7 @@ public class ElecProdForecastController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('prediction:prod:list')")
     @GetMapping("/list")
-    public TableDataInfo list(ElecProdForecast elecProdForecast)
-    {
+    public TableDataInfo list(ElecProdForecast elecProdForecast) {
         startPage();
         List<ElecProdForecast> list = elecProdForecastService.selectElecProdForecastList(elecProdForecast);
         return getDataTable(list);
@@ -55,8 +53,7 @@ public class ElecProdForecastController extends BaseController
     @PreAuthorize("@ss.hasPermi('prediction:prod:export')")
     @Log(title = "电力产能预测", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
-    public void export(HttpServletResponse response, ElecProdForecast elecProdForecast)
-    {
+    public void export(HttpServletResponse response, ElecProdForecast elecProdForecast) {
         List<ElecProdForecast> list = elecProdForecastService.selectElecProdForecastList(elecProdForecast);
         ExcelUtil<ElecProdForecast> util = new ExcelUtil<ElecProdForecast>(ElecProdForecast.class);
         util.exportExcel(response, list, "电力产能预测数据");
@@ -67,8 +64,7 @@ public class ElecProdForecastController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('prediction:prod:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(elecProdForecastService.selectElecProdForecastById(id));
     }
 
@@ -78,8 +74,7 @@ public class ElecProdForecastController extends BaseController
     @PreAuthorize("@ss.hasPermi('prediction:prod:add')")
     @Log(title = "电力产能预测", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody ElecProdForecast elecProdForecast)
-    {
+    public AjaxResult add(@RequestBody ElecProdForecast elecProdForecast) {
         return toAjax(elecProdForecastService.insertElecProdForecast(elecProdForecast));
     }
 
@@ -89,8 +84,7 @@ public class ElecProdForecastController extends BaseController
     @PreAuthorize("@ss.hasPermi('prediction:prod:edit')")
     @Log(title = "电力产能预测", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody ElecProdForecast elecProdForecast)
-    {
+    public AjaxResult edit(@RequestBody ElecProdForecast elecProdForecast) {
         return toAjax(elecProdForecastService.updateElecProdForecast(elecProdForecast));
     }
 
@@ -99,9 +93,13 @@ public class ElecProdForecastController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('prediction:prod:remove')")
     @Log(title = "电力产能预测", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(elecProdForecastService.deleteElecProdForecastByIds(ids));
     }
+
+    @GetMapping("/date/range")
+    public AjaxResult calcElecProdForecastDateRange(ElecProdForecast elecProdForecast) {
+        return AjaxResult.success(elecProdForecastService.calcElecProdForecastDateRange(elecProdForecast));
+    }
 }

+ 125 - 22
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecPvSupplyHController.java

@@ -1,27 +1,34 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.huashe.common.exception.BusinessException;
+import com.huashe.common.utils.ListUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.ems.domain.ElecPvSupplyH;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.model.PvSupplyExportVO;
+import com.ruoyi.ems.model.PvSupplyVO;
 import com.ruoyi.ems.model.QueryMeter;
 import com.ruoyi.ems.service.IElecPvSupplyHService;
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.servlet.http.HttpServletResponse;
+import java.time.LocalDate;
 import java.util.List;
 
 /**
- * 光伏并网计量-小时Controller
+ * 光伏并网计量Controller
  *
  * @author ruoyi
  * @date 2024-08-02
@@ -34,34 +41,130 @@ public class ElecPvSupplyHController extends BaseController {
     private IElecPvSupplyHService pvSupplyHService;
 
     /**
-     * 查询光伏并网计量光伏并网计量-小时列表
+     * 查询光伏产能统计列表
      */
     @PreAuthorize("@ss.hasPermi('ems:prod:list')")
     @GetMapping("/hour/list")
-    public TableDataInfo list(ElecPvSupplyH pvSupplyH) {
-        startPage();
-        List<ElecPvSupplyH> list = pvSupplyHService.selectPvSupplyHList(pvSupplyH);
-        return getDataTable(list);
+    @ApiOperation("查询光伏产能统计列表")
+    public TableDataInfo list(@ApiParam("查询参数") QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        List<PvSupplyVO> list = pvSupplyHService.selectPvSupplyList(queryMeter);
+        int total = list.size();
+        list = ListUtils.subList(list, (queryMeter.getPageNum() - 1) * queryMeter.getPageSize(),
+            queryMeter.getPageNum() * queryMeter.getPageSize() - 1);
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(200);
+        rspData.setRows(list);
+        rspData.setMsg("查询成功");
+        rspData.setTotal(total);
+        return rspData;
+    }
+
+    /**
+     * 查询光伏产能统计汇总
+     */
+    @PreAuthorize("@ss.hasPermi('ems:prod:list')")
+    @GetMapping("/hour/summary")
+    @ApiOperation("查询光伏产能统计汇总")
+    public AjaxResult getSummary(@ApiParam("查询参数") QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        PvSupplyVO summary = pvSupplyHService.selectPvSupplySummary(queryMeter);
+        return success(summary);
+    }
+    /**
+     * 导出光伏产能统计数据
+     */
+    /**
+     * 导出光伏产能统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('ems:prod:export')")
+    @Log(title = "光伏产能统计", businessType = BusinessType.EXPORT)
+    @PostMapping("/hour/export")
+    @ApiOperation("导出光伏产能统计数据")
+    public void export(HttpServletResponse response, QueryMeter queryMeter) {
+        try {
+            validateAndSetDefaults(queryMeter);
+            List<PvSupplyVO> list = pvSupplyHService.exportPvSupplyList(queryMeter);
+            // 转换为导出VO
+            List<PvSupplyExportVO> exportList = pvSupplyHService.convertToPvSupplyExportVOList(list,
+                queryMeter.getTimeDimension());
+            ExcelUtil<PvSupplyExportVO> util = new ExcelUtil<>(PvSupplyExportVO.class);
+            String fileName = String.format("光伏产能统计_%s_%s.xlsx", queryMeter.getTimeDimension(),
+                System.currentTimeMillis());
+            util.exportExcel(response, exportList, fileName);
+        }
+        catch (BusinessException e) {
+            handleExportException(response, e);
+        }
     }
 
     /**
-     * 查询光伏并网计量光伏并网计量-小时列表
+     * 查询光伏产能按区域分布(用于饼图)
+     * 当areaCode为空或-1时,返回所有区域的分组数据
      */
     @PreAuthorize("@ss.hasPermi('ems:prod:list')")
-    @GetMapping("/day/list")
-    public AjaxResult listByDay(QueryMeter param) {
-        List<ElecPvSupplyH> list = pvSupplyHService.selectPvSupplyDayList(param);
+    @GetMapping("/hour/distribution")
+    @ApiOperation("查询光伏产能按区域分布")
+    public AjaxResult getDistribution(@ApiParam("查询参数") QueryMeter queryMeter) {
+        // 设置默认时间范围
+        if (StringUtils.isBlank(queryMeter.getStartRecTime()) || StringUtils.isBlank(queryMeter.getEndRecTime())) {
+            // 默认最近30天
+            LocalDate today = LocalDate.now();
+            LocalDate startDate = today.minusDays(30);
+            queryMeter.setStartRecTime(startDate.toString() + " 00:00:00");
+            queryMeter.setEndRecTime(today.toString() + " 23:59:59");
+        }
+        List<PvSupplyVO> list = pvSupplyHService.selectPvSupplyDistribution(queryMeter);
         return success(list);
     }
 
     /**
-     * 导出光伏并网计量光伏并网计量-小时列表
+     * 查询光伏产能分布明细(按区域和日期分组,用于对比柱状图)
      */
-    @Log(title = "光伏并网计量光伏并网计量-小时", businessType = BusinessType.EXPORT)
-    @PostMapping("/hour/export")
-    public void export(HttpServletResponse response, ElecPvSupplyH pvSupplyH) {
-        List<ElecPvSupplyH> list = pvSupplyHService.selectPvSupplyHList(pvSupplyH);
-        ExcelUtil<ElecPvSupplyH> util = new ExcelUtil<ElecPvSupplyH>(ElecPvSupplyH.class);
-        util.exportExcel(response, list, "光伏并网计量光伏并网计量-小时数据");
+    @PreAuthorize("@ss.hasPermi('ems:prod:list')")
+    @GetMapping("/hour/distribution/detail")
+    @ApiOperation("查询光伏产能分布明细")
+    public AjaxResult getDistributionDetail(@ApiParam("查询参数") QueryMeter queryMeter) {
+        // 设置默认时间范围
+        if (StringUtils.isBlank(queryMeter.getStartRecTime()) || StringUtils.isBlank(queryMeter.getEndRecTime())) {
+            LocalDate today = LocalDate.now();
+            LocalDate startDate = today.minusDays(30);
+            queryMeter.setStartRecTime(startDate.toString() + " 00:00:00");
+            queryMeter.setEndRecTime(today.toString() + " 23:59:59");
+        }
+        List<PvSupplyVO> list = pvSupplyHService.selectPvSupplyDistributionDetail(queryMeter);
+        return success(list);
+    }
+
+    /**
+     * 校验并设置默认参数
+     */
+    private void validateAndSetDefaults(QueryMeter queryMeter) {
+        if (queryMeter == null) {
+            throw new IllegalArgumentException("查询参数不能为空");
+        }
+        // 设置默认时间维度
+        if (StringUtils.isBlank(queryMeter.getTimeDimension())) {
+            queryMeter.setTimeDimension("month");
+        }
+        // 设置默认排序
+        if (StringUtils.isBlank(queryMeter.getOrderFlag())) {
+            queryMeter.setOrderFlag("desc");
+        }
+    }
+
+    /**
+     * 统一异常处理
+     */
+    private void handleExportException(HttpServletResponse response, BusinessException e) {
+        response.reset();
+        response.setContentType("application/json");
+        response.setCharacterEncoding("utf-8");
+        try {
+            response.getWriter().print("{\"code\":400,\"msg\":\"" + e.getMessage() + "\"}");
+        }
+        catch (Exception ex) {
+            logger.error("导出异常处理失败", ex);
+        }
     }
-}
+}

+ 8 - 3
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecStoreController.java

@@ -1,18 +1,18 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.ElecStoreH;
 import com.ruoyi.ems.model.ElecStoreAnalyze;
 import com.ruoyi.ems.model.QueryMeter;
 import com.ruoyi.ems.service.IElecStoreService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -75,6 +75,11 @@ public class ElecStoreController extends BaseController {
         util.exportExcel(response, list, "储能计量-小时数据");
     }
 
+    @GetMapping("/sum/h/storage")
+    public AjaxResult selectStoreHByDataRange(ElecStoreH elecStoreH) {
+        return success(elecStoreService.selectStoreHByDataRange(elecStoreH));
+    }
+
     /**
      * 获取储能计量-小时详细信息
      */

+ 89 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ElecStoreIndexController.java

@@ -0,0 +1,89 @@
+package com.ruoyi.web.controller.ems;
+
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.domain.ElecStoreIndex;
+import com.ruoyi.ems.service.IElecStoreIndexService;
+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 com.huashe.common.domain.AjaxResult;
+
+import java.util.List;
+
+/**
+ * 储能设施指标Controller
+ *
+ * @author ruoyi
+ * @date 2026-01-28
+ */
+@RestController
+@RequestMapping("/ems/elec/store/index")
+public class ElecStoreIndexController extends BaseController {
+    @Autowired
+    private IElecStoreIndexService elecStoreIndexService;
+
+    /**
+     * 查询储能设施指标列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:ElecStoreIndex:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ElecStoreIndex elecStoreIndex) {
+        startPage();
+        List<ElecStoreIndex> list = elecStoreIndexService.selectElecStoreIndexList(elecStoreIndex);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('ems:ElecStoreIndex:list')")
+    @GetMapping("/latest")
+    public AjaxResult latest(ElecStoreIndex elecStoreIndex) {
+        List<ElecStoreIndex> list = elecStoreIndexService.selectLatestByDevice(elecStoreIndex);
+        return success(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('ems:ElecStoreIndex:list')")
+    @GetMapping("/listAll")
+    public AjaxResult listAll(ElecStoreIndex elecStoreIndex) {
+        List<ElecStoreIndex> list = elecStoreIndexService.selectElecStoreIndexList(elecStoreIndex);
+        return success(list);
+    }
+
+    /**
+     * 新增储能设施指标
+     */
+    @PreAuthorize("@ss.hasPermi('ems:ElecStoreIndex:add')")
+    @Log(title = "储能设施指标", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ElecStoreIndex elecStoreIndex) {
+        return toAjax(elecStoreIndexService.insertElecStoreIndex(elecStoreIndex));
+    }
+
+    /**
+     * 修改储能设施指标
+     */
+    @PreAuthorize("@ss.hasPermi('ems:ElecStoreIndex:edit')")
+    @Log(title = "储能设施指标", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ElecStoreIndex elecStoreIndex) {
+        return toAjax(elecStoreIndexService.updateElecStoreIndex(elecStoreIndex));
+    }
+
+    /**
+     * 删除储能设施指标
+     */
+    @PreAuthorize("@ss.hasPermi('ems:ElecStoreIndex:remove')")
+    @Log(title = "储能设施指标", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(elecStoreIndexService.deleteElecStoreIndexByIds(ids));
+    }
+}

+ 3 - 5
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsCommonController.java

@@ -42,7 +42,7 @@ import java.util.List;
  */
 @RestController
 @RequestMapping("/ems/common")
-@Api(value = "CommonController", description = "公共数据接口")
+@Api(value = "EmsCommonController", description = "公共数据接口")
 public class EmsCommonController extends BaseController {
     @Autowired
     private IElecPriceTypeService valencyTypeService;
@@ -94,12 +94,10 @@ public class EmsCommonController extends BaseController {
         return success(types);
     }
 
-
     @GetMapping("/devProcess")
     @ApiOperation(value = "getDevProcess", notes = "维表-查询工艺类型")
-    public AjaxResult getDevProcess(DevProcess devProcess)
-    {
-        List<DevProcess> list =  processService.selectDevProcessList(devProcess);
+    public AjaxResult getDevProcess(DevProcess devProcess) {
+        List<DevProcess> list = processService.selectDevProcessList(devProcess);
         return success(list);
     }
 }

+ 8 - 13
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsDisStaCoalController.java

@@ -1,15 +1,15 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.EmsDisStaCoal;
 import com.ruoyi.ems.service.IEmsDisStaCoalService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -23,15 +23,14 @@ import java.util.List;
 
 /**
  * 能源折标准煤维表Controller
- * 
+ *
  * @author ruoyi
  * @date 2024-12-02
  */
 @RestController
 @RequestMapping("/ems/disStaCoal")
 @Api(value = "EmsDisStaCoalController", description = "能源折标准煤维表")
-public class EmsDisStaCoalController extends BaseController
-{
+public class EmsDisStaCoalController extends BaseController {
     @Autowired
     private IEmsDisStaCoalService disStaCoalService;
 
@@ -40,8 +39,7 @@ public class EmsDisStaCoalController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('ems:disStaCoal:list')")
     @GetMapping("/list")
-    public TableDataInfo list(EmsDisStaCoal disStaCoal)
-    {
+    public TableDataInfo list(EmsDisStaCoal disStaCoal) {
         startPage();
         List<EmsDisStaCoal> list = disStaCoalService.selectDisStaCoalList(disStaCoal);
         return getDataTable(list);
@@ -52,8 +50,7 @@ public class EmsDisStaCoalController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('ems:disStaCoal:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(disStaCoalService.selectDisStaCoalById(id));
     }
 
@@ -63,8 +60,7 @@ public class EmsDisStaCoalController extends BaseController
     @PreAuthorize("@ss.hasPermi('ems:disStaCoal:add')")
     @Log(title = "能源折标准煤维", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody EmsDisStaCoal disStaCoal)
-    {
+    public AjaxResult add(@RequestBody EmsDisStaCoal disStaCoal) {
         return toAjax(disStaCoalService.insertDisStaCoal(disStaCoal));
     }
 
@@ -74,8 +70,7 @@ public class EmsDisStaCoalController extends BaseController
     @PreAuthorize("@ss.hasPermi('ems:disStaCoal:edit')")
     @Log(title = "能源折标准煤维", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody EmsDisStaCoal disStaCoal)
-    {
+    public AjaxResult edit(@RequestBody EmsDisStaCoal disStaCoal) {
         return toAjax(disStaCoalService.updateDisStaCoal(disStaCoal));
     }
 

+ 103 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsEcoDController.java

@@ -0,0 +1,103 @@
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.domain.EmsEcoD;
+import com.ruoyi.ems.service.IEmsEcoDService;
+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 javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 节能计量日Controller
+ *
+ * @author ruoyi
+ * @date 2025-04-22
+ */
+@RestController
+@RequestMapping("/ems/EmsEcoD")
+public class EmsEcoDController extends BaseController {
+    @Autowired
+    private IEmsEcoDService emsEcoDService;
+
+    /**
+     * 查询节能计量日列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:EmsEcoD:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(EmsEcoD admEmsEcoD) {
+        startPage();
+        List<EmsEcoD> list = emsEcoDService.selectEmsEcoDList(admEmsEcoD);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出节能计量日列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:EmsEcoD:export')")
+    @Log(title = "节能计量日", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, EmsEcoD admEmsEcoD) {
+        List<EmsEcoD> list = emsEcoDService.selectEmsEcoDList(admEmsEcoD);
+        ExcelUtil<EmsEcoD> util = new ExcelUtil<EmsEcoD>(EmsEcoD.class);
+        util.exportExcel(response, list, "节能计量日数据");
+    }
+
+    /**
+     * 获取节能计量日详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ems:EmsEcoD:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(emsEcoDService.selectEmsEcoDById(id));
+    }
+
+    /**
+     * 新增节能计量日
+     */
+    @PreAuthorize("@ss.hasPermi('ems:EmsEcoD:add')")
+    @Log(title = "节能计量日", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody EmsEcoD admEmsEcoD) {
+        return toAjax(emsEcoDService.insertEmsEcoD(admEmsEcoD));
+    }
+
+    /**
+     * 修改节能计量日
+     */
+    @PreAuthorize("@ss.hasPermi('ems:EmsEcoD:edit')")
+    @Log(title = "节能计量日", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody EmsEcoD admEmsEcoD) {
+        return toAjax(emsEcoDService.updateEmsEcoD(admEmsEcoD));
+    }
+
+    /**
+     * 删除节能计量日
+     */
+    @PreAuthorize("@ss.hasPermi('ems:EmsEcoD:remove')")
+    @Log(title = "节能计量日", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(emsEcoDService.deleteEmsEcoDByIds(ids));
+    }
+
+    @GetMapping(value = "/date/range")
+    public AjaxResult calcAdmEmsEcoDateRange(EmsEcoD admEmsEcoD) {
+        return success(emsEcoDService.calcEmsEcoDateRange(admEmsEcoD));
+    }
+}

+ 77 - 18
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsFacsController.java

@@ -1,16 +1,22 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.domain.Area;
 import com.ruoyi.ems.domain.EmsFacs;
+import com.ruoyi.ems.enums.FacsCategoryType;
+import com.ruoyi.ems.model.TreeEntity;
+import com.ruoyi.ems.service.IAreaService;
 import com.ruoyi.ems.service.IEmsFacsService;
+import com.ruoyi.ems.util.AreaUtils;
 import io.swagger.annotations.Api;
+import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -22,7 +28,10 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 能源设施/系统Controller
@@ -35,7 +44,10 @@ import java.util.List;
 @Api(value = "EmsFacsController", description = "能源设施管理")
 public class EmsFacsController extends BaseController {
     @Autowired
-    private IEmsFacsService emsFacsService;
+    private IEmsFacsService facsService;
+
+    @Autowired
+    private IAreaService areaService;
 
     /**
      * 查询能源设施/系统列表
@@ -44,7 +56,7 @@ public class EmsFacsController extends BaseController {
     @GetMapping("/list")
     public TableDataInfo list(EmsFacs emsFacs) {
         startPage();
-        List<EmsFacs> list = emsFacsService.selectEmsFacsList(emsFacs);
+        List<EmsFacs> list = facsService.selectEmsFacsList(emsFacs);
         return getDataTable(list);
     }
 
@@ -55,7 +67,7 @@ public class EmsFacsController extends BaseController {
     @Log(title = "能源设施/系统", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
     public void export(HttpServletResponse response, EmsFacs emsFacs) {
-        List<EmsFacs> list = emsFacsService.selectEmsFacsList(emsFacs);
+        List<EmsFacs> list = facsService.selectEmsFacsList(emsFacs);
         ExcelUtil<EmsFacs> util = new ExcelUtil<EmsFacs>(EmsFacs.class);
         util.exportExcel(response, list, "能源设施/系统数据");
     }
@@ -66,7 +78,7 @@ public class EmsFacsController extends BaseController {
     @PreAuthorize("@ss.hasPermi('basecfg:emsfacs:query')")
     @GetMapping(value = "/{id}")
     public AjaxResult getInfo(@PathVariable("id") Long id) {
-        return success(emsFacsService.selectEmsFacsById(id));
+        return success(facsService.selectEmsFacsById(id));
     }
 
     /**
@@ -76,7 +88,7 @@ public class EmsFacsController extends BaseController {
     @Log(title = "能源设施/系统", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody EmsFacs emsFacs) {
-        return toAjax(emsFacsService.insertEmsFacs(emsFacs));
+        return toAjax(facsService.insertEmsFacs(emsFacs));
     }
 
     /**
@@ -86,7 +98,7 @@ public class EmsFacsController extends BaseController {
     @Log(title = "能源设施/系统", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody EmsFacs emsFacs) {
-        return toAjax(emsFacsService.updateEmsFacs(emsFacs));
+        return toAjax(facsService.updateEmsFacs(emsFacs));
     }
 
     /**
@@ -96,7 +108,7 @@ public class EmsFacsController extends BaseController {
     @Log(title = "能源设施/系统", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids) {
-        return toAjax(emsFacsService.deleteEmsFacsByIds(ids));
+        return toAjax(facsService.deleteEmsFacsByIds(ids));
     }
 
     /**
@@ -112,18 +124,65 @@ public class EmsFacsController extends BaseController {
         emsFacs.setRefArea(refArea);
         emsFacs.setFacsCategory(facsCategory);
         emsFacs.setFacsSubCategory(subCategory);
-
-        return success(emsFacsService.selectEmsFacsList(emsFacs));
+        return success(facsService.selectEmsFacsList(emsFacs));
     }
 
     /**
-     * 网-服务区日功率指标
+     * 查询区域树 (根据设施类型过滤)
      *
-     * @param areaCode
-     * @return
+     * @param parentCode   父区域编码
+     * @param facsCategory 设施类别
      */
-    @GetMapping("/area/day/power/index")
-    public AjaxResult qryAreaDayPowerIndex(@RequestParam("areaCode") String areaCode) {
-        return success(emsFacsService.qryAreaDayPowerIndex(areaCode));
+    @GetMapping("/getFacsCategoryTree")
+    public AjaxResult getFacsCategoryTree(
+        @RequestParam(name = "parentCode", required = false, defaultValue = "0") String parentCode,
+        @RequestParam(name = "facsCategory", required = false) String facsCategory) {
+        // 查询区域树
+        List<Area> areaTree = areaService.selectAreaTree(parentCode, false);
+        // 查询设施列表
+        EmsFacs facsParam = new EmsFacs();
+        facsParam.setFacsCategory(facsCategory);
+        List<EmsFacs> facsList = facsService.selectEmsFacsList(facsParam);
+        Map<String, List<EmsFacs>> groupByArea = facsList.stream()
+            .collect(Collectors.groupingBy(EmsFacs::getRefArea, Collectors.toList()));
+        List<TreeEntity> retList = AreaUtils.convertAreaTree(areaTree);
+        for (TreeEntity treeEntity : retList) {
+            List<EmsFacs> facsTemp = groupByArea.get(treeEntity.getId());
+            if (CollectionUtils.isNotEmpty(facsTemp)) {
+                Map<String, List<EmsFacs>> groupByCaregory = facsTemp.stream()
+                    .collect(Collectors.groupingBy(EmsFacs::getFacsCategory, Collectors.toList()));
+                List<TreeEntity> facsCategoryList = new ArrayList<>();
+                facsCategoryList.addAll(buildSubTree(FacsCategoryType.E, groupByCaregory));
+                facsCategoryList.addAll(buildSubTree(FacsCategoryType.W, groupByCaregory));
+                facsCategoryList.addAll(buildSubTree(FacsCategoryType.C, groupByCaregory));
+                facsCategoryList.addAll(buildSubTree(FacsCategoryType.Z, groupByCaregory));
+                treeEntity.setChildren(facsCategoryList);
+            }
+        }
+        return success(retList);
+    }
+
+    private List<TreeEntity> buildSubTree(FacsCategoryType type, Map<String, List<EmsFacs>> groupByCaregory) {
+        List<TreeEntity> retList = new ArrayList<>();
+        List<EmsFacs> facsList = groupByCaregory.get(type.name());
+        if (CollectionUtils.isNotEmpty(facsList)) {
+            TreeEntity cnEntity = new TreeEntity();
+            cnEntity.setId(type.name());
+            cnEntity.setLabel(type.getTypeName());
+            cnEntity.setChildren(convertEntity(facsList));
+            retList.add(cnEntity);
+        }
+        return retList;
+    }
+
+    public static List<TreeEntity> convertEntity(List<EmsFacs> facsList) {
+        List<TreeEntity> retList = new ArrayList<>();
+        facsList.forEach(facs -> {
+            TreeEntity treeEntity = new TreeEntity();
+            treeEntity.setId(facs.getFacsCode());
+            treeEntity.setLabel(facs.getFacsName());
+            retList.add(treeEntity);
+        });
+        return retList;
     }
 }

+ 27 - 2
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjAbilityController.java

@@ -1,12 +1,15 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.huashe.common.exception.BusinessException;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.ems.domain.EmsObjAbility;
+import com.ruoyi.ems.model.AbilityPayload;
+import com.ruoyi.ems.service.IAbilityCallService;
 import com.ruoyi.ems.service.IEmsObjAbilityService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -35,6 +38,28 @@ public class EmsObjAbilityController extends BaseController {
     @Autowired
     private IEmsObjAbilityService abilityService;
 
+    @Autowired
+    private IAbilityCallService abilityCallService;
+
+    /**
+     * 查询能源对象能力列表
+     */
+    @PostMapping("/call")
+    public AjaxResult callAbility(@RequestBody AbilityPayload abilityPayload) {
+        AjaxResult ajaxResult = null;
+        try {
+            String ret = abilityCallService.devAbilityCall(abilityPayload);
+            ajaxResult = success(ret);
+        }
+        catch (BusinessException e) {
+            ajaxResult = error(e.getMessage());
+        }
+        catch (Exception e) {
+            ajaxResult = error("内部错误!");
+        }
+        return ajaxResult;
+    }
+
     /**
      * 查询能源对象能力列表
      */

+ 84 - 29
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjAttrController.java

@@ -2,22 +2,24 @@ package com.ruoyi.web.controller.ems;
 
 import com.alibaba.fastjson2.JSONObject;
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.huashe.common.domain.JsonEntity;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.exception.Assert;
 import com.ruoyi.ems.domain.EmsDevice;
-import com.ruoyi.ems.domain.EmsFacs;
 import com.ruoyi.ems.domain.EmsObjAttr;
-import com.ruoyi.ems.domain.EmsObjAttrValue;
+import com.ruoyi.ems.domain.EmsObjAttrDto;
+import com.ruoyi.ems.domain.EmsObjAttrEnum;
+import com.ruoyi.ems.domain.EmsSubsystem;
 import com.ruoyi.ems.enums.DevObjType;
-import com.ruoyi.ems.service.IEmsDeviceService;
-import com.ruoyi.ems.service.IEmsFacsService;
+import com.ruoyi.ems.mapper.EmsDeviceMapper;
+import com.ruoyi.ems.mapper.EmsSubsystemMapper;
+import com.ruoyi.ems.service.IEmsObjAttrEnumService;
 import com.ruoyi.ems.service.IEmsObjAttrService;
-import com.ruoyi.ems.service.IEmsObjAttrValueService;
 import io.swagger.annotations.Api;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -29,8 +31,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 能源对象属性Controller
@@ -46,13 +49,13 @@ public class EmsObjAttrController extends BaseController {
     private IEmsObjAttrService attrService;
 
     @Autowired
-    private IEmsObjAttrValueService objAttrValueService;
+    private IEmsObjAttrEnumService enumService;
 
     @Autowired
-    private IEmsDeviceService deviceService;
+    private EmsDeviceMapper deviceService;
 
     @Autowired
-    private IEmsFacsService facsService;
+    private EmsSubsystemMapper subsystemService;
 
     /**
      * 查询能源对象属性列表
@@ -65,6 +68,16 @@ public class EmsObjAttrController extends BaseController {
     }
 
     /**
+     * 查询能源对象属性列表
+     */
+    @GetMapping("/enum/list")
+    public AjaxResult list(@RequestParam(name = "modelCode") String modelCode,
+        @RequestParam(name = "attrKey") String attrKey) {
+        List<EmsObjAttrEnum> list = enumService.getObjAttrEnumList(modelCode, attrKey);
+        return AjaxResult.success(list);
+    }
+
+    /**
      * 获取能源对象属性详细信息
      */
     @GetMapping(value = "/{id}")
@@ -77,35 +90,64 @@ public class EmsObjAttrController extends BaseController {
      */
     @GetMapping("/getObjAttr")
     public AjaxResult list(@RequestParam(name = "objType") Integer objType,
-        @RequestParam(name = "objCode") String objCode) {
+        @RequestParam(name = "objCode") String objCode,
+        @RequestParam(name = "modelCode", required = false) String modelCode) {
         AjaxResult result = null;
-
         try {
-            String modeCode = null;
-
-            if (objType == DevObjType.DEVC.getCode()) {
-                EmsDevice device = deviceService.selectByCode(objCode);
-                Assert.notNull(device, -1, "能源设备不存在");
-                modeCode = device.getDeviceModel();
-            }
-
             JSONObject json = new JSONObject();
             json.put("objType", objType);
             json.put("objCode", objCode);
-            json.put("modeCode", modeCode);
-
-            List<EmsObjAttr> modelAttrs = attrService.selectByModelCode(modeCode);
-            json.put("attrs", CollectionUtils.isNotEmpty(modelAttrs) ? modelAttrs : new ArrayList<>());
-
-            List<EmsObjAttrValue> attrValues = objAttrValueService.selectByObjCode(modeCode, objCode);
-            json.put("attrValues", attrValues);
-
+            if (null == modelCode) {
+                modelCode = getModeCode(objType, objCode);
+                json.put("modelCode", modelCode);
+            }
+            // 获取属性列表
+            List<EmsObjAttrDto> attrDtos = attrService.selectObjAttrItem(modelCode, objType, objCode);
+            if (CollectionUtils.isNotEmpty(attrDtos)) {
+                Map<String, List<EmsObjAttrDto>> attrMap = attrDtos.stream()
+                    .collect(Collectors.groupingBy(EmsObjAttrDto::getAttrGroup, Collectors.toList()));
+                json.putAll(attrMap);
+            }
             result = success(json);
         }
         catch (Exception e) {
             result = error(e.getMessage());
         }
+        return result;
+    }
 
+    /**
+     * 查询能源对象属性值列表
+     */
+    @GetMapping("/getObjAttrBatch")
+    public AjaxResult listAll(@RequestParam(name = "objType") Integer objType,
+        @RequestParam(name = "modelCode") String modelCode) {
+        AjaxResult result = null;
+        try {
+            // 获取属性列表
+            List<EmsObjAttrDto> attrDtos = attrService.selectObjAttrItem(modelCode, objType, null);
+            JsonEntity.ObjBuilder json = JsonEntity.objBuilder();
+            if (CollectionUtils.isNotEmpty(attrDtos)) {
+                attrDtos = attrDtos.stream().filter(attr -> StringUtils.isNotEmpty(attr.getObjCode()))
+                    .collect(Collectors.toList());
+                Map<String, List<EmsObjAttrDto>> objAttrMap = attrDtos.stream()
+                    .collect(Collectors.groupingBy(EmsObjAttrDto::getObjCode, Collectors.toList()));
+                for (Map.Entry<String, List<EmsObjAttrDto>> entry : objAttrMap.entrySet()) {
+                    String objCode = entry.getKey();
+                    List<EmsObjAttrDto> subDtos = entry.getValue();
+                    JsonEntity.ObjBuilder nodeJson = JsonEntity.objBuilder().putKv("objType", objType)
+                        .putKv("objCode", objCode).putKv("modelCode", modelCode);
+                    Map<String, List<EmsObjAttrDto>> gpAttrMap = subDtos.stream()
+                        .collect(Collectors.groupingBy(EmsObjAttrDto::getAttrGroup, Collectors.toList()));
+                    nodeJson.putMap(gpAttrMap);
+                    json.putKv(objCode, nodeJson.build().getJsonObj());
+                }
+            }
+            result = success(json.build().getJsonObj());
+        }
+        catch (Exception e) {
+            result = error(e.getMessage());
+        }
         return result;
     }
 
@@ -144,4 +186,17 @@ public class EmsObjAttrController extends BaseController {
     public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(attrService.deleteObjAttrByIds(ids));
     }
+
+    private String getModeCode(Integer objType, String objCode) {
+        String modeCode = null;
+        if (objType == DevObjType.DEVC.getCode()) {
+            EmsDevice device = deviceService.selectDeviceByCode(objCode);
+            modeCode = null != device ? device.getDeviceModel() : null;
+        }
+        else if (objType == DevObjType.SYSTEM.getCode()) {
+            EmsSubsystem subsystem = subsystemService.selectEmsSubsystemByCode(objCode);
+            modeCode = null != subsystem ? subsystem.getModelCode() : null;
+        }
+        return modeCode;
+    }
 }

+ 14 - 16
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjAttrValueController.java

@@ -1,12 +1,11 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.huashe.common.exception.Assert;
 import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.exception.Assert;
 import com.ruoyi.ems.domain.EmsDevice;
-import com.ruoyi.ems.domain.EmsFacs;
 import com.ruoyi.ems.domain.EmsObjAttrValue;
 import com.ruoyi.ems.enums.DevObjType;
 import com.ruoyi.ems.service.IEmsDeviceService;
@@ -65,19 +64,21 @@ public class EmsObjAttrValueController extends BaseController {
     /**
      * 新增能源对象属性值
      */
-    @Log(title = "能源对象属性值", businessType = BusinessType.INSERT)
-    @PostMapping("/batch")
-    public AjaxResult addBatch(@RequestBody List<EmsObjAttrValue> list) {
-        return toAjax(objAttrValueService.insertBatch(list));
+    @Log(title = "能源对象属性值", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult merge(@RequestBody EmsObjAttrValue EmsObjAttrValue) {
+        return toAjax(objAttrValueService.mergeObjAttrValue(EmsObjAttrValue));
     }
 
     /**
-     * 修改能源对象属性值
+     * 新增能源对象属性值
      */
-    @Log(title = "能源对象属性值", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody EmsObjAttrValue EmsObjAttrValue) {
-        return toAjax(objAttrValueService.mergeObjAttrValue(EmsObjAttrValue));
+    @Log(title = "能源对象属性值", businessType = BusinessType.INSERT)
+    @PostMapping("/batch")
+    public AjaxResult addBatch(@RequestBody List<EmsObjAttrValue> list) {
+        EmsObjAttrValue objAttrValue = list.get(0);
+        objAttrValueService.deleteByObjCode(objAttrValue.getModelCode(), objAttrValue.getObjCode());
+        return toAjax(objAttrValueService.insertBatch(list));
     }
 
     /**
@@ -96,17 +97,14 @@ public class EmsObjAttrValueController extends BaseController {
     @DeleteMapping("/deleteByObj")
     public AjaxResult removeByObj(@RequestParam(name = "objType") Integer objType,
         @RequestParam(name = "objCode") String objCode) {
-
         String modeCode = null;
         int cnt = 0;
-
-       if (objType == DevObjType.DEVC.getCode()) {
+        if (objType == DevObjType.DEVC.getCode()) {
             EmsDevice device = deviceService.selectByCode(objCode);
             Assert.notNull(device, -1, "能源设备不存在");
             modeCode = device.getDeviceModel();
             cnt = objAttrValueService.deleteByObjCode(modeCode, objCode);
         }
-
         return toAjax(cnt);
     }
 }

+ 10 - 17
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjEventController.java

@@ -1,9 +1,9 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
 import com.ruoyi.ems.domain.EmsObjEvent;
 import com.ruoyi.ems.service.IEmsObjEventService;
@@ -22,15 +22,14 @@ import java.util.List;
 
 /**
  * 能源对象事件Controller
- * 
+ *
  * @author ruoyi
  * @date 2024-09-23
  */
 @RestController
 @RequestMapping("/ems/object/event")
 @Api(value = "EmsObjEventController", description = "能源对象属性管理")
-public class EmsObjEventController extends BaseController
-{
+public class EmsObjEventController extends BaseController {
     @Autowired
     private IEmsObjEventService objEventService;
 
@@ -38,8 +37,7 @@ public class EmsObjEventController extends BaseController
      * 查询能源对象事件列表
      */
     @GetMapping("/list")
-    public TableDataInfo list(EmsObjEvent objEvent)
-    {
+    public TableDataInfo list(EmsObjEvent objEvent) {
         startPage();
         List<EmsObjEvent> list = objEventService.selectObjEventList(objEvent);
         return getDataTable(list);
@@ -49,8 +47,7 @@ public class EmsObjEventController extends BaseController
      * 获取能源对象事件详细信息
      */
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(objEventService.selectObjEventById(id));
     }
 
@@ -59,8 +56,7 @@ public class EmsObjEventController extends BaseController
      */
     @Log(title = "能源对象事件", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody EmsObjEvent objEvent)
-    {
+    public AjaxResult add(@RequestBody EmsObjEvent objEvent) {
         return toAjax(objEventService.insertObjEvent(objEvent));
     }
 
@@ -69,8 +65,7 @@ public class EmsObjEventController extends BaseController
      */
     @Log(title = "能源对象事件", businessType = BusinessType.INSERT)
     @PostMapping("/batch")
-    public AjaxResult addBatch(@RequestBody List<EmsObjEvent> list)
-    {
+    public AjaxResult addBatch(@RequestBody List<EmsObjEvent> list) {
         return toAjax(objEventService.insertBatch(list));
     }
 
@@ -79,8 +74,7 @@ public class EmsObjEventController extends BaseController
      */
     @Log(title = "能源对象事件", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody EmsObjEvent objEvent)
-    {
+    public AjaxResult edit(@RequestBody EmsObjEvent objEvent) {
         return toAjax(objEventService.updateObjEvent(objEvent));
     }
 
@@ -88,9 +82,8 @@ public class EmsObjEventController extends BaseController
      * 删除能源对象事件
      */
     @Log(title = "能源对象事件", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(objEventService.deleteObjEventByIds(ids));
     }
 }

+ 79 - 25
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjFlowRelController.java

@@ -1,37 +1,32 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.EmsObjFlowRel;
 import com.ruoyi.ems.service.IEmsObjFlowRelService;
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-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 java.util.List;
+import java.util.Map;
 
 /**
  * 能源设施能流关系Controller
- * 
+ *
  * @author ruoyi
  * @date 2024-07-10
  */
 @RestController
 @RequestMapping("/ems/object/flowrel")
 @Api(value = "EmsObjFlowRelController", description = "对象能流关系管理")
-public class EmsObjFlowRelController extends BaseController
-{
+public class EmsObjFlowRelController extends BaseController {
     @Autowired
     private IEmsObjFlowRelService facsFlowRelService;
 
@@ -40,8 +35,8 @@ public class EmsObjFlowRelController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('basecfg:flowrel:list')")
     @GetMapping("/list")
-    public TableDataInfo list(EmsObjFlowRel flowRel)
-    {
+    @ApiOperation("查询能流关系列表")
+    public TableDataInfo list(EmsObjFlowRel flowRel) {
         startPage();
         List<EmsObjFlowRel> list = facsFlowRelService.selectFlowRelList(flowRel);
         return getDataTable(list);
@@ -52,8 +47,8 @@ public class EmsObjFlowRelController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('basecfg:flowrel:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    @ApiOperation("获取能流关系详情")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(facsFlowRelService.selectFlowRelById(id));
     }
 
@@ -63,8 +58,8 @@ public class EmsObjFlowRelController extends BaseController
     @PreAuthorize("@ss.hasPermi('basecfg:flowrel:add')")
     @Log(title = "能源设施能流关系", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody EmsObjFlowRel facsFlowRel)
-    {
+    @ApiOperation("新增能流关系")
+    public AjaxResult add(@RequestBody EmsObjFlowRel facsFlowRel) {
         return toAjax(facsFlowRelService.insertFlowRel(facsFlowRel));
     }
 
@@ -74,8 +69,8 @@ public class EmsObjFlowRelController extends BaseController
     @PreAuthorize("@ss.hasPermi('basecfg:flowrel:edit')")
     @Log(title = "能源设施能流关系", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody EmsObjFlowRel flowRel)
-    {
+    @ApiOperation("修改能流关系")
+    public AjaxResult edit(@RequestBody EmsObjFlowRel flowRel) {
         return toAjax(facsFlowRelService.updateFlowRel(flowRel));
     }
 
@@ -84,9 +79,68 @@ public class EmsObjFlowRelController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('basecfg:flowrel:remove')")
     @Log(title = "能源设施能流关系", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
+    @DeleteMapping("/{ids}")
+    @ApiOperation("删除能流关系")
+    public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(facsFlowRelService.deleteFlowRelByIds(ids));
     }
-}
+
+    /**
+     * 获取能流拓扑数据
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:flowrel:list')")
+    @GetMapping("/topology")
+    @ApiOperation("获取能流拓扑数据")
+    public AjaxResult getTopology(@ApiParam("区域代码") @RequestParam(required = false) String areaCode,
+        @ApiParam("能源分类代码") @RequestParam(required = false) String emsClsCode) {
+        Map<String, Object> topology = facsFlowRelService.getFlowTopology(areaCode, emsClsCode);
+        return success(topology);
+    }
+
+    /**
+     * 验证能流关系是否存在
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:flowrel:query')")
+    @PostMapping("/validate")
+    @ApiOperation("验证能流关系是否存在")
+    public AjaxResult validate(@RequestBody EmsObjFlowRel flowRel) {
+        boolean exists = facsFlowRelService.checkFlowRelExists(flowRel);
+        return success(exists);
+    }
+
+    /**
+     * 获取上游能流(输入到该对象的能流)
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:flowrel:list')")
+    @GetMapping("/upstream")
+    @ApiOperation("获取上游能流")
+    public AjaxResult getUpstream(@ApiParam("对象代码") @RequestParam String objCode,
+        @ApiParam("对象类型") @RequestParam Integer objType) {
+        List<EmsObjFlowRel> list = facsFlowRelService.getUpstreamFlow(objCode, objType);
+        return success(list);
+    }
+
+    /**
+     * 获取下游能流(从该对象输出的能流)
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:flowrel:list')")
+    @GetMapping("/downstream")
+    @ApiOperation("获取下游能流")
+    public AjaxResult getDownstream(@ApiParam("对象代码") @RequestParam String objCode,
+        @ApiParam("对象类型") @RequestParam Integer objType) {
+        List<EmsObjFlowRel> list = facsFlowRelService.getDownstreamFlow(objCode, objType);
+        return success(list);
+    }
+
+    /**
+     * 获取指定能源类型的流动关系
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:flowrel:list')")
+    @GetMapping("/energytype")
+    @ApiOperation("按能源类型查询能流关系")
+    public AjaxResult getByEnergyType(@ApiParam("能源分类代码") @RequestParam(required = false) String emsClsCode,
+        @ApiParam("区域代码") @RequestParam(required = false) String areaCode) {
+        List<EmsObjFlowRel> list = facsFlowRelService.getFlowRelByEnergyType(emsClsCode, areaCode);
+        return success(list);
+    }
+}

+ 76 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjLogController.java

@@ -0,0 +1,76 @@
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.ems.domain.EmsObjAbilityCallLog;
+import com.ruoyi.ems.domain.EmsObjEventLog;
+import com.ruoyi.ems.service.IEmsObjAbilityCallLogService;
+import com.ruoyi.ems.service.IEmsObjEventLogService;
+import io.swagger.annotations.Api;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 用电业务Controller
+ *
+ * @author ruoyi
+ * @date 2024-07-23
+ */
+@RestController
+@RequestMapping("/ems/object/log")
+@Api(value = "EmsObjLogController", description = "能源对象日志服务")
+public class EmsObjLogController extends BaseController {
+    /**
+     * 事件日志
+     */
+    @Autowired
+    private IEmsObjEventLogService eventLogService;
+
+    /**
+     * 能力调用日志
+     */
+    @Autowired
+    private IEmsObjAbilityCallLogService abilityCallLogService;
+
+    /**
+     * 获取上报日志详细
+     */
+    @GetMapping(value = "/eventLog/{id}")
+    public AjaxResult getEventLogInfo(@PathVariable("id") Long id) {
+        return success(eventLogService.selectLogById(id));
+    }
+
+    /**
+     * 查询上报日志列表
+     */
+    @GetMapping("/eventLog/list")
+    public TableDataInfo listEventLog(EmsObjEventLog param) {
+        startPage();
+        List<EmsObjEventLog> list = eventLogService.selectLog(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取上报日志详细
+     */
+    @GetMapping(value = "/callLog/{id}")
+    public AjaxResult getCallLogInfo(@PathVariable("id") Long id) {
+        return success(abilityCallLogService.selectLogById(id));
+    }
+
+    /**
+     * 查询上报日志列表
+     */
+    @GetMapping("/callLog/list")
+    public TableDataInfo listCallLog(EmsObjAbilityCallLog param) {
+        startPage();
+        List<EmsObjAbilityCallLog> list = abilityCallLogService.selectLogList(param);
+        return getDataTable(list);
+    }
+}

+ 1 - 1
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsObjModelController.java

@@ -1,9 +1,9 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
 import com.ruoyi.ems.domain.EmsObjModel;
 import com.ruoyi.ems.service.IEmsObjModelService;

+ 15 - 32
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsSubsystemController.java

@@ -1,16 +1,15 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.EmsSubsystem;
 import com.ruoyi.ems.service.IEmsSubsystemService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -18,9 +17,9 @@ 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.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
-import javax.servlet.http.HttpServletResponse;
 import java.util.List;
 
 /**
@@ -32,8 +31,7 @@ import java.util.List;
 @RestController
 @RequestMapping("/ems/subsystem")
 @Api(value = "EmsSubsystemController", description = "能源子系统管理")
-public class EmsSubsystemController extends BaseController
-{
+public class EmsSubsystemController extends BaseController {
     @Autowired
     private IEmsSubsystemService emsSubsystemService;
 
@@ -41,8 +39,7 @@ public class EmsSubsystemController extends BaseController
      * 查询能源子系统列表
      */
     @GetMapping("/list")
-    public TableDataInfo list(EmsSubsystem emsSubsystem)
-    {
+    public TableDataInfo list(EmsSubsystem emsSubsystem) {
         startPage();
         List<EmsSubsystem> list = emsSubsystemService.selectEmsSubsystemList(emsSubsystem);
         return getDataTable(list);
@@ -52,23 +49,13 @@ public class EmsSubsystemController extends BaseController
      * 查询能源子系统列表
      */
     @GetMapping("/listAll")
-    public AjaxResult listAll(EmsSubsystem emsSubsystem)
-    {
-        return success( emsSubsystemService.selectEmsSubsystemList(emsSubsystem));
+    public AjaxResult listAll(EmsSubsystem emsSubsystem) {
+        return success(emsSubsystemService.selectEmsSubsystemList(emsSubsystem));
     }
 
-
-    /**
-     * 导出能源子系统列表
-     */
-    @PreAuthorize("@ss.hasPermi('adapter:subsystem:export')")
-    @Log(title = "能源子系统", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(HttpServletResponse response, EmsSubsystem emsSubsystem)
-    {
-        List<EmsSubsystem> list = emsSubsystemService.selectEmsSubsystemList(emsSubsystem);
-        ExcelUtil<EmsSubsystem> util = new ExcelUtil<EmsSubsystem>(EmsSubsystem.class);
-        util.exportExcel(response, list, "能源子系统数据");
+    @GetMapping("/getByCode")
+    public AjaxResult getByCode(@RequestParam(name = "systemCode") String systemCode) {
+        return success(emsSubsystemService.selectEmsSubsystemByCode(systemCode));
     }
 
     /**
@@ -76,8 +63,7 @@ public class EmsSubsystemController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('adapter:subsystem:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(emsSubsystemService.selectEmsSubsystemById(id));
     }
 
@@ -87,8 +73,7 @@ public class EmsSubsystemController extends BaseController
     @PreAuthorize("@ss.hasPermi('adapter:subsystem:add')")
     @Log(title = "能源子系统", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody EmsSubsystem emsSubsystem)
-    {
+    public AjaxResult add(@RequestBody EmsSubsystem emsSubsystem) {
         return toAjax(emsSubsystemService.insertEmsSubsystem(emsSubsystem));
     }
 
@@ -98,8 +83,7 @@ public class EmsSubsystemController extends BaseController
     @PreAuthorize("@ss.hasPermi('adapter:subsystem:edit')")
     @Log(title = "能源子系统", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody EmsSubsystem emsSubsystem)
-    {
+    public AjaxResult edit(@RequestBody EmsSubsystem emsSubsystem) {
         return toAjax(emsSubsystemService.updateEmsSubsystem(emsSubsystem));
     }
 
@@ -108,9 +92,8 @@ public class EmsSubsystemController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('adapter:subsystem:remove')")
     @Log(title = "能源子系统", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(emsSubsystemService.deleteEmsSubsystemByIds(ids));
     }
 }

+ 0 - 108
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EmsTagController.java

@@ -1,108 +0,0 @@
-package com.ruoyi.web.controller.ems;
-
-import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.ems.domain.EmsTag;
-import com.ruoyi.ems.service.IEmsTagService;
-import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-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 javax.servlet.http.HttpServletResponse;
-import java.util.List;
-
-/**
- * 标签分类Controller
- * 
- * @author ruoyi
- * @date 2024-08-16
- */
-@RestController
-@RequestMapping("/ems/tag")
-@Api(value = "EmsTagController", description = "标签管理管理")
-public class EmsTagController extends BaseController
-{
-    @Autowired
-    private IEmsTagService emsTagService;
-
-    /**
-     * 查询标签分类列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:tag:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(EmsTag emsTag)
-    {
-        startPage();
-        List<EmsTag> list = emsTagService.selectEmsTagList(emsTag);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出标签分类列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:tag:export')")
-    @Log(title = "标签分类", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(HttpServletResponse response, EmsTag dimEmsTag)
-    {
-        List<EmsTag> list = emsTagService.selectEmsTagList(dimEmsTag);
-        ExcelUtil<EmsTag> util = new ExcelUtil<EmsTag>(EmsTag.class);
-        util.exportExcel(response, list, "标签分类数据");
-    }
-
-    /**
-     * 获取标签分类详细信息
-     */
-    @PreAuthorize("@ss.hasPermi('ems:tag:query')")
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
-        return success(emsTagService.selectEmsTagById(id));
-    }
-
-    /**
-     * 新增标签分类
-     */
-    @PreAuthorize("@ss.hasPermi('ems:tag:add')")
-    @Log(title = "标签分类", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody EmsTag dimEmsTag)
-    {
-        return toAjax(emsTagService.insertEmsTag(dimEmsTag));
-    }
-
-    /**
-     * 修改标签分类
-     */
-    @PreAuthorize("@ss.hasPermi('ems:tag:edit')")
-    @Log(title = "标签分类", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody EmsTag dimEmsTag)
-    {
-
-        return toAjax(emsTagService.updateEmsTag(dimEmsTag));
-    }
-
-    /**
-     * 删除标签分类
-     */
-    @PreAuthorize("@ss.hasPermi('ems:tag:remove')")
-    @Log(title = "标签分类", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
-        return toAjax(emsTagService.deleteEmsTagByIds(ids));
-    }
-}

+ 564 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/EnergyConsumptionController.java

@@ -0,0 +1,564 @@
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.huashe.common.utils.ListUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.PageDomain;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.page.TableSupport;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.domain.ElecMeterH;
+import com.ruoyi.ems.domain.EnergyMeter;
+import com.ruoyi.ems.domain.WaterMeterH;
+import com.ruoyi.ems.model.ElecConsumptionExportVO;
+import com.ruoyi.ems.model.ElecConsumptionVO;
+import com.ruoyi.ems.model.QueryMeter;
+import com.ruoyi.ems.model.WaterConsumptionExportVO;
+import com.ruoyi.ems.model.WaterConsumptionVO;
+import com.ruoyi.ems.service.IElecConsumptionService;
+import com.ruoyi.ems.service.IWaterConsumptionService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 用能统计分析Controller - 重构版本
+ *
+ * @author ruoyi
+ * @date 2024-12-20
+ */
+@RestController
+@RequestMapping("/ems/consumption")
+@Api(value = "EnergyConsumptionController", description = "用能统计分析")
+public class EnergyConsumptionController extends BaseController {
+    @Autowired
+    private IElecConsumptionService elecConsumptionService;
+
+    @Autowired
+    private IWaterConsumptionService waterConsumptionService;
+
+    /**
+     * 查询用电小时计量类型列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/elec/hourTypes")
+    @ApiOperation("查询区域用电统计列表")
+    public AjaxResult getElecHourTypes(@ApiParam("查询参数") QueryMeter queryMeter) {
+        List<String> list = elecConsumptionService.getElecHourTypes(queryMeter);
+        return success(list);
+    }
+    // ==================== 电表计抄表查询 ====================
+
+    /**
+     * 查询电表计小时抄表列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/mdev/elec/hour/list")
+    @ApiOperation("查询电表小时抄表列表")
+    public TableDataInfo getMDevElecList(@ApiParam("查询参数") QueryMeter queryMeter) {
+        startPage();
+        List<ElecMeterH> list = elecConsumptionService.selectMDevElecConsumptionList(queryMeter);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询表计小时抄表列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/mdev/elec/hour/summary")
+    @ApiOperation("查询电表小时抄表统计")
+    public AjaxResult getMDevElecConsumptionSummary(@ApiParam("查询参数") QueryMeter queryMeter) {
+        ElecConsumptionVO summary = elecConsumptionService.selectMDevElecConsumptionSummary(queryMeter);
+        return success(summary);
+    }
+
+    /**
+     * 导出区域用电统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:export')")
+    @Log(title = "区域用电统计", businessType = BusinessType.EXPORT)
+    @PostMapping("/mdev/elec/hour/export")
+    @ApiOperation("导出区域用电统计数据")
+    public void exportMDevElecList(HttpServletResponse response, @RequestBody QueryMeter queryMeter) {
+        List<ElecMeterH> list = elecConsumptionService.selectMDevElecConsumptionList(queryMeter);
+        ExcelUtil<ElecMeterH> util = new ExcelUtil<>(ElecMeterH.class);
+        String fileName = String.format("电表抄表_%s_%s.xlsx", queryMeter.getTimeDimension(),
+            System.currentTimeMillis());
+        util.exportExcel(response, list, fileName);
+    }
+    // ==================== 区域用电统计 ====================
+
+    /**
+     * 查询区域用电统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/area/elec/list")
+    @ApiOperation("查询区域用电统计列表")
+    public TableDataInfo getAreaElecConsumptionList(@ApiParam("查询参数") QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(1);
+        List<ElecConsumptionVO> list = elecConsumptionService.selectAreaElecConsumptionList(queryMeter);
+        int total = list.size();
+        list = ListUtils.subList(list, (queryMeter.getPageNum() - 1) * queryMeter.getPageSize(),
+            queryMeter.getPageNum() * queryMeter.getPageSize() - 1);
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(200);
+        rspData.setRows(list);
+        rspData.setMsg("查询成功");
+        rspData.setTotal(total);
+        return rspData;
+    }
+
+    /**
+     * 查询区域用电统计汇总
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/area/elec/summary")
+    @ApiOperation("查询区域用电统计汇总")
+    public AjaxResult getAreaElecConsumptionSummary(@ApiParam("查询参数") QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(1);
+        ElecConsumptionVO summary = elecConsumptionService.selectAreaElecConsumptionSummary(queryMeter);
+        return success(summary);
+    }
+
+    /**
+     * 导出区域用电统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:export')")
+    @Log(title = "区域用电统计", businessType = BusinessType.EXPORT)
+    @PostMapping("/area/elec/export")
+    @ApiOperation("导出区域用电统计数据")
+    public void exportAreaElecConsumption(HttpServletResponse response, @RequestBody QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(1);
+        List<ElecConsumptionVO> list = elecConsumptionService.exportAreaElecConsumptionList(queryMeter);
+        // 转换为导出VO
+        List<ElecConsumptionExportVO> exportList = elecConsumptionService.convertToElecExportVOList(list,
+            queryMeter.getTimeDimension());
+        ExcelUtil<ElecConsumptionExportVO> util = new ExcelUtil<>(ElecConsumptionExportVO.class);
+        String fileName = String.format("区域用电统计_%s_%s.xlsx", queryMeter.getTimeDimension(),
+            System.currentTimeMillis());
+        util.exportExcel(response, exportList, fileName);
+    }
+
+    /**
+     * 查询区域用能统计数据
+     *
+     * @param queryMeter 查询条件
+     * @return
+     */
+    @GetMapping("/area/elec/hour/list")
+    public TableDataInfo getAreaElecHourMeter(QueryMeter queryMeter) {
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+        List<ElecMeterH> list = elecConsumptionService.getAreaElecHourMeter(queryMeter);
+        int total = list.size();
+        list = ListUtils.subList(list, (pageNum - 1) * pageSize, pageNum * pageSize - 1);
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(200);
+        rspData.setRows(list);
+        rspData.setMsg("查询成功");
+        rspData.setTotal(total);
+        return rspData;
+    }
+
+    /**
+     * 查询区域小时用电明细(按时间序列)
+     */
+    @GetMapping("/area/elec/hour/byTimeIndex")
+    @ApiOperation("查询按时间序列的区域用电统计")
+    public AjaxResult getAreaElecByTimeIndex(QueryMeter queryMeter) {
+        if (StringUtils.isBlank(queryMeter.getOrderFlag())) {
+            queryMeter.setOrderFlag("asc");
+        }
+        List<ElecMeterH> list = elecConsumptionService.getAreaElecHourMeter(queryMeter);
+        return success(list);
+    }
+
+    /**
+     * 计算时间范围内日平均用电
+     *
+     * @param queryMeter 支持areaCode、deviceCode、date范围筛选
+     * @return EnergyMeter
+     */
+    @GetMapping("/elec/day/avg")
+    @ApiOperation("计算时间范围内日平均用电")
+    public AjaxResult getElecDayAvg(@ApiParam("查询参数") QueryMeter queryMeter) {
+        EnergyMeter result = elecConsumptionService.selectElecDayAvg(queryMeter);
+        return success(result);
+    }
+
+    /**
+     * 按设备类型查询日用电
+     *
+     * @param queryMeter 查询条件(startRecTime、areaCode)
+     * @return 设备用电列表
+     */
+    @GetMapping("/elec/sum/device/day")
+    @ApiOperation("按设备类型查询日用电")
+    public AjaxResult qryDeviceElecByDay(QueryMeter queryMeter) {
+        List<EnergyMeter> result = elecConsumptionService.qryDeviceElecByDay(queryMeter);
+        return success(result);
+    }
+    // ==================== 设施用电统计 ====================
+
+    /**
+     * 查询设施用电统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/facs/elec/list")
+    @ApiOperation("查询设施用电统计列表")
+    public TableDataInfo getFacsElecConsumptionList(@ApiParam("查询参数") QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(2);
+        if (StringUtils.isBlank(queryMeter.getFacsCategory())) {
+            queryMeter.setFacsCategory("Z");
+        }
+        List<ElecConsumptionVO> list = elecConsumptionService.selectFacsElecConsumptionList(queryMeter);
+        int total = list.size();
+        list = ListUtils.subList(list, (queryMeter.getPageNum() - 1) * queryMeter.getPageSize(),
+            queryMeter.getPageNum() * queryMeter.getPageSize() - 1);
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(200);
+        rspData.setRows(list);
+        rspData.setMsg("查询成功");
+        rspData.setTotal(total);
+        return rspData;
+    }
+
+    /**
+     * 查询设施用电统计汇总
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/facs/elec/summary")
+    @ApiOperation("查询设施用电统计汇总")
+    public AjaxResult getFacsElecConsumptionSummary(@ApiParam("查询参数") QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(2);
+        if (StringUtils.isBlank(queryMeter.getFacsCategory())) {
+            queryMeter.setFacsCategory("Z");
+        }
+        ElecConsumptionVO summary = elecConsumptionService.selectFacsElecConsumptionSummary(queryMeter);
+        return success(summary);
+    }
+
+    /**
+     * 导出设施用电统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:export')")
+    @Log(title = "设施用电统计", businessType = BusinessType.EXPORT)
+    @PostMapping("/facs/elec/export")
+    @ApiOperation("导出设施用电统计数据")
+    public void exportFacsElecConsumption(HttpServletResponse response, @RequestBody QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(2);
+        if (StringUtils.isBlank(queryMeter.getFacsCategory())) {
+            queryMeter.setFacsCategory("Z");
+        }
+        List<ElecConsumptionVO> list = elecConsumptionService.exportFacsElecConsumptionList(queryMeter);
+        // 转换为导出VO
+        List<ElecConsumptionExportVO> exportList = elecConsumptionService.convertToElecExportVOList(list,
+            queryMeter.getTimeDimension());
+        ExcelUtil<ElecConsumptionExportVO> util = new ExcelUtil<>(ElecConsumptionExportVO.class);
+        String fileName = String.format("设施用电统计_%s_%s.xlsx", queryMeter.getTimeDimension(),
+            System.currentTimeMillis());
+        util.exportExcel(response, exportList, fileName);
+    }
+
+    /**
+     * 查询设施用能统计数据
+     *
+     * @param queryMeter 查询条件
+     * @return
+     */
+    @GetMapping("/facs/elec/listFacsMeterHourSta")
+    public AjaxResult listFacsMeterHourSta(QueryMeter queryMeter) {
+        queryMeter.setMeterCls(45);
+        List<ElecConsumptionVO> list = elecConsumptionService.getFacsMeterHourSta(queryMeter);
+        return success(list);
+    }
+
+    /**
+     * 查询设施用能统计数据
+     *
+     * @param queryMeter 查询条件
+     * @return
+     */
+    @GetMapping("/facs/elec/hour/list")
+    public TableDataInfo getFacsElecHourMeter(QueryMeter queryMeter) {
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+        queryMeter.setMeterCls(45);
+        List<ElecMeterH> list = elecConsumptionService.getFacsElecHourMeter(queryMeter);
+        int total = list.size();
+        list = ListUtils.subList(list, (pageNum - 1) * pageSize, pageNum * pageSize - 1);
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(200);
+        rspData.setRows(list);
+        rspData.setMsg("查询成功");
+        rspData.setTotal(total);
+        return rspData;
+    }
+    // ==================== 水表计抄表查询 ====================
+
+    /**
+     * 查询电表计小时抄表列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/mdev/water/hour/list")
+    @ApiOperation("查询水表计小时抄表列表")
+    public TableDataInfo getMDevWaterList(@ApiParam("查询参数") QueryMeter queryMeter) {
+        startPage();
+        List<WaterMeterH> list = waterConsumptionService.selectMDevWaterConsumptionList(queryMeter);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询表计小时抄表列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/mdev/water/hour/summary")
+    @ApiOperation("查询水表计小时抄表统计")
+    public AjaxResult getMDevWaterConsumptionSummary(@ApiParam("查询参数") QueryMeter queryMeter) {
+        WaterConsumptionVO summary = waterConsumptionService.selectMDevWaterConsumptionSummary(queryMeter);
+        return success(summary);
+    }
+
+    /**
+     * 导出区域用电统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:export')")
+    @Log(title = "区域用水统计", businessType = BusinessType.EXPORT)
+    @PostMapping("/mdev/water/hour/export")
+    @ApiOperation("导出区域用水统计数据")
+    public void exportMDevWaterList(HttpServletResponse response, @RequestBody QueryMeter queryMeter) {
+        List<WaterMeterH> list = waterConsumptionService.selectMDevWaterConsumptionList(queryMeter);
+        ExcelUtil<WaterMeterH> util = new ExcelUtil<>(WaterMeterH.class);
+        String fileName = String.format("水表抄表_%s_%s.xlsx", queryMeter.getTimeDimension(),
+            System.currentTimeMillis());
+        util.exportExcel(response, list, fileName);
+    }
+    // ==================== 区域用水统计 ====================
+
+    /**
+     * 查询区域小时用水明细
+     */
+    @GetMapping("/area/water/hour/list")
+    @ApiOperation("查询区域小时用水明细")
+    public TableDataInfo getAreaWaterHourMeter(QueryMeter queryMeter) {
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+        List<WaterMeterH> list = waterConsumptionService.getAreaWaterHourMeter(queryMeter);
+        int total = list.size();
+        list = ListUtils.subList(list, (pageNum - 1) * pageSize, pageNum * pageSize - 1);
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(200);
+        rspData.setRows(list);
+        rspData.setMsg("查询成功");
+        rspData.setTotal(total);
+        return rspData;
+    }
+
+    /**
+     * 查询设施小时用水明细
+     */
+    @GetMapping("/facs/water/hour/list")
+    @ApiOperation("查询设施小时用水明细")
+    public TableDataInfo getFacsWaterHourMeter(QueryMeter queryMeter) {
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+        queryMeter.setMeterCls(70);
+        List<WaterMeterH> list = waterConsumptionService.getFacsWaterHourMeter(queryMeter);
+        int total = list.size();
+        list = ListUtils.subList(list, (pageNum - 1) * pageSize, pageNum * pageSize - 1);
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(200);
+        rspData.setRows(list);
+        rspData.setMsg("查询成功");
+        rspData.setTotal(total);
+        return rspData;
+    }
+
+    /**
+     * 查询设施小时用水汇总统计
+     */
+    @GetMapping("/facs/water/listFacsMeterHourSta")
+    @ApiOperation("查询设施小时用水汇总")
+    public AjaxResult listFacsWaterMeterHourSta(QueryMeter queryMeter) {
+        queryMeter.setMeterCls(70);
+        List<WaterConsumptionVO> list = waterConsumptionService.getFacsWaterHourSta(queryMeter);
+        return success(list);
+    }
+
+    /**
+     * 查询区域用水统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/area/water/list")
+    @ApiOperation("查询区域用水统计列表")
+    public TableDataInfo getAreaWaterConsumptionList(@ApiParam("查询参数") QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(1);
+        List<WaterConsumptionVO> list = waterConsumptionService.selectAreaWaterConsumptionList(queryMeter);
+        int total = list.size();
+        list = ListUtils.subList(list, (queryMeter.getPageNum() - 1) * queryMeter.getPageSize(),
+            queryMeter.getPageNum() * queryMeter.getPageSize() - 1);
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(200);
+        rspData.setRows(list);
+        rspData.setMsg("查询成功");
+        rspData.setTotal(total);
+        return rspData;
+    }
+
+    /**
+     * 查询区域用水统计汇总
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/area/water/summary")
+    @ApiOperation("查询区域用水统计汇总")
+    public AjaxResult getAreaWaterConsumptionSummary(@ApiParam("查询参数") QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(1);
+        WaterConsumptionVO summary = waterConsumptionService.selectAreaWaterConsumptionSummary(queryMeter);
+        return success(summary);
+    }
+
+    /**
+     * 导出区域用水统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:export')")
+    @Log(title = "区域用水统计", businessType = BusinessType.EXPORT)
+    @PostMapping("/area/water/export")
+    @ApiOperation("导出区域用水统计数据")
+    public void exportAreaWaterConsumption(HttpServletResponse response, @RequestBody QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(1);
+        List<WaterConsumptionVO> list = waterConsumptionService.exportAreaWaterConsumptionList(queryMeter);
+        // 转换为导出VO
+        List<WaterConsumptionExportVO> exportList = waterConsumptionService.convertToWaterExportVOList(list,
+            queryMeter.getTimeDimension());
+        ExcelUtil<WaterConsumptionExportVO> util = new ExcelUtil<>(WaterConsumptionExportVO.class);
+        String fileName = String.format("区域用水统计_%s_%s.xlsx", queryMeter.getTimeDimension(),
+            System.currentTimeMillis());
+        util.exportExcel(response, exportList, fileName);
+    }
+
+    /**
+     * 查询按时间序列(小时)的用水统计
+     */
+    @GetMapping("/area/water/hour/byTimeIndex")
+    @ApiOperation("查询按时间序列的用水统计")
+    public AjaxResult getAreaWaterByTimeIndex(@RequestParam(name = "startRecTime") String startRecTime,
+        @RequestParam(name = "endRecTime", required = false) String endRecTime,
+        @RequestParam(name = "areaCode", required = false) String areaCode) {
+        QueryMeter queryMeter = new QueryMeter();
+        queryMeter.setStartRecTime(startRecTime);
+        queryMeter.setEndRecTime(endRecTime != null ? endRecTime : startRecTime + " 23:59:59");
+        queryMeter.setAreaCode(areaCode);
+        queryMeter.setObjCode(areaCode);
+        List<WaterMeterH> list = waterConsumptionService.getAreaWaterHourMeter(queryMeter);
+        return success(list);
+    }
+    // ==================== 设施用水统计 ====================
+
+    /**
+     * 查询设施用水统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/facs/water/list")
+    @ApiOperation("查询设施用水统计列表")
+    public TableDataInfo getFacsWaterConsumptionList(@ApiParam("查询参数") QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(2);
+        if (StringUtils.isBlank(queryMeter.getFacsCategory())) {
+            queryMeter.setFacsCategory("Z");
+        }
+        List<WaterConsumptionVO> list = waterConsumptionService.selectFacsWaterConsumptionList(queryMeter);
+        int total = list.size();
+        list = ListUtils.subList(list, (queryMeter.getPageNum() - 1) * queryMeter.getPageSize(),
+            queryMeter.getPageNum() * queryMeter.getPageSize() - 1);
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(200);
+        rspData.setRows(list);
+        rspData.setMsg("查询成功");
+        rspData.setTotal(total);
+        return rspData;
+    }
+
+    /**
+     * 查询设施用水统计汇总
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:list')")
+    @GetMapping("/facs/water/summary")
+    @ApiOperation("查询设施用水统计汇总")
+    public AjaxResult getFacsWaterConsumptionSummary(@ApiParam("查询参数") QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(2);
+        if (StringUtils.isBlank(queryMeter.getFacsCategory())) {
+            queryMeter.setFacsCategory("Z");
+        }
+        WaterConsumptionVO summary = waterConsumptionService.selectFacsWaterConsumptionSummary(queryMeter);
+        return success(summary);
+    }
+
+    /**
+     * 导出设施用水统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('ems:consumption:export')")
+    @Log(title = "设施用水统计", businessType = BusinessType.EXPORT)
+    @PostMapping("/facs/water/export")
+    @ApiOperation("导出设施用水统计数据")
+    public void exportFacsWaterConsumption(HttpServletResponse response, @RequestBody QueryMeter queryMeter) {
+        validateAndSetDefaults(queryMeter);
+        queryMeter.setObjType(2);
+        if (StringUtils.isBlank(queryMeter.getFacsCategory())) {
+            queryMeter.setFacsCategory("Z");
+        }
+        List<WaterConsumptionVO> list = waterConsumptionService.exportFacsWaterConsumptionList(queryMeter);
+        // 转换为导出VO
+        List<WaterConsumptionExportVO> exportList = waterConsumptionService.convertToWaterExportVOList(list,
+            queryMeter.getTimeDimension());
+        ExcelUtil<WaterConsumptionExportVO> util = new ExcelUtil<>(WaterConsumptionExportVO.class);
+        String fileName = String.format("设施用水统计_%s_%s.xlsx", queryMeter.getTimeDimension(),
+            System.currentTimeMillis());
+        util.exportExcel(response, exportList, fileName);
+    }
+    // ==================== 参数校验方法 ====================
+
+    /**
+     * 校验并设置默认参数
+     */
+    private void validateAndSetDefaults(QueryMeter queryMeter) {
+        if (queryMeter == null) {
+            throw new IllegalArgumentException("查询参数不能为空");
+        }
+        // 设置默认时间维度
+        if (StringUtils.isBlank(queryMeter.getTimeDimension())) {
+            queryMeter.setTimeDimension("month");
+        }
+        // 设置默认排序
+        if (StringUtils.isBlank(queryMeter.getOrderFlag())) {
+            queryMeter.setOrderFlag("desc");
+        }
+    }
+}

+ 3 - 3
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/FacsCategoryController.java

@@ -1,16 +1,16 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.FacsCategory;
 import com.ruoyi.ems.service.IFacsCategoryService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;

+ 70 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/FdEnergyPriceConfigController.java

@@ -0,0 +1,70 @@
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.domain.FdEnergyPriceConfig;
+import com.ruoyi.ems.service.IFdEnergyPriceConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+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 java.util.List;
+
+/**
+ * 非电能源价格配置Controller
+ *
+ * @author ruoyi
+ * @date 2025-08-16
+ */
+@RestController
+@RequestMapping("/ems/basecfg/fdPrice")
+public class FdEnergyPriceConfigController extends BaseController {
+    @Autowired
+    private IFdEnergyPriceConfigService fdEnergyPriceConfigService;
+
+    /**
+     * 查询非电能源价格配置列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(FdEnergyPriceConfig config) {
+        startPage();
+        List<FdEnergyPriceConfig> list = fdEnergyPriceConfigService.selectList(config);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取非电能源价格配置详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ems:FdEnergyPriceConfig:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(fdEnergyPriceConfigService.selectConfigById(id));
+    }
+
+    /**
+     * 新增非电能源价格配置
+     */
+    @Log(title = "非电能源价格配置", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FdEnergyPriceConfig config) {
+        return toAjax(fdEnergyPriceConfigService.insertConfig(config));
+    }
+
+    /**
+     * 修改非电能源价格配置
+     */
+    @Log(title = "非电能源价格配置", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FdEnergyPriceConfig config) {
+        return toAjax(fdEnergyPriceConfigService.updateConfig(config));
+    }
+}

+ 236 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/InspectionPlanController.java

@@ -0,0 +1,236 @@
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.ems.domain.InspectionPlan;
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.domain.ManualReportSubmit;
+import com.ruoyi.ems.service.IInspectionPlanService;
+import com.ruoyi.ems.task.InspectionScheduler;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 巡检计划Controller
+ * 【重要改动】
+ * 在增删改操作后同步调度器状态
+ * 调度器仅在 EMS 主模块加载,Service 层保持纯净
+ */
+@RestController
+@RequestMapping("/ems/inspection/plan")
+@Api(value = "InspectionPlanController", description = "巡检计划管理")
+public class InspectionPlanController extends BaseController {
+    private static final Logger log = LoggerFactory.getLogger(InspectionPlanController.class);
+    private static final int PLAN_TYPE_AUTO = 2;
+
+    @Autowired
+    private IInspectionPlanService planService;
+
+    /**
+     * 注入调度器(仅在EMS主模块存在)
+     */
+    @Autowired
+    private InspectionScheduler inspectionScheduler;
+
+    /**
+     * 查询巡检计划列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:list')")
+    @GetMapping("/list")
+    @ApiOperation("查询巡检计划列表")
+    public TableDataInfo list(InspectionPlan plan) {
+        startPage();
+        List<InspectionPlan> list = planService.selectList(plan);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出巡检计划列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:export')")
+    @Log(title = "巡检计划", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    @ApiOperation("导出巡检计划")
+    public void export(HttpServletResponse response, InspectionPlan plan) {
+        List<InspectionPlan> list = planService.selectList(plan);
+        ExcelUtil<InspectionPlan> util = new ExcelUtil<>(InspectionPlan.class);
+        util.exportExcel(response, list, "巡检计划数据");
+    }
+
+    /**
+     * 获取巡检计划详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:query')")
+    @GetMapping("/{id}")
+    @ApiOperation("获取巡检计划详情")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(planService.selectById(id));
+    }
+
+    /**
+     * 根据计划代码获取详情
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:query')")
+    @GetMapping("/code/{planCode}")
+    @ApiOperation("根据计划代码获取详情")
+    public AjaxResult getByCode(@PathVariable("planCode") String planCode) {
+        return success(planService.selectByPlanCode(planCode));
+    }
+
+    /**
+     * 新增巡检计划
+     * 【改动】新增后同步注册到调度器
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:add')")
+    @Log(title = "巡检计划", businessType = BusinessType.INSERT)
+    @PostMapping
+    @ApiOperation("新增巡检计划")
+    public AjaxResult add(@RequestBody InspectionPlan plan) {
+        plan.setCreateBy(SecurityUtils.getUsername());
+        int result = planService.insert(plan);
+        // 如果是自动巡检且配置了cron,注册到调度器
+        if (result > 0 && isSchedulablePlan(plan)) {
+            syncSchedulerRegister(plan);
+        }
+        return toAjax(result);
+    }
+
+    /**
+     * 修改巡检计划
+     * 【改动】修改后刷新调度器配置
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:edit')")
+    @Log(title = "巡检计划", businessType = BusinessType.UPDATE)
+    @PutMapping
+    @ApiOperation("修改巡检计划")
+    public AjaxResult edit(@RequestBody InspectionPlan plan) {
+        plan.setUpdateBy(SecurityUtils.getUsername());
+        int result = planService.update(plan);
+        // 刷新调度器配置
+        if (result > 0) {
+            syncSchedulerRefresh(plan.getPlanCode());
+        }
+        return toAjax(result);
+    }
+
+    /**
+     * 删除巡检计划
+     * 【改动】删除前从调度器注销
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:remove')")
+    @Log(title = "巡检计划", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    @ApiOperation("删除巡检计划")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        // 先从调度器注销
+        for (Long id : ids) {
+            InspectionPlan plan = planService.selectById(id);
+            if (plan != null) {
+                syncSchedulerUnregister(plan.getPlanCode());
+            }
+        }
+        return toAjax(planService.deleteByIds(ids));
+    }
+
+    /**
+     * 执行自动巡检
+     * 仅用于自动巡检计划(planType=2)
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:execute')")
+    @Log(title = "执行自动巡检", businessType = BusinessType.OTHER)
+    @PostMapping("/execute/{planCode}")
+    @ApiOperation("执行自动巡检")
+    public AjaxResult execute(@PathVariable("planCode") String planCode) {
+        try {
+            String executor = SecurityUtils.getUsername();
+            InspectionReport report = planService.executeAutoInspection(planCode, executor);
+            return success(report);
+        }
+        catch (Exception e) {
+            return error(e.getMessage());
+        }
+    }
+
+    /**
+     * 提交手动巡检报告
+     * 用于手动巡检计划(planType=1),工作人员现场巡检后提交报告
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:execute')")
+    @Log(title = "提交手动巡检报告", businessType = BusinessType.INSERT)
+    @PostMapping("/submitManual")
+    @ApiOperation("提交手动巡检报告")
+    public AjaxResult submitManualReport(@RequestBody ManualReportSubmit submit) {
+        try {
+            String submitter = SecurityUtils.getUsername();
+            submit.setSubmitter(submitter);
+            InspectionReport report = planService.submitManualReport(submit);
+            return success(report);
+        }
+        catch (Exception e) {
+            return error(e.getMessage());
+        }
+    }
+    // ==================== 调度器同步方法 ====================
+
+    /**
+     * 判断计划是否可调度
+     */
+    private boolean isSchedulablePlan(InspectionPlan plan) {
+        return plan != null && plan.getPlanType() != null && plan.getPlanType() == PLAN_TYPE_AUTO
+            && StringUtils.isNotBlank(plan.getCronExpression());
+    }
+
+    /**
+     * 同步注册到调度器
+     */
+    private void syncSchedulerRegister(InspectionPlan plan) {
+        try {
+            inspectionScheduler.registerPlan(plan);
+            log.info("巡检计划[{}]已注册到调度器", plan.getPlanCode());
+        }
+        catch (Exception e) {
+            // 注册失败不影响业务,仅记录日志
+            log.warn("同步注册调度器失败: planCode={}, error={}", plan.getPlanCode(), e.getMessage());
+        }
+    }
+
+    /**
+     * 同步刷新调度器
+     */
+    private void syncSchedulerRefresh(String planCode) {
+        try {
+            inspectionScheduler.refreshPlan(planCode);
+            log.info("巡检计划[{}]调度配置已刷新", planCode);
+        }
+        catch (Exception e) {
+            log.warn("同步刷新调度器失败: planCode={}, error={}", planCode, e.getMessage());
+        }
+    }
+
+    /**
+     * 同步注销调度器
+     */
+    private void syncSchedulerUnregister(String planCode) {
+        try {
+            inspectionScheduler.unregisterPlan(planCode);
+            log.info("巡检计划[{}]已从调度器注销", planCode);
+        }
+        catch (Exception e) {
+            log.warn("同步注销调度器失败: planCode={}, error={}", planCode, e.getMessage());
+        }
+    }
+}

+ 128 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/InspectionReportController.java

@@ -0,0 +1,128 @@
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.domain.ManualReportSubmit;
+import com.ruoyi.ems.service.IInspectionReportService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 巡检报告Controller
+ */
+@RestController
+@RequestMapping("/ems/inspection/report")
+@Api(value = "InspectionReportController", description = "巡检报告管理")
+public class InspectionReportController extends BaseController {
+    @Autowired
+    private IInspectionReportService reportService;
+
+    /**
+     * 查询巡检报告列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:report:list')")
+    @GetMapping("/list")
+    @ApiOperation("查询巡检报告列表")
+    public TableDataInfo list(InspectionReport report) {
+        startPage();
+        List<InspectionReport> list = reportService.selectList(report);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出巡检报告列表
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:report:export')")
+    @Log(title = "巡检报告", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    @ApiOperation("导出巡检报告")
+    public void export(HttpServletResponse response, InspectionReport report) {
+        List<InspectionReport> list = reportService.selectList(report);
+        ExcelUtil<InspectionReport> util = new ExcelUtil<>(InspectionReport.class);
+        util.exportExcel(response, list, "巡检报告数据");
+    }
+
+    /**
+     * 获取巡检报告详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:report:query')")
+    @GetMapping("/{id}")
+    @ApiOperation("获取巡检报告详情")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(reportService.selectById(id));
+    }
+
+    /**
+     * 根据报告代码获取详情
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:report:query')")
+    @GetMapping("/code/{reportCode}")
+    @ApiOperation("根据报告代码获取详情")
+    public AjaxResult getByCode(@PathVariable("reportCode") String reportCode) {
+        return success(reportService.selectByReportCode(reportCode));
+    }
+
+    /**
+     * 修改巡检报告(手动备注)
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:report:edit')")
+    @Log(title = "巡检报告", businessType = BusinessType.UPDATE)
+    @PutMapping
+    @ApiOperation("修改巡检报告")
+    public AjaxResult edit(@RequestBody InspectionReport report) {
+        return toAjax(reportService.update(report));
+    }
+
+    /**
+     * 更新手动巡检报告
+     * 用于编辑已提交的手动巡检报告,支持更新巡检结果、报告内容、图片等
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:report:edit')")
+    @Log(title = "更新手动巡检报告", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateManual")
+    @ApiOperation("更新手动巡检报告")
+    public AjaxResult updateManualReport(@RequestBody ManualReportSubmit submit) {
+        try {
+            submit.setSubmitter(SecurityUtils.getUsername());
+            int result = reportService.updateManualReport(submit);
+            return toAjax(result);
+        }
+        catch (Exception e) {
+            return error(e.getMessage());
+        }
+    }
+
+    /**
+     * 删除巡检报告
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:report:remove')")
+    @Log(title = "巡检报告", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    @ApiOperation("删除巡检报告")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(reportService.deleteByIds(ids));
+    }
+
+    /**
+     * 提交手动巡检结果
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:report:edit')")
+    @Log(title = "提交手动巡检", businessType = BusinessType.UPDATE)
+    @PostMapping("/submit")
+    @ApiOperation("提交手动巡检结果")
+    public AjaxResult submitManual(@RequestBody InspectionReport report) {
+        return toAjax(reportService.submitManualReport(report));
+    }
+}

+ 151 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/InspectionSchedulerController.java

@@ -0,0 +1,151 @@
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.task.InspectionScheduler;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * 巡检调度器管理Controller
+ * 提供调度器状态查询、手动触发、刷新配置等功能
+ * 仅在 EMS 主模块可用
+ */
+@RestController
+@RequestMapping("/ems/inspection/scheduler")
+@Api(value = "InspectionSchedulerController", description = "巡检调度器管理")
+public class InspectionSchedulerController extends BaseController {
+    @Autowired
+    private InspectionScheduler inspectionScheduler;
+
+    /**
+     * 获取调度器状态
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:query')")
+    @GetMapping("/status")
+    @ApiOperation("获取调度器状态")
+    public AjaxResult getStatus() {
+        Map<String, Object> status = inspectionScheduler.getStatus();
+        return success(status);
+    }
+
+    /**
+     * 重新加载所有巡检计划
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:edit')")
+    @Log(title = "重载巡检计划", businessType = BusinessType.OTHER)
+    @PostMapping("/reload")
+    @ApiOperation("重新加载所有巡检计划")
+    public AjaxResult reload() {
+        try {
+            inspectionScheduler.loadEnabledPlans();
+            return success("巡检计划重新加载完成,当前注册数量: " + inspectionScheduler.getRegisteredCount());
+        }
+        catch (Exception e) {
+            return error("重新加载失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 刷新指定巡检计划
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:edit')")
+    @Log(title = "刷新巡检计划", businessType = BusinessType.OTHER)
+    @PostMapping("/refresh/{planCode}")
+    @ApiOperation("刷新指定巡检计划")
+    public AjaxResult refresh(@PathVariable("planCode") String planCode) {
+        try {
+            inspectionScheduler.refreshPlan(planCode);
+            boolean isRegistered = inspectionScheduler.isRegistered(planCode);
+            return success(isRegistered ? "计划刷新成功,已重新注册" : "计划已注销(可能未配置cron或非自动巡检类型)");
+        }
+        catch (Exception e) {
+            return error("刷新失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 注册指定巡检计划
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:edit')")
+    @Log(title = "注册巡检计划", businessType = BusinessType.OTHER)
+    @PostMapping("/register/{planCode}")
+    @ApiOperation("注册指定巡检计划到调度器")
+    public AjaxResult register(@PathVariable("planCode") String planCode) {
+        try {
+            inspectionScheduler.registerPlan(planCode);
+            return success("计划已注册到调度器");
+        }
+        catch (Exception e) {
+            return error("注册失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 注销指定巡检计划
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:edit')")
+    @Log(title = "注销巡检计划", businessType = BusinessType.OTHER)
+    @PostMapping("/unregister/{planCode}")
+    @ApiOperation("注销指定巡检计划的定时任务")
+    public AjaxResult unregister(@PathVariable("planCode") String planCode) {
+        try {
+            inspectionScheduler.unregisterPlan(planCode);
+            return success("计划已从调度器注销");
+        }
+        catch (Exception e) {
+            return error("注销失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 手动触发一次巡检(用于测试)
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:execute')")
+    @Log(title = "手动触发巡检", businessType = BusinessType.OTHER)
+    @PostMapping("/trigger/{planCode}")
+    @ApiOperation("手动触发一次巡检执行")
+    public AjaxResult trigger(@PathVariable("planCode") String planCode) {
+        try {
+            // 检查是否正在执行
+            if (inspectionScheduler.isExecuting(planCode)) {
+                return error("该计划正在执行中,请稍后再试");
+            }
+            InspectionReport report = inspectionScheduler.triggerInspection(planCode);
+            return success(report);
+        }
+        catch (Exception e) {
+            return error("触发执行失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 检查计划是否已注册
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:query')")
+    @GetMapping("/registered/{planCode}")
+    @ApiOperation("检查计划是否已注册")
+    public AjaxResult isRegistered(@PathVariable("planCode") String planCode) {
+        boolean registered = inspectionScheduler.isRegistered(planCode);
+        return success(registered);
+    }
+
+    /**
+     * 检查计划是否正在执行
+     */
+    @PreAuthorize("@ss.hasPermi('ems:inspection:plan:query')")
+    @GetMapping("/executing/{planCode}")
+    @ApiOperation("检查计划是否正在执行")
+    public AjaxResult isExecuting(@PathVariable("planCode") String planCode) {
+        boolean executing = inspectionScheduler.isExecuting(planCode);
+        return success(executing);
+    }
+}

+ 28 - 15
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/MeterBoundaryRelController.java → ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/MeterBoundaryController.java

@@ -1,15 +1,17 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.MeterBoundaryRel;
+import com.ruoyi.ems.model.BoundaryObj;
+import com.ruoyi.ems.service.IBoundaryObjService;
 import com.ruoyi.ems.service.IMeterBoundaryRelService;
 import io.swagger.annotations.Api;
 import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -28,16 +30,30 @@ import java.util.List;
  * @date 2024-12-09
  */
 @RestController
-@RequestMapping("/ems/meterBoundaryRel")
-@Api(value = "MeterBoundaryRelController", description = "计量设备绑定边界对象关系")
-public class MeterBoundaryRelController extends BaseController {
+@RequestMapping("/ems/meterBoundary")
+@Api(value = "MeterBoundaryController", description = "计量设备绑定边界对象管理")
+public class MeterBoundaryController extends BaseController {
     @Autowired
     private IMeterBoundaryRelService relService;
 
+    @Autowired
+    private IBoundaryObjService boundaryObjService;
+
+    /**
+     * 查询区域-边界对象-绑定设备拓扑
+     */
+    @PreAuthorize("@ss.hasPermi('ems:meterBoundary:list')")
+    @GetMapping("/getBoundaryTreeByArea")
+    public AjaxResult getBoundaryTreeByArea(@RequestParam(value = "areaCode") String areaCode,
+        @RequestParam(value = "meterCls") Integer meterCls) {
+        BoundaryObj list = boundaryObjService.getBoundaryByArea(areaCode, meterCls);
+        return success(list);
+    }
+
     /**
      * 查询计量设备绑定边界对象关系列表
      */
-    @PreAuthorize("@ss.hasPermi('ems:meterBoundaryRel:list')")
+    @PreAuthorize("@ss.hasPermi('ems:meterBoundary:list')")
     @GetMapping("/listByObj")
     public AjaxResult list(@RequestParam(value = "objType") Integer objType,
         @RequestParam(value = "boundaryObj") String boundaryObj, @RequestParam(value = "meterCls") Integer meterCls) {
@@ -48,41 +64,38 @@ public class MeterBoundaryRelController extends BaseController {
     /**
      * 新增计量设备绑定边界对象关系
      */
-    @PreAuthorize("@ss.hasPermi('ems:meterBoundaryRel:add')")
+    @PreAuthorize("@ss.hasPermi('ems:meterBoundary:add')")
     @Log(title = "计量设备绑定边界对象关系", businessType = BusinessType.INSERT)
     @PostMapping("/addBatch")
     public AjaxResult addBatch(@RequestBody List<MeterBoundaryRel> list) {
-        return toAjax(relService.insertRelBatch(list));
+        return success(relService.insertRelBatch(list));
     }
 
     /**
      * 新增计量设备绑定边界对象关系
      */
-    @PreAuthorize("@ss.hasPermi('ems:meterBoundaryRel:add')")
+    @PreAuthorize("@ss.hasPermi('ems:meterBoundary:add')")
     @Log(title = "计量设备绑定边界对象关系", businessType = BusinessType.UPDATE)
     @PutMapping("/merge")
     public AjaxResult merge(@RequestParam(value = "objType") Integer objType,
         @RequestParam(value = "boundaryObj") String boundaryObj, @RequestParam(value = "meterCls") Integer meterCls,
         @RequestBody List<MeterBoundaryRel> list) {
-
         relService.deleteRelByObj(objType, boundaryObj, meterCls);
         int cnt = 0;
-
         if (CollectionUtils.isNotEmpty(list)) {
             cnt = relService.insertRelBatch(list);
         }
-
-        return toAjax(cnt);
+        return success(cnt);
     }
 
     /**
      * 删除计量设备绑定边界对象关系
      */
-    @PreAuthorize("@ss.hasPermi('ems:meterBoundaryRel:remove')")
+    @PreAuthorize("@ss.hasPermi('ems:meterBoundary:remove')")
     @Log(title = "计量设备绑定边界对象关系", businessType = BusinessType.DELETE)
     @DeleteMapping("/delByObj")
     public AjaxResult delByObj(@RequestParam(value = "objType") Integer objType,
         @RequestParam(value = "boundaryObj") String boundaryObj, @RequestParam(value = "meterCls") Integer meterCls) {
-        return toAjax(relService.deleteRelByObj(objType, boundaryObj, meterCls));
+        return success(relService.deleteRelByObj(objType, boundaryObj, meterCls));
     }
 }

+ 139 - 3
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/MeterDeviceController.java

@@ -1,17 +1,24 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.huashe.common.exception.BusinessException;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.domain.Area;
 import com.ruoyi.ems.domain.MeterDevice;
 import com.ruoyi.ems.model.QueryDevice;
+import com.ruoyi.ems.model.TreeEntity;
+import com.ruoyi.ems.service.IAreaService;
 import com.ruoyi.ems.service.IMeterDeviceService;
+import com.ruoyi.ems.util.AreaUtils;
 import io.swagger.annotations.Api;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -19,10 +26,14 @@ 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.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 计量设备Controller
@@ -37,6 +48,9 @@ public class MeterDeviceController extends BaseController {
     @Autowired
     private IMeterDeviceService meterDeviceService;
 
+    @Autowired
+    private IAreaService areaService;
+
     /**
      * 查询计量设备列表
      */
@@ -49,6 +63,123 @@ public class MeterDeviceController extends BaseController {
     }
 
     /**
+     * 递归查询 区域 下的设备(分页)
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:device:list')")
+    @GetMapping("/listRecursionByArea")
+    public TableDataInfo listRecursionByArea(QueryDevice queryDevice) {
+        TableDataInfo tabInfo = null;
+        try {
+            if (StringUtils.isNotEmpty(queryDevice.getLocationRef())) {
+                List<Area> areaTree = areaService.selectAreaTree(queryDevice.getLocationRef(), true);
+                List<String> areaCodes = new ArrayList<>();
+                areaCodes.add(queryDevice.getLocationRef());
+                // 递归取出区域子节点code做查询条件(需要将子节点区域关联设备一并取出)
+                AreaUtils.getCodeRecursion(areaTree, areaCodes);
+                queryDevice.setAreaCodes(areaCodes);
+            }
+            startPage();
+            List<MeterDevice> list = meterDeviceService.selectMeterDeviceList(queryDevice);
+            tabInfo = getDataTable(list);
+        }
+        catch (BusinessException e) {
+            tabInfo = new TableDataInfo();
+            tabInfo.setCode(e.getCode());
+            tabInfo.setMsg(e.getMessage());
+        }
+        return tabInfo;
+    }
+
+    /**
+     * 根据设施获取设备树结构
+     *
+     * @param parentCode 区域code
+     * @return 树结构
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:meterdevc:list')")
+    @GetMapping("/getTreeByArea")
+    public AjaxResult getTreeByArea(
+        @RequestParam(name = "parentCode", required = false, defaultValue = "0") String parentCode,
+        QueryDevice queryParam) {
+        List<TreeEntity> retList = new ArrayList<>();
+        // 查询区域树
+        List<Area> areas = areaService.selectAreaTree(parentCode, true);
+        // 查询设备列表并按区域分组
+        List<MeterDevice> list = meterDeviceService.selectMeterDeviceList(queryParam);
+        Map<String, List<MeterDevice>> groupMap = list.stream()
+            .collect(Collectors.groupingBy(MeterDevice::getLocationRef, Collectors.toList()));
+        // 递归处理每个顶级区域,生成完整树形结构
+        for (Area area : areas) {
+            TreeEntity treeNode = buildRecursiveTree(area, groupMap);
+            if (treeNode != null) { // 仅添加有内容的节点
+                retList.add(treeNode);
+            }
+        }
+        return success(retList);
+    }
+
+    /**
+     * 递归构建区域树节点,区分区域和设备类型
+     *
+     * @param area     当前区域节点
+     * @param groupMap 设备按区域分组的映射
+     * @return 当前区域对应的树形节点(若无可挂载内容则返回null)
+     */
+    private TreeEntity buildRecursiveTree(Area area, Map<String, List<MeterDevice>> groupMap) {
+        // 当前区域的树形节点(类型为area)
+        TreeEntity currentNode = new TreeEntity();
+        currentNode.setId(area.getAreaCode()); // 使用区域编码作为节点ID
+        currentNode.setLabel(area.getAreaName()); // 区域名称作为节点标签
+        currentNode.setType("area"); // 标记为区域类型
+        List<TreeEntity> childNodes = new ArrayList<>();
+        // 1. 处理本级设备:转换为device类型节点
+        if (groupMap.containsKey(area.getAreaCode())) {
+            List<TreeEntity> deviceNodes = convertToTreeEntity(groupMap.get(area.getAreaCode()));
+            // 为每个设备节点设置类型为device
+            deviceNodes.forEach(deviceNode -> {
+                deviceNode.setType("device");
+                // 设备节点无子节点,确保children为空
+                deviceNode.setChildren(null);
+            });
+            childNodes.addAll(deviceNodes);
+        }
+        // 2. 递归处理所有子区域(类型为area)
+        if (CollectionUtils.isNotEmpty(area.getChildren())) {
+            for (Object childObj : area.getChildren()) {
+                Area childArea = (Area) childObj;
+                TreeEntity childTreeNode = buildRecursiveTree(childArea, groupMap);
+                if (childTreeNode != null) {
+                    childNodes.add(childTreeNode);
+                }
+            }
+        }
+        // 3. 若当前节点没有任何子节点,不保留该节点
+        if (childNodes.isEmpty()) {
+            return null;
+        }
+        currentNode.setChildren(childNodes);
+        return currentNode;
+    }
+
+    /**
+     * 将设备列表转换为TreeEntity列表(基础转换,不包含类型设置)
+     *
+     * @param devices 设备列表
+     * @return 转换后的TreeEntity列表
+     */
+    private List<TreeEntity> convertToTreeEntity(List<MeterDevice> devices) {
+        List<TreeEntity> treeEntities = new ArrayList<>();
+        for (MeterDevice device : devices) {
+            TreeEntity entity = new TreeEntity();
+            entity.setId(device.getDeviceCode()); // 使用设备编码作为ID
+            entity.setLabel(device.getDeviceName()); // 使用设备名称作为标签
+            // 设备节点的子节点在后续处理中会被设为null
+            treeEntities.add(entity);
+        }
+        return treeEntities;
+    }
+
+    /**
      * 导出计量设备列表
      */
     @PreAuthorize("@ss.hasPermi('ems:meterdevc:export')")
@@ -98,4 +229,9 @@ public class MeterDeviceController extends BaseController {
     public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(meterDeviceService.deleteMeterDeviceByIds(ids));
     }
+
+    @GetMapping("/cnt")
+    public AjaxResult cntMeterDevice(@RequestParam(name = "areaCode", required = false) String areaCode) {
+        return success(meterDeviceService.cntMeterDevice(areaCode));
+    }
 }

+ 16 - 28
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/MeterReadingController.java

@@ -1,12 +1,13 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
+import com.huashe.common.exception.Assert;
+import com.huashe.common.exception.BusinessException;
 import com.huashe.common.utils.DateUtils;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.exception.Assert;
-import com.ruoyi.common.exception.BusinessException;
+import org.springframework.security.access.prepost.PreAuthorize;
 import com.ruoyi.ems.domain.MeterDevice;
 import com.ruoyi.ems.domain.MeterReading;
 import com.ruoyi.ems.model.QueryMeter;
@@ -15,7 +16,6 @@ import com.ruoyi.ems.service.IMeterReadingService;
 import io.swagger.annotations.Api;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -71,7 +71,11 @@ public class MeterReadingController extends BaseController {
     @PostMapping
     public AjaxResult add(@RequestBody MeterReading nowItem) {
         try {
-            MeterReading lastItem = meterReadingService.selectLastItem(nowItem.getAreaCode(), nowItem.getDeviceCode());
+            MeterDevice meterDevice = meterDeviceService.selectMeterDeviceByCode(nowItem.getDeviceCode());
+            Assert.notNull(meterDevice, -1, "设备不存在");
+            nowItem.setAreaCode(meterDevice.getAreaCode());
+            MeterReading lastItem = meterReadingService.selectLastItem(meterDevice.getAreaCode(),
+                nowItem.getDeviceCode());
             checkAdd(lastItem, nowItem);
             fillData(lastItem, nowItem);
             int count = meterReadingService.insert(nowItem);
@@ -90,12 +94,10 @@ public class MeterReadingController extends BaseController {
     @PutMapping
     public AjaxResult edit(@RequestBody MeterReading nowItem) {
         try {
-            MeterReading lastItem = meterReadingService.selectLastItem(nowItem.getAreaCode(), nowItem.getDeviceCode());
-            checkMod(lastItem, nowItem);
-            meterReadingService.deleteById(nowItem.getId());
+            MeterReading lastItem = meterReadingService.selectById(nowItem.getId());
+            Assert.notNull(lastItem, -1, "记录不存在");
             nowItem.setMeterReading(nowItem.getMeterReading());
-            fillData(lastItem, nowItem);
-            int count = meterReadingService.insert(nowItem);
+            int count = meterReadingService.update(nowItem);
             return toAjax(count);
         }
         catch (BusinessException e) {
@@ -104,7 +106,7 @@ public class MeterReadingController extends BaseController {
     }
 
     /**
-     * 删除动力箱柜
+     * 删除
      */
     @PreAuthorize("@ss.hasPermi('ems:meterReading:remove')")
     @Log(title = "抄表记录", businessType = BusinessType.DELETE)
@@ -115,36 +117,22 @@ public class MeterReadingController extends BaseController {
 
     private void checkAdd(MeterReading lastItem, MeterReading nowItem) {
         if (null != lastItem) {
-            Date date = new Date();
-            String nowMonth = DateUtils.dateToString(date, "yyyyMM");
-            Assert.isTrue(!StringUtils.equals(nowMonth, lastItem.getMeterMonth()), -1, "本月已存在抄表记录");
-            Assert.isTrue(nowItem.getMeterReading() >= lastItem.getMeterReading(), -1, "抄表值不能小于上一条记录");
-        }
-    }
-
-    private void checkMod(MeterReading lastItem, MeterReading nowItem) {
-        if (null != lastItem) {
+            Assert.isTrue(!StringUtils.equals(nowItem.getMeterMonth(), lastItem.getMeterMonth()), -1,
+                "本月已存在抄表记录");
             Assert.isTrue(nowItem.getMeterReading() >= lastItem.getMeterReading(), -1, "抄表值不能小于上一条记录");
         }
     }
 
     private void fillData(MeterReading lastItem, MeterReading meterRec) {
         MeterDevice meterDevice = meterDeviceService.selectMeterDeviceByCode(meterRec.getDeviceCode());
-        Date date = new Date();
-
         if (null != lastItem) {
-            meterRec.setYear(DateUtils.dateToString(date, "yyyy"));
-            meterRec.setMeterMonth(DateUtils.dateToString(date, "yyyyMM"));
+            meterRec.setYear(StringUtils.substring(meterRec.getMeterMonth(), 0, 4));
             meterRec.setLastReading(lastItem.getMeterReading());
             meterRec.setLastTime(lastItem.getMeterTime());
-            meterRec.setMeterTime(date);
             meterRec.setIncrease(
                 (meterRec.getMeterReading() - lastItem.getMeterReading()) * meterDevice.getMagnification());
         }
         else {
-            meterRec.setYear(DateUtils.dateToString(date, "yyyy"));
-            meterRec.setMeterMonth(DateUtils.dateToString(date, "yyyyMM"));
-            meterRec.setMeterTime(date);
             meterRec.setIncrease(meterRec.getMeterReading() * meterDevice.getMagnification());
         }
     }

+ 105 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/ObjTagController.java

@@ -0,0 +1,105 @@
+/*
+ * 文 件 名:  ObjTagController
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/9/3
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.ems.domain.ObjTagRel;
+import com.ruoyi.ems.service.IObjTagRelService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * <一句话功能简述>
+ * <功能详细描述>
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/9/3]
+ * @see [相关类/方法]
+ * @since [产品/模块版本]
+ */
+@RestController
+@RequestMapping("/ems/tag")
+@Api(value = "ObjTagController", description = "对象标签接口")
+public class ObjTagController extends BaseController {
+    @Resource
+    private IObjTagRelService objTagRelService;
+
+    /**
+     * 新增能源对象属性
+     */
+    @PostMapping(value = "/rel")
+    @ApiOperation(value = "saveObjTagRel", notes = "保存标签-对象关系")
+    public AjaxResult saveObjTagRel(ObjTagRel objTagRel) {
+        return toAjax(objTagRelService.save(objTagRel));
+    }
+
+    /**
+     * 删除能源对象属性
+     */
+    @DeleteMapping(value = "/rel")
+    @ApiOperation(value = "removeObjTagRel", notes = "保存标签-对象关系")
+    public AjaxResult removeObjTagRel(ObjTagRel objTagRel) {
+        return toAjax(objTagRelService.remove(objTagRel));
+    }
+
+    /**
+     * 查询对象&标签关系列表
+     *
+     * @param objType 标签类型
+     * @return 对象&标签关系集合
+     */
+    @GetMapping(value = "/rel/getByObjType")
+    @ApiOperation(value = "getRelByObjType", notes = "根据对象类型查询绑定关系")
+    public AjaxResult getRelByObjType(@RequestParam(name = "objType") Integer objType) {
+        List<ObjTagRel> list = objTagRelService.selectListByType(objType);
+        return success(list);
+    }
+
+    /**
+     * 查询对象&标签关系
+     *
+     * @param objType 标签类型
+     * @param tagCode 标签代码
+     * @return 结果
+     */
+    @GetMapping(value = "/rel/selectByTagCode")
+    @ApiOperation(value = "selectRelByTagCode", notes = "根据标签代码查询绑定关系")
+    public AjaxResult selectRelByTagCode(@RequestParam(name = "objType") Integer objType,
+        @RequestParam(name = "tagCode") String tagCode) {
+        List<ObjTagRel> list = objTagRelService.selectListByTagCode(objType, tagCode);
+        return success(list);
+    }
+
+    /**
+     * 查询对象&标签关系
+     *
+     * @param objType 标签类型
+     * @param objCode 标签代码
+     * @return 结果
+     */
+    @GetMapping(value = "/rel/selectByObjCode")
+    @ApiOperation(value = "selectRelByObjCode", notes = "根据对象代码查询绑定关系")
+    public AjaxResult selectRelByObjCode(@RequestParam(name = "objType") Integer objType,
+        @RequestParam(name = "objCode") String objCode) {
+        List<ObjTagRel> list = objTagRelService.selectListByObjCode(objType, objCode);
+        return success(list);
+    }
+}

+ 451 - 141
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/OpEnergyStrategyController.java

@@ -1,23 +1,37 @@
 package com.ruoyi.web.controller.ems;
 
 import com.alibaba.fastjson2.JSONObject;
+import lombok.extern.slf4j.Slf4j;
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
+import com.huashe.common.exception.BusinessException;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.exception.BusinessException;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.ems.domain.OpEnergyStrategy;
+import com.ruoyi.ems.domain.OpEnergyStrategyExecLog;
 import com.ruoyi.ems.domain.OpEnergyStrategyParam;
 import com.ruoyi.ems.domain.OpEnergyStrategyStep;
-import com.ruoyi.ems.model.ParamOption;
+import com.ruoyi.ems.domain.OpEnergyStrategyStepLog;
+import com.ruoyi.ems.domain.OpEnergyStrategyTemplate;
+import com.ruoyi.ems.domain.OpEnergyStrategyTrigger;
+import com.ruoyi.ems.service.IOpEnergyStrategyExecLogService;
 import com.ruoyi.ems.service.IOpEnergyStrategyParamService;
 import com.ruoyi.ems.service.IOpEnergyStrategyService;
 import com.ruoyi.ems.service.IOpEnergyStrategyStepService;
+import com.ruoyi.ems.service.IOpEnergyStrategyTemplateService;
+import com.ruoyi.ems.service.IOpEnergyStrategyTriggerService;
+import com.ruoyi.ems.strategy.PollingMonitorService;
+import com.ruoyi.ems.strategy.executor.StrategyExecutor;
+import com.ruoyi.ems.task.StrategyScheduler;
+import com.ruoyi.ems.task.StrategyTriggerListener;
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
 import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -28,53 +42,56 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * 能源策略Controller
- *
- * @author ruoyi
- * @date 2024-08-08
  */
 @RestController
 @RequestMapping("/ems/energyStrategy")
 @Api(value = "OpEnergyStrategyController", description = "能源策略管理接口")
 public class OpEnergyStrategyController extends BaseController {
+    private static final Logger log = LoggerFactory.getLogger(OpEnergyStrategyController.class);
     @Autowired
     private IOpEnergyStrategyService strategyService;
 
     @Autowired
+    private IOpEnergyStrategyTemplateService templateService;
+
+    @Autowired
     private IOpEnergyStrategyParamService paramService;
 
     @Autowired
     private IOpEnergyStrategyStepService stepService;
 
-    private static final Map<String, List<ParamOption>> PARAM_OPTIONS = new HashMap<>();
+    @Autowired
+    private IOpEnergyStrategyTriggerService triggerService;
 
-    static {
-        List<ParamOption> dcList = Arrays.asList(new ParamOption("maxPowerTrack", "最大功率点跟踪(MPPT)",
-                "通过实时调整光伏阵列的工作状态,使光伏系统始终运行在最大功率点"),
-            new ParamOption("inverterControl", "逆变器控制", "通过光伏逆变器实施控制"),
-            new ParamOption("powerAndVoltage", "无功功率及电压控制",
-                "根据电网需求,通过调节光伏系统输出的无功功率和电压,实现电压稳定和电力因素的优化"));
+    @Autowired
+    private IOpEnergyStrategyExecLogService execLogService;
+
+    @Autowired
+    private StrategyExecutor strategyExecutor;
 
-        PARAM_OPTIONS.put("default.controlMode", dcList);
+    // 策略调度器
+    @Autowired
+    private StrategyScheduler strategyScheduler;
 
-        List<ParamOption> diList = Arrays.asList(
-            new ParamOption("svpwm", "空间矢量控制(SVPWM)", "通过对逆变器开关状态的优化控制,实现并网电流的高精度控制"),
-            new ParamOption("dtc", "直流转矩控制", "以控制逆变器输出转矩和磁链为目标,快速响应"));
+    // 触发监听器
+    @Autowired
+    private StrategyTriggerListener triggerListener;
 
-        PARAM_OPTIONS.put("inverterControl.controlMode", diList);
-    }
+    // 轮询监控服务
+    @Autowired
+    private PollingMonitorService pollingMonitorService;
 
-    /**
-     * 查询能源策略列表
-     */
+    // ==================== 策略基础管理 ====================
+    @PreAuthorize("@ss.hasPermi('power-mgr:strategy:list')")
     @GetMapping("/list")
     public TableDataInfo list(OpEnergyStrategy strategy) {
         startPage();
@@ -82,17 +99,16 @@ public class OpEnergyStrategyController extends BaseController {
         return getDataTable(list);
     }
 
-    /**
-     * 获取能源策略详细信息
-     */
     @GetMapping(value = "/{id}")
     public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(strategyService.selectStrategyById(id));
     }
 
-    /**
-     * 新增能源策略
-     */
+    @GetMapping(value = "/code/{strategyCode}")
+    public AjaxResult getByCode(@PathVariable("strategyCode") String strategyCode) {
+        return success(strategyService.selectStrategyByCode(strategyCode));
+    }
+
     @PreAuthorize("@ss.hasPermi('power-mgr:strategy:add')")
     @Log(title = "能源策略", businessType = BusinessType.INSERT)
     @PostMapping
@@ -102,190 +118,484 @@ public class OpEnergyStrategyController extends BaseController {
 
     /**
      * 修改能源策略
+     * 重构:修改后刷新调度器
      */
     @PreAuthorize("@ss.hasPermi('power-mgr:strategy:edit')")
     @Log(title = "能源策略", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody OpEnergyStrategy strategy) {
-        return toAjax(strategyService.updateStrategy(strategy));
+        int result = strategyService.updateStrategy(strategy);
+        if (result > 0 && strategy.getStrategyState() != null && strategy.getStrategyState() == 1) {
+            // 刷新调度器中的策略配置
+            strategyScheduler.refreshStrategy(strategy.getStrategyCode());
+            log.info("策略[{}]已更新并刷新调度器", strategy.getStrategyCode());
+        }
+        return toAjax(result);
     }
 
     /**
      * 删除能源策略
+     * 删除前从调度器注销
      */
     @PreAuthorize("@ss.hasPermi('power-mgr:strategy:remove')")
     @Log(title = "能源策略", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids) {
+        // 先从调度器注销
+        for (Long id : ids) {
+            OpEnergyStrategy strategy = strategyService.selectStrategyById(id);
+            if (strategy != null) {
+                strategyScheduler.unregisterStrategy(strategy.getStrategyCode());
+                // 【新增】同时注销轮询监控
+                pollingMonitorService.unregisterPollingStrategy(strategy.getStrategyCode());
+                log.info("策略[{}]已从调度器注销", strategy.getStrategyCode());
+            }
+        }
         return toAjax(strategyService.deleteStrategyByIds(ids));
     }
 
     /**
-     * 获取能源策略参数
+     * 启用/停用策略
+     * 重构:统一通过 StrategyScheduler 管理触发器注册
+     */
+    @PutMapping("/state/{strategyCode}/{state}")
+    @ApiOperation("启用/停用策略")
+    public AjaxResult changeState(@PathVariable String strategyCode, @PathVariable Integer state) {
+        OpEnergyStrategy strategy = strategyService.selectStrategyByCode(strategyCode);
+        if (strategy == null) {
+            return error("策略不存在");
+        }
+        strategy.setStrategyState(state);
+        int result = strategyService.updateStrategy(strategy);
+        if (result > 0) {
+            if (state == 1) {
+                // 启用策略 - 由调度器统一处理所有触发器类型
+                strategyScheduler.registerStrategy(strategy);
+                log.info("策略[{}]已启用并注册到调度器", strategyCode);
+            }
+            else {
+                // 停用策略 - 注销所有触发器
+                strategyScheduler.unregisterStrategy(strategyCode);
+                log.info("策略[{}]已停用并从调度器注销", strategyCode);
+            }
+        }
+        return toAjax(result);
+    }
+
+    @GetMapping("/sceneCount")
+    public AjaxResult getSceneTypeCount(@RequestParam(required = false) String areaCode) {
+        return success(strategyService.getSceneTypeCount(areaCode));
+    }
+    // ==================== 属性变化通知接口(供采集模块调用) ====================
+
+    /**
+     * 属性值变化通知接口
+     * 由 ems-dev-adapter 采集模块调用
+     *
+     * @param objCode  设备代码
+     * @param attrKey  属性键
+     * @param oldValue 旧值(可选)
+     * @param newValue 新值
+     * @return 触发的策略数量
+     */
+    @PostMapping("/onAttrValueChanged")
+    @ApiOperation("属性值变化通知(供采集模块调用)")
+    public AjaxResult onAttrValueChanged(@ApiParam("设备代码") @RequestParam String objCode,
+        @ApiParam("属性键") @RequestParam String attrKey,
+        @ApiParam("旧值") @RequestParam(required = false) String oldValue,
+        @ApiParam("新值") @RequestParam String newValue) {
+        log.debug("收到属性变化通知: obj={}, attr={}, {} -> {}", objCode, attrKey, oldValue, newValue);
+        int triggeredCount = triggerListener.handleAttrChange(objCode, attrKey, oldValue, newValue);
+        Map<String, Object> result = new HashMap<>();
+        result.put("objCode", objCode);
+        result.put("attrKey", attrKey);
+        result.put("triggeredStrategies", triggeredCount);
+        return success(result);
+    }
+
+    /**
+     * 批量属性变化通知接口
      *
-     * @param strategyCode 策略编码
-     * @return 参数集合
+     * @param request 包含 objCode 和 attributes 的请求体
+     */
+    @PostMapping("/onAttrValueChangedBatch")
+    @ApiOperation("批量属性值变化通知(供采集模块调用)")
+    public AjaxResult onAttrValueChangedBatch(@RequestBody Map<String, Object> request) {
+        String objCode = (String) request.get("objCode");
+        @SuppressWarnings("unchecked") Map<String, Object> attributes = (Map<String, Object>) request.get("attributes");
+        if (objCode == null || attributes == null) {
+            return error("参数不完整,需要 objCode 和 attributes");
+        }
+        log.debug("收到批量属性变化通知: obj={}, attrCount={}", objCode, attributes.size());
+        int totalTriggered = 0;
+        for (Map.Entry<String, Object> entry : attributes.entrySet()) {
+            String attrKey = entry.getKey();
+            Object newValue = entry.getValue();
+            int triggered = triggerListener.handleAttrChange(objCode, attrKey, null, newValue);
+            totalTriggered += triggered;
+        }
+        Map<String, Object> result = new HashMap<>();
+        result.put("objCode", objCode);
+        result.put("attributeCount", attributes.size());
+        result.put("triggeredStrategies", totalTriggered);
+        return success(result);
+    }
+
+    /**
+     * 设备事件通知接口
+     */
+    @PostMapping("/onDeviceEvent")
+    @ApiOperation("设备事件通知(供采集模块调用)")
+    public AjaxResult onDeviceEvent(@ApiParam("设备代码") @RequestParam String objCode,
+        @ApiParam("事件键") @RequestParam String eventKey,
+        @RequestBody(required = false) Map<String, Object> eventData) {
+        log.debug("收到设备事件通知: obj={}, event={}", objCode, eventKey);
+        if (eventData == null) {
+            eventData = new HashMap<>();
+        }
+        int triggeredCount = triggerListener.handleDeviceEvent(objCode, eventKey, eventData);
+        Map<String, Object> result = new HashMap<>();
+        result.put("objCode", objCode);
+        result.put("eventKey", eventKey);
+        result.put("triggeredStrategies", triggeredCount);
+        return success(result);
+    }
+    // ==================== 调度器状态接口 ====================
+
+    /**
+     * 获取调度器状态
+     * 新增:更详细的状态信息
+     */
+    @GetMapping("/scheduler/status")
+    @ApiOperation("获取调度器状态")
+    public AjaxResult getSchedulerStatus() {
+        Map<String, Object> status = strategyScheduler.getStatus();
+        // 添加触发监听器状态
+        if (triggerListener != null) {
+            status.put("triggers", triggerListener.getRegisteredTriggers());
+        }
+        // 添加轮询监控状态
+        if (pollingMonitorService != null) {
+            status.put("polling", pollingMonitorService.getPollingStatus());
+        }
+        return success(status);
+    }
+
+    /**
+     * 重新加载所有策略
+     */
+    @PostMapping("/scheduler/reload")
+    @ApiOperation("重新加载所有策略")
+    public AjaxResult reloadScheduler() {
+        strategyScheduler.loadEnabledStrategies();
+        return success("调度器已重新加载");
+    }
+    // ==================== 轮询监控接口 ====================
+
+    /**
+     * 获取轮询监控状态
+     */
+    @GetMapping("/polling/status")
+    @ApiOperation("获取轮询监控状态")
+    public AjaxResult getPollingStatus() {
+        return success(pollingMonitorService.getPollingStatus());
+    }
+
+    /**
+     * 刷新指定策略的轮询配置
+     */
+    @PostMapping("/polling/refresh/{strategyCode}")
+    @ApiOperation("刷新策略轮询配置")
+    public AjaxResult refreshPollingStrategy(@PathVariable String strategyCode) {
+        pollingMonitorService.refreshPollingStrategy(strategyCode);
+        return success("轮询配置已刷新");
+    }
+
+    /**
+     * 手动触发一次轮询检查(用于调试)
+     * 【改造点】:完善实现,调用 PollingMonitorService.triggerPollingCheck()
      */
+    @PostMapping("/polling/trigger/{strategyCode}")
+    @ApiOperation("手动触发轮询检查")
+    public AjaxResult manualTriggerPolling(@PathVariable String strategyCode) {
+        if (!pollingMonitorService.hasPollingTask(strategyCode)) {
+            return error("该策略未配置轮询监控或未启用");
+        }
+        try {
+            // 【改造】调用轮询服务的手动触发方法
+            pollingMonitorService.triggerPollingCheck(strategyCode);
+            Map<String, Object> result = new HashMap<>();
+            result.put("strategyCode", strategyCode);
+            result.put("message", "已触发轮询检查,请查看日志确认执行结果");
+            return success(result);
+        }
+        catch (Exception e) {
+            log.error("手动触发轮询检查失败: strategy={}", strategyCode, e);
+            return error("触发轮询检查失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 【新增】检查策略是否有轮询任务
+     */
+    @GetMapping("/polling/check/{strategyCode}")
+    @ApiOperation("检查策略是否有轮询任务")
+    public AjaxResult checkPollingTask(@PathVariable String strategyCode) {
+        boolean hasTask = pollingMonitorService.hasPollingTask(strategyCode);
+        Map<String, Object> result = new HashMap<>();
+        result.put("strategyCode", strategyCode);
+        result.put("hasPollingTask", hasTask);
+        result.put("message", hasTask ? "已注册轮询监控" : "未注册轮询监控");
+        return success(result);
+    }
+
+    // ==================== 策略参数管理 ====================
     @GetMapping(value = "/param")
     public AjaxResult getStrategyParam(@RequestParam(name = "strategyCode") String strategyCode) {
-        // 补充策略参数
         List<OpEnergyStrategyParam> paramList = paramService.selectParamByStrategyCode(strategyCode);
         return success(buildStrategyParams(paramList));
     }
 
-    /**
-     * 修改能源策略
-     */
     @PreAuthorize("@ss.hasPermi('power-mgr:strategy:edit')")
     @Log(title = "能源策略参数", businessType = BusinessType.UPDATE)
     @PutMapping("/param")
     public AjaxResult editParam(@RequestBody OpEnergyStrategyParam strategyParam) {
-        AjaxResult result = null;
-
         try {
             int updateCnt = paramService.updateParamValue(strategyParam);
-            result = toAjax(updateCnt);
+            return toAjax(updateCnt);
         }
         catch (BusinessException e) {
-            result = error(e.getMessage());
+            return error(e.getMessage());
         }
+    }
 
-        return result;
+    private Map<String, Map<String, JSONObject>> buildStrategyParams(List<OpEnergyStrategyParam> paramList) {
+        Map<String, Map<String, JSONObject>> params = new HashMap<>();
+        Map<String, List<OpEnergyStrategyParam>> groupedMap = paramList.stream()
+            .collect(Collectors.groupingBy(OpEnergyStrategyParam::getParamGroup, Collectors.toList()));
+        for (Map.Entry<String, List<OpEnergyStrategyParam>> entry : groupedMap.entrySet()) {
+            String groupName = entry.getKey();
+            List<OpEnergyStrategyParam> groupParams = entry.getValue();
+            Map<String, JSONObject> groupParamMap = new HashMap<>();
+            for (OpEnergyStrategyParam param : groupParams) {
+                JSONObject option = new JSONObject();
+                option.put("paramName", param.getParamName());
+                option.put("paramValue", param.getParamValue());
+                option.put("editEnable", param.getEditEnable());
+                option.put("paramValueFormat", param.getParamValueFormat());
+                groupParamMap.put(param.getParamKey(), option);
+            }
+            params.put(groupName, groupParamMap);
+        }
+        return params;
     }
 
-    /**
-     * 获取能源策略执行步骤
-     *
-     * @param strategyCode 策略编码
-     * @return 步骤列表
-     */
+    // ==================== 策略步骤管理 ====================
     @GetMapping(value = "/step")
     public AjaxResult getStrategyStep(@RequestParam(name = "strategyCode") String strategyCode) {
         return success(stepService.selectStepByStrategyCode(strategyCode));
     }
 
-    /**
-     * 新增能源策略执行步骤
-     *
-     * @param strategyStep 策略步骤
-     * @return 步骤列表
-     */
     @PostMapping(value = "/step")
     public AjaxResult addStrategyStep(@RequestBody OpEnergyStrategyStep strategyStep) {
         return toAjax(stepService.insertStep(strategyStep));
     }
 
-    /**
-     * 删除能源策略执行步骤
-     *
-     * @param strategyCode 策略代码
-     * @return 步骤列表
-     */
-    @DeleteMapping(value = "/step")
-    public AjaxResult delStrategyStep(@RequestParam(name = "strategyCode") String strategyCode) {
-        return toAjax(stepService.deleteStepByStrategyCode(strategyCode));
-    }
-
-    /**
-     * 修改能源策略执行步骤
-     *
-     * @param strategyStep 策略步骤
-     * @return 步骤列表
-     */
     @PutMapping(value = "/step")
     public AjaxResult editStrategyStep(@RequestBody OpEnergyStrategyStep strategyStep) {
         return toAjax(stepService.updateStep(strategyStep));
     }
 
-    /**
-     * 修改能源策略执行步骤
-     *
-     * @param strategySteps 策略步骤
-     * @return 步骤列表
-     */
+    @DeleteMapping(value = "/step/{id}")
+    public AjaxResult delStrategyStep(@PathVariable Long id) {
+        return toAjax(stepService.deleteStep(id));
+    }
+
     @PutMapping(value = "/step/batch")
-    public AjaxResult editStrategyStep(List<OpEnergyStrategyStep> strategySteps) {
+    public AjaxResult editStrategyStepBatch(@RequestBody List<OpEnergyStrategyStep> strategySteps) {
+        if (CollectionUtils.isEmpty(strategySteps)) {
+            return error("步骤列表不能为空");
+        }
         stepService.deleteStepByStrategyCode(strategySteps.get(0).getStrategyCode());
         return toAjax(stepService.insertStepBatch(strategySteps));
     }
 
+    // ==================== 触发器管理 ====================
+    @GetMapping("/trigger/{strategyCode}")
+    @ApiOperation("获取触发器列表")
+    public AjaxResult getTriggers(@PathVariable String strategyCode) {
+        List<OpEnergyStrategyTrigger> triggers = triggerService.selectByStrategyCode(strategyCode);
+        return success(triggers);
+    }
+
     /**
-     * 获取能源策略参数选项
+     * 保存触发器
      *
-     * @param strategyType 策略类型
-     * @param paramKey     参数键
-     * @return 选项
+     * @param trigger
+     * @return
      */
-    @GetMapping(value = "/param/option")
-    public AjaxResult getStrategyParamOption(@RequestParam(name = "strategyType") int strategyType,
-        @RequestParam(name = "paramKey") String paramKey) {
-        List<ParamOption> options = null;
-
-        switch (strategyType) {
-            case 1:
-                options = getYwParamOption(paramKey);
-                break;
-            case 2:
-                options = getYhParamOption(paramKey);
-                break;
-            case 3:
-                options = getWcParamOption(paramKey);
-                break;
+    @PostMapping("/trigger")
+    @ApiOperation("保存触发器")
+    public AjaxResult saveTrigger(@RequestBody OpEnergyStrategyTrigger trigger) {
+        // 确保 strategyCode 存在
+        if (trigger.getStrategyCode() == null || trigger.getStrategyCode().isEmpty()) {
+            return error("策略代码不能为空");
         }
-
-        return CollectionUtils.isNotEmpty(options) ? success(options) : success(new ArrayList<>());
+        int result;
+        if (trigger.getId() == null) {
+            result = triggerService.insertTrigger(trigger);
+        }
+        else {
+            result = triggerService.updateTrigger(trigger);
+        }
+        if (result > 0) {
+            // 同步更新策略的触发类型(用于列表筛选)
+            syncStrategyTriggerType(trigger.getStrategyCode());
+            // 检查策略是否已启用,如果是则刷新调度器
+            OpEnergyStrategy strategy = strategyService.selectStrategyByCode(trigger.getStrategyCode());
+            if (strategy != null && strategy.getStrategyState() == 1) {
+                strategyScheduler.refreshStrategy(trigger.getStrategyCode());
+                log.info("触发器保存后刷新调度器: strategy={}", trigger.getStrategyCode());
+            }
+        }
+        return toAjax(result);
     }
 
-    private List<ParamOption> getYwParamOption(String paramKey) {
-        return PARAM_OPTIONS.get(paramKey);
+    /**
+     * 删除触发器
+     */
+    @DeleteMapping("/trigger/{id}")
+    @ApiOperation("删除触发器")
+    public AjaxResult deleteTrigger(@PathVariable Long id) {
+        // 先查询触发器获取策略代码
+        OpEnergyStrategyTrigger trigger = triggerService.selectById(id);
+        String strategyCode = trigger != null ? trigger.getStrategyCode() : null;
+        int result = triggerService.deleteTrigger(id);
+        if (result > 0 && strategyCode != null) {
+            // 检查策略是否已启用,如果是则刷新调度器
+            OpEnergyStrategy strategy = strategyService.selectStrategyByCode(strategyCode);
+            if (strategy != null && strategy.getStrategyState() == 1) {
+                strategyScheduler.refreshStrategy(strategyCode);
+            }
+        }
+        return toAjax(result);
     }
 
-    private List<ParamOption> getYhParamOption(String paramKey) {
-        return null;
+    // ==================== 模板管理 ====================
+    @GetMapping("/template/list")
+    public AjaxResult listTemplate(OpEnergyStrategyTemplate template) {
+        List<OpEnergyStrategyTemplate> list = templateService.selectTemplateList(template);
+        return success(list);
     }
 
-    private List<ParamOption> getWcParamOption(String paramKey) {
-        return null;
+    // ==================== 策略执行 ====================
+    @PostMapping("/execute/{strategyCode}")
+    @ApiOperation("手动执行策略")
+    public AjaxResult executeStrategy(@PathVariable String strategyCode,
+        @RequestBody(required = false) Map<String, Object> params) {
+        try {
+            if (params == null) {
+                params = new HashMap<>();
+            }
+            // 设置触发类型和触发源
+            params.put("trigger_type", "MANUAL");
+            params.put("trigger_source", "USER_MANUAL");
+            // 设置执行人(尝试获取当前登录用户)
+            try {
+                String username = SecurityUtils.getUsername();
+                if (username != null && !username.isEmpty()) {
+                    params.put("exec_by", username);
+                }
+                else {
+                    params.put("exec_by", "UNKNOWN_USER");
+                }
+            }
+            catch (Exception e) {
+                params.put("exec_by", "ANONYMOUS");
+                log.debug("无法获取当前用户: {}", e.getMessage());
+            }
+            String execId = strategyExecutor.executeStrategy(strategyCode, params);
+            Map<String, Object> result = new HashMap<>();
+            result.put("execId", execId);
+            result.put("message", "策略执行已启动");
+            result.put("triggerType", "MANUAL");
+            result.put("execBy", params.get("exec_by"));
+            return success(result);
+        }
+        catch (Exception e) {
+            log.error("手动执行策略失败: {}", strategyCode, e);
+            return error("策略执行失败: " + e.getMessage());
+        }
     }
 
-    private Map<String, Map<String, JSONObject>> buildStrategyParams(List<OpEnergyStrategyParam> paramList) {
-        Map<String, Map<String, JSONObject>> params = new HashMap<>();
-
-        Map<String, List<OpEnergyStrategyParam>> groupedMap = paramList.stream()
-            .collect(Collectors.groupingBy(OpEnergyStrategyParam::getParamGroup, Collectors.toList()));
-
-        for (Map.Entry<String, List<OpEnergyStrategyParam>> entry : groupedMap.entrySet()) {
-            String groupName = entry.getKey();
-            List<OpEnergyStrategyParam> groupParams = entry.getValue();
+    // ==================== 执行日志 ====================
+    @GetMapping("/execLog/list")
+    @ApiOperation("获取执行日志列表")
+    public TableDataInfo getExecLogList(OpEnergyStrategyExecLog param) {
+        startPage();
+        List<OpEnergyStrategyExecLog> execLogs = execLogService.selectExecLogList(param);
+        return getDataTable(execLogs);
+    }
 
-            if (params.containsKey(groupName)) {
-                Map<String, JSONObject> groupParamMap = params.get(groupName);
+    @GetMapping("/execLog/{execId}")
+    @ApiOperation("获取执行日志详情")
+    public AjaxResult getExecLog(@PathVariable String execId) {
+        OpEnergyStrategyExecLog execLog = execLogService.selectByExecId(execId);
+        if (execLog == null) {
+            return error("执行日志不存在");
+        }
+        List<OpEnergyStrategyStepLog> stepLogs = execLogService.selectStepLogsByExecId(execId);
+        Map<String, Object> result = new HashMap<>();
+        result.put("execLog", execLog);
+        result.put("stepLogs", stepLogs);
+        return success(result);
+    }
 
-                for (OpEnergyStrategyParam param : groupParams) {
-                    JSONObject option = new JSONObject();
-                    option.put("paramName", param.getParamName());
-                    option.put("paramValue", param.getParamValue());
-                    option.put("editEnable", param.getEditEnable());
-                    option.put("paramValueFormat", param.getParamValueFormat());
-                    groupParamMap.put(param.getParamKey(), option);
-                }
-            }
-            else {
-                Map<String, JSONObject> groupParamMap = new HashMap<>();
-
-                for (OpEnergyStrategyParam param : groupParams) {
-                    JSONObject option = new JSONObject();
-                    option.put("paramName", param.getParamName());
-                    option.put("paramValue", param.getParamValue());
-                    option.put("editEnable", param.getEditEnable());
-                    option.put("paramValueFormat", param.getParamValueFormat());
-                    groupParamMap.put(param.getParamKey(), option);
-                }
+    @GetMapping("/execLog/steps/{execId}")
+    @ApiOperation("获取步骤执行日志")
+    public AjaxResult getStepExecLog(@PathVariable String execId) {
+        List<OpEnergyStrategyStepLog> stepLogs = execLogService.selectStepLogsByExecId(execId);
+        return success(stepLogs);
+    }
 
-                params.put(groupName, groupParamMap);
+    /**
+     * 保存触发器后同步更新策略的触发类型
+     */
+    private void syncStrategyTriggerType(String strategyCode) {
+        List<OpEnergyStrategyTrigger> triggers = triggerService.selectByStrategyCode(strategyCode);
+        if (triggers.isEmpty()) {
+            return;
+        }
+        // 取第一个启用的触发器的类型
+        OpEnergyStrategyTrigger firstTrigger = triggers.stream().filter(t -> t.getEnable() == 1).findFirst()
+            .orElse(null);
+        if (firstTrigger != null) {
+            OpEnergyStrategy strategy = strategyService.selectStrategyByCode(strategyCode);
+            if (strategy != null) {
+                Integer triggerType = mapTriggerType(firstTrigger.getTriggerType());
+                strategy.setTriggerType(triggerType);
+                strategyService.updateStrategy(strategy);
             }
         }
+    }
 
-        return params;
+    /**
+     * 触发器类型字符串转整数
+     */
+    private Integer mapTriggerType(String triggerType) {
+        if (triggerType == null)
+            return 3;
+        switch (triggerType) {
+            case "EVENT":
+                return 1;
+            case "TIME":
+                return 2;
+            case "ATTR":
+                return 4;
+            case "POLLING":
+                return 5;
+            default:
+                return 3;
+        }
     }
-}
+}

+ 0 - 102
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/OpInspectionPlanController.java

@@ -1,102 +0,0 @@
-package com.ruoyi.web.controller.ems;
-
-import com.huashe.common.domain.AjaxResult;
-import com.huashe.common.utils.uuid.Seq;
-import com.ruoyi.common.annotation.Log;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.ems.domain.OpInspectionTask;
-import com.ruoyi.ems.service.IOpInspectionPlanService;
-import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-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 javax.servlet.http.HttpServletResponse;
-import java.util.List;
-
-/**
- * 巡检计划Controller
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-@RestController
-@RequestMapping("/ems/inspectionPlan")
-@Api(value = "OpInspectionPlanController", description = "巡检计划")
-public class OpInspectionPlanController extends BaseController {
-    @Autowired
-    private IOpInspectionPlanService inspectionTaskService;
-
-    /**
-     * 查询巡检任务列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-task:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(OpInspectionTask inspectionTask) {
-        startPage();
-        List<OpInspectionTask> list = inspectionTaskService.selectOpInspectionPlanList(inspectionTask);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出巡检任务列表
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-task:export')")
-    @Log(title = "巡检任务", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(HttpServletResponse response, OpInspectionTask inspectionTask) {
-        List<OpInspectionTask> list = inspectionTaskService.selectOpInspectionPlanList(inspectionTask);
-        ExcelUtil<OpInspectionTask> util = new ExcelUtil<OpInspectionTask>(OpInspectionTask.class);
-        util.exportExcel(response, list, "巡检任务数据");
-    }
-
-    /**
-     * 获取巡检任务详细信息
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-task:query')")
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id) {
-        return success(inspectionTaskService.selectOpInspectionPlanById(id));
-    }
-
-    /**
-     * 新增巡检任务
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-task:add')")
-    @Log(title = "巡检任务", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody OpInspectionTask inspectionTask) {
-        inspectionTask.setTaskCode(Seq.getId());
-        return toAjax(inspectionTaskService.insertOpInspectionPlan(inspectionTask));
-    }
-
-    /**
-     * 修改巡检任务
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-task:edit')")
-    @Log(title = "巡检任务", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody OpInspectionTask inspectionTask) {
-        return toAjax(inspectionTaskService.updateOpInspectionPlan(inspectionTask));
-    }
-
-    /**
-     * 删除巡检任务
-     */
-    @PreAuthorize("@ss.hasPermi('ems:inspection-task:remove')")
-    @Log(title = "巡检任务", businessType = BusinessType.DELETE)
-    @DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids) {
-        return toAjax(inspectionTaskService.deleteOpInspectionPlanByIds(ids));
-    }
-}

+ 106 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/PlantCarbonSinkController.java

@@ -0,0 +1,106 @@
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.domain.PlantCarbonSink;
+import com.ruoyi.ems.service.IPlantCarbonSinkService;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 植物碳汇维Controller
+ *
+ * @author ruoyi
+ * @date 2025-11-12
+ */
+@RestController
+@RequestMapping("/ems/plantCarbonSink")
+public class PlantCarbonSinkController extends BaseController {
+    @Autowired
+    private IPlantCarbonSinkService service;
+
+    /**
+     * 查询植物碳汇维列表
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:casink:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(PlantCarbonSink param) {
+        startPage();
+        List<PlantCarbonSink> list = service.selectList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询植物碳汇维列表
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:casink:list')")
+    @GetMapping("/areaCfg")
+    public AjaxResult getAreaCfg(@RequestParam(name = "areaCode") String areaCode) {
+        List<PlantCarbonSink> list = service.getAreaCfg(areaCode);
+        return success(list);
+    }
+
+    /**
+     * 查询植物碳汇维列表
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:casink:list')")
+    @PutMapping("/areaCfg")
+    public AjaxResult mergeAreaCfg(@RequestParam(name = "areaCode") String areaCode,
+        @RequestBody List<PlantCarbonSink> list) {
+        service.mergeAreaCfg(areaCode, list);
+        return success();
+    }
+
+    /**
+     * 获取植物碳汇维详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:casink:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(service.selectById(id));
+    }
+
+    /**
+     * 新增植物碳汇维
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:casink:add')")
+    @Log(title = "植物碳汇维", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody PlantCarbonSink param) {
+        return toAjax(service.insert(param));
+    }
+
+    /**
+     * 修改植物碳汇维
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:casink:edit')")
+    @Log(title = "植物碳汇维", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody PlantCarbonSink param) {
+        return toAjax(service.update(param));
+    }
+
+    /**
+     * 删除植物碳汇维
+     */
+    @PreAuthorize("@ss.hasPermi('basecfg:casink:remove')")
+    @Log(title = "植物碳汇维", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long id) {
+        return toAjax(service.deleteById(id));
+    }
+}

+ 37 - 7
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/WaterMeterHController.java

@@ -1,17 +1,16 @@
 package com.ruoyi.web.controller.ems;
 
 import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.ruoyi.ems.domain.EnergyMeter;
 import com.ruoyi.ems.domain.WaterMeterH;
 import com.ruoyi.ems.model.QueryMeter;
 import com.ruoyi.ems.service.IWaterMeterHService;
 import io.swagger.annotations.Api;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -22,9 +21,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
-import javax.servlet.http.HttpServletResponse;
-import java.util.List;
-
 /**
  * 用水计量-小时Controller
  *
@@ -67,4 +63,38 @@ public class WaterMeterHController extends BaseController {
     public AjaxResult remove(@RequestParam(name = "date") String date) {
         return toAjax(waterMeterHService.deleteWaterMeterHByDate(date));
     }
+
+    @GetMapping("/sum/byDate/{date}")
+    public AjaxResult qryWaterMeterByDate(@PathVariable(name = "date") String date,
+        @RequestParam(name = "areaCode", required = false) String areaCode) {
+        return success(waterMeterHService.qryWaterMeterByDate(date, areaCode));
+    }
+
+    @GetMapping("/sum/timeIndex/byDate/{date}/{timeIndex}")
+    public AjaxResult qryTimeIndexWaterMeterByDay(@PathVariable(name = "date") String date,
+        @PathVariable(name = "timeIndex") Integer timeIndex) {
+        return success(waterMeterHService.qryTimeIndexWaterMeterByDay(date, timeIndex));
+    }
+
+    @GetMapping("/sum/date/byDate/{date}")
+    public AjaxResult qryDateWaterMeterByDay(@PathVariable(name = "date") String date) {
+        return success(waterMeterHService.qryDateWaterMeterByDay(date));
+    }
+
+    @GetMapping("/sum/date/byYear/{date}")
+    public AjaxResult qryDateWaterMeterByYear(@PathVariable(name = "date") String date) {
+        return success(waterMeterHService.qryDateWaterMeterByYear(date));
+    }
+
+    /**
+     * 计算时间范围内日平均
+     *
+     * @param queryMeter 支持areaCode、deviceCode、date范围筛选
+     * @return ElecMeter
+     */
+    @GetMapping("/day/avg")
+    public AjaxResult selectElecDayAvg(QueryMeter queryMeter) {
+        EnergyMeter waterMeter = waterMeterHService.selectWaterDayAvg(queryMeter);
+        return success(waterMeter);
+    }
 }

+ 69 - 0
ems/ems-application/ems-admin/src/main/java/com/ruoyi/web/controller/ems/WeatherController.java

@@ -0,0 +1,69 @@
+/*
+ * 文 件 名:  WeatherController
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/5/8
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.web.controller.ems;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.ems.service.IWeatherService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+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;
+
+/**
+ * 天气相关API
+ * <功能详细描述>
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/5/8]
+ * @see [相关类/方法]
+ * @since [产品/模块版本]
+ */
+@RestController
+@RequestMapping("/ems/weather")
+@Api(value = "WeatherController", description = "天气数据接口")
+public class WeatherController extends BaseController {
+    @Autowired
+    private IWeatherService weatherService;
+
+    /**
+     * 实时天气获取
+     */
+    @RequestMapping(value = "/getWeather", method = RequestMethod.GET)
+    @ApiOperation(value = "/getWeather", notes = "获取实时天气")
+    public AjaxResult getWeather(@RequestParam(value = "adcode") String adcode) {
+        return success(weatherService.getWeatherRt(adcode));
+    }
+
+    /**
+     * 获取历史天气数据
+     */
+    @RequestMapping(value = "/getWeatherHis", method = RequestMethod.GET)
+    @ApiOperation(value = "/getWeatherHis", notes = "获取天气历史")
+    public AjaxResult getWeatherHis(@RequestParam(value = "adcode") String adcode,
+        @RequestParam(value = "startTime") String startTime, @RequestParam(value = "endTime") String endTime) {
+        return success(weatherService.getWeatherHis(adcode, startTime, endTime));
+    }
+
+    /**
+     * 获取历史天气数据
+     */
+    @RequestMapping(value = "/getWeatherForecast", method = RequestMethod.GET)
+    @ApiOperation(value = "/getWeatherForecast", notes = "获取天气预报")
+    public AjaxResult getWeatherForecast(@RequestParam(value = "adcode") String adcode,
+        @RequestParam(value = "startTime", required = false) String startTime,
+        @RequestParam(value = "endTime", required = false) String endTime) {
+        return success(weatherService.getWeatherForecast(adcode, startTime, endTime));
+    }
+}

+ 0 - 22
ems/ems-application/ems-admin/src/main/resources/application.yml

@@ -65,28 +65,6 @@ spring:
     restart:
       # 热部署开关
       enabled: true
-  # redis 配置
-  redis:
-    # 地址
-    host: 172.192.10.105
-    # 端口,默认为6379
-    port: 30013
-    # 数据库索引
-    database: 0
-    # 密码
-    password:
-    # 连接超时时间
-    timeout: 10s
-    lettuce:
-      pool:
-        # 连接池中的最小空闲连接
-        min-idle: 0
-        # 连接池中的最大空闲连接
-        max-idle: 8
-        # 连接池的最大数据库连接数
-        max-active: 8
-        # #连接池最大阻塞等待时间(使用负值表示没有限制)
-        max-wait: -1ms
 
 # token配置
 token:

+ 1 - 1
ems/ems-application/pom.xml

@@ -12,7 +12,7 @@
     <description>华设能源管理系统-单体应用版</description>
 
     <modules>
-
+        <module>ems-admin</module>
     </modules>
     <packaging>pom</packaging>