learshaw 2 месяцев назад
Родитель
Сommit
c5ad92d54e
40 измененных файлов с 3489 добавлено и 2233 удалено
  1. 0 107
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/AdmEmsIndexRangeController.java
  2. 274 0
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/AlarmController.java
  3. 200 0
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/AlarmRuleController.java
  4. 0 158
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/OpAlarmController.java
  5. 0 107
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/OpAlarmPolicyController.java
  6. 0 128
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/AdmEmsIndexRange.java
  7. 272 0
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/Alarm.java
  8. 72 0
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/AlarmHandle.java
  9. 198 0
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/AlarmRule.java
  10. 0 232
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/OpAlarm.java
  11. 0 185
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/OpAlarmPolicy.java
  12. 0 64
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AdmEmsIndexRangeMapper.java
  13. 0 61
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AdmOpAlarmPolicyMapper.java
  14. 32 0
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AlarmHandleMapper.java
  15. 83 0
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AlarmMapper.java
  16. 67 0
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AlarmRuleMapper.java
  17. 0 89
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/OpAlarmMapper.java
  18. 0 61
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IAdmEmsIndexRangeService.java
  19. 0 61
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IAdmOpAlarmPolicyService.java
  20. 91 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IAlarmRuleService.java
  21. 86 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IAlarmService.java
  22. 0 80
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IOpAlarmService.java
  23. 464 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/alarm/AlarmProcessService.java
  24. 446 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/alarm/AlarmRuleEngine.java
  25. 208 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/alarm/InspectionAlarmAdapter.java
  26. 0 87
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/AdmEmsIndexRangeServiceImpl.java
  27. 0 87
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/AdmOpAlarmPolicyServiceImpl.java
  28. 218 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/AlarmRuleServiceImpl.java
  29. 157 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/AlarmServiceImpl.java
  30. 0 4
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/EmsFacsServiceImpl.java
  31. 0 178
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/OpAlarmServiceImpl.java
  32. 0 99
      ems/ems-core/src/main/resources/mapper/ems/AdmEmsIndexRangeMapper.xml
  33. 0 100
      ems/ems-core/src/main/resources/mapper/ems/AdmOpAlarmPolicyMapper.xml
  34. 45 0
      ems/ems-core/src/main/resources/mapper/ems/AlarmHandleMapper.xml
  35. 237 0
      ems/ems-core/src/main/resources/mapper/ems/AlarmMapper.xml
  36. 191 0
      ems/ems-core/src/main/resources/mapper/ems/AlarmRuleMapper.xml
  37. 0 254
      ems/ems-core/src/main/resources/mapper/ems/OpAlarmMapper.xml
  38. 26 27
      ems/sql/ems_init_data_ctfwq.sql
  39. 120 62
      ems/sql/ems_server.sql
  40. 2 2
      ems/sql/ems_sys_data.sql

+ 0 - 107
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/AdmEmsIndexRangeController.java

@@ -1,107 +0,0 @@
-package com.ruoyi.ems.controller;
-
-import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.core.utils.poi.ExcelUtil;
-import com.ruoyi.common.core.web.controller.BaseController;
-import com.ruoyi.common.core.web.page.TableDataInfo;
-import com.ruoyi.common.log.annotation.Log;
-import com.ruoyi.common.log.enums.BusinessType;
-import com.ruoyi.common.security.annotation.RequiresPermissions;
-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.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("/indexRange")
-@Api(value = "AdmEmsIndexRangeController", description = "能源指标范围")
-public class AdmEmsIndexRangeController extends BaseController
-{
-    @Autowired
-    private IAdmEmsIndexRangeService indexRangeService;
-
-    /**
-     * 查询能源指标范围列表
-     */
-    @RequiresPermissions("ems:indexRange:list")
-    @GetMapping("/list")
-    public TableDataInfo list(AdmEmsIndexRange indexRange)
-    {
-        startPage();
-        List<AdmEmsIndexRange> list = indexRangeService.selectAdmEmsIndexRangeList(indexRange);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出能源指标范围列表
-     */
-    @RequiresPermissions("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, "能源指标范围数据");
-    }
-
-    /**
-     * 获取能源指标范围详细信息
-     */
-    @RequiresPermissions("ems:indexRange:query")
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
-        return success(indexRangeService.selectAdmEmsIndexRangeById(id));
-    }
-
-    /**
-     * 新增能源指标范围
-     */
-    @RequiresPermissions("ems:indexRange:add")
-    @Log(title = "能源指标范围", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody AdmEmsIndexRange indexRange)
-    {
-        return toAjax(indexRangeService.insertAdmEmsIndexRange(indexRange));
-    }
-
-    /**
-     * 修改能源指标范围
-     */
-    @RequiresPermissions("ems:indexRange:edit")
-    @Log(title = "能源指标范围", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody AdmEmsIndexRange indexRange)
-    {
-        return toAjax(indexRangeService.updateAdmEmsIndexRange(indexRange));
-    }
-
-    /**
-     * 删除能源指标范围
-     */
-    @RequiresPermissions("ems:indexRange:remove")
-    @Log(title = "能源指标范围", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
-        return toAjax(indexRangeService.deleteAdmEmsIndexRangeByIds(ids));
-    }
-}

+ 274 - 0
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/AlarmController.java

@@ -0,0 +1,274 @@
+/*
+ * 文 件 名:  AlarmController
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2026/2/3
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.ems.controller;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.common.security.utils.SecurityUtils;
+import com.ruoyi.ems.domain.Alarm;
+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("/alarm")
+@Api(value = "AlarmController", description = "告警管理")
+public class AlarmController extends BaseController {
+
+    @Autowired
+    private IAlarmService alarmService;
+
+    @Autowired
+    private AlarmProcessService processService;
+
+    /**
+     * 查询告警列表
+     */
+    @RequiresPermissions("ems:alarm:list")
+    @GetMapping("/list")
+    @ApiOperation("查询告警列表")
+    public TableDataInfo list(Alarm alarm) {
+        startPage();
+        List<Alarm> list = alarmService.selectList(alarm);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询活动告警
+     */
+    @RequiresPermissions("ems:alarm:list")
+    @GetMapping("/active")
+    @ApiOperation("查询活动告警")
+    public AjaxResult listActive(@RequestParam(value = "areaCode", required = false) String areaCode) {
+        return success(alarmService.selectActiveAlarms(areaCode));
+    }
+
+    /**
+     * 导出告警列表
+     */
+    @RequiresPermissions("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, "告警数据");
+    }
+
+    /**
+     * 获取告警详情
+     */
+    @RequiresPermissions("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获取详情
+     */
+    @RequiresPermissions("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);
+    }
+
+    /**
+     * 手动上报告警
+     */
+    @RequiresPermissions("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);
+    }
+
+    /**
+     * 确认告警
+     */
+    @RequiresPermissions("ems:alarm:handle")
+    @Log(title = "告警管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/confirm/{alarmId}")
+    @ApiOperation("确认告警")
+    public AjaxResult confirm(@PathVariable("alarmId") String alarmId,
+        @RequestParam(value = "remark", required = false) String remark) {
+        String operator = SecurityUtils.getUsername();
+        processService.confirmAlarm(alarmId, operator, remark);
+        return success();
+    }
+
+    /**
+     * 处置告警
+     */
+    @RequiresPermissions("ems:alarm:handle")
+    @Log(title = "告警管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/handle/{alarmId}")
+    @ApiOperation("处置告警")
+    public AjaxResult handle(@PathVariable("alarmId") String alarmId,
+        @RequestParam("handleContent") String handleContent,
+        @RequestParam(value = "handleResult", required = false) String handleResult) {
+        String operator = SecurityUtils.getUsername();
+        processService.handleAlarm(alarmId, handleContent, handleResult, operator);
+        return success();
+    }
+
+    /**
+     * 解决告警
+     */
+    @RequiresPermissions("ems:alarm:handle")
+    @Log(title = "告警管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/resolve/{alarmId}")
+    @ApiOperation("解决告警")
+    public AjaxResult resolve(@PathVariable("alarmId") String alarmId,
+        @RequestParam(value = "resolveRemark", required = false) String resolveRemark) {
+        String operator = SecurityUtils.getUsername();
+        processService.resolveAlarm(alarmId, resolveRemark, operator);
+        return success();
+    }
+
+    /**
+     * 关闭告警
+     */
+    @RequiresPermissions("ems:alarm:handle")
+    @Log(title = "告警管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/close/{alarmId}")
+    @ApiOperation("关闭告警")
+    public AjaxResult close(@PathVariable("alarmId") String alarmId,
+        @RequestParam(value = "reason", required = false) String reason) {
+        String operator = SecurityUtils.getUsername();
+        processService.closeAlarm(alarmId, reason, operator);
+        return success();
+    }
+
+    /**
+     * 批量确认告警
+     */
+    @RequiresPermissions("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 + " 条告警");
+    }
+
+    /**
+     * 删除告警
+     */
+    @RequiresPermissions("ems:alarm:remove")
+    @Log(title = "告警管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    @ApiOperation("删除告警")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(alarmService.deleteByIds(ids));
+    }
+
+    // ==================== 统计接口 ====================
+
+    /**
+     * 告警统计概览
+     */
+    @RequiresPermissions("ems:alarm:query")
+    @GetMapping("/stats/overview")
+    @ApiOperation("告警统计概览")
+    public AjaxResult statsOverview(Alarm query) {
+        return success(alarmService.selectAlarmStats(query));
+    }
+
+    /**
+     * 按级别统计
+     */
+    @RequiresPermissions("ems:alarm:query")
+    @GetMapping("/stats/byLevel")
+    @ApiOperation("按级别统计")
+    public AjaxResult countByLevel(Alarm query) {
+        return success(alarmService.countByLevel(query));
+    }
+
+    /**
+     * 按状态统计
+     */
+    @RequiresPermissions("ems:alarm:query")
+    @GetMapping("/stats/byStatus")
+    @ApiOperation("按状态统计")
+    public AjaxResult countByStatus(Alarm query) {
+        return success(alarmService.countByStatus(query));
+    }
+
+    /**
+     * 按时间趋势统计
+     */
+    @RequiresPermissions("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));
+    }
+
+    /**
+     * 按子系统统计
+     */
+    @RequiresPermissions("ems:alarm:query")
+    @GetMapping("/stats/bySubsystem")
+    @ApiOperation("按子系统统计")
+    public AjaxResult countBySubsystem(Alarm query) {
+        return success(alarmService.countBySubsystem(query));
+    }
+
+    /**
+     * 处理率统计
+     */
+    @RequiresPermissions("ems:alarm:query")
+    @GetMapping("/stats/handleRate")
+    @ApiOperation("处理率统计")
+    public AjaxResult handleRate(Alarm query) {
+        return success(alarmService.countHandleRate(query));
+    }
+}

+ 200 - 0
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/AlarmRuleController.java

@@ -0,0 +1,200 @@
+/*
+ * 文 件 名:  AlarmRuleController
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2026/2/3
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.ems.controller;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.common.security.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("/alarm/rule")
+@Api(value = "AlarmRuleController", description = "告警规则管理")
+public class AlarmRuleController extends BaseController {
+
+    @Autowired
+    private IAlarmRuleService ruleService;
+
+    /**
+     * 查询告警规则列表
+     */
+    @RequiresPermissions("ems:alarm-rule:list")
+    @GetMapping("/list")
+    @ApiOperation("查询告警规则列表")
+    public TableDataInfo list(AlarmRule rule) {
+        startPage();
+        List<AlarmRule> list = ruleService.selectList(rule);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询全部规则(不分页)
+     */
+    @RequiresPermissions("ems:alarm-rule:list")
+    @GetMapping("/listAll")
+    @ApiOperation("查询全部告警规则")
+    public AjaxResult listAll(AlarmRule rule) {
+        List<AlarmRule> list = ruleService.selectList(rule);
+        return success(list);
+    }
+
+    /**
+     * 导出告警规则列表
+     */
+    @RequiresPermissions("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, "告警规则数据");
+    }
+
+    /**
+     * 获取告警规则详情
+     */
+    @RequiresPermissions("ems:alarm-rule:query")
+    @GetMapping("/{id}")
+    @ApiOperation("获取告警规则详情")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(ruleService.selectById(id));
+    }
+
+    /**
+     * 根据规则代码获取详情
+     */
+    @RequiresPermissions("ems:alarm-rule:query")
+    @GetMapping("/code/{ruleCode}")
+    @ApiOperation("根据规则代码获取详情")
+    public AjaxResult getByCode(@PathVariable("ruleCode") String ruleCode) {
+        return success(ruleService.selectByRuleCode(ruleCode));
+    }
+
+    /**
+     * 新增告警规则
+     */
+    @RequiresPermissions("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));
+    }
+
+    /**
+     * 修改告警规则
+     */
+    @RequiresPermissions("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));
+    }
+
+    /**
+     * 删除告警规则
+     */
+    @RequiresPermissions("ems:alarm-rule:remove")
+    @Log(title = "告警规则", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    @ApiOperation("删除告警规则")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(ruleService.deleteByIds(ids));
+    }
+
+    /**
+     * 启用/禁用规则
+     */
+    @RequiresPermissions("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));
+    }
+
+    /**
+     * 批量启用/禁用
+     */
+    @RequiresPermissions("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));
+    }
+
+    /**
+     * 复制规则
+     */
+    @RequiresPermissions("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);
+    }
+
+    /**
+     * 查询指定分组的规则
+     */
+    @RequiresPermissions("ems:alarm-rule:list")
+    @GetMapping("/group/{groupCode}")
+    @ApiOperation("查询指定分组的规则")
+    public AjaxResult listByGroup(@PathVariable("groupCode") String groupCode) {
+        return success(ruleService.selectByGroupCode(groupCode));
+    }
+
+    /**
+     * 查询适用于巡检的规则
+     */
+    @RequiresPermissions("ems:alarm-rule:list")
+    @GetMapping("/forInspection")
+    @ApiOperation("查询适用于巡检的规则")
+    public AjaxResult listForInspection(@RequestParam(value = "deviceModel", required = false) String deviceModel) {
+        return success(ruleService.selectRulesForInspection(deviceModel));
+    }
+
+    /**
+     * 查询适用于实时监测的规则
+     */
+    @RequiresPermissions("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));
+    }
+}

+ 0 - 158
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/OpAlarmController.java

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

+ 0 - 107
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/OpAlarmPolicyController.java

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

+ 0 - 128
ems/ems-core/src/main/java/com/ruoyi/ems/domain/AdmEmsIndexRange.java

@@ -1,128 +0,0 @@
-package com.ruoyi.ems.domain;
-
-import com.huashe.common.annotation.Excel;
-import com.huashe.common.domain.BaseEntity;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-
-/**
- * 能源指标范围对象 adm_ems_index_range
- *
- * @author ruoyi
- * @date 2024-08-30
- */
-public class AdmEmsIndexRange extends BaseEntity {
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * 序号
-     */
-    private Long id;
-
-    /**
-     * 对象代码
-     */
-    @Excel(name = "对象代码")
-    private String objCode;
-
-    /**
-     * 对象类型
-     */
-    @Excel(name = "对象类型")
-    private Integer objType;
-
-    /**
-     * 指标名称
-     */
-    @Excel(name = "指标名称")
-    private String indexName;
-
-    /**
-     * 指标描述
-     */
-    @Excel(name = "指标描述")
-    private String indexDesc;
-
-    /**
-     * 指标上限
-     */
-    @Excel(name = "指标上限")
-    private Double indexUpperLimit;
-
-    /**
-     * 指标下限
-     */
-    @Excel(name = "指标下限")
-    private Double indexLowerLimit;
-
-    private String facsType;
-
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public Long getId() {
-        return id;
-    }
-
-    public void setObjCode(String objCode) {
-        this.objCode = objCode;
-    }
-
-    public String getObjCode() {
-        return objCode;
-    }
-
-    public void setObjType(Integer objType) {
-        this.objType = objType;
-    }
-
-    public Integer getObjType() {
-        return objType;
-    }
-
-    public void setIndexName(String indexName) {
-        this.indexName = indexName;
-    }
-
-    public String getIndexName() {
-        return indexName;
-    }
-
-    public void setIndexDesc(String indexDesc) {
-        this.indexDesc = indexDesc;
-    }
-
-    public String getIndexDesc() {
-        return indexDesc;
-    }
-
-    public void setIndexUpperLimit(Double indexUpperLimit) {
-        this.indexUpperLimit = indexUpperLimit;
-    }
-
-    public Double getIndexUpperLimit() {
-        return indexUpperLimit;
-    }
-
-    public void setIndexLowerLimit(Double indexLowerLimit) {
-        this.indexLowerLimit = indexLowerLimit;
-    }
-
-    public Double getIndexLowerLimit() {
-        return indexLowerLimit;
-    }
-
-    public String getFacsType() {
-        return facsType;
-    }
-
-    public void setFacsType(String facsType) {
-        this.facsType = facsType;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("id", getId()).append("objCode", getObjCode()).append("objType", getObjType()).append("indexName", getIndexName()).append("indexDesc", getIndexDesc()).append("indexUpperLimit", getIndexUpperLimit()).append("indexLowerLimit", getIndexLowerLimit()).toString();
-    }
-}

+ 272 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/domain/Alarm.java

@@ -0,0 +1,272 @@
+package com.ruoyi.ems.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.huashe.common.annotation.Excel;
+import com.huashe.common.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 告警记录对象 adm_op_alarm
+ *
+ * 【设计说明】
+ * 1. 完整的告警生命周期:活动 → 确认 → 处置中 → 已解决/已关闭/已恢复
+ * 2. 支持告警聚合(同一规则同一对象的重复告警)
+ * 3. 记录告警来源(实时监测/巡检/手动上报)
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class Alarm extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    /** 告警唯一标识 */
+    @Excel(name = "告警ID")
+    private String alarmId;
+
+    /** 区域代码 */
+    private String areaCode;
+
+    /** 区域名称 */
+    @Excel(name = "区域")
+    private String areaName;
+
+    /** 子系统代码 */
+    private String subsystemCode;
+
+    /** 子系统名称 */
+    @Excel(name = "子系统")
+    private String subsystemName;
+
+    /** 目标类型 0:区域 1:设施 2:设备 */
+    private Integer targetType;
+
+    /** 目标代码 */
+    @Excel(name = "目标代码")
+    private String targetCode;
+
+    /** 目标名称 */
+    @Excel(name = "目标名称")
+    private String targetName;
+
+    /** 设备模型 */
+    private String deviceModel;
+
+    /** 设备模型名称 */
+    private String deviceModelName;
+
+    /** 位置信息 */
+    @Excel(name = "位置")
+    private String location;
+
+    /** 触发规则代码 */
+    private String ruleCode;
+
+    /** 触发规则名称 */
+    @Excel(name = "触发规则")
+    private String ruleName;
+
+    /** 告警属性 */
+    private String attrKey;
+
+    /** 属性名称 */
+    @Excel(name = "告警属性")
+    private String attrName;
+
+    /** 告警时属性值 */
+    @Excel(name = "告警值")
+    private String attrValue;
+
+    /** 阈值 */
+    @Excel(name = "阈值")
+    private String thresholdValue;
+
+    /** 告警级别 1:一般 2:重要 3:紧急 */
+    @Excel(name = "告警级别", readConverterExp = "1=一般,2=重要,3=紧急")
+    private Integer alarmLevel;
+
+    /** 告警代码 */
+    @Excel(name = "告警代码")
+    private String alarmCode;
+
+    /** 告警消息 */
+    @Excel(name = "告警消息")
+    private String alarmMsg;
+
+    /** 告警来源 1:实时监测 2:自动巡检 3:手动上报 */
+    @Excel(name = "告警来源", readConverterExp = "1=实时监测,2=自动巡检,3=手动上报")
+    private Integer alarmSource;
+
+    /** 来源关联(如巡检报告代码) */
+    private String sourceRef;
+
+    /** 告警时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "告警时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date alarmTime;
+
+    /** 告警日期(便于分区和查询) */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date alarmDate;
+
+    /** 告警状态 0:活动 1:已确认 2:处置中 3:已解决 4:已关闭 5:已恢复 */
+    @Excel(name = "告警状态", readConverterExp = "0=活动,1=已确认,2=处置中,3=已解决,4=已关闭,5=已恢复")
+    private Integer alarmStatus;
+
+    /** 确认时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date confirmTime;
+
+    /** 确认人 */
+    private String confirmBy;
+
+    /** 解决时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date resolveTime;
+
+    /** 解决人 */
+    private String resolveBy;
+
+    /** 解决备注 */
+    private String resolveRemark;
+
+    /** 自动恢复时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date recoveryTime;
+
+    /** 持续时长(秒) */
+    private Integer duration;
+
+    /** 重复次数(聚合) */
+    private Integer repeatCount;
+
+    /** 最后发生时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastOccurTime;
+
+    /** 是否已通知 0:否 1:是 */
+    private Integer isNotified;
+
+    /** 通知时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date notifyTime;
+
+    /** 处置记录列表(查询时关联) */
+    private List<AlarmHandle> handleList;
+
+    // ==================== 状态常量 ====================
+
+    public static final int STATUS_ACTIVE = 0;      // 活动
+    public static final int STATUS_CONFIRMED = 1;   // 已确认
+    public static final int STATUS_HANDLING = 2;    // 处置中
+    public static final int STATUS_RESOLVED = 3;    // 已解决
+    public static final int STATUS_CLOSED = 4;      // 已关闭
+    public static final int STATUS_RECOVERED = 5;   // 已恢复
+
+    // ==================== 来源常量 ====================
+
+    public static final int SOURCE_REALTIME = 1;    // 实时监测
+    public static final int SOURCE_INSPECTION = 2;  // 自动巡检
+    public static final int SOURCE_MANUAL = 3;      // 手动上报
+
+    // ==================== 级别常量 ====================
+
+    public static final int LEVEL_NORMAL = 1;       // 一般
+    public static final int LEVEL_IMPORTANT = 2;    // 重要
+    public static final int LEVEL_URGENT = 3;       // 紧急
+
+    /**
+     * 获取告警状态描述
+     */
+    public String getAlarmStatusDesc() {
+        if (alarmStatus == null) {
+            return "未知";
+        }
+        switch (alarmStatus) {
+            case STATUS_ACTIVE:
+                return "活动";
+            case STATUS_CONFIRMED:
+                return "已确认";
+            case STATUS_HANDLING:
+                return "处置中";
+            case STATUS_RESOLVED:
+                return "已解决";
+            case STATUS_CLOSED:
+                return "已关闭";
+            case STATUS_RECOVERED:
+                return "已恢复";
+            default:
+                return "未知";
+        }
+    }
+
+    /**
+     * 获取告警来源描述
+     */
+    public String getAlarmSourceDesc() {
+        if (alarmSource == null) {
+            return "未知";
+        }
+        switch (alarmSource) {
+            case SOURCE_REALTIME:
+                return "实时监测";
+            case SOURCE_INSPECTION:
+                return "自动巡检";
+            case SOURCE_MANUAL:
+                return "手动上报";
+            default:
+                return "未知";
+        }
+    }
+
+    /**
+     * 获取告警级别描述
+     */
+    public String getAlarmLevelDesc() {
+        if (alarmLevel == null) {
+            return "未知";
+        }
+        switch (alarmLevel) {
+            case LEVEL_NORMAL:
+                return "一般";
+            case LEVEL_IMPORTANT:
+                return "重要";
+            case LEVEL_URGENT:
+                return "紧急";
+            default:
+                return "未知";
+        }
+    }
+
+    /**
+     * 判断是否为活动告警
+     */
+    public boolean isActive() {
+        return alarmStatus != null && alarmStatus == STATUS_ACTIVE;
+    }
+
+    /**
+     * 判断是否已结束
+     */
+    public boolean isEnded() {
+        return alarmStatus != null && (alarmStatus == STATUS_RESOLVED
+            || alarmStatus == STATUS_CLOSED
+            || alarmStatus == STATUS_RECOVERED);
+    }
+
+    /**
+     * 计算持续时长(秒)
+     */
+    public Integer calculateDuration() {
+        if (alarmTime == null) {
+            return null;
+        }
+        Date endTime = recoveryTime != null ? recoveryTime :
+            (resolveTime != null ? resolveTime : new Date());
+        return (int) ((endTime.getTime() - alarmTime.getTime()) / 1000);
+    }
+}

+ 72 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/domain/AlarmHandle.java

@@ -0,0 +1,72 @@
+package com.ruoyi.ems.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.huashe.common.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 告警处置记录对象 adm_op_alarm_handle
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AlarmHandle extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    /** 告警ID */
+    private String alarmId;
+
+    /** 处置类型 1:确认 2:派单 3:处置 4:关闭 5:备注 */
+    private Integer handleType;
+
+    /** 处置内容 */
+    private String handleContent;
+
+    /** 处置结果 */
+    private String handleResult;
+
+    /** 处置人 */
+    private String handleBy;
+
+    /** 处置时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date handleTime;
+
+    /** 附件(JSON) */
+    private String attachments;
+
+    // ==================== 处置类型常量 ====================
+
+    public static final int TYPE_CONFIRM = 1;   // 确认
+    public static final int TYPE_DISPATCH = 2;  // 派单
+    public static final int TYPE_HANDLE = 3;    // 处置
+    public static final int TYPE_CLOSE = 4;     // 关闭
+    public static final int TYPE_REMARK = 5;    // 备注
+
+    /**
+     * 获取处置类型描述
+     */
+    public String getHandleTypeDesc() {
+        if (handleType == null) {
+            return "未知";
+        }
+        switch (handleType) {
+            case TYPE_CONFIRM:
+                return "确认";
+            case TYPE_DISPATCH:
+                return "派单";
+            case TYPE_HANDLE:
+                return "处置";
+            case TYPE_CLOSE:
+                return "关闭";
+            case TYPE_REMARK:
+                return "备注";
+            default:
+                return "未知";
+        }
+    }
+}

+ 198 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/domain/AlarmRule.java

@@ -0,0 +1,198 @@
+package com.ruoyi.ems.domain;
+
+import com.huashe.common.annotation.Excel;
+import com.huashe.common.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * 告警规则对象 adm_op_alarm_rule
+ *
+ * 【设计说明】
+ * 1. 统一的规则配置,可同时用于实时监测和巡检
+ * 2. 支持多种检查类型:范围、等值、非空、在线状态、变化率
+ * 3. 支持告警冷却、连续触发等高级特性
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AlarmRule extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    /** 规则代码 */
+    @Excel(name = "规则代码")
+    private String ruleCode;
+
+    /** 规则名称 */
+    @Excel(name = "规则名称")
+    private String ruleName;
+
+    /** 适用区域代码(空则全局) */
+    private String areaCode;
+
+    /** 区域名称 */
+    private String areaName;
+
+    /** 目标类型 0:区域 1:设施 2:设备 */
+    @Excel(name = "目标类型", readConverterExp = "0=区域,1=设施,2=设备")
+    private Integer targetType;
+
+    /** 目标范围(JSON数组) */
+    private String targetScope;
+
+    /** 解析后的目标范围列表 */
+    private List<String> targetScopeList;
+
+    /** 设备模型代码(空则不限) */
+    private String deviceModel;
+
+    /** 设备模型名称 */
+    private String deviceModelName;
+
+    /** 监测属性标识 */
+    @Excel(name = "监测属性")
+    private String attrKey;
+
+    /** 属性名称 */
+    @Excel(name = "属性名称")
+    private String attrName;
+
+    /**
+     * 检查类型
+     * 1:范围 2:等值 3:非空 4:在线状态 5:变化率
+     */
+    @Excel(name = "检查类型", readConverterExp = "1=范围,2=等值,3=非空,4=在线状态,5=变化率")
+    private Integer checkType;
+
+    /** 比较操作符 GT/GE/LT/LE/EQ/NE/RANGE/IN */
+    private String operator;
+
+    /** 阈值(单值或JSON) */
+    private String thresholdValue;
+
+    /** 最小阈值(范围检查) */
+    private String thresholdMin;
+
+    /** 最大阈值(范围检查) */
+    private String thresholdMax;
+
+    /** 告警级别 1:一般 2:重要 3:紧急 */
+    @Excel(name = "告警级别", readConverterExp = "1=一般,2=重要,3=紧急")
+    private Integer alarmLevel;
+
+    /** 告警代码(用于分类) */
+    @Excel(name = "告警代码")
+    private String alarmCode;
+
+    /** 告警消息模板(支持变量替换) */
+    private String alarmMsgTemplate;
+
+    /** 是否检查恢复 0:否 1:是 */
+    private Integer recoveryCheck;
+
+    /** 恢复阈值 */
+    private String recoveryThreshold;
+
+    /** 触发次数(连续N次满足才告警) */
+    private Integer triggerCount;
+
+    /** 触发间隔(秒,防抖) */
+    private Integer triggerInterval;
+
+    /** 冷却时间(秒,同一规则同一对象) */
+    private Integer coolDown;
+
+    /** 是否启用 0:禁用 1:启用 */
+    @Excel(name = "是否启用", readConverterExp = "0=禁用,1=启用")
+    private Integer enabled;
+
+    /** 是否用于巡检 0:否 1:是 */
+    private Integer useForInspection;
+
+    /** 是否用于实时监测 0:否 1:是 */
+    private Integer useForRealtime;
+
+    /** 所属子系统代码 */
+    private String subsystemCode;
+
+    /** 子系统名称 */
+    private String subsystemName;
+
+    /** 规则优先级(数值小优先) */
+    private Integer ruleOrder;
+
+    /** 规则描述 */
+    private String description;
+
+    /** 所属分组列表 */
+    private List<String> groupCodes;
+
+    // ==================== 检查类型常量 ====================
+
+    public static final int CHECK_TYPE_RANGE = 1;
+    public static final int CHECK_TYPE_EQUAL = 2;
+    public static final int CHECK_TYPE_NOT_NULL = 3;
+    public static final int CHECK_TYPE_ONLINE = 4;
+    public static final int CHECK_TYPE_CHANGE_RATE = 5;
+
+    // ==================== 操作符常量 ====================
+
+    public static final String OP_GT = "GT";   // 大于
+    public static final String OP_GE = "GE";   // 大于等于
+    public static final String OP_LT = "LT";   // 小于
+    public static final String OP_LE = "LE";   // 小于等于
+    public static final String OP_EQ = "EQ";   // 等于
+    public static final String OP_NE = "NE";   // 不等于
+    public static final String OP_RANGE = "RANGE"; // 范围
+    public static final String OP_IN = "IN";   // 包含
+
+    // ==================== 告警级别常量 ====================
+
+    public static final int LEVEL_NORMAL = 1;   // 一般
+    public static final int LEVEL_IMPORTANT = 2; // 重要
+    public static final int LEVEL_URGENT = 3;    // 紧急
+
+    /**
+     * 获取格式化的阈值显示
+     */
+    public String getThresholdDisplay() {
+        if (checkType == null) {
+            return "-";
+        }
+        switch (checkType) {
+            case CHECK_TYPE_RANGE:
+                String min = thresholdMin != null ? thresholdMin : "-∞";
+                String max = thresholdMax != null ? thresholdMax : "+∞";
+                return min + " ~ " + max;
+            case CHECK_TYPE_EQUAL:
+            case CHECK_TYPE_ONLINE:
+                return "= " + (thresholdValue != null ? thresholdValue : "");
+            case CHECK_TYPE_NOT_NULL:
+                return "非空";
+            default:
+                return thresholdValue != null ? thresholdValue : "-";
+        }
+    }
+
+    /**
+     * 获取告警级别描述
+     */
+    public String getAlarmLevelDesc() {
+        if (alarmLevel == null) {
+            return "未知";
+        }
+        switch (alarmLevel) {
+            case LEVEL_NORMAL:
+                return "一般";
+            case LEVEL_IMPORTANT:
+                return "重要";
+            case LEVEL_URGENT:
+                return "紧急";
+            default:
+                return "未知";
+        }
+    }
+}

+ 0 - 232
ems/ems-core/src/main/java/com/ruoyi/ems/domain/OpAlarm.java

@@ -1,232 +0,0 @@
-package com.ruoyi.ems.domain;
-
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.huashe.common.annotation.Excel;
-import com.huashe.common.domain.BaseEntity;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-
-import java.util.Date;
-import java.util.List;
-
-/**
- * 能源设施告警对象 adm_op_alarm
- *
- * @author ruoyi
- * @date 2024-08-26
- */
-public class OpAlarm extends BaseEntity
-{
-    private static final long serialVersionUID = 1L;
-
-    /** 序号 */
-    private Long id;
-
-    /** 园区代码 */
-    private String areaCode;
-
-    @Excel(name = "园区名称")
-    private String areaName;
-
-    private String areaShortName;
-
-    /** 对象类型 */
-    @Excel(name = "对象类型")
-    private Integer objType;
-
-    @Excel(name = "子系统")
-    private String subSystemName;
-
-    private String systemCode;
-
-    /** 对象代码 */
-    @Excel(name = "对象代码")
-    private String objCode;
-
-    @Excel(name = "对象名称")
-    private String objName;
-
-    /** 告警日期 */
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    @Excel(name = "告警日期", width = 30, dateFormat = "yyyy-MM-dd")
-    private Date alarmDate;
-
-    /** 告警时间 */
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    @Excel(name = "告警时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
-    private Date alarmTime;
-
-    /** 告警代码 */
-    @Excel(name = "告警代码")
-    private String alarmCode;
-
-    /** 告警描述 */
-    @Excel(name = "告警描述")
-    private String alarmMsg;
-
-    /** 告警类型 */
-    @Excel(name = "告警类型")
-    private Integer alarmType;
-
-    /** 告警状态 */
-    @Excel(name = "告警状态")
-    private Integer alarmState;
-
-    private List<Integer> alarmStateList;
-
-    public void setId(Long id)
-    {
-        this.id = id;
-    }
-
-    public Long getId()
-    {
-        return id;
-    }
-    public void setAreaCode(String areaCode)
-    {
-        this.areaCode = areaCode;
-    }
-
-    public String getAreaCode()
-    {
-        return areaCode;
-    }
-
-    public String getAreaName() {
-        return areaName;
-    }
-
-    public void setAreaName(String areaName) {
-        this.areaName = areaName;
-    }
-
-    public String getAreaShortName() {
-        return areaShortName;
-    }
-
-    public void setAreaShortName(String areaShortName) {
-        this.areaShortName = areaShortName;
-    }
-
-    public void setObjType(Integer objType)
-    {
-        this.objType = objType;
-    }
-
-    public Integer getObjType()
-    {
-        return objType;
-    }
-    public void setObjCode(String objCode)
-    {
-        this.objCode = objCode;
-    }
-
-    public String getObjCode()
-    {
-        return objCode;
-    }
-    public void setAlarmDate(Date alarmDate)
-    {
-        this.alarmDate = alarmDate;
-    }
-
-    public Date getAlarmDate()
-    {
-        return alarmDate;
-    }
-    public void setAlarmTime(Date alarmTime)
-    {
-        this.alarmTime = alarmTime;
-    }
-
-    public Date getAlarmTime()
-    {
-        return alarmTime;
-    }
-    public void setAlarmCode(String alarmCode)
-    {
-        this.alarmCode = alarmCode;
-    }
-
-    public String getAlarmCode()
-    {
-        return alarmCode;
-    }
-    public void setAlarmMsg(String alarmMsg)
-    {
-        this.alarmMsg = alarmMsg;
-    }
-
-    public String getAlarmMsg()
-    {
-        return alarmMsg;
-    }
-    public void setAlarmType(Integer alarmType)
-    {
-        this.alarmType = alarmType;
-    }
-
-    public Integer getAlarmType()
-    {
-        return alarmType;
-    }
-    public void setAlarmState(Integer alarmState)
-    {
-        this.alarmState = alarmState;
-    }
-
-    public Integer getAlarmState()
-    {
-        return alarmState;
-    }
-
-    public String getSubSystemName() {
-        return subSystemName;
-    }
-
-    public void setSubSystemName(String subSystemName) {
-        this.subSystemName = subSystemName;
-    }
-
-    public String getSystemCode() {
-        return systemCode;
-    }
-
-    public void setSystemCode(String systemCode) {
-        this.systemCode = systemCode;
-    }
-
-    public List<Integer> getAlarmStateList() {
-        return alarmStateList;
-    }
-
-    public void setAlarmStateList(List<Integer> alarmStateList) {
-        this.alarmStateList = alarmStateList;
-    }
-
-    public String getObjName() {
-        return objName;
-    }
-
-    public void setObjName(String objName) {
-        this.objName = objName;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
-            .append("id", getId())
-            .append("areaCode", getAreaCode())
-            .append("objType", getObjType())
-            .append("objCode", getObjCode())
-            .append("alarmDate", getAlarmDate())
-            .append("alarmTime", getAlarmTime())
-            .append("alarmCode", getAlarmCode())
-            .append("alarmMsg", getAlarmMsg())
-            .append("alarmType", getAlarmType())
-            .append("alarmState", getAlarmState())
-            .toString();
-    }
-}

+ 0 - 185
ems/ems-core/src/main/java/com/ruoyi/ems/domain/OpAlarmPolicy.java

@@ -1,185 +0,0 @@
-package com.ruoyi.ems.domain;
-
-import com.huashe.common.annotation.Excel;
-import com.huashe.common.domain.BaseEntity;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-
-/**
- * 能源设施告警策略对象 adm_op_alarm_policy
- * 
- * @author ruoyi
- * @date 2024-08-26
- */
-public class OpAlarmPolicy extends BaseEntity
-{
-    private static final long serialVersionUID = 1L;
-
-    /** 序号 */
-    private Long id;
-
-    private String areaCode;
-
-    @Excel(name = "对象名称")
-    private String areaName;
-
-    /** 策略代码 */
-    @Excel(name = "策略代码")
-    private String policyCode;
-
-    /** 策略名称 */
-    @Excel(name = "策略名称")
-    private String policyName;
-
-    /** 告警对象类型 */
-    @Excel(name = "告警对象类型")
-    private Integer alarmObjType;
-
-    /** 告警对象指标 */
-    @Excel(name = "告警对象指标")
-    private String alarmObjIndex;
-
-    /** 告警规则 */
-    @Excel(name = "告警规则")
-    private Integer alarmRuleType;
-
-    /** 告警阈值 */
-    @Excel(name = "告警阈值")
-    private Double alarmThresholdValue;
-
-    /** 告警代码 */
-    @Excel(name = "告警代码")
-    private String alarmCode;
-
-    /** 告警描述 */
-    @Excel(name = "告警描述")
-    private String alarmMsg;
-
-    /** 告警类型 */
-    @Excel(name = "告警类型")
-    private Integer alarmType;
-
-    public void setId(Long id) 
-    {
-        this.id = id;
-    }
-
-    public Long getId() 
-    {
-        return id;
-    }
-
-    public String getAreaCode() {
-        return areaCode;
-    }
-
-    public void setAreaCode(String areaCode) {
-        this.areaCode = areaCode;
-    }
-
-    public String getAreaName() {
-        return areaName;
-    }
-
-    public void setAreaName(String areaName) {
-        this.areaName = areaName;
-    }
-
-    public void setPolicyCode(String policyCode)
-    {
-        this.policyCode = policyCode;
-    }
-
-    public String getPolicyCode() 
-    {
-        return policyCode;
-    }
-    public void setPolicyName(String policyName) 
-    {
-        this.policyName = policyName;
-    }
-
-    public String getPolicyName() 
-    {
-        return policyName;
-    }
-    public void setAlarmObjType(Integer alarmObjType) 
-    {
-        this.alarmObjType = alarmObjType;
-    }
-
-    public Integer getAlarmObjType() 
-    {
-        return alarmObjType;
-    }
-    public void setAlarmObjIndex(String alarmObjIndex) 
-    {
-        this.alarmObjIndex = alarmObjIndex;
-    }
-
-    public String getAlarmObjIndex() 
-    {
-        return alarmObjIndex;
-    }
-    public void setAlarmRuleType(Integer alarmRuleType) 
-    {
-        this.alarmRuleType = alarmRuleType;
-    }
-
-    public Integer getAlarmRuleType() 
-    {
-        return alarmRuleType;
-    }
-    public void setAlarmThresholdValue(Double alarmThresholdValue) 
-    {
-        this.alarmThresholdValue = alarmThresholdValue;
-    }
-
-    public Double getAlarmThresholdValue() 
-    {
-        return alarmThresholdValue;
-    }
-    public void setAlarmCode(String alarmCode) 
-    {
-        this.alarmCode = alarmCode;
-    }
-
-    public String getAlarmCode() 
-    {
-        return alarmCode;
-    }
-    public void setAlarmMsg(String alarmMsg) 
-    {
-        this.alarmMsg = alarmMsg;
-    }
-
-    public String getAlarmMsg() 
-    {
-        return alarmMsg;
-    }
-    public void setAlarmType(Integer alarmType) 
-    {
-        this.alarmType = alarmType;
-    }
-
-    public Integer getAlarmType() 
-    {
-        return alarmType;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
-            .append("id", getId())
-            .append("policyCode", getPolicyCode())
-            .append("policyName", getPolicyName())
-            .append("alarmObjType", getAlarmObjType())
-            .append("alarmObjIndex", getAlarmObjIndex())
-            .append("alarmRuleType", getAlarmRuleType())
-            .append("alarmThresholdValue", getAlarmThresholdValue())
-            .append("alarmCode", getAlarmCode())
-            .append("alarmMsg", getAlarmMsg())
-            .append("alarmType", getAlarmType())
-            .toString();
-    }
-}

+ 0 - 64
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AdmEmsIndexRangeMapper.java

@@ -1,64 +0,0 @@
-package com.ruoyi.ems.mapper;
-
-import com.ruoyi.ems.domain.AdmEmsIndexRange;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * 能源指标范围Mapper接口
- *
- * @author ruoyi
- * @date 2024-08-30
- */
-public interface AdmEmsIndexRangeMapper {
-    /**
-     * 查询能源指标范围
-     *
-     * @param id 能源指标范围主键
-     * @return 能源指标范围
-     */
-    AdmEmsIndexRange selectAdmEmsIndexRangeById(Long id);
-
-    /**
-     * 查询能源指标范围列表
-     *
-     * @param indexRange 能源指标范围
-     * @return 能源指标范围集合
-     */
-    List<AdmEmsIndexRange> selectAdmEmsIndexRangeList(AdmEmsIndexRange indexRange);
-
-    /**
-     * 新增能源指标范围
-     *
-     * @param indexRange 能源指标范围
-     * @return 结果
-     */
-    int insertAdmEmsIndexRange(AdmEmsIndexRange indexRange);
-
-    /**
-     * 修改能源指标范围
-     *
-     * @param indexRange 能源指标范围
-     * @return 结果
-     */
-    int updateAdmEmsIndexRange(AdmEmsIndexRange indexRange);
-
-    /**
-     * 删除能源指标范围
-     *
-     * @param id 能源指标范围主键
-     * @return 结果
-     */
-    int deleteAdmEmsIndexRangeById(Long id);
-
-    /**
-     * 批量删除能源指标范围
-     *
-     * @param ids 需要删除的数据主键集合
-     * @return 结果
-     */
-    int deleteAdmEmsIndexRangeByIds(Long[] ids);
-
-    List<Map<String, Object>> selectAdmEmsIndexRange();
-}

+ 0 - 61
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AdmOpAlarmPolicyMapper.java

@@ -1,61 +0,0 @@
-package com.ruoyi.ems.mapper;
-
-import com.ruoyi.ems.domain.OpAlarmPolicy;
-
-import java.util.List;
-
-/**
- * 能源设施告警策略Mapper接口
- *
- * @author ruoyi
- * @date 2024-08-26
- */
-public interface AdmOpAlarmPolicyMapper {
-    /**
-     * 查询能源设施告警策略
-     *
-     * @param id 能源设施告警策略主键
-     * @return 能源设施告警策略
-     */
-    OpAlarmPolicy selectAdmOpAlarmPolicyById(Long id);
-
-    /**
-     * 查询能源设施告警策略列表
-     *
-     * @param opAlarmPolicy 能源设施告警策略
-     * @return 能源设施告警策略集合
-     */
-    List<OpAlarmPolicy> selectAdmOpAlarmPolicyList(OpAlarmPolicy opAlarmPolicy);
-
-    /**
-     * 新增能源设施告警策略
-     *
-     * @param opAlarmPolicy 能源设施告警策略
-     * @return 结果
-     */
-    int insertAdmOpAlarmPolicy(OpAlarmPolicy opAlarmPolicy);
-
-    /**
-     * 修改能源设施告警策略
-     *
-     * @param opAlarmPolicy 能源设施告警策略
-     * @return 结果
-     */
-    int updateAdmOpAlarmPolicy(OpAlarmPolicy opAlarmPolicy);
-
-    /**
-     * 删除能源设施告警策略
-     *
-     * @param id 能源设施告警策略主键
-     * @return 结果
-     */
-    int deleteAdmOpAlarmPolicyById(Long id);
-
-    /**
-     * 批量删除能源设施告警策略
-     *
-     * @param ids 需要删除的数据主键集合
-     * @return 结果
-     */
-    int deleteAdmOpAlarmPolicyByIds(Long[] ids);
-}

+ 32 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AlarmHandleMapper.java

@@ -0,0 +1,32 @@
+package com.ruoyi.ems.mapper;
+
+import com.ruoyi.ems.domain.AlarmHandle;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 告警处置记录Mapper接口
+ */
+public interface AlarmHandleMapper {
+
+    /**
+     * 根据ID查询
+     */
+    AlarmHandle selectById(Long id);
+
+    /**
+     * 根据告警ID查询处置记录列表
+     */
+    List<AlarmHandle> selectByAlarmId(@Param("alarmId") String alarmId);
+
+    /**
+     * 新增处置记录
+     */
+    int insert(AlarmHandle handle);
+
+    /**
+     * 批量删除
+     */
+    int deleteByAlarmId(@Param("alarmId") String alarmId);
+}

+ 83 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AlarmMapper.java

@@ -0,0 +1,83 @@
+package com.ruoyi.ems.mapper;
+
+import com.ruoyi.ems.domain.Alarm;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 告警记录Mapper接口
+ */
+public interface AlarmMapper {
+
+    /**
+     * 根据ID查询
+     */
+    Alarm selectById(Long id);
+
+    /**
+     * 根据告警ID查询
+     */
+    Alarm selectByAlarmId(String alarmId);
+
+    /**
+     * 查询告警列表
+     */
+    List<Alarm> selectList(Alarm alarm);
+
+    /**
+     * 查询活动告警列表
+     */
+    List<Alarm> selectActiveAlarms(@Param("areaCode") String areaCode);
+
+    /**
+     * 查询指定规则和目标的活动告警(用于聚合)
+     */
+    Alarm selectActiveAlarm(@Param("ruleCode") String ruleCode, @Param("targetCode") String targetCode);
+
+    /**
+     * 新增告警
+     */
+    int insert(Alarm alarm);
+
+    /**
+     * 更新告警
+     */
+    int update(Alarm alarm);
+
+    /**
+     * 批量删除
+     */
+    int deleteByIds(Long[] ids);
+
+    /**
+     * 按告警级别统计
+     */
+    List<Map<String, Object>> countByLevel(Alarm query);
+
+    /**
+     * 按告警状态统计
+     */
+    List<Map<String, Object>> countByStatus(Alarm query);
+
+    /**
+     * 按时间趋势统计(日维度,按小时)
+     */
+    List<Map<String, Object>> countByDay(Alarm query);
+
+    /**
+     * 按时间趋势统计(月维度,按天)
+     */
+    List<Map<String, Object>> countByMonth(Alarm query);
+
+    /**
+     * 按子系统统计
+     */
+    List<Map<String, Object>> countBySubsystem(Alarm query);
+
+    /**
+     * 统计处理率
+     */
+    Map<String, Object> countHandleRate(Alarm query);
+}

+ 67 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AlarmRuleMapper.java

@@ -0,0 +1,67 @@
+package com.ruoyi.ems.mapper;
+
+import com.ruoyi.ems.domain.AlarmRule;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 告警规则Mapper接口
+ */
+public interface AlarmRuleMapper {
+
+    /**
+     * 根据ID查询
+     */
+    AlarmRule selectById(Long id);
+
+    /**
+     * 根据规则代码查询
+     */
+    AlarmRule selectByRuleCode(String ruleCode);
+
+    /**
+     * 查询规则列表
+     */
+    List<AlarmRule> selectList(AlarmRule query);
+
+    /**
+     * 查询适用于实时监测的规则
+     */
+    List<AlarmRule> selectForRealtime(AlarmRule query);
+
+    /**
+     * 查询适用于巡检的规则
+     */
+    List<AlarmRule> selectForInspection(AlarmRule query);
+
+    /**
+     * 根据分组代码查询规则
+     */
+    List<AlarmRule> selectByGroupCode(@Param("groupCode") String groupCode);
+
+    /**
+     * 新增规则
+     */
+    int insert(AlarmRule rule);
+
+    /**
+     * 更新规则
+     */
+    int update(AlarmRule rule);
+
+    /**
+     * 更新启用状态
+     */
+    int updateEnabled(@Param("ruleCode") String ruleCode, @Param("enabled") Integer enabled);
+
+    /**
+     * 批量更新启用状态
+     */
+    int batchUpdateEnabled(@Param("ruleCodes") List<String> ruleCodes, @Param("enabled") Integer enabled);
+
+    /**
+     * 批量删除
+     */
+    int deleteByIds(Long[] ids);
+}

+ 0 - 89
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/OpAlarmMapper.java

@@ -1,89 +0,0 @@
-package com.ruoyi.ems.mapper;
-
-import java.util.List;
-import java.util.Map;
-
-import org.apache.ibatis.annotations.Param;
-
-import com.ruoyi.ems.domain.OpAlarm;
-
-/**
- * 能源设施告警Mapper接口
- *
- * @author ruoyi
- * @date 2024-08-26
- */
-public interface OpAlarmMapper {
-    /**
-     * 查询能源设施告警
-     *
-     * @param id 能源设施告警主键
-     * @return 能源设施告警
-     */
-    OpAlarm selectAdmOpAlarmById(Long id);
-
-    /**
-     * 查询能源设施告警列表
-     *
-     * @param admOpAlarm 能源设施告警
-     * @return 能源设施告警集合
-     */
-    List<OpAlarm> selectAdmOpAlarmList(OpAlarm admOpAlarm);
-
-    /**
-     * 新增能源设施告警
-     *
-     * @param admOpAlarm 能源设施告警
-     * @return 结果
-     */
-    int insertAdmOpAlarm(OpAlarm admOpAlarm);
-
-    /**
-     * 修改能源设施告警
-     *
-     * @param admOpAlarm 能源设施告警
-     * @return 结果
-     */
-    int updateAdmOpAlarm(OpAlarm admOpAlarm);
-
-    /**
-     * 删除能源设施告警
-     *
-     * @param id 能源设施告警主键
-     * @return 结果
-     */
-    int deleteAdmOpAlarmById(Long id);
-
-    /**
-     * 批量删除能源设施告警
-     *
-     * @param ids 需要删除的数据主键集合
-     * @return 结果
-     */
-    int deleteAdmOpAlarmByIds(Long[] ids);
-
-    List<Map<String, Object>> qryAlarmTypeIndex(@Param("areaCode") String areaCode,
-        @Param("startTime") String startTime, @Param("endTime") String endTime);
-
-    List<Map<String, Object>> qryAlarmTypeIndexByDate(OpAlarm admOpAlarm);
-    List<Map<String, Object>> qryAlarmTypeIndexDay(@Param("alarmDate") String alarmDate,
-        @Param("areaCode") String areaCode);
-
-    List<Map<String, Object>> qryAlarmTypeIndexMonth(@Param("alarmDate") String alarmDate,
-        @Param("areaCode") String areaCode);
-
-    List<Map<String, Object>> qryAlarmTypeIndexYear(@Param("alarmDate") String alarmDate,
-        @Param("areaCode") String areaCode);
-
-    List<Map<String, Object>> qrySubSysIndexDay(@Param("alarmDate") String alarmDate,
-        @Param("areaCode") String areaCode);
-
-    List<Map<String, Object>> qrySubSysIndexMonth(@Param("alarmDate") String alarmDate,
-        @Param("areaCode") String areaCode);
-
-    List<Map<String, Object>> qrySubSysIndexYear(@Param("alarmDate") String alarmDate,
-        @Param("areaCode") String areaCode);
-
-    Map<String, Object> cntHandledAlarmByDate(OpAlarm admOpAlarm);
-
-}

+ 0 - 61
ems/ems-core/src/main/java/com/ruoyi/ems/service/IAdmEmsIndexRangeService.java

@@ -1,61 +0,0 @@
-package com.ruoyi.ems.service;
-
-import com.ruoyi.ems.domain.AdmEmsIndexRange;
-
-import java.util.List;
-
-/**
- * 能源指标范围Service接口
- *
- * @author ruoyi
- * @date 2024-08-30
- */
-public interface IAdmEmsIndexRangeService {
-    /**
-     * 查询能源指标范围
-     *
-     * @param id 能源指标范围主键
-     * @return 能源指标范围
-     */
-    AdmEmsIndexRange selectAdmEmsIndexRangeById(Long id);
-
-    /**
-     * 查询能源指标范围列表
-     *
-     * @param indexRange 能源指标范围
-     * @return 能源指标范围集合
-     */
-    List<AdmEmsIndexRange> selectAdmEmsIndexRangeList(AdmEmsIndexRange indexRange);
-
-    /**
-     * 新增能源指标范围
-     *
-     * @param indexRange 能源指标范围
-     * @return 结果
-     */
-    int insertAdmEmsIndexRange(AdmEmsIndexRange indexRange);
-
-    /**
-     * 修改能源指标范围
-     *
-     * @param indexRange 能源指标范围
-     * @return 结果
-     */
-    int updateAdmEmsIndexRange(AdmEmsIndexRange indexRange);
-
-    /**
-     * 批量删除能源指标范围
-     *
-     * @param ids 需要删除的能源指标范围主键集合
-     * @return 结果
-     */
-    int deleteAdmEmsIndexRangeByIds(Long[] ids);
-
-    /**
-     * 删除能源指标范围信息
-     *
-     * @param id 能源指标范围主键
-     * @return 结果
-     */
-    int deleteAdmEmsIndexRangeById(Long id);
-}

+ 0 - 61
ems/ems-core/src/main/java/com/ruoyi/ems/service/IAdmOpAlarmPolicyService.java

@@ -1,61 +0,0 @@
-package com.ruoyi.ems.service;
-
-import com.ruoyi.ems.domain.OpAlarmPolicy;
-
-import java.util.List;
-
-/**
- * 能源设施告警策略Service接口
- *
- * @author ruoyi
- * @date 2024-08-26
- */
-public interface IAdmOpAlarmPolicyService {
-    /**
-     * 查询能源设施告警策略
-     *
-     * @param id 能源设施告警策略主键
-     * @return 能源设施告警策略
-     */
-    OpAlarmPolicy selectAdmOpAlarmPolicyById(Long id);
-
-    /**
-     * 查询能源设施告警策略列表
-     *
-     * @param opAlarmPolicy 能源设施告警策略
-     * @return 能源设施告警策略集合
-     */
-    List<OpAlarmPolicy> selectAdmOpAlarmPolicyList(OpAlarmPolicy opAlarmPolicy);
-
-    /**
-     * 新增能源设施告警策略
-     *
-     * @param opAlarmPolicy 能源设施告警策略
-     * @return 结果
-     */
-    int insertAdmOpAlarmPolicy(OpAlarmPolicy opAlarmPolicy);
-
-    /**
-     * 修改能源设施告警策略
-     *
-     * @param opAlarmPolicy 能源设施告警策略
-     * @return 结果
-     */
-    int updateAdmOpAlarmPolicy(OpAlarmPolicy opAlarmPolicy);
-
-    /**
-     * 批量删除能源设施告警策略
-     *
-     * @param ids 需要删除的能源设施告警策略主键集合
-     * @return 结果
-     */
-    int deleteAdmOpAlarmPolicyByIds(Long[] ids);
-
-    /**
-     * 删除能源设施告警策略信息
-     *
-     * @param id 能源设施告警策略主键
-     * @return 结果
-     */
-    int deleteAdmOpAlarmPolicyById(Long id);
-}

+ 91 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/IAlarmRuleService.java

@@ -0,0 +1,91 @@
+/*
+ * 文 件 名:  IAlarmRuleService
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2026/2/3
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.ems.service;
+
+import com.ruoyi.ems.domain.AlarmRule;
+import java.util.List;
+
+/**
+ * 告警规则Service接口
+ */
+public interface IAlarmRuleService {
+
+    /**
+     * 根据ID查询规则
+     */
+    AlarmRule selectById(Long id);
+
+    /**
+     * 根据规则代码查询
+     */
+    AlarmRule selectByRuleCode(String ruleCode);
+
+    /**
+     * 查询规则列表
+     */
+    List<AlarmRule> selectList(AlarmRule query);
+
+    /**
+     * 新增规则
+     */
+    int insert(AlarmRule rule);
+
+    /**
+     * 修改规则
+     */
+    int update(AlarmRule rule);
+
+    /**
+     * 删除规则
+     */
+    int deleteByIds(Long[] ids);
+
+    /**
+     * 启用/禁用规则
+     */
+    int updateEnabled(String ruleCode, Integer enabled);
+
+    /**
+     * 查询适用于实时监测的规则
+     *
+     * @param deviceModel 设备模型(可选)
+     * @param attrKey 属性标识
+     * @return 规则列表
+     */
+    List<AlarmRule> selectRulesForRealtime(String deviceModel, String attrKey);
+
+    /**
+     * 查询适用于巡检的规则
+     *
+     * @param deviceModel 设备模型(可选)
+     * @return 规则列表
+     */
+    List<AlarmRule> selectRulesForInspection(String deviceModel);
+
+    /**
+     * 查询指定分组的规则
+     */
+    List<AlarmRule> selectByGroupCode(String groupCode);
+
+    /**
+     * 复制规则
+     *
+     * @param sourceRuleCode 源规则代码
+     * @param newRuleName 新规则名称
+     * @return 新规则
+     */
+    AlarmRule copyRule(String sourceRuleCode, String newRuleName);
+
+    /**
+     * 批量更新规则启用状态
+     */
+    int batchUpdateEnabled(List<String> ruleCodes, Integer enabled);
+}

+ 86 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/IAlarmService.java

@@ -0,0 +1,86 @@
+package com.ruoyi.ems.service;
+
+import com.ruoyi.ems.domain.Alarm;
+import com.ruoyi.ems.domain.AlarmHandle;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 告警Service接口
+ */
+public interface IAlarmService {
+
+    /**
+     * 根据ID查询告警
+     */
+    Alarm selectById(Long id);
+
+    /**
+     * 根据告警ID查询
+     */
+    Alarm selectByAlarmId(String alarmId);
+
+    /**
+     * 查询告警列表
+     */
+    List<Alarm> selectList(Alarm alarm);
+
+    /**
+     * 查询活动告警列表
+     */
+    List<Alarm> selectActiveAlarms(String areaCode);
+
+    /**
+     * 新增告警
+     */
+    int insert(Alarm alarm);
+
+    /**
+     * 更新告警
+     */
+    int update(Alarm alarm);
+
+    /**
+     * 批量删除告警
+     */
+    int deleteByIds(Long[] ids);
+
+    /**
+     * 查询告警的处置记录
+     */
+    List<AlarmHandle> selectHandlesByAlarmId(String alarmId);
+
+    // ==================== 统计接口 ====================
+
+    /**
+     * 告警统计概览
+     */
+    Map<String, Object> selectAlarmStats(Alarm query);
+
+    /**
+     * 按告警级别统计
+     */
+    List<Map<String, Object>> countByLevel(Alarm query);
+
+    /**
+     * 按告警状态统计
+     */
+    List<Map<String, Object>> countByStatus(Alarm query);
+
+    /**
+     * 按时间趋势统计
+     * @param granularity 粒度:day-日(按小时), month-月(按天)
+     */
+    List<Map<String, Object>> countByTimeTrend(Alarm query, String granularity);
+
+    /**
+     * 按子系统统计
+     */
+    List<Map<String, Object>> countBySubsystem(Alarm query);
+
+    /**
+     * 处理率统计
+     */
+    Map<String, Object> countHandleRate(Alarm query);
+}

+ 0 - 80
ems/ems-core/src/main/java/com/ruoyi/ems/service/IOpAlarmService.java

@@ -1,80 +0,0 @@
-package com.ruoyi.ems.service;
-
-import com.ruoyi.ems.domain.OpAlarm;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * 能源设施告警Service接口
- *
- * @author ruoyi
- * @date 2024-08-26
- */
-public interface IOpAlarmService {
-    /**
-     * 查询能源设施告警
-     *
-     * @param id 能源设施告警主键
-     * @return 能源设施告警
-     */
-    OpAlarm selectOpAlarmById(Long id);
-
-    /**
-     * 查询能源设施告警列表
-     *
-     * @param opAlarm 能源设施告警
-     * @return 能源设施告警集合
-     */
-    List<OpAlarm> selectOpAlarmList(OpAlarm opAlarm);
-
-    /**
-     * 新增能源设施告警
-     *
-     * @param opAlarm 能源设施告警
-     * @return 结果
-     */
-    int insertOpAlarm(OpAlarm opAlarm);
-
-    /**
-     * 修改能源设施告警
-     *
-     * @param opAlarm 能源设施告警
-     * @return 结果
-     */
-    int updateOpAlarm(OpAlarm opAlarm);
-
-    /**
-     * 批量删除能源设施告警
-     *
-     * @param ids 需要删除的能源设施告警主键集合
-     * @return 结果
-     */
-    int deleteOpAlarmByIds(Long[] ids);
-
-    /**
-     * 删除能源设施告警信息
-     *
-     * @param id 能源设施告警主键
-     * @return 结果
-     */
-    int deleteOpAlarmById(Long id);
-
-    List<Map<String, Object>> qryAlarmTypeIndex(String areaCode, String startTime, String endTime);
-
-    List<Map<String, Object>> qryAlarmTypeIndexByDate(OpAlarm params);
-
-    List<Map<String, Object>> qryAlarmTypeIndexDay(String areaCode);
-
-    List<Map<String, Object>> qryAlarmTypeIndexMonth(String areaCode);
-
-    List<Map<String, Object>> qryAlarmTypeIndexYear(String areaCode);
-
-    List<Map<String, Object>> qrySubSysIndexDay(String areaCode);
-
-    List<Map<String, Object>> qrySubSysIndexMonth(String areaCode);
-
-    List<Map<String, Object>> qrySubSysIndexYear(String areaCode);
-
-    Map<String, Object> cntHandledAlarmByDate(OpAlarm admOpAlarm);
-}

+ 464 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/alarm/AlarmProcessService.java

@@ -0,0 +1,464 @@
+/*
+ * 文 件 名:  AlarmProcessService
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2026/2/3
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.ems.service.alarm;
+
+/**
+ * <一句话功能简述>
+ * <功能详细描述>
+ *
+ * @author lvwenbin
+ * @version [版本号, 2026/2/3]
+ * @see [相关类/方法]
+ * @since [产品/模块版本]
+ */
+
+import com.ruoyi.ems.domain.Alarm;
+import com.ruoyi.ems.domain.AlarmHandle;
+import com.ruoyi.ems.domain.AlarmRule;
+import com.ruoyi.ems.domain.EmsDevice;
+import com.ruoyi.ems.mapper.AlarmMapper;
+import com.ruoyi.ems.mapper.AlarmHandleMapper;
+import com.ruoyi.ems.service.IAlarmRuleService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 告警处理服务
+ *
+ * 【核心功能】
+ * 1. 告警生成:根据规则引擎结果生成告警
+ * 2. 告警聚合:同一规则同一对象的重复告警进行聚合
+ * 3. 告警恢复:检测到恢复时自动更新状态
+ * 4. 告警冷却:防止频繁告警
+ * 5. 告警处置:确认、处置、关闭等操作
+ */
+@Slf4j
+@Service
+public class AlarmProcessService {
+
+    @Autowired
+    private AlarmRuleEngine ruleEngine;
+
+    @Autowired
+    private AlarmMapper alarmMapper;
+
+    @Autowired
+    private AlarmHandleMapper handleMapper;
+
+    @Autowired
+    private IAlarmRuleService ruleService;
+
+    /** 告警冷却缓存 Map<ruleCode_targetCode, lastAlarmTime> */
+    private final ConcurrentHashMap<String, Long> coolDownCache = new ConcurrentHashMap<>();
+
+    /** 连续触发计数 Map<ruleCode_targetCode, count> */
+    private final ConcurrentHashMap<String, Integer> triggerCountCache = new ConcurrentHashMap<>();
+
+    /**
+     * 处理设备属性变化(实时监测入口)
+     *
+     * @param device 设备信息
+     * @param attrKey 变化的属性
+     * @param attrValue 属性新值
+     * @param oldValue 属性旧值(可选)
+     * @return 生成的告警列表
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public List<Alarm> processAttrChange(EmsDevice device, String attrKey, String attrValue, String oldValue) {
+        List<Alarm> alarms = new ArrayList<>();
+
+        if (device == null || StringUtils.isBlank(attrKey)) {
+            return alarms;
+        }
+
+        // 查询适用的实时监测规则
+        List<AlarmRule> rules = ruleService.selectRulesForRealtime(device.getDeviceModel(), attrKey);
+        if (CollectionUtils.isEmpty(rules)) {
+            return alarms;
+        }
+
+        // 构建属性值Map
+        Map<String, String> attrValues = new HashMap<>();
+        attrValues.put(attrKey, attrValue);
+
+        // 执行规则检查
+        List<AlarmRuleEngine.CheckResult> results = ruleEngine.checkRules(rules, device, attrValues);
+
+        for (AlarmRuleEngine.CheckResult result : results) {
+            if (result.isTriggered()) {
+                Alarm alarm = processTriggered(result, device, Alarm.SOURCE_REALTIME, null);
+                if (alarm != null) {
+                    alarms.add(alarm);
+                }
+            } else if (result.isRecovered()) {
+                processRecovered(result, device);
+            }
+        }
+
+        return alarms;
+    }
+
+    /**
+     * 批量处理设备检查(巡检入口)
+     *
+     * @param device 设备信息
+     * @param attrValues 属性值Map
+     * @param sourceRef 来源引用(如巡检报告代码)
+     * @return 生成的告警列表
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public List<Alarm> processInspection(EmsDevice device, Map<String, String> attrValues, String sourceRef) {
+        List<Alarm> alarms = new ArrayList<>();
+
+        if (device == null || attrValues == null || attrValues.isEmpty()) {
+            return alarms;
+        }
+
+        // 查询适用的巡检规则
+        List<AlarmRule> rules = ruleService.selectRulesForInspection(device.getDeviceModel());
+        if (CollectionUtils.isEmpty(rules)) {
+            return alarms;
+        }
+
+        // 执行规则检查
+        List<AlarmRuleEngine.CheckResult> results = ruleEngine.checkRules(rules, device, attrValues);
+
+        for (AlarmRuleEngine.CheckResult result : results) {
+            if (result.isTriggered()) {
+                Alarm alarm = processTriggered(result, device, Alarm.SOURCE_INSPECTION, sourceRef);
+                if (alarm != null) {
+                    alarms.add(alarm);
+                }
+            } else if (result.isRecovered()) {
+                processRecovered(result, device);
+            }
+        }
+
+        return alarms;
+    }
+
+    /**
+     * 手动上报告警
+     *
+     * @param alarm 告警信息
+     * @param reporter 上报人
+     * @return 告警记录
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public Alarm reportAlarm(Alarm alarm, String reporter) {
+        if (alarm == null) {
+            throw new RuntimeException("告警信息不能为空");
+        }
+
+        // 设置基本信息
+        alarm.setAlarmId(generateAlarmId());
+        alarm.setAlarmSource(Alarm.SOURCE_MANUAL);
+        alarm.setAlarmTime(new Date());
+        alarm.setAlarmDate(new Date());
+        alarm.setAlarmStatus(Alarm.STATUS_ACTIVE);
+        alarm.setRepeatCount(1);
+        alarm.setLastOccurTime(new Date());
+        alarm.setIsNotified(0);
+        alarm.setCreateBy(reporter);
+
+        alarmMapper.insert(alarm);
+        log.info("手动上报告警成功: alarmId={}, target={}", alarm.getAlarmId(), alarm.getTargetCode());
+
+        return alarm;
+    }
+
+    /**
+     * 确认告警
+     *
+     * @param alarmId 告警ID
+     * @param operator 操作人
+     * @param remark 备注
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void confirmAlarm(String alarmId, String operator, String remark) {
+        Alarm alarm = alarmMapper.selectByAlarmId(alarmId);
+        if (alarm == null) {
+            throw new RuntimeException("告警不存在: " + alarmId);
+        }
+
+        if (alarm.getAlarmStatus() != Alarm.STATUS_ACTIVE) {
+            throw new RuntimeException("只能确认活动状态的告警");
+        }
+
+        // 更新状态
+        alarm.setAlarmStatus(Alarm.STATUS_CONFIRMED);
+        alarm.setConfirmTime(new Date());
+        alarm.setConfirmBy(operator);
+        alarm.setUpdateBy(operator);
+        alarmMapper.update(alarm);
+
+        // 记录处置
+        addHandle(alarmId, AlarmHandle.TYPE_CONFIRM, remark, null, operator);
+
+        log.info("告警已确认: alarmId={}, operator={}", alarmId, operator);
+    }
+
+    /**
+     * 处置告警
+     *
+     * @param alarmId 告警ID
+     * @param handleContent 处置内容
+     * @param handleResult 处置结果
+     * @param operator 操作人
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void handleAlarm(String alarmId, String handleContent, String handleResult, String operator) {
+        Alarm alarm = alarmMapper.selectByAlarmId(alarmId);
+        if (alarm == null) {
+            throw new RuntimeException("告警不存在: " + alarmId);
+        }
+
+        if (alarm.isEnded()) {
+            throw new RuntimeException("告警已结束,无法处置");
+        }
+
+        // 更新状态
+        alarm.setAlarmStatus(Alarm.STATUS_HANDLING);
+        alarm.setUpdateBy(operator);
+        alarmMapper.update(alarm);
+
+        // 记录处置
+        addHandle(alarmId, AlarmHandle.TYPE_HANDLE, handleContent, handleResult, operator);
+
+        log.info("告警处置中: alarmId={}, operator={}", alarmId, operator);
+    }
+
+    /**
+     * 解决告警
+     *
+     * @param alarmId 告警ID
+     * @param resolveRemark 解决备注
+     * @param operator 操作人
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void resolveAlarm(String alarmId, String resolveRemark, String operator) {
+        Alarm alarm = alarmMapper.selectByAlarmId(alarmId);
+        if (alarm == null) {
+            throw new RuntimeException("告警不存在: " + alarmId);
+        }
+
+        if (alarm.isEnded()) {
+            throw new RuntimeException("告警已结束");
+        }
+
+        Date now = new Date();
+
+        // 更新状态
+        alarm.setAlarmStatus(Alarm.STATUS_RESOLVED);
+        alarm.setResolveTime(now);
+        alarm.setResolveBy(operator);
+        alarm.setResolveRemark(resolveRemark);
+        alarm.setDuration(alarm.calculateDuration());
+        alarm.setUpdateBy(operator);
+        alarmMapper.update(alarm);
+
+        // 记录处置
+        addHandle(alarmId, AlarmHandle.TYPE_CLOSE, resolveRemark, "已解决", operator);
+
+        log.info("告警已解决: alarmId={}, operator={}, duration={}s", alarmId, operator, alarm.getDuration());
+    }
+
+    /**
+     * 关闭告警(无需处置)
+     *
+     * @param alarmId 告警ID
+     * @param reason 关闭原因
+     * @param operator 操作人
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void closeAlarm(String alarmId, String reason, String operator) {
+        Alarm alarm = alarmMapper.selectByAlarmId(alarmId);
+        if (alarm == null) {
+            throw new RuntimeException("告警不存在: " + alarmId);
+        }
+
+        if (alarm.isEnded()) {
+            throw new RuntimeException("告警已结束");
+        }
+
+        Date now = new Date();
+
+        // 更新状态
+        alarm.setAlarmStatus(Alarm.STATUS_CLOSED);
+        alarm.setResolveTime(now);
+        alarm.setResolveBy(operator);
+        alarm.setResolveRemark(reason);
+        alarm.setDuration(alarm.calculateDuration());
+        alarm.setUpdateBy(operator);
+        alarmMapper.update(alarm);
+
+        // 记录处置
+        addHandle(alarmId, AlarmHandle.TYPE_CLOSE, reason, "已关闭", operator);
+
+        log.info("告警已关闭: alarmId={}, operator={}, reason={}", alarmId, operator, reason);
+    }
+
+    /**
+     * 批量确认告警
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public int batchConfirm(List<String> alarmIds, String operator) {
+        int count = 0;
+        for (String alarmId : alarmIds) {
+            try {
+                confirmAlarm(alarmId, operator, "批量确认");
+                count++;
+            } catch (Exception e) {
+                log.warn("批量确认告警失败: alarmId={}, error={}", alarmId, e.getMessage());
+            }
+        }
+        return count;
+    }
+
+    // ==================== 私有方法 ====================
+
+    /**
+     * 处理触发的告警
+     */
+    private Alarm processTriggered(AlarmRuleEngine.CheckResult result, EmsDevice device, int source, String sourceRef) {
+        AlarmRule rule = result.getRule();
+        if (rule == null) {
+            return null;
+        }
+
+        String cacheKey = rule.getRuleCode() + "_" + device.getDeviceCode();
+
+        // 检查冷却时间
+        if (!checkCoolDown(cacheKey, rule.getCoolDown())) {
+            log.debug("告警处于冷却期,跳过: rule={}, device={}", rule.getRuleCode(), device.getDeviceCode());
+            return null;
+        }
+
+        // 检查连续触发次数
+        if (!checkTriggerCount(cacheKey, rule.getTriggerCount())) {
+            log.debug("连续触发次数未达到阈值: rule={}, device={}", rule.getRuleCode(), device.getDeviceCode());
+            return null;
+        }
+
+        // 查找是否存在活动告警(用于聚合)
+        Alarm existingAlarm = alarmMapper.selectActiveAlarm(rule.getRuleCode(), device.getDeviceCode());
+
+        if (existingAlarm != null) {
+            // 聚合:更新重复次数和最后发生时间
+            existingAlarm.setRepeatCount(existingAlarm.getRepeatCount() + 1);
+            existingAlarm.setLastOccurTime(new Date());
+            existingAlarm.setAttrValue(result.getAttrValue());
+            alarmMapper.update(existingAlarm);
+
+            log.debug("告警聚合: alarmId={}, repeatCount={}", existingAlarm.getAlarmId(), existingAlarm.getRepeatCount());
+            return existingAlarm;
+        }
+
+        // 创建新告警
+        Alarm alarm = ruleEngine.createAlarm(result, device, source, sourceRef);
+        if (alarm != null) {
+            alarmMapper.insert(alarm);
+
+            // 更新冷却缓存
+            coolDownCache.put(cacheKey, System.currentTimeMillis());
+            // 重置触发计数
+            triggerCountCache.remove(cacheKey);
+
+            log.info("生成告警: alarmId={}, rule={}, device={}, value={}",
+                alarm.getAlarmId(), rule.getRuleCode(), device.getDeviceCode(), result.getAttrValue());
+        }
+
+        return alarm;
+    }
+
+    /**
+     * 处理恢复
+     */
+    private void processRecovered(AlarmRuleEngine.CheckResult result, EmsDevice device) {
+        AlarmRule rule = result.getRule();
+        if (rule == null) {
+            return;
+        }
+
+        // 查找活动告警
+        Alarm activeAlarm = alarmMapper.selectActiveAlarm(rule.getRuleCode(), device.getDeviceCode());
+        if (activeAlarm == null) {
+            return;
+        }
+
+        // 更新为已恢复
+        Date now = new Date();
+        activeAlarm.setAlarmStatus(Alarm.STATUS_RECOVERED);
+        activeAlarm.setRecoveryTime(now);
+        activeAlarm.setDuration(activeAlarm.calculateDuration());
+        alarmMapper.update(activeAlarm);
+
+        log.info("告警自动恢复: alarmId={}, rule={}, device={}",
+            activeAlarm.getAlarmId(), rule.getRuleCode(), device.getDeviceCode());
+    }
+
+    /**
+     * 检查冷却时间
+     */
+    private boolean checkCoolDown(String cacheKey, Integer coolDown) {
+        if (coolDown == null || coolDown <= 0) {
+            return true;
+        }
+
+        Long lastTime = coolDownCache.get(cacheKey);
+        if (lastTime == null) {
+            return true;
+        }
+
+        long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
+        return elapsed >= coolDown;
+    }
+
+    /**
+     * 检查连续触发次数
+     */
+    private boolean checkTriggerCount(String cacheKey, Integer requiredCount) {
+        if (requiredCount == null || requiredCount <= 1) {
+            return true;
+        }
+
+        Integer currentCount = triggerCountCache.compute(cacheKey, (k, v) -> v == null ? 1 : v + 1);
+        return currentCount >= requiredCount;
+    }
+
+    /**
+     * 添加处置记录
+     */
+    private void addHandle(String alarmId, int handleType, String content, String result, String operator) {
+        AlarmHandle handle = new AlarmHandle();
+        handle.setAlarmId(alarmId);
+        handle.setHandleType(handleType);
+        handle.setHandleContent(content);
+        handle.setHandleResult(result);
+        handle.setHandleBy(operator);
+        handle.setHandleTime(new Date());
+        handleMapper.insert(handle);
+    }
+
+    /**
+     * 生成告警ID
+     */
+    private String generateAlarmId() {
+        return "ALM" + System.currentTimeMillis() + String.format("%04d", new Random().nextInt(10000));
+    }
+}

+ 446 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/alarm/AlarmRuleEngine.java

@@ -0,0 +1,446 @@
+/*
+ * 文 件 名:  AlarmRuleEngine
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2026/2/3
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.ems.service.alarm;
+
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.ems.domain.Alarm;
+import com.ruoyi.ems.domain.AlarmRule;
+import com.ruoyi.ems.domain.EmsDevice;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 告警规则引擎
+ *
+ * 【核心功能】
+ * 1. 规则匹配:判断设备属性值是否触发规则
+ * 2. 告警生成:根据规则生成告警记录
+ * 3. 恢复检测:检测告警是否已恢复
+ * 4. 消息渲染:支持模板变量替换
+ *
+ * 【设计说明】
+ * 此引擎可被多个场景复用:
+ * - 实时数据监测
+ * - 自动巡检
+ * - 手动触发检查
+ */
+@Slf4j
+@Component
+public class AlarmRuleEngine {
+
+    /** 模板变量正则 */
+    private static final Pattern TEMPLATE_VAR_PATTERN = Pattern.compile("\\$\\{(\\w+)}");
+
+    /**
+     * 检查结果
+     */
+    @Data
+    public static class CheckResult {
+        /** 是否触发告警 */
+        private boolean triggered;
+
+        /** 是否恢复 */
+        private boolean recovered;
+
+        /** 检查的属性值 */
+        private String attrValue;
+
+        /** 告警消息 */
+        private String message;
+
+        /** 触发的规则 */
+        private AlarmRule rule;
+
+        /** 检查时间 */
+        private Date checkTime;
+
+        public static CheckResult normal(String attrValue) {
+            CheckResult result = new CheckResult();
+            result.setTriggered(false);
+            result.setRecovered(false);
+            result.setAttrValue(attrValue);
+            result.setCheckTime(new Date());
+            return result;
+        }
+
+        public static CheckResult triggered(AlarmRule rule, String attrValue, String message) {
+            CheckResult result = new CheckResult();
+            result.setTriggered(true);
+            result.setRecovered(false);
+            result.setRule(rule);
+            result.setAttrValue(attrValue);
+            result.setMessage(message);
+            result.setCheckTime(new Date());
+            return result;
+        }
+
+        public static CheckResult recovered(AlarmRule rule, String attrValue) {
+            CheckResult result = new CheckResult();
+            result.setTriggered(false);
+            result.setRecovered(true);
+            result.setRule(rule);
+            result.setAttrValue(attrValue);
+            result.setCheckTime(new Date());
+            return result;
+        }
+    }
+
+    /**
+     * 检查单个规则
+     *
+     * @param rule 告警规则
+     * @param device 设备信息
+     * @param attrValue 属性当前值
+     * @return 检查结果
+     */
+    public CheckResult checkRule(AlarmRule rule, EmsDevice device, String attrValue) {
+        if (rule == null || rule.getCheckType() == null) {
+            return CheckResult.normal(attrValue);
+        }
+
+        // 规则未启用
+        if (rule.getEnabled() == null || rule.getEnabled() != 1) {
+            return CheckResult.normal(attrValue);
+        }
+
+        // 检查设备模型是否匹配
+        if (StringUtils.isNotBlank(rule.getDeviceModel())
+            && !StringUtils.equals(rule.getDeviceModel(), device.getDeviceModel())) {
+            return CheckResult.normal(attrValue);
+        }
+
+        // 检查目标范围
+        if (!isTargetInScope(rule, device)) {
+            return CheckResult.normal(attrValue);
+        }
+
+        boolean triggered = false;
+        String message = null;
+
+        try {
+            switch (rule.getCheckType()) {
+                case AlarmRule.CHECK_TYPE_RANGE:
+                    triggered = checkRange(attrValue, rule.getThresholdMin(), rule.getThresholdMax(), rule.getOperator());
+                    break;
+                case AlarmRule.CHECK_TYPE_EQUAL:
+                    triggered = checkEqual(attrValue, rule.getThresholdValue(), rule.getOperator());
+                    break;
+                case AlarmRule.CHECK_TYPE_NOT_NULL:
+                    triggered = checkNotNull(attrValue, rule.getOperator());
+                    break;
+                case AlarmRule.CHECK_TYPE_ONLINE:
+                    triggered = checkOnline(attrValue, rule.getThresholdValue());
+                    break;
+                case AlarmRule.CHECK_TYPE_CHANGE_RATE:
+                    // 变化率检查需要历史值,暂不实现
+                    triggered = false;
+                    break;
+                default:
+                    log.warn("未知的检查类型: {}", rule.getCheckType());
+            }
+        } catch (Exception e) {
+            log.error("规则检查异常: ruleCode={}, attrValue={}, error={}",
+                rule.getRuleCode(), attrValue, e.getMessage());
+            return CheckResult.normal(attrValue);
+        }
+
+        if (triggered) {
+            message = renderMessage(rule, device, attrValue);
+            return CheckResult.triggered(rule, attrValue, message);
+        }
+
+        // 检查是否恢复(如果规则启用了恢复检查)
+        if (rule.getRecoveryCheck() != null && rule.getRecoveryCheck() == 1) {
+            boolean recovered = checkRecovery(rule, attrValue);
+            if (recovered) {
+                return CheckResult.recovered(rule, attrValue);
+            }
+        }
+
+        return CheckResult.normal(attrValue);
+    }
+
+    /**
+     * 批量检查规则
+     *
+     * @param rules 规则列表
+     * @param device 设备信息
+     * @param attrValues 属性值Map
+     * @return 触发的检查结果列表
+     */
+    public List<CheckResult> checkRules(List<AlarmRule> rules, EmsDevice device, Map<String, String> attrValues) {
+        List<CheckResult> results = new ArrayList<>();
+
+        if (rules == null || rules.isEmpty() || attrValues == null) {
+            return results;
+        }
+
+        // 按优先级排序
+        rules.sort(Comparator.comparingInt(r -> r.getRuleOrder() != null ? r.getRuleOrder() : 0));
+
+        for (AlarmRule rule : rules) {
+            String attrValue = attrValues.get(rule.getAttrKey());
+
+            // 特殊处理设备状态
+            if ("deviceStatus".equals(rule.getAttrKey()) && device.getDeviceStatus() != null) {
+                attrValue = device.getDeviceStatus().toString();
+            }
+
+            CheckResult result = checkRule(rule, device, attrValue);
+            if (result.isTriggered() || result.isRecovered()) {
+                results.add(result);
+            }
+        }
+
+        return results;
+    }
+
+    /**
+     * 根据检查结果生成告警对象
+     *
+     * @param result 检查结果
+     * @param device 设备信息
+     * @param source 告警来源
+     * @param sourceRef 来源引用
+     * @return 告警对象
+     */
+    public Alarm createAlarm(CheckResult result, EmsDevice device, int source, String sourceRef) {
+        if (!result.isTriggered() || result.getRule() == null) {
+            return null;
+        }
+
+        AlarmRule rule = result.getRule();
+        Date now = new Date();
+
+        Alarm alarm = new Alarm();
+        alarm.setAlarmId(generateAlarmId());
+        alarm.setAreaCode(device.getAreaCode());
+        alarm.setSubsystemCode(rule.getSubsystemCode());
+        alarm.setTargetType(rule.getTargetType() != null ? rule.getTargetType() : 2);
+        alarm.setTargetCode(device.getDeviceCode());
+        alarm.setTargetName(device.getDeviceName());
+        alarm.setDeviceModel(device.getDeviceModel());
+        alarm.setDeviceModelName(device.getDeviceModelName());
+        alarm.setLocation(device.getLocation());
+        alarm.setRuleCode(rule.getRuleCode());
+        alarm.setRuleName(rule.getRuleName());
+        alarm.setAttrKey(rule.getAttrKey());
+        alarm.setAttrName(rule.getAttrName());
+        alarm.setAttrValue(result.getAttrValue());
+        alarm.setThresholdValue(rule.getThresholdDisplay());
+        alarm.setAlarmLevel(rule.getAlarmLevel() != null ? rule.getAlarmLevel() : Alarm.LEVEL_NORMAL);
+        alarm.setAlarmCode(rule.getAlarmCode());
+        alarm.setAlarmMsg(result.getMessage());
+        alarm.setAlarmSource(source);
+        alarm.setSourceRef(sourceRef);
+        alarm.setAlarmTime(now);
+        alarm.setAlarmDate(now);
+        alarm.setAlarmStatus(Alarm.STATUS_ACTIVE);
+        alarm.setRepeatCount(1);
+        alarm.setLastOccurTime(now);
+        alarm.setIsNotified(0);
+
+        return alarm;
+    }
+
+    // ==================== 私有方法 ====================
+
+    /**
+     * 检查目标是否在规则范围内
+     */
+    private boolean isTargetInScope(AlarmRule rule, EmsDevice device) {
+        // 如果没有限定范围,则匹配所有
+        if (StringUtils.isBlank(rule.getTargetScope())) {
+            return true;
+        }
+
+        try {
+            List<String> scopeList = JSON.parseArray(rule.getTargetScope(), String.class);
+            if (scopeList == null || scopeList.isEmpty()) {
+                return true;
+            }
+
+            // 根据目标类型判断
+            Integer targetType = rule.getTargetType();
+            if (targetType == null) {
+                targetType = 2; // 默认设备
+            }
+
+            switch (targetType) {
+                case 0: // 区域
+                    return scopeList.contains(device.getAreaCode());
+                case 1: // 设施
+                    return scopeList.contains(device.getRefFacs());
+                case 2: // 设备
+                    return scopeList.contains(device.getDeviceCode());
+                default:
+                    return true;
+            }
+        } catch (Exception e) {
+            log.warn("解析目标范围失败: {}", rule.getTargetScope());
+            return true;
+        }
+    }
+
+    /**
+     * 范围检查
+     */
+    private boolean checkRange(String value, String min, String max, String operator) {
+        if (StringUtils.isBlank(value)) {
+            return false;
+        }
+
+        try {
+            BigDecimal val = new BigDecimal(value);
+            BigDecimal minVal = StringUtils.isNotBlank(min) ? new BigDecimal(min) : null;
+            BigDecimal maxVal = StringUtils.isNotBlank(max) ? new BigDecimal(max) : null;
+
+            // 根据操作符判断
+            if (AlarmRule.OP_LT.equals(operator) && maxVal != null) {
+                return val.compareTo(maxVal) < 0;
+            }
+            if (AlarmRule.OP_LE.equals(operator) && maxVal != null) {
+                return val.compareTo(maxVal) <= 0;
+            }
+            if (AlarmRule.OP_GT.equals(operator) && minVal != null) {
+                return val.compareTo(minVal) > 0;
+            }
+            if (AlarmRule.OP_GE.equals(operator) && minVal != null) {
+                return val.compareTo(minVal) >= 0;
+            }
+
+            // 默认范围检查:超出范围则触发
+            boolean outOfRange = false;
+            if (minVal != null && val.compareTo(minVal) < 0) {
+                outOfRange = true;
+            }
+            if (maxVal != null && val.compareTo(maxVal) > 0) {
+                outOfRange = true;
+            }
+            return outOfRange;
+
+        } catch (NumberFormatException e) {
+            log.warn("数值格式错误: value={}", value);
+            return false;
+        }
+    }
+
+    /**
+     * 等值检查
+     */
+    private boolean checkEqual(String value, String expected, String operator) {
+        if (AlarmRule.OP_NE.equals(operator)) {
+            return !StringUtils.equals(value, expected);
+        }
+        // 默认等值检查
+        return StringUtils.equals(value, expected);
+    }
+
+    /**
+     * 非空检查
+     */
+    private boolean checkNotNull(String value, String operator) {
+        boolean isNull = StringUtils.isBlank(value);
+        if (AlarmRule.OP_NE.equals(operator)) {
+            // 期望非空,值为空则触发
+            return isNull;
+        }
+        // 默认:值为空则触发
+        return isNull;
+    }
+
+    /**
+     * 在线状态检查
+     */
+    private boolean checkOnline(String value, String expected) {
+        String onlineValue = StringUtils.isNotBlank(expected) ? expected : "1";
+        // 不等于在线值则触发告警
+        return !StringUtils.equals(value, onlineValue);
+    }
+
+    /**
+     * 检查是否恢复
+     */
+    private boolean checkRecovery(AlarmRule rule, String value) {
+        if (StringUtils.isBlank(value)) {
+            return false;
+        }
+
+        // 如果有单独的恢复阈值
+        String recoveryThreshold = rule.getRecoveryThreshold();
+        if (StringUtils.isNotBlank(recoveryThreshold)) {
+            return StringUtils.equals(value, recoveryThreshold);
+        }
+
+        // 默认:检查是否回到正常范围
+        switch (rule.getCheckType()) {
+            case AlarmRule.CHECK_TYPE_RANGE:
+                return !checkRange(value, rule.getThresholdMin(), rule.getThresholdMax(), rule.getOperator());
+            case AlarmRule.CHECK_TYPE_EQUAL:
+                return !checkEqual(value, rule.getThresholdValue(), rule.getOperator());
+            case AlarmRule.CHECK_TYPE_ONLINE:
+                return !checkOnline(value, rule.getThresholdValue());
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * 渲染告警消息
+     */
+    private String renderMessage(AlarmRule rule, EmsDevice device, String attrValue) {
+        String template = rule.getAlarmMsgTemplate();
+        if (StringUtils.isBlank(template)) {
+            // 默认模板
+            template = "设备【${targetName}】${attrName}异常,当前值:${attrValue},阈值:${threshold}";
+        }
+
+        // 构建变量Map
+        Map<String, String> vars = new HashMap<>();
+        vars.put("targetCode", device.getDeviceCode());
+        vars.put("targetName", device.getDeviceName() != null ? device.getDeviceName() : device.getDeviceCode());
+        vars.put("deviceModel", device.getDeviceModel() != null ? device.getDeviceModel() : "");
+        vars.put("location", device.getLocation() != null ? device.getLocation() : "");
+        vars.put("areaCode", device.getAreaCode() != null ? device.getAreaCode() : "");
+        vars.put("attrKey", rule.getAttrKey() != null ? rule.getAttrKey() : "");
+        vars.put("attrName", rule.getAttrName() != null ? rule.getAttrName() : rule.getAttrKey());
+        vars.put("attrValue", attrValue != null ? attrValue : "");
+        vars.put("threshold", rule.getThresholdDisplay());
+        vars.put("ruleName", rule.getRuleName() != null ? rule.getRuleName() : "");
+        vars.put("ruleCode", rule.getRuleCode() != null ? rule.getRuleCode() : "");
+
+        // 替换变量
+        Matcher matcher = TEMPLATE_VAR_PATTERN.matcher(template);
+        StringBuffer sb = new StringBuffer();
+        while (matcher.find()) {
+            String varName = matcher.group(1);
+            String varValue = vars.getOrDefault(varName, "");
+            matcher.appendReplacement(sb, Matcher.quoteReplacement(varValue));
+        }
+        matcher.appendTail(sb);
+
+        return sb.toString();
+    }
+
+    /**
+     * 生成告警ID
+     */
+    private String generateAlarmId() {
+        return "ALM" + System.currentTimeMillis() + String.format("%04d", new Random().nextInt(10000));
+    }
+}

+ 208 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/alarm/InspectionAlarmAdapter.java

@@ -0,0 +1,208 @@
+/*
+ * 文 件 名:  InspectionAlarmAdapter
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2026/2/3
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.ems.service.alarm;
+
+import com.ruoyi.ems.domain.Alarm;
+import com.ruoyi.ems.domain.AlarmRule;
+import com.ruoyi.ems.domain.CheckItemResult;
+import com.ruoyi.ems.domain.EmsDevice;
+import com.ruoyi.ems.domain.EmsObjAttrValue;
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.domain.InspectionReportDetail;
+import com.ruoyi.ems.domain.InspectionRule;
+import com.ruoyi.ems.service.IEmsObjAttrValueService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 巡检与告警集成适配器
+ *
+ * 【功能说明】
+ * 将自动巡检的检查结果转换为告警
+ * 实现巡检和告警的联动
+ *
+ * 【使用方式】
+ * 在InspectionPlanServiceImpl中调用此适配器
+ */
+@Slf4j
+@Component
+public class InspectionAlarmAdapter {
+
+    @Autowired
+    private AlarmProcessService alarmProcessService;
+
+    @Autowired
+    private IEmsObjAttrValueService attrValueService;
+
+    /**
+     * 处理巡检结果,生成告警
+     *
+     * @param report 巡检报告
+     * @param details 巡检明细列表
+     * @return 生成的告警列表
+     */
+    public List<Alarm> processInspectionResult(InspectionReport report, List<InspectionReportDetail> details) {
+        List<Alarm> alarms = new ArrayList<>();
+
+        if (report == null || CollectionUtils.isEmpty(details)) {
+            return alarms;
+        }
+
+        log.info("开始处理巡检结果生成告警: reportCode={}, detailCount={}",
+            report.getReportCode(), details.size());
+
+        for (InspectionReportDetail detail : details) {
+            // 只处理异常的设备
+            if (detail.getResultStatus() == null || detail.getResultStatus() == 0) {
+                continue;
+            }
+
+            try {
+                List<Alarm> deviceAlarms = processDeviceDetail(detail, report.getReportCode());
+                alarms.addAll(deviceAlarms);
+            } catch (Exception e) {
+                log.error("处理设备巡检结果异常: deviceCode={}, error={}",
+                    detail.getDeviceCode(), e.getMessage());
+            }
+        }
+
+        log.info("巡检结果告警生成完成: reportCode={}, alarmCount={}",
+            report.getReportCode(), alarms.size());
+
+        return alarms;
+    }
+
+    /**
+     * 处理单个设备的巡检明细
+     */
+    private List<Alarm> processDeviceDetail(InspectionReportDetail detail, String reportCode) {
+        List<Alarm> alarms = new ArrayList<>();
+
+        // 构建设备信息
+        EmsDevice device = new EmsDevice();
+        device.setDeviceCode(detail.getDeviceCode());
+        device.setDeviceName(detail.getDeviceName());
+        device.setDeviceModel(detail.getDeviceModel());
+        device.setDeviceModelName(detail.getDeviceModelName());
+        device.setLocation(detail.getLocation());
+        // 从areaPath解析areaCode
+        if (detail.getAreaPath() != null && detail.getAreaPath().contains(",")) {
+            String[] paths = detail.getAreaPath().split(",");
+            if (paths.length > 0) {
+                device.setAreaCode(paths[paths.length - 1]);
+            }
+        }
+
+        // 获取设备属性值
+        Map<String, String> attrValues = getDeviceAttrValues(detail);
+
+        // 调用告警处理服务
+        List<Alarm> generatedAlarms = alarmProcessService.processInspection(device, attrValues, reportCode);
+        alarms.addAll(generatedAlarms);
+
+        return alarms;
+    }
+
+    /**
+     * 从巡检明细中提取属性值
+     */
+    private Map<String, String> getDeviceAttrValues(InspectionReportDetail detail) {
+        Map<String, String> attrValues = new HashMap<>();
+
+        // 从checkItems中提取
+        List<CheckItemResult> checkItems = detail.getCheckItemList();
+        if (CollectionUtils.isNotEmpty(checkItems)) {
+            for (CheckItemResult item : checkItems) {
+                if (item.getAttrKey() != null && item.getActualValue() != null) {
+                    attrValues.put(item.getAttrKey(), item.getActualValue());
+                }
+            }
+        }
+
+        // 如果checkItems为空,从数据库获取
+        if (attrValues.isEmpty() && detail.getDeviceModel() != null) {
+            try {
+                List<EmsObjAttrValue> dbValues = attrValueService.selectByObjCode(
+                    detail.getDeviceModel(), detail.getDeviceCode());
+                if (CollectionUtils.isNotEmpty(dbValues)) {
+                    attrValues = dbValues.stream()
+                        .filter(v -> v.getAttrKey() != null && v.getAttrValue() != null)
+                        .collect(Collectors.toMap(
+                            EmsObjAttrValue::getAttrKey,
+                            EmsObjAttrValue::getAttrValue,
+                            (v1, v2) -> v2));
+                }
+            } catch (Exception e) {
+                log.warn("获取设备属性值失败: deviceCode={}", detail.getDeviceCode());
+            }
+        }
+
+        return attrValues;
+    }
+
+    /**
+     * 将巡检规则转换为告警规则格式
+     *
+     * 【说明】
+     * 此方法用于复用巡检规则配置
+     * 当巡检规则和告警规则需要统一时使用
+     *
+     * @param inspectionRule 巡检规则
+     * @return 告警规则
+     */
+    public AlarmRule convertToAlarmRule(InspectionRule inspectionRule) {
+        if (inspectionRule == null) {
+            return null;
+        }
+
+        AlarmRule alarmRule = new AlarmRule();
+        alarmRule.setRuleCode("IR_" + inspectionRule.getRuleCode());
+        alarmRule.setRuleName(inspectionRule.getRuleName());
+        alarmRule.setDeviceModel(inspectionRule.getDeviceModel());
+        alarmRule.setAttrKey(inspectionRule.getAttrKey());
+        alarmRule.setAttrName(inspectionRule.getAttrName());
+        alarmRule.setCheckType(inspectionRule.getCheckType());
+        alarmRule.setThresholdMin(inspectionRule.getMinValue());
+        alarmRule.setThresholdMax(inspectionRule.getMaxValue());
+        alarmRule.setThresholdValue(inspectionRule.getExpectValue());
+        alarmRule.setAlarmLevel(AlarmRule.LEVEL_NORMAL); // 默认一般级别
+        alarmRule.setEnabled(inspectionRule.getEnabled());
+        alarmRule.setUseForInspection(1);
+        alarmRule.setUseForRealtime(0); // 巡检规则默认不用于实时监测
+
+        // 根据检查类型设置操作符
+        if (inspectionRule.getCheckType() != null) {
+            switch (inspectionRule.getCheckType()) {
+                case 1: // 范围
+                    alarmRule.setOperator(AlarmRule.OP_RANGE);
+                    break;
+                case 2: // 等值
+                    alarmRule.setOperator(AlarmRule.OP_EQ);
+                    break;
+                case 4: // 在线状态
+                    alarmRule.setOperator(AlarmRule.OP_EQ);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        return alarmRule;
+    }
+}

+ 0 - 87
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/AdmEmsIndexRangeServiceImpl.java

@@ -1,87 +0,0 @@
-package com.ruoyi.ems.service.impl;
-
-import com.ruoyi.ems.domain.AdmEmsIndexRange;
-import com.ruoyi.ems.mapper.AdmEmsIndexRangeMapper;
-import com.ruoyi.ems.service.IAdmEmsIndexRangeService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.util.List;
-
-/**
- * 能源指标范围Service业务层处理
- *
- * @author ruoyi
- * @date 2024-08-30
- */
-@Service
-public class AdmEmsIndexRangeServiceImpl implements IAdmEmsIndexRangeService {
-    @Autowired
-    private AdmEmsIndexRangeMapper indexRangeMapper;
-
-    /**
-     * 查询能源指标范围
-     *
-     * @param id 能源指标范围主键
-     * @return 能源指标范围
-     */
-    @Override
-    public AdmEmsIndexRange selectAdmEmsIndexRangeById(Long id) {
-        return indexRangeMapper.selectAdmEmsIndexRangeById(id);
-    }
-
-    /**
-     * 查询能源指标范围列表
-     *
-     * @param indexRange 能源指标范围
-     * @return 能源指标范围
-     */
-    @Override
-    public List<AdmEmsIndexRange> selectAdmEmsIndexRangeList(AdmEmsIndexRange indexRange) {
-        return indexRangeMapper.selectAdmEmsIndexRangeList(indexRange);
-    }
-
-    /**
-     * 新增能源指标范围
-     *
-     * @param indexRange 能源指标范围
-     * @return 结果
-     */
-    @Override
-    public int insertAdmEmsIndexRange(AdmEmsIndexRange indexRange) {
-        return indexRangeMapper.insertAdmEmsIndexRange(indexRange);
-    }
-
-    /**
-     * 修改能源指标范围
-     *
-     * @param indexRange 能源指标范围
-     * @return 结果
-     */
-    @Override
-    public int updateAdmEmsIndexRange(AdmEmsIndexRange indexRange) {
-        return indexRangeMapper.updateAdmEmsIndexRange(indexRange);
-    }
-
-    /**
-     * 批量删除能源指标范围
-     *
-     * @param ids 需要删除的能源指标范围主键
-     * @return 结果
-     */
-    @Override
-    public int deleteAdmEmsIndexRangeByIds(Long[] ids) {
-        return indexRangeMapper.deleteAdmEmsIndexRangeByIds(ids);
-    }
-
-    /**
-     * 删除能源指标范围信息
-     *
-     * @param id 能源指标范围主键
-     * @return 结果
-     */
-    @Override
-    public int deleteAdmEmsIndexRangeById(Long id) {
-        return indexRangeMapper.deleteAdmEmsIndexRangeById(id);
-    }
-}

+ 0 - 87
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/AdmOpAlarmPolicyServiceImpl.java

@@ -1,87 +0,0 @@
-package com.ruoyi.ems.service.impl;
-
-import com.ruoyi.ems.domain.OpAlarmPolicy;
-import com.ruoyi.ems.mapper.AdmOpAlarmPolicyMapper;
-import com.ruoyi.ems.service.IAdmOpAlarmPolicyService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.util.List;
-
-/**
- * 能源设施告警策略Service业务层处理
- *
- * @author ruoyi
- * @date 2024-08-26
- */
-@Service
-public class AdmOpAlarmPolicyServiceImpl implements IAdmOpAlarmPolicyService {
-    @Autowired
-    private AdmOpAlarmPolicyMapper admOpAlarmPolicyMapper;
-
-    /**
-     * 查询能源设施告警策略
-     *
-     * @param id 能源设施告警策略主键
-     * @return 能源设施告警策略
-     */
-    @Override
-    public OpAlarmPolicy selectAdmOpAlarmPolicyById(Long id) {
-        return admOpAlarmPolicyMapper.selectAdmOpAlarmPolicyById(id);
-    }
-
-    /**
-     * 查询能源设施告警策略列表
-     *
-     * @param admOpAlarmPolicy 能源设施告警策略
-     * @return 能源设施告警策略
-     */
-    @Override
-    public List<OpAlarmPolicy> selectAdmOpAlarmPolicyList(OpAlarmPolicy admOpAlarmPolicy) {
-        return admOpAlarmPolicyMapper.selectAdmOpAlarmPolicyList(admOpAlarmPolicy);
-    }
-
-    /**
-     * 新增能源设施告警策略
-     *
-     * @param admOpAlarmPolicy 能源设施告警策略
-     * @return 结果
-     */
-    @Override
-    public int insertAdmOpAlarmPolicy(OpAlarmPolicy admOpAlarmPolicy) {
-        return admOpAlarmPolicyMapper.insertAdmOpAlarmPolicy(admOpAlarmPolicy);
-    }
-
-    /**
-     * 修改能源设施告警策略
-     *
-     * @param admOpAlarmPolicy 能源设施告警策略
-     * @return 结果
-     */
-    @Override
-    public int updateAdmOpAlarmPolicy(OpAlarmPolicy admOpAlarmPolicy) {
-        return admOpAlarmPolicyMapper.updateAdmOpAlarmPolicy(admOpAlarmPolicy);
-    }
-
-    /**
-     * 批量删除能源设施告警策略
-     *
-     * @param ids 需要删除的能源设施告警策略主键
-     * @return 结果
-     */
-    @Override
-    public int deleteAdmOpAlarmPolicyByIds(Long[] ids) {
-        return admOpAlarmPolicyMapper.deleteAdmOpAlarmPolicyByIds(ids);
-    }
-
-    /**
-     * 删除能源设施告警策略信息
-     *
-     * @param id 能源设施告警策略主键
-     * @return 结果
-     */
-    @Override
-    public int deleteAdmOpAlarmPolicyById(Long id) {
-        return admOpAlarmPolicyMapper.deleteAdmOpAlarmPolicyById(id);
-    }
-}

+ 218 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/AlarmRuleServiceImpl.java

@@ -0,0 +1,218 @@
+/*
+ * 文 件 名:  AlarmRuleServiceImpl
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2026/2/3
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.ems.service.impl;
+
+
+import com.alibaba.fastjson2.JSON;
+import com.huashe.common.utils.uuid.Seq;
+
+import com.ruoyi.ems.domain.AlarmRule;
+import com.ruoyi.ems.mapper.AlarmRuleMapper;
+import com.ruoyi.ems.service.IAlarmRuleService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 告警规则Service实现
+ */
+@Slf4j
+@Service
+public class AlarmRuleServiceImpl implements IAlarmRuleService {
+
+    private static final String CACHE_NAME = "alarmRules";
+
+    @Autowired
+    private AlarmRuleMapper ruleMapper;
+
+    @Override
+    public AlarmRule selectById(Long id) {
+        AlarmRule rule = ruleMapper.selectById(id);
+        parseTargetScope(rule);
+        return rule;
+    }
+
+    @Override
+    @Cacheable(value = CACHE_NAME, key = "#ruleCode")
+    public AlarmRule selectByRuleCode(String ruleCode) {
+        AlarmRule rule = ruleMapper.selectByRuleCode(ruleCode);
+        parseTargetScope(rule);
+        return rule;
+    }
+
+    @Override
+    public List<AlarmRule> selectList(AlarmRule query) {
+        List<AlarmRule> list = ruleMapper.selectList(query);
+        if (CollectionUtils.isNotEmpty(list)) {
+            list.forEach(this::parseTargetScope);
+        }
+        return list;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @CacheEvict(value = CACHE_NAME, allEntries = true)
+    public int insert(AlarmRule rule) {
+        // 生成规则代码
+        if (StringUtils.isBlank(rule.getRuleCode())) {
+            rule.setRuleCode("RULE_" + Seq.getId());
+        }
+
+        // 序列化目标范围
+        if (CollectionUtils.isNotEmpty(rule.getTargetScopeList())) {
+            rule.setTargetScope(JSON.toJSONString(rule.getTargetScopeList()));
+        }
+
+        // 默认值
+        if (rule.getEnabled() == null) {
+            rule.setEnabled(1);
+        }
+        if (rule.getUseForInspection() == null) {
+            rule.setUseForInspection(1);
+        }
+        if (rule.getUseForRealtime() == null) {
+            rule.setUseForRealtime(1);
+        }
+        if (rule.getRecoveryCheck() == null) {
+            rule.setRecoveryCheck(1);
+        }
+        if (rule.getTriggerCount() == null) {
+            rule.setTriggerCount(1);
+        }
+        if (rule.getCoolDown() == null) {
+            rule.setCoolDown(300);
+        }
+
+        return ruleMapper.insert(rule);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @CacheEvict(value = CACHE_NAME, allEntries = true)
+    public int update(AlarmRule rule) {
+        // 序列化目标范围
+        if (rule.getTargetScopeList() != null) {
+            rule.setTargetScope(JSON.toJSONString(rule.getTargetScopeList()));
+        }
+        return ruleMapper.update(rule);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @CacheEvict(value = CACHE_NAME, allEntries = true)
+    public int deleteByIds(Long[] ids) {
+        return ruleMapper.deleteByIds(ids);
+    }
+
+    @Override
+    @CacheEvict(value = CACHE_NAME, key = "#ruleCode")
+    public int updateEnabled(String ruleCode, Integer enabled) {
+        return ruleMapper.updateEnabled(ruleCode, enabled);
+    }
+
+    @Override
+    public List<AlarmRule> selectRulesForRealtime(String deviceModel, String attrKey) {
+        AlarmRule query = new AlarmRule();
+        query.setEnabled(1);
+        query.setUseForRealtime(1);
+        query.setDeviceModel(deviceModel);
+        query.setAttrKey(attrKey);
+
+        List<AlarmRule> list = ruleMapper.selectForRealtime(query);
+        if (CollectionUtils.isNotEmpty(list)) {
+            list.forEach(this::parseTargetScope);
+        }
+        return list;
+    }
+
+    @Override
+    public List<AlarmRule> selectRulesForInspection(String deviceModel) {
+        AlarmRule query = new AlarmRule();
+        query.setEnabled(1);
+        query.setUseForInspection(1);
+        query.setDeviceModel(deviceModel);
+
+        List<AlarmRule> list = ruleMapper.selectForInspection(query);
+        if (CollectionUtils.isNotEmpty(list)) {
+            list.forEach(this::parseTargetScope);
+        }
+        return list;
+    }
+
+    @Override
+    public List<AlarmRule> selectByGroupCode(String groupCode) {
+        List<AlarmRule> list = ruleMapper.selectByGroupCode(groupCode);
+        if (CollectionUtils.isNotEmpty(list)) {
+            list.forEach(this::parseTargetScope);
+        }
+        return list;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public AlarmRule copyRule(String sourceRuleCode, String newRuleName) {
+        AlarmRule source = selectByRuleCode(sourceRuleCode);
+        if (source == null) {
+            throw new RuntimeException("源规则不存在: " + sourceRuleCode);
+        }
+
+        AlarmRule newRule = new AlarmRule();
+        BeanUtils.copyProperties(source, newRule);
+
+        // 重置关键字段
+        newRule.setId(null);
+        newRule.setRuleCode("RULE_" + Seq.getId());
+        newRule.setRuleName(newRuleName);
+        newRule.setCreateTime(null);
+        newRule.setUpdateTime(null);
+
+        insert(newRule);
+        return newRule;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @CacheEvict(value = CACHE_NAME, allEntries = true)
+    public int batchUpdateEnabled(List<String> ruleCodes, Integer enabled) {
+        if (CollectionUtils.isEmpty(ruleCodes)) {
+            return 0;
+        }
+        return ruleMapper.batchUpdateEnabled(ruleCodes, enabled);
+    }
+
+    /**
+     * 解析目标范围JSON
+     */
+    private void parseTargetScope(AlarmRule rule) {
+        if (rule == null) {
+            return;
+        }
+        if (StringUtils.isNotBlank(rule.getTargetScope())) {
+            try {
+                rule.setTargetScopeList(JSON.parseArray(rule.getTargetScope(), String.class));
+            } catch (Exception e) {
+                log.warn("解析目标范围失败: {}", rule.getTargetScope());
+                rule.setTargetScopeList(new ArrayList<>());
+            }
+        } else {
+            rule.setTargetScopeList(new ArrayList<>());
+        }
+    }
+}

+ 157 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/AlarmServiceImpl.java

@@ -0,0 +1,157 @@
+package com.ruoyi.ems.service.impl;
+
+import com.ruoyi.ems.service.IAlarmService;
+import com.ruoyi.ems.domain.Alarm;
+import com.ruoyi.ems.domain.AlarmHandle;
+import com.ruoyi.ems.mapper.AlarmHandleMapper;
+import com.ruoyi.ems.mapper.AlarmMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 告警Service实现类
+ */
+@Slf4j
+@Service
+public class AlarmServiceImpl implements IAlarmService {
+
+    @Autowired
+    private AlarmMapper alarmMapper;
+
+    @Autowired
+    private AlarmHandleMapper handleMapper;
+
+    @Override
+    public Alarm selectById(Long id) {
+        return alarmMapper.selectById(id);
+    }
+
+    @Override
+    public Alarm selectByAlarmId(String alarmId) {
+        return alarmMapper.selectByAlarmId(alarmId);
+    }
+
+    @Override
+    public List<Alarm> selectList(Alarm alarm) {
+        return alarmMapper.selectList(alarm);
+    }
+
+    @Override
+    public List<Alarm> selectActiveAlarms(String areaCode) {
+        return alarmMapper.selectActiveAlarms(areaCode);
+    }
+
+    @Override
+    public int insert(Alarm alarm) {
+        return alarmMapper.insert(alarm);
+    }
+
+    @Override
+    public int update(Alarm alarm) {
+        return alarmMapper.update(alarm);
+    }
+
+    @Override
+    public int deleteByIds(Long[] ids) {
+        return alarmMapper.deleteByIds(ids);
+    }
+
+    @Override
+    public List<AlarmHandle> selectHandlesByAlarmId(String alarmId) {
+        return handleMapper.selectByAlarmId(alarmId);
+    }
+
+    // ==================== 统计接口实现 ====================
+
+    @Override
+    public Map<String, Object> selectAlarmStats(Alarm query) {
+        Map<String, Object> stats = new HashMap<>();
+
+        // 按级别统计
+        List<Map<String, Object>> levelStats = countByLevel(query);
+        stats.put("levelStats", levelStats);
+
+        // 按状态统计
+        List<Map<String, Object>> statusStats = countByStatus(query);
+        stats.put("statusStats", statusStats);
+
+        // 处理率
+        Map<String, Object> handleRate = countHandleRate(query);
+        stats.put("handleRate", handleRate);
+
+        // 计算汇总
+        int totalCount = 0;
+        int activeCount = 0;
+        int urgentCount = 0;
+
+        for (Map<String, Object> item : levelStats) {
+            int cnt = ((Number) item.getOrDefault("cnt", 0)).intValue();
+            totalCount += cnt;
+            Integer level = (Integer) item.get("alarmLevel");
+            if (level != null && level == Alarm.LEVEL_URGENT) {
+                urgentCount = cnt;
+            }
+        }
+
+        for (Map<String, Object> item : statusStats) {
+            Integer status = (Integer) item.get("alarmStatus");
+            if (status != null && status <= Alarm.STATUS_HANDLING) {
+                activeCount += ((Number) item.getOrDefault("cnt", 0)).intValue();
+            }
+        }
+
+        stats.put("totalCount", totalCount);
+        stats.put("activeCount", activeCount);
+        stats.put("urgentCount", urgentCount);
+
+        return stats;
+    }
+
+    @Override
+    public List<Map<String, Object>> countByLevel(Alarm query) {
+        return alarmMapper.countByLevel(query);
+    }
+
+    @Override
+    public List<Map<String, Object>> countByStatus(Alarm query) {
+        return alarmMapper.countByStatus(query);
+    }
+
+    @Override
+    public List<Map<String, Object>> countByTimeTrend(Alarm query, String granularity) {
+        if ("month".equalsIgnoreCase(granularity)) {
+            return alarmMapper.countByMonth(query);
+        } else {
+            return alarmMapper.countByDay(query);
+        }
+    }
+
+    @Override
+    public List<Map<String, Object>> countBySubsystem(Alarm query) {
+        return alarmMapper.countBySubsystem(query);
+    }
+
+    @Override
+    public Map<String, Object> countHandleRate(Alarm query) {
+        Map<String, Object> result = alarmMapper.countHandleRate(query);
+        if (result == null) {
+            result = new HashMap<>();
+            result.put("total", 0);
+            result.put("handled", 0);
+            result.put("pending", 0);
+            result.put("rate", 0.0);
+        } else {
+            // 计算处理率
+            int total = ((Number) result.getOrDefault("total", 0)).intValue();
+            int handled = ((Number) result.getOrDefault("handled", 0)).intValue();
+            double rate = total > 0 ? (handled * 100.0 / total) : 0.0;
+            result.put("rate", Math.round(rate * 100) / 100.0);
+        }
+        return result;
+    }
+}

+ 0 - 4
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/EmsFacsServiceImpl.java

@@ -4,7 +4,6 @@ import com.huashe.common.utils.DateUtils;
 import com.ruoyi.ems.domain.Area;
 import com.ruoyi.ems.domain.EmsDevice;
 import com.ruoyi.ems.domain.EmsFacs;
-import com.ruoyi.ems.mapper.AdmEmsIndexRangeMapper;
 import com.ruoyi.ems.mapper.EmsFacsMapper;
 import com.ruoyi.ems.model.BoundaryObj;
 import com.ruoyi.ems.model.QueryDevice;
@@ -45,9 +44,6 @@ public class EmsFacsServiceImpl implements IEmsFacsService {
     @Autowired
     private IAreaService areaService;
 
-    @Autowired
-    private AdmEmsIndexRangeMapper admEmsIndexRangeMapper;
-
     /**
      * 查询能源设施
      *

+ 0 - 178
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/OpAlarmServiceImpl.java

@@ -1,178 +0,0 @@
-package com.ruoyi.ems.service.impl;
-
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.lang3.ObjectUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import com.ruoyi.common.core.utils.DateTimeUtil;
-import com.ruoyi.ems.domain.OpAlarm;
-import com.ruoyi.ems.domain.Area;
-import com.ruoyi.ems.domain.EmsDevice;
-import com.ruoyi.ems.domain.EmsFacs;
-import com.ruoyi.ems.enums.AlarmObjType;
-import com.ruoyi.ems.mapper.OpAlarmMapper;
-import com.ruoyi.ems.mapper.AreaMapper;
-import com.ruoyi.ems.mapper.EmsDeviceMapper;
-import com.ruoyi.ems.mapper.EmsFacsMapper;
-import com.ruoyi.ems.service.IOpAlarmService;
-
-/**
- * 能源设施告警Service业务层处理
- *
- * @author ruoyi
- * @date 2024-08-26
- */
-@Service
-public class OpAlarmServiceImpl implements IOpAlarmService {
-    @Autowired
-    private OpAlarmMapper opAlarmMapper;
-
-    @Autowired
-    private AreaMapper areaMapper;
-
-    @Autowired
-    private EmsFacsMapper emsFacsMapper;
-
-    @Autowired
-    private EmsDeviceMapper emsDeviceMapper;
-
-    /**
-     * 查询能源设施告警
-     *
-     * @param id 能源设施告警主键
-     * @return 能源设施告警
-     */
-    @Override
-    public OpAlarm selectOpAlarmById(Long id) {
-        return opAlarmMapper.selectAdmOpAlarmById(id);
-    }
-
-    /**
-     * 查询能源设施告警列表
-     *
-     * @param admOpAlarm 能源设施告警
-     * @return 能源设施告警
-     */
-    @Override
-    public List<OpAlarm> selectOpAlarmList(OpAlarm admOpAlarm) {
-        return opAlarmMapper.selectAdmOpAlarmList(admOpAlarm);
-    }
-
-    /**
-     * 新增能源设施告警
-     *
-     * @param admOpAlarm 能源设施告警
-     * @return 结果
-     */
-    @Override
-    public int insertOpAlarm(OpAlarm admOpAlarm) {
-        admOpAlarm.setObjName(getObjInfo(admOpAlarm.getObjType(), admOpAlarm.getObjCode()));
-        return opAlarmMapper.insertAdmOpAlarm(admOpAlarm);
-    }
-
-    /**
-     * 修改能源设施告警
-     *
-     * @param admOpAlarm 能源设施告警
-     * @return 结果
-     */
-    @Override
-    public int updateOpAlarm(OpAlarm admOpAlarm) {
-        admOpAlarm.setObjName(getObjInfo(admOpAlarm.getObjType(), admOpAlarm.getObjCode()));
-        return opAlarmMapper.updateAdmOpAlarm(admOpAlarm);
-    }
-
-    /**
-     * 批量删除能源设施告警
-     *
-     * @param ids 需要删除的能源设施告警主键
-     * @return 结果
-     */
-    @Override
-    public int deleteOpAlarmByIds(Long[] ids) {
-        return opAlarmMapper.deleteAdmOpAlarmByIds(ids);
-    }
-
-    /**
-     * 删除能源设施告警信息
-     *
-     * @param id 能源设施告警主键
-     * @return 结果
-     */
-    @Override
-    public int deleteOpAlarmById(Long id) {
-        return opAlarmMapper.deleteAdmOpAlarmById(id);
-    }
-
-    @Override
-    public List<Map<String, Object>> qryAlarmTypeIndex(String areaCode, String startTime, String endTime) {
-        return opAlarmMapper.qryAlarmTypeIndex(areaCode, startTime, endTime);
-    }
-
-    @Override
-    public List<Map<String, Object>> qryAlarmTypeIndexByDate(OpAlarm params) {
-        return opAlarmMapper.qryAlarmTypeIndexByDate(params);
-    }
-
-    @Override
-    public List<Map<String, Object>> qryAlarmTypeIndexDay(String areaCode) {
-        String dateTime = DateTimeUtil.currentDateTime(DateTimeUtil.DateFormatter.yyyy_MM_dd);
-        return opAlarmMapper.qryAlarmTypeIndexDay(dateTime, areaCode);
-    }
-
-    @Override
-    public List<Map<String, Object>> qryAlarmTypeIndexMonth(String areaCode) {
-        String firstDayOfRecentMonth = DateTimeUtil.getFirstDayOfRecentMonth();
-        return opAlarmMapper.qryAlarmTypeIndexMonth(firstDayOfRecentMonth, areaCode);
-    }
-
-    @Override
-    public List<Map<String, Object>> qryAlarmTypeIndexYear(String areaCode) {
-        String firstDayOfRecentYear = DateTimeUtil.getFirstDayOfRecentYear();
-        return opAlarmMapper.qryAlarmTypeIndexYear(firstDayOfRecentYear, areaCode);
-    }
-
-    @Override
-    public List<Map<String, Object>> qrySubSysIndexDay(String areaCode) {
-        String dateTime = DateTimeUtil.currentDateTime(DateTimeUtil.DateFormatter.yyyy_MM_dd);
-        return opAlarmMapper.qrySubSysIndexDay(dateTime, areaCode);
-    }
-
-    @Override
-    public List<Map<String, Object>> qrySubSysIndexMonth(String areaCode) {
-        String firstDayOfRecentMonth = DateTimeUtil.getFirstDayOfRecentMonth();
-        return opAlarmMapper.qrySubSysIndexMonth(firstDayOfRecentMonth, areaCode);
-    }
-
-    @Override
-    public List<Map<String, Object>> qrySubSysIndexYear(String areaCode) {
-        String firstDayOfRecentYear = DateTimeUtil.getFirstDayOfRecentYear();
-        return opAlarmMapper.qrySubSysIndexYear(firstDayOfRecentYear, areaCode);
-    }
-
-    @Override
-    public Map<String, Object> cntHandledAlarmByDate(OpAlarm admOpAlarm) {
-        return opAlarmMapper.cntHandledAlarmByDate(admOpAlarm);
-    }
-
-    private String getObjInfo(Integer objType, String objCode) {
-        if (AlarmObjType.AREA.getCode().equals(objType)) {
-            Area area = areaMapper.selectAreaByCode(objCode);
-            return ObjectUtils.isEmpty(area) ? StringUtils.EMPTY : area.getAreaName();
-        }
-        if (AlarmObjType.FACS.getCode().equals(objType)) {
-            EmsFacs emsFacs = emsFacsMapper.selectEmsFacsByCode(objCode);
-            return ObjectUtils.isEmpty(emsFacs) ? StringUtils.EMPTY : emsFacs.getFacsName();
-        }
-        if (AlarmObjType.DEVICE.getCode().equals(objType)) {
-            EmsDevice emsDevice = emsDeviceMapper.selectDeviceByCode(objCode);
-            return ObjectUtils.isEmpty(emsDevice) ? StringUtils.EMPTY : emsDevice.getDeviceName();
-        }
-
-        return StringUtils.EMPTY;
-    }
-}

+ 0 - 99
ems/ems-core/src/main/resources/mapper/ems/AdmEmsIndexRangeMapper.xml

@@ -1,99 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE mapper
-        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.ruoyi.ems.mapper.AdmEmsIndexRangeMapper">
-
-    <resultMap type="com.ruoyi.ems.domain.AdmEmsIndexRange" id="AdmEmsIndexRangeResult">
-        <result property="id" column="id"/>
-        <result property="objCode" column="obj_code"/>
-        <result property="objType" column="obj_type"/>
-        <result property="indexName" column="index_name"/>
-        <result property="indexDesc" column="index_desc"/>
-        <result property="indexUpperLimit" column="index_upper_limit"/>
-        <result property="indexLowerLimit" column="index_lower_limit"/>
-    </resultMap>
-
-    <sql id="selectAdmEmsIndexRangeVo">
-        select id, obj_code, obj_type, index_name, index_desc, index_upper_limit, index_lower_limit
-        from adm_ems_index_range
-    </sql>
-
-    <select id="selectAdmEmsIndexRangeList" parameterType="com.ruoyi.ems.domain.AdmEmsIndexRange"
-            resultMap="AdmEmsIndexRangeResult">
-        <include refid="selectAdmEmsIndexRangeVo"/>
-        <where>
-            <if test="objType != null ">and obj_type = #{objType}</if>
-            <if test="indexName != null  and indexName != ''">and index_name like concat('%', #{indexName}, '%')</if>
-            <if test="indexDesc != null  and indexDesc != ''">and index_desc = #{indexDesc}</if>
-        </where>
-    </select>
-
-    <select id="selectAdmEmsIndexRangeById" parameterType="Long" resultMap="AdmEmsIndexRangeResult">
-        <include refid="selectAdmEmsIndexRangeVo"/>
-        where id = #{id}
-    </select>
-
-    <insert id="insertAdmEmsIndexRange" parameterType="com.ruoyi.ems.domain.AdmEmsIndexRange" useGeneratedKeys="true"
-            keyProperty="id">
-        insert into adm_ems_index_range
-        <trim prefix="(" suffix=")" suffixOverrides=",">
-            <if test="objCode != null and objCode != ''">obj_code,</if>
-            <if test="objType != null">obj_type,</if>
-            <if test="indexName != null and indexName != ''">index_name,</if>
-            <if test="indexDesc != null">index_desc,</if>
-            <if test="indexUpperLimit != null">index_upper_limit,</if>
-            <if test="indexLowerLimit != null">index_lower_limit,</if>
-        </trim>
-        <trim prefix="values (" suffix=")" suffixOverrides=",">
-            <if test="objCode != null and objCode != ''">#{objCode},</if>
-            <if test="objType != null">#{objType},</if>
-            <if test="indexName != null and indexName != ''">#{indexName},</if>
-            <if test="indexDesc != null">#{indexDesc},</if>
-            <if test="indexUpperLimit != null">#{indexUpperLimit},</if>
-            <if test="indexLowerLimit != null">#{indexLowerLimit},</if>
-        </trim>
-    </insert>
-
-    <update id="updateAdmEmsIndexRange" parameterType="com.ruoyi.ems.domain.AdmEmsIndexRange">
-        update adm_ems_index_range
-        <trim prefix="SET" suffixOverrides=",">
-            <if test="objCode != null and objCode != ''">obj_code = #{objCode},</if>
-            <if test="objType != null">obj_type = #{objType},</if>
-            <if test="indexName != null and indexName != ''">index_name = #{indexName},</if>
-            <if test="indexDesc != null">index_desc = #{indexDesc},</if>
-            <if test="indexUpperLimit != null">index_upper_limit = #{indexUpperLimit},</if>
-            <if test="indexLowerLimit != null">index_lower_limit = #{indexLowerLimit},</if>
-        </trim>
-        where id = #{id}
-    </update>
-
-    <delete id="deleteAdmEmsIndexRangeById" parameterType="Long">
-        delete
-        from adm_ems_index_range
-        where id = #{id}
-    </delete>
-
-    <delete id="deleteAdmEmsIndexRangeByIds" parameterType="String">
-        delete from adm_ems_index_range where id in
-        <foreach item="id" collection="array" open="(" separator="," close=")">
-            #{id}
-        </foreach>
-    </delete>
-
-    <select id="selectAdmEmsIndexRange" resultType="Map">
-        select indexRange.id,
-               obj_code objCode,
-               obj_type objType,
-               index_name indexName,
-               index_desc indexDesc,
-               index_upper_limit indexUpperLimit,
-               index_lower_limit indexLowerLimit,
-               facs.facs_category facsCategory,
-               facs.facs_name facsName
-        FROM adm_ems_index_range indexRange
-                 LEFT JOIN adm_ems_facs facs ON indexRange.obj_code = facs.facs_code
-            AND indexRange.obj_type = 1
-    </select>
-
-</mapper>

+ 0 - 100
ems/ems-core/src/main/resources/mapper/ems/AdmOpAlarmPolicyMapper.xml

@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.ruoyi.ems.mapper.AdmOpAlarmPolicyMapper">
-
-    <resultMap type="com.ruoyi.ems.domain.OpAlarmPolicy" id="AdmOpAlarmPolicyResult">
-        <result property="id"    column="id"    />
-        <result property="areaCode"             column="area_code"    />
-        <result property="areaName"             column="area_name"    />
-        <result property="policyCode"           column="policy_code"    />
-        <result property="policyName"           column="policy_name"    />
-        <result property="alarmObjType"         column="alarm_obj_type"    />
-        <result property="alarmObjIndex"        column="alarm_obj_index"    />
-        <result property="alarmRuleType"        column="alarm_rule_type"    />
-        <result property="alarmThresholdValue"  column="alarm_threshold_value"    />
-        <result property="alarmCode"            column="alarm_code"    />
-        <result property="alarmMsg"             column="alarm_msg"    />
-        <result property="alarmType"            column="alarm_type"    />
-    </resultMap>
-
-    <sql id="selectAdmOpAlarmPolicyVo">
-        select id, policy_code, policy_name, alarm_obj_type, alarm_obj_index, alarm_rule_type, alarm_threshold_value, alarm_code, alarm_msg, alarm_type from adm_op_alarm_policy
-    </sql>
-
-    <select id="selectAdmOpAlarmPolicyList" parameterType="com.ruoyi.ems.domain.OpAlarmPolicy" resultMap="AdmOpAlarmPolicyResult">
-        select
-            p.id, p.area_code, a.area_name, p.policy_code, p.policy_name, p.alarm_obj_type, p.alarm_obj_index, p.alarm_rule_type, p.alarm_threshold_value, p.alarm_code, p.alarm_msg, p.alarm_type
-        from adm_op_alarm_policy p
-            left join adm_area a on p.area_code = a.area_code
-        <where>
-            <if test="areaCode != null and areaCode != '' and areaCode != '-1'">and p.area_code= #{areaCode}</if>
-            <if test="alarmObjType != null "> and p.alarm_obj_type = #{alarmObjType}</if>
-            <if test="alarmCode != null  and alarmCode != ''"> and p.alarm_code = #{alarmCode}</if>
-            <if test="alarmType != null "> and p.alarm_type = #{alarmType}</if>
-            <if test="policyName != null and policyName != ''"> and p.policy_name like concat('%', #{policyName}, '%')</if>
-        </where>
-    </select>
-
-    <select id="selectAdmOpAlarmPolicyById" parameterType="Long" resultMap="AdmOpAlarmPolicyResult">
-        <include refid="selectAdmOpAlarmPolicyVo"/>
-        where id = #{id}
-    </select>
-
-    <insert id="insertAdmOpAlarmPolicy" parameterType="com.ruoyi.ems.domain.OpAlarmPolicy" useGeneratedKeys="true" keyProperty="id">
-        insert into adm_op_alarm_policy
-        <trim prefix="(" suffix=")" suffixOverrides=",">
-            <if test="areaCode != null and areaCode != ''">area_code,</if>
-            <if test="policyCode != null and policyCode != ''">policy_code,</if>
-            <if test="policyName != null and policyName != ''">policy_name,</if>
-            <if test="alarmObjType != null">alarm_obj_type,</if>
-            <if test="alarmObjIndex != null and alarmObjIndex != ''">alarm_obj_index,</if>
-            <if test="alarmRuleType != null">alarm_rule_type,</if>
-            <if test="alarmThresholdValue != null">alarm_threshold_value,</if>
-            <if test="alarmCode != null">alarm_code,</if>
-            <if test="alarmMsg != null">alarm_msg,</if>
-            <if test="alarmType != null">alarm_type,</if>
-         </trim>
-        <trim prefix="values (" suffix=")" suffixOverrides=",">
-            <if test="areaCode != null and areaCode != ''">#{areaCode},</if>
-            <if test="policyCode != null and policyCode != ''">#{policyCode},</if>
-            <if test="policyName != null and policyName != ''">#{policyName},</if>
-            <if test="alarmObjType != null">#{alarmObjType},</if>
-            <if test="alarmObjIndex != null and alarmObjIndex != ''">#{alarmObjIndex},</if>
-            <if test="alarmRuleType != null">#{alarmRuleType},</if>
-            <if test="alarmThresholdValue != null">#{alarmThresholdValue},</if>
-            <if test="alarmCode != null">#{alarmCode},</if>
-            <if test="alarmMsg != null">#{alarmMsg},</if>
-            <if test="alarmType != null">#{alarmType},</if>
-         </trim>
-    </insert>
-
-    <update id="updateAdmOpAlarmPolicy" parameterType="com.ruoyi.ems.domain.OpAlarmPolicy">
-        update adm_op_alarm_policy
-        <trim prefix="SET" suffixOverrides=",">
-            <if test="areaCode != null and areaCode != ''">area_code = #{areaCode},</if>
-            <if test="policyCode != null and policyCode != ''">policy_code = #{policyCode},</if>
-            <if test="policyName != null and policyName != ''">policy_name = #{policyName},</if>
-            <if test="alarmObjType != null">alarm_obj_type = #{alarmObjType},</if>
-            <if test="alarmObjIndex != null and alarmObjIndex != ''">alarm_obj_index = #{alarmObjIndex},</if>
-            <if test="alarmRuleType != null">alarm_rule_type = #{alarmRuleType},</if>
-            <if test="alarmThresholdValue != null">alarm_threshold_value = #{alarmThresholdValue},</if>
-            <if test="alarmCode != null">alarm_code = #{alarmCode},</if>
-            <if test="alarmMsg != null">alarm_msg = #{alarmMsg},</if>
-            <if test="alarmType != null">alarm_type = #{alarmType},</if>
-        </trim>
-        where id = #{id}
-    </update>
-
-    <delete id="deleteAdmOpAlarmPolicyById" parameterType="Long">
-        delete from adm_op_alarm_policy where id = #{id}
-    </delete>
-
-    <delete id="deleteAdmOpAlarmPolicyByIds" parameterType="String">
-        delete from adm_op_alarm_policy where id in
-        <foreach item="id" collection="array" open="(" separator="," close=")">
-            #{id}
-        </foreach>
-    </delete>
-</mapper>

+ 45 - 0
ems/ems-core/src/main/resources/mapper/ems/AlarmHandleMapper.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.ems.mapper.AlarmHandleMapper">
+
+    <resultMap type="com.ruoyi.ems.domain.AlarmHandle" id="AlarmHandleResult">
+        <id property="id" column="id"/>
+        <result property="alarmId" column="alarm_id"/>
+        <result property="handleType" column="handle_type"/>
+        <result property="handleContent" column="handle_content"/>
+        <result property="handleResult" column="handle_result"/>
+        <result property="handleBy" column="handle_by"/>
+        <result property="handleTime" column="handle_time"/>
+        <result property="attachments" column="attachments"/>
+        <result property="createTime" column="create_time"/>
+    </resultMap>
+
+    <select id="selectById" parameterType="Long" resultMap="AlarmHandleResult">
+        SELECT id, alarm_id, handle_type, handle_content, handle_result,
+               handle_by, handle_time, attachments, create_time
+        FROM adm_op_alarm_handle
+        WHERE id = #{id}
+    </select>
+
+    <select id="selectByAlarmId" parameterType="String" resultMap="AlarmHandleResult">
+        SELECT id, alarm_id, handle_type, handle_content, handle_result,
+               handle_by, handle_time, attachments, create_time
+        FROM adm_op_alarm_handle
+        WHERE alarm_id = #{alarmId}
+        ORDER BY handle_time ASC
+    </select>
+
+    <insert id="insert" parameterType="com.ruoyi.ems.domain.AlarmHandle" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO adm_op_alarm_handle (
+            alarm_id, handle_type, handle_content, handle_result,
+            handle_by, handle_time, attachments, create_time
+        ) VALUES (
+                     #{alarmId}, #{handleType}, #{handleContent}, #{handleResult},
+                     #{handleBy}, #{handleTime}, #{attachments}, NOW()
+                 )
+    </insert>
+
+    <delete id="deleteByAlarmId" parameterType="String">
+        DELETE FROM adm_op_alarm_handle WHERE alarm_id = #{alarmId}
+    </delete>
+</mapper>

+ 237 - 0
ems/ems-core/src/main/resources/mapper/ems/AlarmMapper.xml

@@ -0,0 +1,237 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.ems.mapper.AlarmMapper">
+
+    <resultMap type="com.ruoyi.ems.domain.Alarm" id="AlarmResult">
+        <id property="id" column="id"/>
+        <result property="alarmId" column="alarm_id"/>
+        <result property="areaCode" column="area_code"/>
+        <result property="areaName" column="area_name"/>
+        <result property="subsystemCode" column="subsystem_code"/>
+        <result property="subsystemName" column="subsystem_name"/>
+        <result property="targetType" column="target_type"/>
+        <result property="targetCode" column="target_code"/>
+        <result property="targetName" column="target_name"/>
+        <result property="deviceModel" column="device_model"/>
+        <result property="deviceModelName" column="device_model_name"/>
+        <result property="location" column="location"/>
+        <result property="ruleCode" column="rule_code"/>
+        <result property="ruleName" column="rule_name"/>
+        <result property="attrKey" column="attr_key"/>
+        <result property="attrName" column="attr_name"/>
+        <result property="attrValue" column="attr_value"/>
+        <result property="thresholdValue" column="threshold_value"/>
+        <result property="alarmLevel" column="alarm_level"/>
+        <result property="alarmCode" column="alarm_code"/>
+        <result property="alarmMsg" column="alarm_msg"/>
+        <result property="alarmSource" column="alarm_source"/>
+        <result property="sourceRef" column="source_ref"/>
+        <result property="alarmTime" column="alarm_time"/>
+        <result property="alarmDate" column="alarm_date"/>
+        <result property="alarmStatus" column="alarm_status"/>
+        <result property="confirmTime" column="confirm_time"/>
+        <result property="confirmBy" column="confirm_by"/>
+        <result property="resolveTime" column="resolve_time"/>
+        <result property="resolveBy" column="resolve_by"/>
+        <result property="resolveRemark" column="resolve_remark"/>
+        <result property="recoveryTime" column="recovery_time"/>
+        <result property="duration" column="duration"/>
+        <result property="repeatCount" column="repeat_count"/>
+        <result property="lastOccurTime" column="last_occur_time"/>
+        <result property="isNotified" column="is_notified"/>
+        <result property="notifyTime" column="notify_time"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <sql id="selectVo">
+        SELECT al.id, al.alarm_id, al.area_code, a.area_name, al.subsystem_code, s.system_name AS subsystem_name,
+               al.target_type, al.target_code, al.target_name, al.device_model, m.model_name AS device_model_name,
+               al.location, al.rule_code, al.rule_name, al.attr_key, al.attr_name, al.attr_value, al.threshold_value,
+               al.alarm_level, al.alarm_code, al.alarm_msg, al.alarm_source, al.source_ref,
+               al.alarm_time, al.alarm_date, al.alarm_status,
+               al.confirm_time, al.confirm_by, al.resolve_time, al.resolve_by, al.resolve_remark,
+               al.recovery_time, al.duration, al.repeat_count, al.last_occur_time,
+               al.is_notified, al.notify_time, al.create_time, al.update_time
+        FROM adm_op_alarm al
+                 LEFT JOIN adm_area a ON al.area_code = a.area_code
+                 LEFT JOIN adm_ems_subsystem s ON al.subsystem_code = s.system_code
+                 LEFT JOIN adm_ems_obj_model m ON al.device_model = m.model_code
+    </sql>
+
+    <select id="selectById" parameterType="Long" resultMap="AlarmResult">
+        <include refid="selectVo"/>
+        WHERE al.id = #{id}
+    </select>
+
+    <select id="selectByAlarmId" parameterType="String" resultMap="AlarmResult">
+        <include refid="selectVo"/>
+        WHERE al.alarm_id = #{alarmId}
+    </select>
+
+    <select id="selectList" parameterType="com.ruoyi.ems.domain.Alarm" resultMap="AlarmResult">
+        <include refid="selectVo"/>
+        <where>
+            <if test="alarmId != null and alarmId != ''">AND al.alarm_id = #{alarmId}</if>
+            <if test="areaCode != null and areaCode != '' and areaCode != '-1'">AND al.area_code = #{areaCode}</if>
+            <if test="subsystemCode != null and subsystemCode != ''">AND al.subsystem_code = #{subsystemCode}</if>
+            <if test="targetType != null">AND al.target_type = #{targetType}</if>
+            <if test="targetCode != null and targetCode != ''">AND al.target_code = #{targetCode}</if>
+            <if test="targetName != null and targetName != ''">AND al.target_name LIKE CONCAT('%', #{targetName}, '%')</if>
+            <if test="deviceModel != null and deviceModel != ''">AND al.device_model = #{deviceModel}</if>
+            <if test="ruleCode != null and ruleCode != ''">AND al.rule_code = #{ruleCode}</if>
+            <if test="alarmLevel != null">AND al.alarm_level = #{alarmLevel}</if>
+            <if test="alarmCode != null and alarmCode != ''">AND al.alarm_code = #{alarmCode}</if>
+            <if test="alarmSource != null">AND al.alarm_source = #{alarmSource}</if>
+            <if test="alarmStatus != null">AND al.alarm_status = #{alarmStatus}</if>
+            <if test="startRecTime != null and startRecTime != '' and endRecTime != null and endRecTime != ''">
+                AND al.alarm_time >= #{startRecTime} AND al.alarm_time &lt;= #{endRecTime}
+            </if>
+            <if test="alarmDate != null">AND al.alarm_date = #{alarmDate}</if>
+        </where>
+        ORDER BY al.alarm_time DESC
+    </select>
+
+    <!-- 查询活动告警 -->
+    <select id="selectActiveAlarms" parameterType="String" resultMap="AlarmResult">
+        <include refid="selectVo"/>
+        WHERE al.alarm_status IN (0, 1, 2)
+        <if test="areaCode != null and areaCode != '' and areaCode != '-1'">
+            AND al.area_code = #{areaCode}
+        </if>
+        ORDER BY al.alarm_level DESC, al.alarm_time DESC
+    </select>
+
+    <!-- 查询指定规则和目标的活动告警(用于聚合) -->
+    <select id="selectActiveAlarm" resultMap="AlarmResult">
+        <include refid="selectVo"/>
+        WHERE al.rule_code = #{ruleCode}
+        AND al.target_code = #{targetCode}
+        AND al.alarm_status IN (0, 1, 2)
+        ORDER BY al.alarm_time DESC
+        LIMIT 1
+    </select>
+
+    <!-- 按告警级别统计 -->
+    <select id="countByLevel" parameterType="com.ruoyi.ems.domain.Alarm" resultType="java.util.Map">
+        SELECT alarm_level AS alarmLevel, COUNT(*) AS cnt
+        FROM adm_op_alarm
+        <where>
+            <if test="areaCode != null and areaCode != '' and areaCode != '-1'">AND area_code = #{areaCode}</if>
+            <if test="startRecTime != null and startRecTime != '' and endRecTime != null and endRecTime != ''">
+                AND alarm_time >= #{startRecTime} AND alarm_time &lt;= #{endRecTime}
+            </if>
+            <if test="alarmDate != null">AND alarm_date = #{alarmDate}</if>
+        </where>
+        GROUP BY alarm_level
+    </select>
+
+    <!-- 按告警状态统计 -->
+    <select id="countByStatus" parameterType="com.ruoyi.ems.domain.Alarm" resultType="java.util.Map">
+        SELECT alarm_status AS alarmStatus, COUNT(*) AS cnt
+        FROM adm_op_alarm
+        <where>
+            <if test="areaCode != null and areaCode != '' and areaCode != '-1'">AND area_code = #{areaCode}</if>
+            <if test="startRecTime != null and startRecTime != '' and endRecTime != null and endRecTime != ''">
+                AND alarm_time >= #{startRecTime} AND alarm_time &lt;= #{endRecTime}
+            </if>
+        </where>
+        GROUP BY alarm_status
+    </select>
+
+    <!-- 按时间趋势统计(日) -->
+    <select id="countByDay" parameterType="com.ruoyi.ems.domain.Alarm" resultType="java.util.Map">
+        SELECT DATE_FORMAT(alarm_time, '%H:00') AS timeIndex, COUNT(*) AS cnt
+        FROM adm_op_alarm
+        <where>
+            <if test="areaCode != null and areaCode != '' and areaCode != '-1'">AND area_code = #{areaCode}</if>
+            <if test="alarmDate != null">AND alarm_date = #{alarmDate}</if>
+        </where>
+        GROUP BY DATE_FORMAT(alarm_time, '%H:00')
+        ORDER BY timeIndex
+    </select>
+
+    <!-- 按时间趋势统计(月) -->
+    <select id="countByMonth" parameterType="com.ruoyi.ems.domain.Alarm" resultType="java.util.Map">
+        SELECT DATE_FORMAT(alarm_date, '%Y-%m-%d') AS timeIndex, COUNT(*) AS cnt
+        FROM adm_op_alarm
+        <where>
+            <if test="areaCode != null and areaCode != '' and areaCode != '-1'">AND area_code = #{areaCode}</if>
+            <if test="startRecTime != null and startRecTime != ''">AND alarm_date >= #{startRecTime}</if>
+        </where>
+        GROUP BY alarm_date
+        ORDER BY timeIndex
+    </select>
+
+    <!-- 按子系统统计 -->
+    <select id="countBySubsystem" parameterType="com.ruoyi.ems.domain.Alarm" resultType="java.util.Map">
+        SELECT al.subsystem_code AS subsystemCode, s.system_name AS subsystemName, COUNT(*) AS cnt
+        FROM adm_op_alarm al
+        LEFT JOIN adm_ems_subsystem s ON al.subsystem_code = s.system_code
+        <where>
+            <if test="areaCode != null and areaCode != '' and areaCode != '-1'">AND al.area_code = #{areaCode}</if>
+            <if test="startRecTime != null and startRecTime != '' and endRecTime != null and endRecTime != ''">
+                AND al.alarm_time >= #{startRecTime} AND al.alarm_time &lt;= #{endRecTime}
+            </if>
+        </where>
+        GROUP BY al.subsystem_code
+    </select>
+
+    <!-- 统计处理率 -->
+    <select id="countHandleRate" parameterType="com.ruoyi.ems.domain.Alarm" resultType="java.util.Map">
+        SELECT
+        COUNT(*) AS total,
+        SUM(CASE WHEN alarm_status IN (3, 4, 5) THEN 1 ELSE 0 END) AS handled,
+        SUM(CASE WHEN alarm_status IN (0, 1, 2) THEN 1 ELSE 0 END) AS pending
+        FROM adm_op_alarm
+        <where>
+            <if test="areaCode != null and areaCode != '' and areaCode != '-1'">AND area_code = #{areaCode}</if>
+            <if test="startRecTime != null and startRecTime != '' and endRecTime != null and endRecTime != ''">
+                AND alarm_time >= #{startRecTime} AND alarm_time &lt;= #{endRecTime}
+            </if>
+        </where>
+    </select>
+
+    <insert id="insert" parameterType="com.ruoyi.ems.domain.Alarm" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO adm_op_alarm (
+            alarm_id, area_code, subsystem_code, target_type, target_code, target_name,
+            device_model, location, rule_code, rule_name, attr_key, attr_name, attr_value, threshold_value,
+            alarm_level, alarm_code, alarm_msg, alarm_source, source_ref,
+            alarm_time, alarm_date, alarm_status, repeat_count, last_occur_time, is_notified, create_time
+        ) VALUES (
+                     #{alarmId}, #{areaCode}, #{subsystemCode}, #{targetType}, #{targetCode}, #{targetName},
+                     #{deviceModel}, #{location}, #{ruleCode}, #{ruleName}, #{attrKey}, #{attrName}, #{attrValue}, #{thresholdValue},
+                     #{alarmLevel}, #{alarmCode}, #{alarmMsg}, #{alarmSource}, #{sourceRef},
+                     #{alarmTime}, #{alarmDate}, #{alarmStatus}, #{repeatCount}, #{lastOccurTime}, #{isNotified}, NOW()
+                 )
+    </insert>
+
+    <update id="update" parameterType="com.ruoyi.ems.domain.Alarm">
+        UPDATE adm_op_alarm
+        <set>
+            <if test="alarmStatus != null">alarm_status = #{alarmStatus},</if>
+            <if test="confirmTime != null">confirm_time = #{confirmTime},</if>
+            <if test="confirmBy != null">confirm_by = #{confirmBy},</if>
+            <if test="resolveTime != null">resolve_time = #{resolveTime},</if>
+            <if test="resolveBy != null">resolve_by = #{resolveBy},</if>
+            <if test="resolveRemark != null">resolve_remark = #{resolveRemark},</if>
+            <if test="recoveryTime != null">recovery_time = #{recoveryTime},</if>
+            <if test="duration != null">duration = #{duration},</if>
+            <if test="repeatCount != null">repeat_count = #{repeatCount},</if>
+            <if test="lastOccurTime != null">last_occur_time = #{lastOccurTime},</if>
+            <if test="attrValue != null">attr_value = #{attrValue},</if>
+            <if test="isNotified != null">is_notified = #{isNotified},</if>
+            <if test="notifyTime != null">notify_time = #{notifyTime},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            update_time = NOW()
+        </set>
+        WHERE id = #{id}
+    </update>
+
+    <delete id="deleteByIds" parameterType="Long">
+        DELETE FROM adm_op_alarm WHERE id IN
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 191 - 0
ems/ems-core/src/main/resources/mapper/ems/AlarmRuleMapper.xml

@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.ems.mapper.AlarmRuleMapper">
+
+    <resultMap type="com.ruoyi.ems.domain.AlarmRule" id="AlarmRuleResult">
+        <id property="id" column="id"/>
+        <result property="ruleCode" column="rule_code"/>
+        <result property="ruleName" column="rule_name"/>
+        <result property="areaCode" column="area_code"/>
+        <result property="areaName" column="area_name"/>
+        <result property="targetType" column="target_type"/>
+        <result property="targetScope" column="target_scope"/>
+        <result property="deviceModel" column="device_model"/>
+        <result property="deviceModelName" column="device_model_name"/>
+        <result property="attrKey" column="attr_key"/>
+        <result property="attrName" column="attr_name"/>
+        <result property="checkType" column="check_type"/>
+        <result property="operator" column="operator"/>
+        <result property="thresholdValue" column="threshold_value"/>
+        <result property="thresholdMin" column="threshold_min"/>
+        <result property="thresholdMax" column="threshold_max"/>
+        <result property="alarmLevel" column="alarm_level"/>
+        <result property="alarmCode" column="alarm_code"/>
+        <result property="alarmMsgTemplate" column="alarm_msg_template"/>
+        <result property="recoveryCheck" column="recovery_check"/>
+        <result property="recoveryThreshold" column="recovery_threshold"/>
+        <result property="triggerCount" column="trigger_count"/>
+        <result property="triggerInterval" column="trigger_interval"/>
+        <result property="coolDown" column="cool_down"/>
+        <result property="enabled" column="enabled"/>
+        <result property="useForInspection" column="use_for_inspection"/>
+        <result property="useForRealtime" column="use_for_realtime"/>
+        <result property="subsystemCode" column="subsystem_code"/>
+        <result property="subsystemName" column="subsystem_name"/>
+        <result property="ruleOrder" column="rule_order"/>
+        <result property="description" column="description"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <sql id="selectVo">
+        SELECT r.id, r.rule_code, r.rule_name, r.area_code, a.area_name,
+               r.target_type, r.target_scope, r.device_model, m.model_name AS device_model_name,
+               r.attr_key, r.attr_name, r.check_type, r.operator,
+               r.threshold_value, r.threshold_min, r.threshold_max,
+               r.alarm_level, r.alarm_code, r.alarm_msg_template,
+               r.recovery_check, r.recovery_threshold, r.trigger_count, r.trigger_interval, r.cool_down,
+               r.enabled, r.use_for_inspection, r.use_for_realtime,
+               r.subsystem_code, s.system_name AS subsystem_name,
+               r.rule_order, r.description, r.create_by, r.create_time, r.update_by, r.update_time
+        FROM adm_op_alarm_rule r
+                 LEFT JOIN adm_area a ON r.area_code = a.area_code
+                 LEFT JOIN adm_ems_obj_model m ON r.device_model = m.model_code
+                 LEFT JOIN adm_ems_subsystem s ON r.subsystem_code = s.system_code
+    </sql>
+
+    <select id="selectById" parameterType="Long" resultMap="AlarmRuleResult">
+        <include refid="selectVo"/>
+        WHERE r.id = #{id}
+    </select>
+
+    <select id="selectByRuleCode" parameterType="String" resultMap="AlarmRuleResult">
+        <include refid="selectVo"/>
+        WHERE r.rule_code = #{ruleCode}
+    </select>
+
+    <select id="selectList" parameterType="com.ruoyi.ems.domain.AlarmRule" resultMap="AlarmRuleResult">
+        <include refid="selectVo"/>
+        <where>
+            <if test="ruleCode != null and ruleCode != ''">AND r.rule_code = #{ruleCode}</if>
+            <if test="ruleName != null and ruleName != ''">AND r.rule_name LIKE CONCAT('%', #{ruleName}, '%')</if>
+            <if test="areaCode != null and areaCode != '' and areaCode != '-1'">AND r.area_code = #{areaCode}</if>
+            <if test="targetType != null">AND r.target_type = #{targetType}</if>
+            <if test="deviceModel != null and deviceModel != ''">AND r.device_model = #{deviceModel}</if>
+            <if test="attrKey != null and attrKey != ''">AND r.attr_key = #{attrKey}</if>
+            <if test="checkType != null">AND r.check_type = #{checkType}</if>
+            <if test="alarmLevel != null">AND r.alarm_level = #{alarmLevel}</if>
+            <if test="alarmCode != null and alarmCode != ''">AND r.alarm_code = #{alarmCode}</if>
+            <if test="enabled != null">AND r.enabled = #{enabled}</if>
+            <if test="useForInspection != null">AND r.use_for_inspection = #{useForInspection}</if>
+            <if test="useForRealtime != null">AND r.use_for_realtime = #{useForRealtime}</if>
+            <if test="subsystemCode != null and subsystemCode != ''">AND r.subsystem_code = #{subsystemCode}</if>
+        </where>
+        ORDER BY r.rule_order, r.create_time DESC
+    </select>
+
+    <!-- 查询适用于实时监测的规则 -->
+    <select id="selectForRealtime" parameterType="com.ruoyi.ems.domain.AlarmRule" resultMap="AlarmRuleResult">
+        <include refid="selectVo"/>
+        WHERE r.enabled = 1 AND r.use_for_realtime = 1
+        <if test="deviceModel != null and deviceModel != ''">
+            AND (r.device_model IS NULL OR r.device_model = '' OR r.device_model = #{deviceModel})
+        </if>
+        <if test="attrKey != null and attrKey != ''">
+            AND r.attr_key = #{attrKey}
+        </if>
+        ORDER BY r.rule_order
+    </select>
+
+    <!-- 查询适用于巡检的规则 -->
+    <select id="selectForInspection" parameterType="com.ruoyi.ems.domain.AlarmRule" resultMap="AlarmRuleResult">
+        <include refid="selectVo"/>
+        WHERE r.enabled = 1 AND r.use_for_inspection = 1
+        <if test="deviceModel != null and deviceModel != ''">
+            AND (r.device_model IS NULL OR r.device_model = '' OR r.device_model = #{deviceModel})
+        </if>
+        ORDER BY r.rule_order
+    </select>
+
+    <!-- 根据分组查询 -->
+    <select id="selectByGroupCode" parameterType="String" resultMap="AlarmRuleResult">
+        <include refid="selectVo"/>
+        WHERE r.rule_code IN (
+        SELECT rel.rule_code FROM adm_op_alarm_rule_group_rel rel WHERE rel.group_code = #{groupCode}
+        )
+        ORDER BY r.rule_order
+    </select>
+
+    <insert id="insert" parameterType="com.ruoyi.ems.domain.AlarmRule" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO adm_op_alarm_rule (
+            rule_code, rule_name, area_code, target_type, target_scope, device_model,
+            attr_key, attr_name, check_type, operator, threshold_value, threshold_min, threshold_max,
+            alarm_level, alarm_code, alarm_msg_template, recovery_check, recovery_threshold,
+            trigger_count, trigger_interval, cool_down, enabled, use_for_inspection, use_for_realtime,
+            subsystem_code, rule_order, description, create_by, create_time
+        ) VALUES (
+                     #{ruleCode}, #{ruleName}, #{areaCode}, #{targetType}, #{targetScope}, #{deviceModel},
+                     #{attrKey}, #{attrName}, #{checkType}, #{operator}, #{thresholdValue}, #{thresholdMin}, #{thresholdMax},
+                     #{alarmLevel}, #{alarmCode}, #{alarmMsgTemplate}, #{recoveryCheck}, #{recoveryThreshold},
+                     #{triggerCount}, #{triggerInterval}, #{coolDown}, #{enabled}, #{useForInspection}, #{useForRealtime},
+                     #{subsystemCode}, #{ruleOrder}, #{description}, #{createBy}, NOW()
+                 )
+    </insert>
+
+    <update id="update" parameterType="com.ruoyi.ems.domain.AlarmRule">
+        UPDATE adm_op_alarm_rule
+        <set>
+            <if test="ruleName != null and ruleName != ''">rule_name = #{ruleName},</if>
+            <if test="areaCode != null">area_code = #{areaCode},</if>
+            <if test="targetType != null">target_type = #{targetType},</if>
+            <if test="targetScope != null">target_scope = #{targetScope},</if>
+            <if test="deviceModel != null">device_model = #{deviceModel},</if>
+            <if test="attrKey != null">attr_key = #{attrKey},</if>
+            <if test="attrName != null">attr_name = #{attrName},</if>
+            <if test="checkType != null">check_type = #{checkType},</if>
+            <if test="operator != null">operator = #{operator},</if>
+            <if test="thresholdValue != null">threshold_value = #{thresholdValue},</if>
+            <if test="thresholdMin != null">threshold_min = #{thresholdMin},</if>
+            <if test="thresholdMax != null">threshold_max = #{thresholdMax},</if>
+            <if test="alarmLevel != null">alarm_level = #{alarmLevel},</if>
+            <if test="alarmCode != null">alarm_code = #{alarmCode},</if>
+            <if test="alarmMsgTemplate != null">alarm_msg_template = #{alarmMsgTemplate},</if>
+            <if test="recoveryCheck != null">recovery_check = #{recoveryCheck},</if>
+            <if test="recoveryThreshold != null">recovery_threshold = #{recoveryThreshold},</if>
+            <if test="triggerCount != null">trigger_count = #{triggerCount},</if>
+            <if test="triggerInterval != null">trigger_interval = #{triggerInterval},</if>
+            <if test="coolDown != null">cool_down = #{coolDown},</if>
+            <if test="enabled != null">enabled = #{enabled},</if>
+            <if test="useForInspection != null">use_for_inspection = #{useForInspection},</if>
+            <if test="useForRealtime != null">use_for_realtime = #{useForRealtime},</if>
+            <if test="subsystemCode != null">subsystem_code = #{subsystemCode},</if>
+            <if test="ruleOrder != null">rule_order = #{ruleOrder},</if>
+            <if test="description != null">description = #{description},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            update_time = NOW()
+        </set>
+        WHERE id = #{id}
+    </update>
+
+    <update id="updateEnabled">
+        UPDATE adm_op_alarm_rule SET enabled = #{enabled}, update_time = NOW()
+        WHERE rule_code = #{ruleCode}
+    </update>
+
+    <update id="batchUpdateEnabled">
+        UPDATE adm_op_alarm_rule SET enabled = #{enabled}, update_time = NOW()
+        WHERE rule_code IN
+        <foreach item="ruleCode" collection="ruleCodes" open="(" separator="," close=")">
+            #{ruleCode}
+        </foreach>
+    </update>
+
+    <delete id="deleteByIds" parameterType="Long">
+        DELETE FROM adm_op_alarm_rule WHERE id IN
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 0 - 254
ems/ems-core/src/main/resources/mapper/ems/OpAlarmMapper.xml

@@ -1,254 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE mapper
-        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.ruoyi.ems.mapper.OpAlarmMapper">
-
-    <resultMap type="com.ruoyi.ems.domain.OpAlarm" id="AdmOpAlarmResult">
-        <result property="id" column="id"/>
-        <result property="areaCode" column="area_code"/>
-        <result property="areaName" column="area_name"/>
-        <result property="areaShortName" column="area_short_name"/>
-        <result property="objType" column="obj_type"/>
-        <result property="objCode" column="obj_code"/>
-        <result property="alarmDate" column="alarm_date"/>
-        <result property="alarmTime" column="alarm_time"/>
-        <result property="alarmCode" column="alarm_code"/>
-        <result property="subSystemName" column="sub_system_name"/>
-        <result property="systemCode" column="system_code"/>
-        <result property="objName" column="obj_name"/>
-        <result property="alarmMsg" column="alarm_msg"/>
-        <result property="alarmType" column="alarm_type"/>
-        <result property="alarmState" column="alarm_state"/>
-    </resultMap>
-
-    <sql id="selectOpAlarmVo">
-        select
-            id, area_code, obj_type, obj_code, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state, obj_name, system_code
-        from
-            adm_op_alarm alarm
-    </sql>
-
-    <select id="selectAdmOpAlarmList" parameterType="com.ruoyi.ems.domain.OpAlarm" resultMap="AdmOpAlarmResult">
-        select
-            alarm.id, alarm.area_code, a.area_name, a.short_name as area_short_name, alarm.obj_type, alarm.obj_code, alarm.alarm_date, alarm.alarm_time, alarm.alarm_code, alarm.alarm_msg, alarm.alarm_type, alarm.alarm_state, alarm.obj_name, sub.system_name sub_system_name, alarm.system_code
-        from
-            adm_op_alarm alarm
-            left join adm_ems_subsystem sub on sub.system_code = alarm.system_code
-            left join adm_area a on alarm.area_code = a.area_code
-        <where>
-            <if test="areaCode != null and areaCode != '' and areaCode != '-1'">and alarm.area_code = #{areaCode}</if>
-            <if test="objType != null ">and alarm.obj_type = #{objType}</if>
-            <if test="objCode != null  and objCode != ''">and alarm.obj_code = #{objCode}</if>
-            <if test="alarmCode != null  and alarmCode != ''">and alarm.alarm_code = #{alarmCode}</if>
-            <if test="objName !=null and objName!=''">and alarm.obj_name like concat('%', #{objName}, '%')</if>
-            <if test="systemCode !=null and systemCode != ''">and alarm.system_code = #{systemCode}</if>
-            <if test="alarmMsg != null  and alarmMsg != ''">and alarm.alarm_msg like concat('%', #{alarmMsg}, '%')</if>
-            <if test="alarmType != null ">and alarm.alarm_type = #{alarmType}</if>
-            <if test="alarmState != null ">and alarm.alarm_state = #{alarmState}</if>
-            <if test="startRecTime != null  and startRecTime != '' and endRecTime != null and endRecTime !=''">
-                and alarm.alarm_time &gt;= #{startRecTime} and alarm.alarm_time &lt;= #{endRecTime}
-            </if>
-            <if test="alarmStateList != null and alarmStateList.size() > 0">
-                and alarm.alarm_state in
-                <foreach collection="alarmStateList" item="alarmState" open="(" close=")" separator=",">
-                    #{alarmState}
-                </foreach>
-            </if>
-        </where>
-        order by alarm.alarm_time desc
-    </select>
-
-    <select id="selectAdmOpAlarmById" parameterType="Long" resultMap="AdmOpAlarmResult">
-        <include refid="selectOpAlarmVo"/>
-        where alarm.id = #{id}
-    </select>
-
-    <insert id="insertAdmOpAlarm" parameterType="com.ruoyi.ems.domain.OpAlarm" useGeneratedKeys="true"
-            keyProperty="id">
-        insert into adm_op_alarm
-        <trim prefix="(" suffix=")" suffixOverrides=",">
-            <if test="areaCode != null and areaCode != ''">area_code,</if>
-            <if test="objType != null">obj_type,</if>
-            <if test="objCode != null and objCode != ''">obj_code,</if>
-            <if test="objName != null and objName!=''">obj_name,</if>
-            <if test="alarmDate != null">alarm_date,</if>
-            <if test="alarmTime != null">alarm_time,</if>
-            <if test="systemCode !=null">system_code,</if>
-            <if test="alarmCode != null">alarm_code,</if>
-            <if test="alarmMsg != null">alarm_msg,</if>
-            <if test="alarmType != null">alarm_type,</if>
-            <if test="alarmState != null">alarm_state,</if>
-        </trim>
-        <trim prefix="values (" suffix=")" suffixOverrides=",">
-            <if test="areaCode != null and areaCode != ''">#{areaCode},</if>
-            <if test="objType != null">#{objType},</if>
-            <if test="objCode != null and objCode != ''">#{objCode},</if>
-            <if test="objName != null and objName!=''">#{objName},</if>
-            <if test="alarmDate != null">#{alarmDate},</if>
-            <if test="alarmTime != null">#{alarmTime},</if>
-            <if test="systemCode !=null">#{systemCode},</if>
-            <if test="alarmCode != null">#{alarmCode},</if>
-            <if test="alarmMsg != null">#{alarmMsg},</if>
-            <if test="alarmType != null">#{alarmType},</if>
-            <if test="alarmState != null">#{alarmState},</if>
-        </trim>
-    </insert>
-
-    <update id="updateAdmOpAlarm" parameterType="com.ruoyi.ems.domain.OpAlarm">
-        update adm_op_alarm
-        <trim prefix="SET" suffixOverrides=",">
-            <if test="areaCode != null and areaCode != ''">area_code = #{areaCode},</if>
-            <if test="objType != null">obj_type = #{objType},</if>
-            <if test="objCode != null and objCode != ''">obj_code = #{objCode},</if>
-            <if test="objName != null and objName!=''">obj_name = #{objName},</if>
-            <if test="alarmDate != null">alarm_date = #{alarmDate},</if>
-            <if test="alarmTime != null">alarm_time = #{alarmTime},</if>
-            <if test="alarmCode != null">alarm_code = #{alarmCode},</if>
-            <if test="systemCode !=null">system_code = #{systemCode},</if>
-            <if test="alarmMsg != null">alarm_msg = #{alarmMsg},</if>
-            <if test="alarmType != null">alarm_type = #{alarmType},</if>
-            <if test="alarmState != null">alarm_state = #{alarmState},</if>
-        </trim>
-        where id = #{id}
-    </update>
-
-    <delete id="deleteAdmOpAlarmById" parameterType="Long">
-        delete
-        from adm_op_alarm
-        where id = #{id}
-    </delete>
-
-    <delete id="deleteAdmOpAlarmByIds" parameterType="String">
-        delete from adm_op_alarm where id in
-        <foreach item="id" collection="array" open="(" separator="," close=")">
-            #{id}
-        </foreach>
-    </delete>
-
-    <select id="qryAlarmTypeIndex" resultType="java.util.Map">
-        SELECT count(*) cnt,
-        alarm_type alarmType
-        FROM adm_op_alarm
-        <where>
-            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and area_code = #{areaCode}</if>
-            <if test="startTime != null  and startTime != '' and endTime != null and endTime !=''">
-                and alarm_time &gt;= #{startTime} and alarm_time &lt;= #{endTime}
-            </if>
-        </where>
-        GROUP BY alarm_type
-    </select>
-
-    <select id="qryAlarmTypeIndexByDate" parameterType="OpAlarm" resultType="java.util.Map">
-        SELECT count(*) cnt,
-        alarm_type alarmType
-        FROM adm_op_alarm
-        <where>
-            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and area_code = #{areaCode}</if>
-            <if test="startRecTime != null and startRecTime != ''">
-                and alarm_date like CONCAT(#{startRecTime},'%')
-            </if>
-        </where>
-        GROUP BY alarm_type
-    </select>
-
-    <select id="qryAlarmTypeIndexDay" resultType="java.util.Map">
-        SELECT count(*)                         cnt,
-               alarm_type                       alarmType,
-               DATE_FORMAT(alarm_time, '%H:00') dateIndex
-        FROM adm_op_alarm
-        <where>
-            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and area_code = #{areaCode}</if>
-            <if test="alarmDate != null and alarmDate != ''">and alarm_date = #{alarmDate}</if>
-        </where>
-        GROUP BY DATE_FORMAT(alarm_time, '%H:00'), alarm_type
-        ORDER BY alarm_type, dateIndex
-    </select>
-
-    <select id="qryAlarmTypeIndexMonth" resultType="java.util.Map">
-        SELECT count(*)                               cnt,
-               alarm_type                             alarmType,
-               DATE_FORMAT(alarm_date, '%Y-%m-%d') AS dateIndex
-        FROM adm_op_alarm
-        <where>
-            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and area_code = #{areaCode}</if>
-            <if test="alarmDate != null and alarmDate != ''">and alarm_date &gt;= #{alarmDate}</if>
-        </where>
-        GROUP BY alarm_type, alarm_date
-        ORDER BY alarm_type, dateIndex
-    </select>
-
-    <select id="qryAlarmTypeIndexYear" resultType="java.util.Map">
-        SELECT count(*)                            cnt,
-               alarm_type                          alarmType,
-               DATE_FORMAT(alarm_date, '%Y-%m') AS dateIndex
-        FROM adm_op_alarm
-        <where>
-            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and area_code = #{areaCode}</if>
-            <if test="alarmDate != null and alarmDate != ''">and alarm_date &gt;= #{alarmDate}</if>
-        </where>
-        GROUP BY alarm_type, DATE_FORMAT(alarm_date, '%Y-%m')
-        ORDER BY alarm_type, dateIndex
-    </select>
-
-    <select id="qrySubSysIndexDay" resultType="java.util.Map">
-        SELECT count(*)                         cnt,
-               alarm.system_code                systemCode,
-               sub.system_name                  systemName,
-               DATE_FORMAT(alarm_time, '%H:00') dateIndex
-        FROM adm_op_alarm alarm
-                 left join adm_ems_subsystem sub
-                           on alarm.system_code = sub.system_code
-        <where>
-            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and area_code = #{areaCode}</if>
-            <if test="alarmDate != null and alarmDate != ''">and alarm_date = #{alarmDate}</if>
-        </where>
-        GROUP BY DATE_FORMAT(alarm_time, '%H:00'), alarm.system_code
-        ORDER BY alarm.system_code, dateIndex
-    </select>
-
-    <select id="qrySubSysIndexMonth" resultType="java.util.Map">
-        SELECT count(*)                               cnt,
-               alarm.system_code                      systemCode,
-               sub.system_name                        systemName,
-               DATE_FORMAT(alarm_date, '%Y-%m-%d') AS dateIndex
-        FROM adm_op_alarm alarm
-                 left join adm_ems_subsystem sub
-                           on alarm.system_code = sub.system_code
-        <where>
-            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and area_code = #{areaCode}</if>
-            <if test="alarmDate != null and alarmDate != ''">and alarm_date &gt;= #{alarmDate}</if>
-        </where>
-        GROUP BY alarm.system_code, alarm_date
-        ORDER BY alarm.system_code, dateIndex
-    </select>
-
-    <select id="qrySubSysIndexYear" resultType="java.util.Map">
-        SELECT count(*)                            cnt,
-               alarm.system_code                   systemCode,
-               sub.system_name                     systemName,
-               DATE_FORMAT(alarm_date, '%Y-%m') AS dateIndex
-        FROM adm_op_alarm alarm
-                 left join adm_ems_subsystem sub
-                           on alarm.system_code = sub.system_code
-        <where>
-            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and area_code = #{areaCode}</if>
-            <if test="alarmDate != null and alarmDate != ''">and alarm_date &gt;= #{alarmDate}</if>
-        </where>
-        GROUP BY alarm.system_code,
-                 DATE_FORMAT(alarm_date, '%Y-%m')
-        ORDER BY alarm.system_code,
-                 dateIndex
-    </select>
-    <select id="cntHandledAlarmByDate" parameterType="OpAlarm"  resultType="java.util.Map">
-        SELECT
-        count(id) cnt,
-        sum( CASE WHEN aoa.alarm_state = 1 THEN 1 ELSE 0 END) handledCnt
-        from
-        adm_op_alarm aoa
-        <where>
-            <if test="areaCode != null and areaCode != '' and areaCode !='-1'">and area_code = #{areaCode}</if>
-            <if test="startRecTime != null and startRecTime != ''">and aoa.alarm_date like CONCAT(#{startRecTime}, '%')</if>
-        </where>
-    </select>
-</mapper>

+ 26 - 27
ems/sql/ems_init_data_ctfwq.sql

@@ -2198,35 +2198,34 @@ INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `mo
 INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `model_code`, `man_facturer`, `contact_person`, `contact_number`, `maintainer_person`, `maintainer_number`, `descr`) VALUES ('SYS_GF', '光伏系统', '光伏', 'M_E5_SYS_PHOTOVOLTAIC', '古瑞瓦特', '张', '1380000', '李工', '123123', NULL);
 INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `model_code`, `man_facturer`, `contact_person`, `contact_number`, `maintainer_person`, `maintainer_number`, `descr`) VALUES ('SYS_CD', '充电桩系统', '充电桩', 'M_W2_SYS_CHARGING', '科士达', NULL, NULL, NULL, NULL, NULL);
 
--- 台账数据
-INSERT INTO `adm_ems_device_rbook` (`record_code`, `area_code`, `obj_type`, `obj_code`, `obj_name`, `record_time`, `ins_location`, `maintain_title`, `maintain_content`, `maintain_person`) VALUES ('TZ-20240901001', '321283124S3001', 1, 'W201', '北区-电网', '2024-09-01 10:32:00', '北区-广场', '北区广场变压器维护', '执行例行维护', '李大航');
-INSERT INTO `adm_ems_device_rbook` (`record_code`, `area_code`, `obj_type`, `obj_code`, `obj_name`, `record_time`, `ins_location`, `maintain_title`, `maintain_content`, `maintain_person`) VALUES ('TZ-20240901002', '321283124S3001', 2, 'Z010-R101-001', '北区-开水炉', '2024-09-10 17:34:56', '北区/综合楼/一楼开水间', '开水炉除垢', '执行开水炉除垢', '王凯');
 
 -- 告警策略
-INSERT INTO `adm_op_alarm_policy` (`area_code`, `policy_code`, `policy_name`, `alarm_obj_type`, `alarm_obj_index`, `alarm_rule_type`, `alarm_threshold_value`, `alarm_code`, `alarm_msg`, `alarm_type`) VALUES ('321283124S3001', '10001', '负荷监测', 2, 'power', 1, 2000, '1001', '负荷超限', 3);
-INSERT INTO `adm_op_alarm_policy` (`area_code`, `policy_code`, `policy_name`, `alarm_obj_type`, `alarm_obj_index`, `alarm_rule_type`, `alarm_threshold_value`, `alarm_code`, `alarm_msg`, `alarm_type`) VALUES ('321283124S3001', '10002', '电压检测', 2, 'voltage', 2, 210, '1002', '低压警报', 2);
-INSERT INTO `adm_op_alarm_policy` (`area_code`, `policy_code`, `policy_name`, `alarm_obj_type`, `alarm_obj_index`, `alarm_rule_type`, `alarm_threshold_value`, `alarm_code`, `alarm_msg`, `alarm_type`) VALUES ('321283124S3002', '20001', '电压检测', 2, 'power', 1, 2000, '1001', '负荷超限', 3);
-INSERT INTO `adm_op_alarm_policy` (`area_code`, `policy_code`, `policy_name`, `alarm_obj_type`, `alarm_obj_index`, `alarm_rule_type`, `alarm_threshold_value`, `alarm_code`, `alarm_msg`, `alarm_type`) VALUES ('321283124S3002', '20002', '电压检测', 2, 'voltage', 2, 210, '1002', '低压警报', 2);
-
--- 告警数据
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3001', 'SYS_GF', 1, 'B-107', '特色小吃', '2024-11-14', '2024-11-14 09:12:23', '00001', '北区光伏告警', 1, 2);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3001', 'SYS_CN', 1, 'B-101', '开水泡面间', '2024-11-14', '2024-11-14 20:15:23', '00002', '北区开水间配电告警', 1, 1);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3001', 'SYS_BA', 2, 'Z101', '照明灯', '2024-11-13', '2024-11-13 12:15:21', '00003', '北区照明设施告警', 2, 1);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3001', 'SYS_BA', 3, 'Z010-R103-001', '空调', '2024-11-12', '2024-11-12 09:17:49', '0004', '北区公共区域空调告警', 2, 2);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3001', 'SYS_CN', 2, 'E501', '北区-光伏', '2024-11-15', '2024-11-15 15:02:00', 'E003', '未知告警', 2, 2);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3002', 'SYS_CN', 2, 'E502', '南区-光伏', '2024-11-15', '2024-11-15 15:03:00', 'Ecc01', '未知告警', 3, 1);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3001', 'SYS_BA', 4, 'D-B-1001', '综合楼配电间(常泰高速服务区(北区))', '2024-11-15', '2024-11-15 09:33:00', 'E0003', '电流阈值告警', 2, 2);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3001', 'SYS_BA', 4, 'D-B-1001', '综合楼配电间(常泰高速服务区(北区))', '2025-06-15', '2025-06-15 15:21:00', 'E003', '配电柜温度告警', 1, 1);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3001', 'SYS_BA', 4, 'D-B-1001', '综合楼配电间(常泰高速服务区(北区))', '2025-06-24', '2025-06-24 15:21:00', 'E003', '配电柜温度告警', 1, 2);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3002', 'SYS_CN', 2, 'E502', '南区-光伏', '2025-06-24', '2025-06-24 15:03:00', 'Ecc01', '未知告警', 3, 1);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3001', 'SYS_BA', 3, 'Z010-R103-001', '空调', '2025-06-24', '2025-06-24 09:17:49', '0004', '北区公共区域空调告警', 2, 2);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3001', 'SYS_CN', 1, 'B-101', '开水泡面间', '2025-05-14', '2025-05-14 20:15:23', '00002', '北区开水间配电告警', 1, 1);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3003', 'SYS_BA', 4, 'D-B-1001', '综合楼配电间(常泰高速服务区(北区))', '2024-11-15', '2024-11-15 09:33:00', 'E0003', '电流阈值告警', 2, 2);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3003', 'SYS_BA', 4, 'D-B-1001', '综合楼配电间(常泰高速服务区(北区))', '2025-06-15', '2025-06-15 15:21:00', 'E003', '配电柜温度告警', 1, 1);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3003', 'SYS_BA', 4, 'D-B-1001', '综合楼配电间(常泰高速服务区(北区))', '2025-06-24', '2025-06-24 15:21:00', 'E003', '配电柜温度告警', 1, 2);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3003', 'SYS_CN', 2, 'E502', '主路-光伏', '2025-06-24', '2025-06-24 15:03:00', 'Ecc01', '未知告警', 3, 1);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3003', 'SYS_BA', 3, 'Z010-R103-001', '空调', '2025-06-24', '2025-06-24 09:17:49', '0004', '北区公共区域空调告警', 2, 2);
-INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3003', 'SYS_CN', 1, 'B-101', '开水泡面间', '2025-05-14', '2025-05-14 20:15:23', '00002', '北区开水间配电告警', 1, 1);
+-- 通用设备离线规则
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `target_type`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_value`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `cool_down`, `use_for_inspection`, `use_for_realtime`, `description`) VALUES  ('RULE_DEVICE_OFFLINE', '设备离线告警', 2, 'deviceStatus', '设备状态', 4, 'EQ', '0', 2, 'DEVICE_OFFLINE', '设备【${targetName}】已离线,位置:${location}', 600, 1, 1, '检测设备是否离线');
+
+-- 电力保护装置规则
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_min`, `threshold_max`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `cool_down`, `description`) VALUES ('RULE_ELEC_FREQ_LOW', '频率过低告警', 'M_W4_DEV_ELEC_MONITOR_BHZZ', 'Fr', '频率', 1, 'LT', NULL, '49.5', 2, 'ELEC_FREQ_LOW', '设备【${targetName}】频率过低,当前值:${attrValue}Hz,阈值:49.5Hz', 300, '电网频率低于49.5Hz告警');
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_min`, `threshold_max`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `cool_down`, `description`) VALUES ('RULE_ELEC_FREQ_HIGH', '频率过高告警', 'M_W4_DEV_ELEC_MONITOR_BHZZ', 'Fr', '频率', 1, 'GT', '50.5', NULL, 2, 'ELEC_FREQ_HIGH', '设备【${targetName}】频率过高,当前值:${attrValue}Hz,阈值:50.5Hz', 300, '电网频率高于50.5Hz告警');
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_min`, `threshold_max`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `cool_down`, `description`) VALUES ('RULE_ELEC_VOLTAGE_LOW', '电压过低告警', 'M_W4_DEV_ELEC_MONITOR_BHZZ', 'Uab', 'AB线电压', 1, 'LT', NULL, '9500', 1, 'ELEC_VOLTAGE_LOW', '设备【${targetName}】电压过低,当前值:${attrValue}V', 300, '10kV线电压低于9.5kV告警');
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_min`, `threshold_max`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `cool_down`, `description`) VALUES ('RULE_ELEC_VOLTAGE_HIGH', '电压过高告警', 'M_W4_DEV_ELEC_MONITOR_BHZZ', 'Uab', 'AB线电压', 1, 'GT', '11000', NULL, 2, 'ELEC_VOLTAGE_HIGH', '设备【${targetName}】电压过高,当前值:${attrValue}V', 300, '10kV线电压高于11kV告警');
+
+-- 新风设备规则
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_value`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `description`) VALUES ('RULE_XF_FAULT', '新风机故障告警', 'M_Z020_DEV_BA_XF', 'xfFault', '新风机故障', 2, 'EQ', '1', 2, 'HVAC_XF_FAULT', '新风机【${targetName}】发生故障,位置:${location}', '新风机故障状态告警');
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_value`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `description`) VALUES ('RULE_XF_FILTER_ALARM', '新风滤网压差告警', 'M_Z020_DEV_BA_XF', 'lwDpAlarm', '滤网压差报警', 2, 'EQ', '1', 1, 'HVAC_FILTER_ALARM', '新风机【${targetName}】滤网需要清洗或更换', '滤网堵塞告警');
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_value`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `description`) VALUES ('RULE_XF_ANTIFREEZE', '新风防冻告警', 'M_Z020_DEV_BA_XF', 'afAlarm', '防冻开关报警', 2, 'EQ', '1', 3, 'HVAC_ANTIFREEZE', '新风机【${targetName}】触发防冻保护,请立即检查!', '防冻保护告警');
+
+-- 水箱规则
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_min`, `threshold_max`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `description`) VALUES ('RULE_WT_LEVEL_LOW', '水箱低液位告警', 'M_Z020_DEV_BA_WT', 'tankLevel', '水箱液位', 1, 'LT', NULL, '20', 2, 'WT_LEVEL_LOW', '水箱【${targetName}】液位过低,当前:${attrValue}%', '水箱液位低于20%告警');
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_min`, `threshold_max`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `description`) VALUES('RULE_WT_LEVEL_HIGH', '水箱高液位告警', 'M_Z020_DEV_BA_WT', 'tankLevel', '水箱液位', 1, 'GT', '95', NULL, 1, 'WT_LEVEL_HIGH', '水箱【${targetName}】液位过高,当前:${attrValue}%', '水箱液位高于95%告警');
+
+-- 充电桩规则
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_value`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `description`) VALUES ('RULE_CHARGING_OFFLINE', '充电桩离线告警', 'M_W2_DEV_CHARGING_HOST', 'deviceStatus', '设备状态', 4, 'EQ', '0', 2, 'CHARGING_OFFLINE', '充电桩【${targetName}】已离线,位置:${location}', '充电桩主机离线');
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_value`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `description`) VALUES ('RULE_CHARGING_GUN_FAULT', '充电枪故障告警', 'M_W2_DEV_CHARGING_PILE', 'faultCode', '故障代码', 3, 'NE', '0', 2, 'CHARGING_GUN_FAULT', '充电枪【${targetName}】发生故障,故障码:${attrValue}', '充电枪故障');
+
+-- 照明系统规则
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_min`, `threshold_max`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `description`) VALUES ('RULE_LIGHT_VOLTAGE_LOW', '照明电压过低', 'M_Z010_DEV_SQUARE_LIGHT', 'voltage', '电压', 1, 'LT', NULL, '180', 1, 'LIGHT_VOLTAGE_LOW', '照明设备【${targetName}】电压过低,当前:${attrValue}V', '照明电压低于180V');
+INSERT INTO `adm_op_alarm_rule` (`rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `operator`, `threshold_min`, `threshold_max`, `alarm_level`, `alarm_code`, `alarm_msg_template`, `description`) VALUES ('RULE_LIGHT_TEMP_HIGH', '照明温度过高', 'M_Z010_DEV_SQUARE_LIGHT', 'temperature', '设备温度', 1, 'GT', '70', NULL, 2, 'LIGHT_TEMP_HIGH', '照明设备【${targetName}】温度过高,当前:${attrValue}℃', '设备温度超过70℃');
+
 
 -- 巡检计划
 -- =====================================================

+ 120 - 62
ems/sql/ems_server.sql

@@ -1217,48 +1217,6 @@ INSERT INTO `adm_ems_carbon_account_cfg` (`cfg_code`, `definition`, `emission_ac
 INSERT INTO `adm_ems_carbon_account_cfg` (`cfg_code`, `definition`, `emission_activity`, `emission_source`, `gas_category`, `factor_value`) VALUES ('indirect.gas.electricity', '间接温室气体排放', '外购电力生产消耗排放', '外购电力使用', 'CO₂', null);
 
 
--- ----------------------------
--- 能源设施告警表策略表
--- ----------------------------
-drop table if exists adm_op_alarm_policy;
-create table adm_op_alarm_policy  (
-  `id`                      bigint(20)      not null auto_increment      comment '序号',
-  `area_code`               varchar(32)     not null                     comment '园区代码',
-  `policy_code`             varchar(16)     not null                     comment '策略代码',
-  `policy_name`             varchar(32)     not null                     comment '策略名称',
-  `alarm_obj_type`          int             not null                     comment '告警对象类型',
-  `alarm_obj_index`         varchar(64)     not null                     comment '告警对象指标',
-  `alarm_rule_type`         int             default null                 comment '告警规则 1:大于 2:小于',
-  `alarm_threshold_value`   double          default null                 comment '告警阈值',
-  `alarm_code`              varchar(64)     default null                 comment '告警代码',
-  `alarm_msg`               varchar(200)    default null                 comment '告警描述',
-  `alarm_type`              int             default null                 comment '告警类型 1:一般告警 2:重要告警 3:紧急告警 4:恢复告警 5:诊断告警 6:其他告警',
-  primary key (`id`)
-) engine=innodb auto_increment=1 comment = '能源设施告警表策略表';
-
-
--- ----------------------------
--- 能源设施告警表
--- ----------------------------
-drop table if exists adm_op_alarm;
-create table adm_op_alarm  (
-  `id`                  bigint(20)      not null auto_increment      comment '序号',
-  `area_code`           varchar(32)     not null                     comment '园区代码',
-  `system_code`         varchar(16)     default null                 comment '子系统代码',
-  `obj_type`            int             not null                     comment '对象类型 0:园区,1:区块,2:设施,3:设备',
-  `obj_code`            varchar(32)     not null                     comment '对象代码',
-  `obj_name`            varchar(128)    default null                 comment '对象名称',
-  `alarm_date`          date            default null                 comment '告警日期',
-  `alarm_time`          datetime        default null                 comment '告警时间',
-  `alarm_code`          varchar(64)     default null                 comment '告警代码',
-  `alarm_msg`           varchar(64)     default null                 comment '告警描述',
-  `alarm_type`          int             default null                 comment '告警类型 1:一般告警 2:重要告警 3:紧急告警 4:恢复告警 5:诊断告警 6:其他告警',
-  `alarm_state`         int             default null                 comment '告警状态 0-新增,1-处置中,2-已处置,3-消散',
-  primary key (`id`),
-  key inx_op_alarm_date(`alarm_date`)
-) engine=innodb auto_increment=1 comment = '能源设施告警表';
-
-
 -- 1. 巡检计划表(支持手动/自动巡检)
 DROP TABLE IF EXISTS adm_op_inspection_plan;
 CREATE TABLE adm_op_inspection_plan (
@@ -1362,7 +1320,124 @@ CREATE TABLE adm_op_inspection_report_detail (
     KEY `idx_device_code` (`device_code`)
 ) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='巡检报告明细表';
 
-                                                                                                                                                                                                          ('IP202501300002', 'IR004', 'CO2浓度检查', 'M_Z020_DEV_BA_XF', 'ppm', 'CO2浓度', 1, '0', '1000', NULL, 4, 1);
+
+-- ----------------------------
+-- 1. 告警规则表(核心配置表,可被巡检复用)
+-- ----------------------------
+DROP TABLE IF EXISTS adm_op_alarm_rule;
+CREATE TABLE adm_op_alarm_rule (
+    `id`                BIGINT(20)      NOT NULL AUTO_INCREMENT  COMMENT '序号',
+    `rule_code`         VARCHAR(64)     NOT NULL                 COMMENT '规则代码',
+    `rule_name`         VARCHAR(128)    NOT NULL                 COMMENT '规则名称',
+    `area_code`         VARCHAR(32)     DEFAULT NULL             COMMENT '适用区域代码(空则全局)',
+    `target_type`       TINYINT         NOT NULL DEFAULT 2       COMMENT '目标类型 0:区域 1:设施 2:设备',
+    `target_scope`      VARCHAR(1024)   DEFAULT NULL             COMMENT '目标范围(JSON数组,指定设备/设施代码,空则该类型全部)',
+    `device_model`      VARCHAR(64)     DEFAULT NULL             COMMENT '设备模型代码(空则不限)',
+    `attr_key`          VARCHAR(128)    NOT NULL                 COMMENT '监测属性标识',
+    `attr_name`         VARCHAR(256)    DEFAULT NULL             COMMENT '属性名称',
+    `check_type`        TINYINT         NOT NULL DEFAULT 1       COMMENT '检查类型 1:范围 2:等值 3:非空 4:在线状态 5:变化率',
+    `operator`          VARCHAR(16)     DEFAULT NULL             COMMENT '比较操作符 GT/GE/LT/LE/EQ/NE/RANGE/IN',
+    `threshold_value`   VARCHAR(128)    DEFAULT NULL             COMMENT '阈值(单值或JSON)',
+    `threshold_min`     VARCHAR(64)     DEFAULT NULL             COMMENT '最小阈值(范围检查)',
+    `threshold_max`     VARCHAR(64)     DEFAULT NULL             COMMENT '最大阈值(范围检查)',
+    `alarm_level`       TINYINT         NOT NULL DEFAULT 1       COMMENT '告警级别 1:一般 2:重要 3:紧急',
+    `alarm_code`        VARCHAR(64)     DEFAULT NULL             COMMENT '告警代码(用于分类)',
+    `alarm_msg_template` VARCHAR(512)   DEFAULT NULL             COMMENT '告警消息模板(支持变量替换)',
+    `recovery_check`    TINYINT         DEFAULT 1                COMMENT '是否检查恢复 0:否 1:是',
+    `recovery_threshold` VARCHAR(128)   DEFAULT NULL             COMMENT '恢复阈值(可与告警阈值不同,形成迟滞)',
+    `trigger_count`     INT             DEFAULT 1                COMMENT '触发次数(连续N次满足才告警)',
+    `trigger_interval`  INT             DEFAULT 0                COMMENT '触发间隔(秒,防抖)',
+    `cool_down`         INT             DEFAULT 300              COMMENT '冷却时间(秒,同一规则同一对象)',
+    `enabled`           TINYINT         DEFAULT 1                COMMENT '是否启用 0:禁用 1:启用',
+    `use_for_inspection` TINYINT        DEFAULT 1                COMMENT '是否用于巡检 0:否 1:是',
+    `use_for_realtime`  TINYINT         DEFAULT 1                COMMENT '是否用于实时监测 0:否 1:是',
+    `subsystem_code`    VARCHAR(32)     DEFAULT NULL             COMMENT '所属子系统代码',
+    `rule_order`        INT             DEFAULT 0                COMMENT '规则优先级(数值小优先)',
+    `description`       VARCHAR(512)    DEFAULT NULL             COMMENT '规则描述',
+    `create_by`         VARCHAR(64)     DEFAULT NULL             COMMENT '创建者',
+    `create_time`       DATETIME        DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_by`         VARCHAR(64)     DEFAULT NULL             COMMENT '更新者',
+    `update_time`       DATETIME        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `ux_rule_code` (`rule_code`),
+    KEY `idx_device_model` (`device_model`),
+    KEY `idx_area_code` (`area_code`),
+    KEY `idx_enabled` (`enabled`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='告警规则表';
+
+-- ----------------------------
+-- 2. 告警记录表(重构后)
+-- ----------------------------
+DROP TABLE IF EXISTS adm_op_alarm;
+CREATE TABLE adm_op_alarm (
+    `id`                BIGINT(20)      NOT NULL AUTO_INCREMENT  COMMENT '序号',
+    `alarm_id`          VARCHAR(64)     NOT NULL                 COMMENT '告警唯一标识',
+    `area_code`         VARCHAR(32)     NOT NULL                 COMMENT '区域代码',
+    `subsystem_code`    VARCHAR(32)     DEFAULT NULL             COMMENT '子系统代码',
+    `target_type`       TINYINT         NOT NULL                 COMMENT '目标类型 0:区域 1:设施 2:设备',
+    `target_code`       VARCHAR(64)     NOT NULL                 COMMENT '目标代码',
+    `target_name`       VARCHAR(128)    DEFAULT NULL             COMMENT '目标名称',
+    `device_model`      VARCHAR(64)     DEFAULT NULL             COMMENT '设备模型',
+    `device_model_name` VARCHAR(128)    DEFAULT NULL             COMMENT '设备模型名称',
+    `location`          VARCHAR(256)    DEFAULT NULL             COMMENT '位置信息',
+    `rule_code`         VARCHAR(64)     DEFAULT NULL             COMMENT '触发规则代码',
+    `rule_name`         VARCHAR(128)    DEFAULT NULL             COMMENT '触发规则名称',
+    `attr_key`          VARCHAR(128)    DEFAULT NULL             COMMENT '告警属性',
+    `attr_name`         VARCHAR(256)    DEFAULT NULL             COMMENT '属性名称',
+    `attr_value`        VARCHAR(256)    DEFAULT NULL             COMMENT '告警时属性值',
+    `threshold_value`   VARCHAR(128)    DEFAULT NULL             COMMENT '阈值',
+    `alarm_level`       TINYINT         NOT NULL DEFAULT 1       COMMENT '告警级别 1:一般 2:重要 3:紧急',
+    `alarm_code`        VARCHAR(64)     DEFAULT NULL             COMMENT '告警代码',
+    `alarm_msg`         VARCHAR(512)    DEFAULT NULL             COMMENT '告警消息',
+    `alarm_source`      TINYINT         DEFAULT 1                COMMENT '告警来源 1:实时监测 2:自动巡检 3:手动上报',
+    `source_ref`        VARCHAR(64)     DEFAULT NULL             COMMENT '来源关联(如巡检报告代码)',
+    `alarm_time`        DATETIME        NOT NULL                 COMMENT '告警时间',
+    `alarm_date`        DATE            NOT NULL                 COMMENT '告警日期(便于分区和查询)',
+    `alarm_status`      TINYINT         NOT NULL DEFAULT 0       COMMENT '告警状态 0:活动 1:已确认 2:处置中 3:已解决 4:已关闭 5:已恢复',
+    `confirm_time`      DATETIME        DEFAULT NULL             COMMENT '确认时间',
+    `confirm_by`        VARCHAR(64)     DEFAULT NULL             COMMENT '确认人',
+    `resolve_time`      DATETIME        DEFAULT NULL             COMMENT '解决时间',
+    `resolve_by`        VARCHAR(64)     DEFAULT NULL             COMMENT '解决人',
+    `resolve_remark`    VARCHAR(512)    DEFAULT NULL             COMMENT '解决备注',
+    `recovery_time`     DATETIME        DEFAULT NULL             COMMENT '自动恢复时间',
+    `duration`          INT             DEFAULT NULL             COMMENT '持续时长(秒)',
+    `repeat_count`      INT             DEFAULT 1                COMMENT '重复次数(聚合)',
+    `last_occur_time`   DATETIME        DEFAULT NULL             COMMENT '最后发生时间',
+    `is_notified`       TINYINT         DEFAULT 0                COMMENT '是否已通知 0:否 1:是',
+    `notify_time`       DATETIME        DEFAULT NULL             COMMENT '通知时间',
+    `create_by`         VARCHAR(64)     DEFAULT NULL             COMMENT '创建者',
+    `create_time`       DATETIME        DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_by`         VARCHAR(64)     DEFAULT NULL              COMMENT '更新者',
+    `update_time`       DATETIME        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `ux_alarm_id` (`alarm_id`),
+    KEY `idx_alarm_date` (`alarm_date`),
+    KEY `idx_alarm_time` (`alarm_time`),
+    KEY `idx_area_code` (`area_code`),
+    KEY `idx_target` (`target_type`, `target_code`),
+    KEY `idx_status` (`alarm_status`),
+    KEY `idx_level` (`alarm_level`),
+    KEY `idx_rule_code` (`rule_code`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='告警记录表';
+
+-- ----------------------------
+-- 3. 告警处置记录表(完整追溯)
+-- ----------------------------
+DROP TABLE IF EXISTS adm_op_alarm_handle;
+CREATE TABLE adm_op_alarm_handle (
+    `id`                BIGINT(20)      NOT NULL AUTO_INCREMENT  COMMENT '序号',
+    `alarm_id`          VARCHAR(64)     NOT NULL                 COMMENT '告警ID',
+    `handle_type`       TINYINT         NOT NULL                 COMMENT '处置类型 1:确认 2:派单 3:处置 4:关闭 5:备注',
+    `handle_content`    VARCHAR(1024)   DEFAULT NULL             COMMENT '处置内容',
+    `handle_result`     VARCHAR(512)    DEFAULT NULL             COMMENT '处置结果',
+    `handle_by`         VARCHAR(64)     NOT NULL                 COMMENT '处置人',
+    `handle_time`       DATETIME        NOT NULL                 COMMENT '处置时间',
+    `attachments`       VARCHAR(1024)   DEFAULT NULL             COMMENT '附件(JSON)',
+    `create_time`       DATETIME        DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_alarm_id` (`alarm_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='告警处置记录表';
+                                                                                                                                                                                                   ('IP202501300002', 'IR004', 'CO2浓度检查', 'M_Z020_DEV_BA_XF', 'ppm', 'CO2浓度', 1, '0', '1000', NULL, 4, 1);
 -- ----------------------------
 -- 设备台账表
 -- ----------------------------
@@ -1371,7 +1446,7 @@ create table adm_ems_device_ledger (
   `id`               bigint(20)      not null auto_increment      comment '序号',
   `record_code`      varchar(32)     not null                     comment '记录编号',
   `area_code`        varchar(32)     not null                     comment '园区代码',
-  `obj_type`         int             not null                     comment '对象类型 1:区域 2:设施 3:设备',
+  `obj_type`         int             not null                     comment '对象类型 2:设施 3:设备',
   `obj_code`         varchar(64)     not null                     comment '对象代码',
   `obj_name`         varchar(64)     default null                 comment '对象名称',
   `record_time`      timestamp       not null                     comment '日期 yyyy-MM-dd HH:mm:ss',
@@ -1382,7 +1457,7 @@ create table adm_ems_device_ledger (
   `create_time`      datetime        default CURRENT_TIMESTAMP    comment '创建时间',
   `update_time`      datetime        default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment '更新时间',
   primary key (`id`),
-  key inx_ems_device_rbook(`record_code`)
+  key inx_ems_device_ledger(`record_code`)
 ) engine=innodb auto_increment=1 comment = '设备台账表';
 
 
@@ -1710,23 +1785,6 @@ create table adm_ems_ca_meter_d (
 
 
 -- ----------------------------
--- 能源指标范围表
--- ----------------------------
-drop table if exists adm_ems_index_range;
-create table adm_ems_index_range (
-  `id`                        bigint(20)      not null auto_increment      comment '序号',
-  `obj_code`                  varchar(16)     not null                     comment '对象代码',
-  `obj_type`                  int             not null                     comment '对象类型',
-  `index_name`                varchar(64)     not null                     comment '指标名称',
-  `index_desc`                varchar(200)    default null                 comment '指标描述',
-  `index_upper_limit`         double          default null                 comment '指标上限',
-  `index_lower_limit`         double          default null                 comment '指标下限',
-  primary key (`id`),
-  key ux_ems_elec_pg_index(`obj_code`)
-) engine=innodb auto_increment=1 comment = '能源指标范围表';
-
-
--- ----------------------------
 -- 储能设施指标表
 -- ----------------------------
 drop table if exists adm_ems_elec_store_index;

+ 2 - 2
ems/sql/ems_sys_data.sql

@@ -57,8 +57,8 @@ insert into sys_menu values ('142',  '设备台账',       '4',    '2',  'device
 insert into sys_menu values ('143',  '设备告警',       '4',    '3',  'device-warn',         'devmgr/warn',   '', 1, 0, 'C', '0', '0',   'analysis:device',        'deviceanalyze',  'admin', sysdate(), '', null, '设备分析');
 insert into sys_menu values ('144',  '抄表管理',       '4',    '4',  'deviceMeter',         '',                       '', 1, 0, 'M', '0', '0',   'device:meter',           'meterReading',   'admin', sysdate(), '', null, '抄表管理');
 
-INSERT INTO sys_menu VALUES ('151',  '告警策略',       '5',    '1',  'warn-strategy',       'alarm/index',            '', 1, 0, 'C', '0', '0',    'warn:strategy', 'warnstrategy', 'admin', '2024-08-29 15:40:27', 'admin', '2024-08-29 16:01:10', '告警策略');
-INSERT INTO sys_menu VALUES ('152',  '告警列表',       '5',    '2',  'warn-list',           'alarm/alarm-info/index', '', 1, 0, 'C', '0', '0',    'warn:list', 'warnmsg', 'admin', '2024-08-29 15:40:27', 'admin', '2024-08-29 16:01:36', '告警策略');
+INSERT INTO sys_menu VALUES ('151',  '告警策略',       '5',    '1',  'warn-rule',           'alarm/rule/index',            '', 1, 0, 'C', '0', '0',    'warn:strategy', 'warnstrategy', 'admin', '2024-08-29 15:40:27', 'admin', '2024-08-29 16:01:10', '告警策略');
+INSERT INTO sys_menu VALUES ('152',  '告警列表',       '5',    '2',  'warn-list',           'alarm/list/index',            '', 1, 0, 'C', '0', '0',    'warn:list', 'warnmsg', 'admin', '2024-08-29 15:40:27', 'admin', '2024-08-29 16:01:36', '告警策略');
 INSERT INTO sys_menu VALUES ('153',  '巡检计划',       '5',    '3',  'oper-plan',           'inspection/plan/index',             '', 1, 0, 'C', '0', '0',    'oper-mgr:task', 'task', 'admin', '2024-08-29 15:40:27', 'admin', '2024-08-29 16:02:38', '巡检任务');
 insert into sys_menu values ('154',  '巡检报告',       '5',    '4',  'oper-report',         'inspection/report/index',      '', 1, 0, 'C', '0', '0',    'oper-mgr:report',        'note',           'admin', sysdate(), '', null, '巡检报告');
 insert into sys_menu values ('155',  '系统对接',       '5',    '5',  'adapter',          null, '', 1, 0, 'M', '0', '0', '',  'client',      'admin', sysdate(), '', null, '系统对接');