Forráskód Böngészése

feat(alarm): 添加告警等级和偏差率计算功能

- 在AlarmData实体类中新增alarmLevel和deviationRatio字段
- 实现AlarmLevelUtil工具类用于计算告警等级和偏差率
- 在各个设备服务的MessageHandler中集成告警等级计算逻辑
- 添加1级紧急告警的日志记录和特殊处理机制
- 更新MyBatis映射文件中的全限定类名引用
- 修改预警系统中的待办列表查询参数和归档逻辑
- 优化设备手机号获取工具的服务依赖注入方式
- 修复预警附件类型长度超限问题并添加截断处理
- 调整预警状态流转逻辑,完善退回草稿功能
- 更新多个微服务的应用配置以支持跨模块依赖
林仔 2 hete
szülő
commit
c07505a805
29 módosított fájl, 283 hozzáadás és 114 törlés
  1. 4 4
      environment-service/src/main/java/com/zksy/environment/EnvironmentApplication.java
  2. 2 1
      environment-service/src/main/java/com/zksy/environment/config/RSServerService.java
  3. 18 0
      environment-service/src/main/java/com/zksy/environment/utils/AlarmUtil.java
  4. 2 2
      firefighting-pressure-service/src/main/java/com/zksy/pressure/PressureApplication.java
  5. 14 1
      firefighting-pressure-service/src/main/java/com/zksy/pressure/utils/MessageHandler.java
  6. 2 2
      flammable-gas-service/src/main/java/com/zksy/gas/GasApplication.java
  7. 14 3
      flammable-gas-service/src/main/java/com/zksy/gas/utils/MessageHandler.java
  8. 2 2
      manhole-service/src/main/java/com/zksy/manhole/ManholeApplication.java
  9. 18 1
      manhole-service/src/main/java/com/zksy/manhole/utils/AlarmUtil.java
  10. 38 8
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/warning/EarlyWarningController.java
  11. 5 1
      pipe-network-service/zksy-system/pom.xml
  12. 8 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/domain/AlarmData.java
  13. 0 5
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/EquipmentBase.java
  14. 1 1
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/EquipmentBaseServiceImpl.java
  15. 1 1
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/warning/service/EarlyWarningService.java
  16. 36 19
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/warning/service/impl/EarlyWarningServiceImpl.java
  17. 12 37
      pipe-network-service/zksy-system/src/main/java/com/zksy/utils/DevicePhoneFetchUtil.java
  18. 4 4
      pipe-network-service/zksy-system/src/main/resources/mapper/base/AlarmAssignDetailMapper.xml
  19. 4 4
      pipe-network-service/zksy-system/src/main/resources/mapper/base/AlarmAssignListMapper.xml
  20. 0 1
      pipe-network-service/zksy-system/src/main/resources/mapper/warning/AlarmWarningRelMapper.xml
  21. 2 2
      radar-service/src/main/java/com/zksy/radar/RadarApplication.java
  22. 14 1
      radar-service/src/main/java/com/zksy/radar/utils/MessageHandler.java
  23. 2 2
      telemetry-service/src/main/java/com/zksy/telemetry/TelemetryApplication.java
  24. 2 2
      water-level-service/src/main/java/com/zksy/water/WaterLevelApplication.java
  25. 14 1
      water-level-service/src/main/java/com/zksy/water/utils/MessageHandler.java
  26. 2 0
      zk-api-service/src/main/java/com/zksy/api/config/AliyunSmsConfig.java
  27. 8 0
      zk-api-service/src/main/java/com/zksy/api/domain/AlarmData.java
  28. 2 0
      zk-api-service/src/main/java/com/zksy/api/service/WarningThresholdService.java
  29. 52 9
      zk-api-service/src/main/java/com/zksy/api/utils/SmsUtil.java

+ 4 - 4
environment-service/src/main/java/com/zksy/environment/EnvironmentApplication.java

@@ -9,12 +9,12 @@ import org.springframework.scheduling.annotation.EnableAsync;
 @MapperScan({
         "com.zksy.environment.mapper",
         "com.zksy.base.mapper",
-        "com.zksy.system.mapper"
+        "com.zksy.system.mapper",
+        "com.zksy.api.mapper"
 })
 @SpringBootApplication(scanBasePackages = {
-        "com.zksy.environment", // 默认就会扫描,不写也行
-        "com.zksy.api",         // 需要跨模块时再加
-        "com.zksy.base.service",
+        "com.zksy.environment",
+        "com.zksy.api",
         "com.zksy.system.service",
         "com.zksy.common",
         "com.zksy.utils"

+ 2 - 1
environment-service/src/main/java/com/zksy/environment/config/RSServerService.java

@@ -5,6 +5,7 @@ import com.zksy.api.service.WarningThresholdService;
 import com.zksy.api.utils.SmsUtil;
 import com.zksy.environment.domain.ERealTimeData;
 import com.zksy.environment.mapper.ERealTimeDataMapper;
+import com.zksy.environment.utils.AlarmUtil;
 import com.zksy.utils.DevicePhoneFetchUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -46,7 +47,7 @@ public class RSServerService {
     @Autowired
     private DevicePhoneFetchUtil devicePhoneFetchUtil;
     @Autowired
-    private com.zksy.environment.utils.AlarmUtil alarmUtil;
+    private AlarmUtil alarmUtil;
 
     @PostConstruct
     public void init() {

+ 18 - 0
environment-service/src/main/java/com/zksy/environment/utils/AlarmUtil.java

@@ -2,6 +2,9 @@ package com.zksy.environment.utils;
 
 import com.zksy.api.domain.AlarmData;
 import com.zksy.api.service.AlarmDataService;
+import com.zksy.api.utils.AlarmLevelUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -11,6 +14,8 @@ import java.time.LocalDateTime;
 @Component
 public class AlarmUtil {
 
+    private static final Logger log = LoggerFactory.getLogger(AlarmUtil.class);
+
     @Autowired
     private AlarmDataService alarmDataService;
 
@@ -48,12 +53,25 @@ public class AlarmUtil {
             alarmData.setMinValue(minValue);
             alarmData.setMaxValue(maxValue);
             alarmData.setActualValue(actualValue);
+
+            // 计算告警等级和偏差率
+            int alarmLevel = AlarmLevelUtil.calculateLevel(actualValue, minValue, maxValue);
+            BigDecimal deviationRatio = AlarmLevelUtil.calculateDeviationRatio(actualValue, minValue, maxValue);
+            alarmData.setAlarmLevel(alarmLevel);
+            alarmData.setDeviationRatio(deviationRatio);
+
             alarmData.setAlarmStatus(0);
             alarmData.setAlarmTime(LocalDateTime.now());
             alarmData.setRemark(remark);
             alarmData.setCreateTime(LocalDateTime.now());
 
             alarmDataService.saveAlarmData(alarmData);
+
+            // 1级告警特殊处理(TODO:调用短信接口或发送特殊通知)
+            if (alarmLevel == 1) {
+                log.warn("【1级紧急告警】deviceCode={}, warningCode={}, deviationRatio={}%",
+                        deviceCode, warningCode, deviationRatio);
+            }
         } catch (Exception e) {
             e.printStackTrace();
         }

+ 2 - 2
firefighting-pressure-service/src/main/java/com/zksy/pressure/PressureApplication.java

@@ -11,12 +11,12 @@ import org.springframework.scheduling.annotation.EnableAsync;
 @MapperScan({
         "com.zksy.pressure.mapper",
         "com.zksy.base.mapper",
-        "com.zksy.system.mapper"
+        "com.zksy.system.mapper",
+        "com.zksy.api.mapper"
 })
 @SpringBootApplication(scanBasePackages = {
         "com.zksy.pressure",
         "com.zksy.api",
-        "com.zksy.base.service",
         "com.zksy.system.service",
         "com.zksy.common",
         "com.zksy.utils"

+ 14 - 1
firefighting-pressure-service/src/main/java/com/zksy/pressure/utils/MessageHandler.java

@@ -7,6 +7,7 @@ import com.zksy.api.domain.WarningThreshold;
 import com.zksy.api.service.WarningThresholdService;
 import com.zksy.api.domain.AlarmData;
 import com.zksy.api.service.AlarmDataService;
+import com.zksy.api.utils.AlarmLevelUtil;
 import com.zksy.api.utils.SmsUtil;
 import com.zksy.common.exception.InvalidMessageException;
 import com.zksy.pressure.domain.FirefightingPressure;
@@ -25,7 +26,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import java.math.BigDecimal;
-import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
@@ -303,11 +303,24 @@ public class MessageHandler extends ChannelInboundHandlerAdapter {
 			alarmData.setMinValue(minValue);
 			alarmData.setMaxValue(maxValue);
 			alarmData.setActualValue(actualValue);
+
+			// 计算告警等级和偏差率
+			int alarmLevel = AlarmLevelUtil.calculateLevel(actualValue, minValue, maxValue);
+			BigDecimal deviationRatio = AlarmLevelUtil.calculateDeviationRatio(actualValue, minValue, maxValue);
+			alarmData.setAlarmLevel(alarmLevel);
+			alarmData.setDeviationRatio(deviationRatio);
+
 			alarmData.setAlarmStatus(0);
 			alarmData.setAlarmTime(LocalDateTime.now());
 			alarmData.setRemark(remark);
 			alarmData.setCreateTime(LocalDateTime.now());
 			alarmDataService.saveAlarmData(alarmData);
+
+			// 1级告警特殊处理(TODO:调用短信接口或发送特殊通知)
+			if (alarmLevel == 1) {
+				log.warn("【1级紧急告警】deviceCode={}, warningCode={}, deviationRatio={}%",
+						deviceCode, warningCode, deviationRatio);
+			}
 		} catch (Exception e) {
 			log.error("保存告警数据失败", e);
 		}

+ 2 - 2
flammable-gas-service/src/main/java/com/zksy/gas/GasApplication.java

@@ -10,12 +10,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
 @MapperScan({
         "com.zksy.gas.mapper",
         "com.zksy.base.mapper",
-        "com.zksy.system.mapper"
+        "com.zksy.system.mapper",
+        "com.zksy.api.mapper"
 })
 @SpringBootApplication(scanBasePackages = {
         "com.zksy.gas",
         "com.zksy.api",
-        "com.zksy.base.service",
         "com.zksy.system.service",
         "com.zksy.common",
         "com.zksy.utils"

+ 14 - 3
flammable-gas-service/src/main/java/com/zksy/gas/utils/MessageHandler.java

@@ -1,11 +1,11 @@
 package com.zksy.gas.utils;
 
-import cn.hutool.core.lang.UUID;
 import com.alibaba.fastjson.JSONObject;
 import com.zksy.api.domain.WarningThreshold;
 import com.zksy.api.service.WarningThresholdService;
 import com.zksy.api.domain.AlarmData;
 import com.zksy.api.service.AlarmDataService;
+import com.zksy.api.utils.AlarmLevelUtil;
 import com.zksy.api.utils.SmsUtil;
 import com.zksy.common.exception.InvalidMessageException;
 import com.zksy.gas.domain.GasMonitorData;
@@ -18,7 +18,6 @@ import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.handler.timeout.ReadTimeoutException;
 import io.netty.util.ReferenceCountUtil;
-import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -27,7 +26,6 @@ import org.springframework.stereotype.Component;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -352,11 +350,24 @@ public class MessageHandler extends ChannelInboundHandlerAdapter {
 			alarmData.setMinValue(minValue);
 			alarmData.setMaxValue(maxValue);
 			alarmData.setActualValue(actualValue);
+
+			// 计算告警等级和偏差率
+			int alarmLevel = AlarmLevelUtil.calculateLevel(actualValue, minValue, maxValue);
+			BigDecimal deviationRatio = AlarmLevelUtil.calculateDeviationRatio(actualValue, minValue, maxValue);
+			alarmData.setAlarmLevel(alarmLevel);
+			alarmData.setDeviationRatio(deviationRatio);
+
 			alarmData.setAlarmStatus(0);
 			alarmData.setAlarmTime(LocalDateTime.now());
 			alarmData.setRemark(remark);
 			alarmData.setCreateTime(LocalDateTime.now());
 			alarmDataService.saveAlarmData(alarmData);
+
+			// 1级告警特殊处理(TODO:调用短信接口或发送特殊通知)
+			if (alarmLevel == 1) {
+				log.warn("【1级紧急告警】deviceCode={}, warningCode={}, deviationRatio={}%",
+						deviceCode, warningCode, deviationRatio);
+			}
 		} catch (Exception e) {
 			log.error("保存告警数据失败", e);
 		}

+ 2 - 2
manhole-service/src/main/java/com/zksy/manhole/ManholeApplication.java

@@ -8,12 +8,12 @@ import org.springframework.scheduling.annotation.EnableScheduling;
 @MapperScan({
         "com.zksy.manhole.mapper",
         "com.zksy.base.mapper",
-        "com.zksy.system.mapper"
+        "com.zksy.system.mapper",
+        "com.zksy.api.mapper"
 })
 @SpringBootApplication(scanBasePackages = {
         "com.zksy.manhole",
         "com.zksy.api",
-        "com.zksy.base.service",
         "com.zksy.system.service",
         "com.zksy.common",
         "com.zksy.utils"

+ 18 - 1
manhole-service/src/main/java/com/zksy/manhole/utils/AlarmUtil.java

@@ -1,7 +1,9 @@
 package com.zksy.manhole.utils;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import com.zksy.api.utils.AlarmLevelUtil;
 import com.zksy.common.utils.AjaxResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpEntity;
@@ -18,6 +20,8 @@ import java.util.Map;
 @Component
 public class AlarmUtil {
 
+    private static final Logger log = LoggerFactory.getLogger(AlarmUtil.class);
+
     @Autowired
     private RestTemplate restTemplate;
 
@@ -58,6 +62,13 @@ public class AlarmUtil {
             alarmData.put("minValue", minValue);
             alarmData.put("maxValue", maxValue);
             alarmData.put("actualValue", actualValue);
+
+            // 计算告警等级和偏差率
+            int alarmLevel = AlarmLevelUtil.calculateLevel(actualValue, minValue, maxValue);
+            BigDecimal deviationRatio = AlarmLevelUtil.calculateDeviationRatio(actualValue, minValue, maxValue);
+            alarmData.put("alarmLevel", alarmLevel);
+            alarmData.put("deviationRatio", deviationRatio);
+
             alarmData.put("alarmStatus", 0);
             alarmData.put("alarmTime", LocalDateTime.now().toString());
             alarmData.put("remark", remark);
@@ -67,6 +78,12 @@ public class AlarmUtil {
             HttpEntity<Map<String, Object>> request = new HttpEntity<>(alarmData, headers);
 
             restTemplate.postForObject(alarmApiUrl, request, AjaxResult.class);
+
+            // 1级告警特殊处理(TODO:调用短信接口或发送特殊通知)
+            if (alarmLevel == 1) {
+                log.warn("【1级紧急告警】deviceCode={}, warningCode={}, deviationRatio={}%",
+                        deviceCode, warningCode, deviationRatio);
+            }
         } catch (Exception e) {
             e.printStackTrace();
         }

+ 38 - 8
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/warning/EarlyWarningController.java

@@ -1,7 +1,9 @@
 package com.zksy.web.controller.warning;
 
+import com.alibaba.fastjson2.JSON;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.base.warning.domain.EarlyWarning;
 import com.zksy.base.warning.domain.WarningArchive;
 import com.zksy.base.warning.domain.WarningDisposal;
 import com.zksy.base.warning.domain.WarningSaveDTO;
@@ -17,8 +19,10 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -52,11 +56,11 @@ public class EarlyWarningController {
     public AjaxResult getTodoList(
             @ApiParam("页码") @RequestParam(defaultValue = "1") Integer pageNum,
             @ApiParam("每页数量") @RequestParam(defaultValue = "10") Integer pageSize,
-            @ApiParam("用户ID") @RequestParam String userId,
+            @ApiParam("用户名") @RequestParam String userName,
             @ApiParam("待办类型") @RequestParam(required = false) String todoType,
             @ApiParam("预警名称") @RequestParam(required = false) String warningName) {
         Page<?> page = new Page<>(pageNum, pageSize);
-        IPage<Map<String, Object>> result = earlyWarningService.getTodoList(page, userId, todoType, warningName);
+        IPage<Map<String, Object>> result = earlyWarningService.getTodoList(page, userName, todoType, warningName);
         return AjaxResult.success(result);
     }
 
@@ -75,18 +79,44 @@ public class EarlyWarningController {
     @ApiOperation("保存预警信息")
     @Log(title = "保存预警信息", businessType = BusinessType.INSERT)
     @PreAuthorize("@ss.hasPermi('warning:warning:add')")
-    public AjaxResult saveWarning(WarningSaveDTO entity) {
-        boolean result = earlyWarningService.saveWarning(entity.getWarning(), entity.getAlarmIds(), entity.getFiles());
-        return result ? AjaxResult.success("保存成功", entity.getWarning().getWarningId()) : AjaxResult.error("保存失败");
+    public AjaxResult saveWarning(
+            @RequestPart("warning") String warningJson,
+            @RequestPart(value = "alarmIds", required = false) String alarmIdsJson,
+            @RequestPart(value = "files", required = false) List<MultipartFile> files) {
+        try {
+            EarlyWarning warning = JSON.parseObject(warningJson, EarlyWarning.class);
+List<String> alarmIds = alarmIdsJson != null && !alarmIdsJson.isEmpty()
+                    ? JSON.parseArray(alarmIdsJson, String.class)
+                    : new ArrayList<>();
+
+            boolean result = earlyWarningService.saveWarning(warning, alarmIds, files);
+            return result ? AjaxResult.success("保存成功", warning.getWarningId()) : AjaxResult.error("保存失败");
+        } catch (Exception e) {
+            log.error("保存预警信息失败", e);
+            return AjaxResult.error("保存失败:" + e.getMessage());
+        }
     }
 
     @PutMapping("/update")
     @ApiOperation("修改预警信息")
     @Log(title = "修改预警信息", businessType = BusinessType.UPDATE)
     @PreAuthorize("@ss.hasPermi('warning:warning:edit')")
-    public AjaxResult updateWarning(WarningSaveDTO entity) {
-        boolean result = earlyWarningService.updateWarning(entity.getWarning(), entity.getAlarmIds(), entity.getFiles());
-        return result ? AjaxResult.success("修改成功") : AjaxResult.error("修改失败");
+    public AjaxResult updateWarning(
+            @RequestPart("warning") String warningJson,
+            @RequestPart(value = "alarmIds", required = false) String alarmIdsJson,
+            @RequestPart(value = "files", required = false) List<MultipartFile> files) {
+        try {
+            EarlyWarning warning = JSON.parseObject(warningJson, EarlyWarning.class);
+            List<String> alarmIds = alarmIdsJson != null && !alarmIdsJson.isEmpty() 
+                ? JSON.parseArray(alarmIdsJson, String.class) 
+                : new ArrayList<>();
+            
+            boolean result = earlyWarningService.updateWarning(warning, alarmIds, files);
+            return result ? AjaxResult.success("修改成功") : AjaxResult.error("修改失败");
+        } catch (Exception e) {
+            log.error("修改预警信息失败", e);
+            return AjaxResult.error("修改失败:" + e.getMessage());
+        }
     }
 
     @DeleteMapping("/delete")

+ 5 - 1
pipe-network-service/zksy-system/pom.xml

@@ -16,12 +16,16 @@
     </description>
 
     <dependencies>
-
         <!-- 通用工具-->
         <dependency>
             <groupId>com.zksy</groupId>
             <artifactId>zksy-common</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.zksy</groupId>
+            <artifactId>zk-api-service</artifactId>
+            <version>1.0.0</version>
+        </dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>

+ 8 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/domain/AlarmData.java

@@ -43,6 +43,14 @@ public class AlarmData implements Serializable {
     @ApiModelProperty(value = "实际值")
     private BigDecimal actualValue;
 
+    @TableField(value = "alarm_level")
+    @ApiModelProperty(value = "告警等级 1-4,1级最高")
+    private Integer alarmLevel;
+
+    @TableField(value = "deviation_ratio")
+    @ApiModelProperty(value = "偏差率(百分比)")
+    private BigDecimal deviationRatio;
+
     @TableField(value = "alarm_status")
     @ApiModelProperty(value = "报警状态 0-未处理 1-已处理")
     private Integer alarmStatus;

+ 0 - 5
pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/EquipmentBase.java

@@ -2,7 +2,6 @@ package com.zksy.base.domain;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.annotations.ApiModelProperty;
@@ -87,8 +86,4 @@ public class EquipmentBase {
     @ApiModelProperty("更新时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime updateTime;
-
-    @TableLogic(value = "0", delval = "2")
-    @ApiModelProperty("删除标志(0=正常 2=删除)")
-    private String delFlag;
 }

+ 1 - 1
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/EquipmentBaseServiceImpl.java

@@ -195,7 +195,7 @@ public class EquipmentBaseServiceImpl extends ServiceImpl<EquipmentBaseMapper, E
         equipmentTestMapper.delete(testWrapper);
         log.info("已删除设备[{}]的测试记录", equipmentId);
 
-        return this.removeById(equipmentId);
+        return this.baseMapper.deleteById(equipmentId) > 0;
     }
 
     @Override

+ 1 - 1
pipe-network-service/zksy-system/src/main/java/com/zksy/base/warning/service/EarlyWarningService.java

@@ -18,7 +18,7 @@ public interface EarlyWarningService extends IService<EarlyWarning> {
 
     IPage<Map<String, Object>> searchWarnings(Page<?> page, String warningName, String warningNo, String warningType, String warningLevel, String status, String location, String publisher, String ownershipUnit, LocalDateTime startTime, LocalDateTime endTime);
 
-    IPage<Map<String, Object>> getTodoList(Page<?> page, String userId, String todoType, String warningName);
+    IPage<Map<String, Object>> getTodoList(Page<?> page, String userName, String todoType, String warningName);
 
     Map<String, Object> getWarningDetail(String warningId);
 

+ 36 - 19
pipe-network-service/zksy-system/src/main/java/com/zksy/base/warning/service/impl/EarlyWarningServiceImpl.java

@@ -7,11 +7,8 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.zksy.base.warning.domain.*;
 import com.zksy.base.warning.mapper.*;
 import com.zksy.base.warning.service.EarlyWarningService;
-import com.zksy.common.config.ZksyConfig;
 import com.zksy.common.core.domain.entity.SysDictData;
 import com.zksy.common.utils.DictUtils;
-import com.zksy.common.utils.file.FileUploadUtils;
-import com.zksy.common.utils.file.FileUtils;
 import com.zksy.service.MinioFileStorageService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -53,8 +50,8 @@ public class EarlyWarningServiceImpl extends ServiceImpl<EarlyWarningMapper, Ear
     }
 
     @Override
-    public IPage<Map<String, Object>> getTodoList(Page<?> page, String userId, String todoType, String warningName) {
-        return earlyWarningMapper.selectTodoList(page, userId, todoType, warningName);
+    public IPage<Map<String, Object>> getTodoList(Page<?> page, String userName, String todoType, String warningName) {
+        return earlyWarningMapper.selectTodoList(page, userName, todoType, warningName);
     }
 
     @Override
@@ -95,7 +92,7 @@ public class EarlyWarningServiceImpl extends ServiceImpl<EarlyWarningMapper, Ear
     }
 
     private Map<String, Object> generateElectronicArchive(Map<String, Object> warningInfo) {
-        String warningId = (String) warningInfo.get("warningId");
+        String warningId = (String) warningInfo.get("warning_id");
         
         // 先查询是否已存在归档记录
         LambdaQueryWrapper<WarningArchive> wrapper = new LambdaQueryWrapper<>();
@@ -116,20 +113,20 @@ public class EarlyWarningServiceImpl extends ServiceImpl<EarlyWarningMapper, Ear
         // 如果不存在,创建新归档
         WarningArchive archive = new WarningArchive();
         archive.setWarningId(warningId);
-        archive.setArchiveNo("EA_" + warningInfo.get("warningNo"));
-        archive.setWarningNo((String) warningInfo.get("warningNo"));
-        archive.setWarningName((String) warningInfo.get("warningName"));
-        archive.setWarningType((String) warningInfo.get("warningType"));
-        archive.setWarningLevel((String) warningInfo.get("warningLevel"));
+        archive.setArchiveNo("EA_" + warningInfo.get("warning_no"));
+        archive.setWarningNo((String) warningInfo.get("warning_no"));
+        archive.setWarningName((String) warningInfo.get("warning_name"));
+        archive.setWarningType((String) warningInfo.get("warning_type"));
+        archive.setWarningLevel((String) warningInfo.get("warning_level"));
         archive.setLocation((String) warningInfo.get("location"));
         archive.setLongitude(warningInfo.get("longitude") != null ? ((Number) warningInfo.get("longitude")).doubleValue() : null);
         archive.setLatitude(warningInfo.get("latitude") != null ? ((Number) warningInfo.get("latitude")).doubleValue() : null);
-        archive.setOwnershipUnit((String) warningInfo.get("ownershipUnit"));
+        archive.setOwnershipUnit((String) warningInfo.get("ownership_unit"));
         archive.setPublisher((String) warningInfo.get("publisher"));
-        archive.setPublishTime(warningInfo.get("publishTime") instanceof LocalDateTime ? (LocalDateTime) warningInfo.get("publishTime") : null);
+        archive.setPublishTime(warningInfo.get("publish_time") instanceof LocalDateTime ? (LocalDateTime) warningInfo.get("publishTime") : null);
         archive.setHandler((String) warningInfo.get("handler"));
-        archive.setWarningContent((String) warningInfo.get("warningContent"));
-        archive.setArchiveContent((String) warningInfo.get("warningContent"));
+        archive.setWarningContent((String) warningInfo.get("warning_content"));
+        archive.setArchiveContent((String) warningInfo.get("warning_content"));
         archive.setArchiveStatus("已归档");
         archive.setCreateTime(LocalDateTime.now());
         
@@ -179,7 +176,8 @@ public class EarlyWarningServiceImpl extends ServiceImpl<EarlyWarningMapper, Ear
                 attachment.setWarningId(warningId);
                 attachment.setAttachmentName(fileName);
                 attachment.setAttachmentUrl(path);
-                attachment.setAttachmentType(file.getContentType());
+                // 部分 MIME(如 vnd.openxmlformats-officedocument.*)长度超过 64,写入前按 DB 列宽截断兜底
+                attachment.setAttachmentType(truncateAttachmentType(file.getContentType()));
                 attachment.setAttachmentSize(file.getSize());
                 attachment.setCreateTime(LocalDateTime.now());
                 warningAttachmentMapper.insert(attachment);
@@ -321,12 +319,16 @@ public class EarlyWarningServiceImpl extends ServiceImpl<EarlyWarningMapper, Ear
         EarlyWarning warning = getById(warningId);
         if (warning == null) return false;
 
-        warning.setStatus("PENDING");
+        warning.setStatus("DRAFT");
         warning.setUpdateTime(LocalDateTime.now());
 
         boolean result = updateById(warning);
         if (result) {
             addDisposalRecord(warningId, "RETURN", null, null, warning.getHandler(), disposalContent);
+            // 退回后删除待办记录,因为直接回到草稿状态
+            LambdaQueryWrapper<WarningTodo> todoWrapper = new LambdaQueryWrapper<>();
+            todoWrapper.eq(WarningTodo::getWarningId, warningId);
+            warningTodoMapper.delete(todoWrapper);
         }
         return result;
     }
@@ -762,12 +764,27 @@ public class EarlyWarningServiceImpl extends ServiceImpl<EarlyWarningMapper, Ear
         result.put("handlerCount", handlers.size());
 
         if (!disposals.isEmpty()) {
-            result.put("firstDisposalTime", disposals.get(0).getCreateTime() != null ? 
+            result.put("firstDisposalTime", disposals.get(0).getCreateTime() != null ?
                     disposals.get(0).getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : null);
-            result.put("lastDisposalTime", disposals.get(disposals.size() - 1).getCreateTime() != null ? 
+            result.put("lastDisposalTime", disposals.get(disposals.size() - 1).getCreateTime() != null ?
                     disposals.get(disposals.size() - 1).getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : null);
         }
 
         return result;
     }
+
+    /**
+     * 截断附件类型到 DB 列宽(varchar(64))以内,避免 Data too long 异常
+     */
+    private static final int ATTACHMENT_TYPE_MAX_LEN = 64;
+
+    private String truncateAttachmentType(String contentType) {
+        if (contentType == null || contentType.isEmpty()) {
+            return contentType;
+        }
+        if (contentType.length() <= ATTACHMENT_TYPE_MAX_LEN) {
+            return contentType;
+        }
+        return contentType.substring(0, ATTACHMENT_TYPE_MAX_LEN);
+    }
 }

+ 12 - 37
pipe-network-service/zksy-system/src/main/java/com/zksy/utils/DevicePhoneFetchUtil.java

@@ -3,8 +3,8 @@ package com.zksy.utils;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.zksy.base.domain.EquipmentMaintain;
 import com.zksy.base.domain.EquipmentStatus;
-import com.zksy.base.service.EquipmentMaintainService;
-import com.zksy.base.service.EquipmentStatusService;
+import com.zksy.base.mapper.EquipmentMaintainMapper;
+import com.zksy.base.mapper.EquipmentStatusMapper;
 import com.zksy.common.core.domain.entity.SysUser;
 import com.zksy.system.service.ISysUserService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -14,29 +14,18 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
-//
+
 @Component
 public class DevicePhoneFetchUtil {
 
-    //换了一种方式注入,待测试
-    @Autowired
-    private  EquipmentMaintainService equipmentMaintainService;
     @Autowired
-    private  EquipmentStatusService equipmentStatusService;
+    private EquipmentMaintainMapper equipmentMaintainMapper;
+
     @Autowired
-    private  ISysUserService sysUserService;
+    private EquipmentStatusMapper equipmentStatusMapper;
 
-//    private final EquipmentMaintainService equipmentMaintainService;
-//    private final EquipmentStatusService equipmentStatusService;
-//    private final ISysUserService sysUserService;
-//
-//    public DevicePhoneFetchUtil(EquipmentMaintainService equipmentMaintainService,
-//                                EquipmentStatusService equipmentStatusService,
-//                                ISysUserService sysUserService) {
-//        this.equipmentMaintainService = equipmentMaintainService;
-//        this.equipmentStatusService = equipmentStatusService;
-//        this.sysUserService = sysUserService;
-//    }
+    @Autowired
+    private ISysUserService sysUserService;
 
     /**
      * 根据设备ID获取所有关联手机号
@@ -45,25 +34,19 @@ public class DevicePhoneFetchUtil {
      */
     public List<String> getPhoneListByDeviceId(String deviceId) {
         Set<String> phoneSet = new HashSet<>();
-
-        //获取维护表中的手机号
         addAllMaintainPersonPhones(phoneSet, deviceId);
-        //获取用户表的手机号
         addUserPhoneByStatus(phoneSet, deviceId);
         return phoneSet.stream()
                 .filter(this::isValidPhone)
                 .collect(Collectors.toList());
     }
 
-    /**
-     * 收集该设备所有维护记录中的维护人手机号
-     */
     private void addAllMaintainPersonPhones(Set<String> phoneSet, String deviceId) {
         LambdaQueryWrapper<EquipmentMaintain> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(EquipmentMaintain::getEquipmentId, deviceId);
 
-        List<EquipmentMaintain> maintainList = equipmentMaintainService.list(queryWrapper);
-        if (maintainList.isEmpty()) {
+        List<EquipmentMaintain> maintainList = equipmentMaintainMapper.selectList(queryWrapper);
+        if (maintainList == null || maintainList.isEmpty()) {
             return;
         }
         maintainList.forEach(maintain -> {
@@ -72,29 +55,21 @@ public class DevicePhoneFetchUtil {
         });
     }
 
-    /**
-     * 从设备状态表关联用户表获取负责人手机号
-     */
     private void addUserPhoneByStatus(Set<String> phoneSet, String deviceId) {
-
         LambdaQueryWrapper<EquipmentStatus> statusWrapper = new LambdaQueryWrapper<>();
         statusWrapper.eq(EquipmentStatus::getEquipmentId, deviceId);
-        EquipmentStatus equipmentStatus = equipmentStatusService.getOne(statusWrapper);
+        EquipmentStatus equipmentStatus = equipmentStatusMapper.selectOne(statusWrapper);
 
         if (equipmentStatus == null || equipmentStatus.getUserId() == null) {
             return;
         }
         SysUser sysUser = sysUserService.selectUserById(equipmentStatus.getUserId());
 
-        //0代表存在的用户
-        if (sysUser != null && sysUser.getPhonenumber() != null && sysUser.getDelFlag().equals("0")) {
+        if (sysUser != null && sysUser.getPhonenumber() != null && "0".equals(sysUser.getDelFlag())) {
             phoneSet.add(sysUser.getPhonenumber());
         }
     }
 
-    /**
-     * 校验手机号格式(11位数字)
-     */
     private boolean isValidPhone(String phone) {
         return phone != null && phone.matches("^1[3-9]\\d{9}$");
     }

+ 4 - 4
pipe-network-service/zksy-system/src/main/resources/mapper/base/AlarmAssignDetailMapper.xml

@@ -4,7 +4,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.zksy.base.mapper.AlarmAssignDetailMapper">
     
-    <resultMap type="AlarmAssignDetail" id="AlarmAssignDetailResult">
+    <resultMap type="com.zksy.base.domain.AlarmAssignDetail" id="AlarmAssignDetailResult">
         <result property="alarmAssignDetailId"    column="alarm_assign_detail_id"    />
         <result property="alarmAssignId"    column="alarm_assign_id"    />
         <result property="handleUserId"    column="handle_user_id"    />
@@ -18,7 +18,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         select alarm_assign_detail_id, alarm_assign_id, handle_user_id, handle_user_name, handle_phone, handle_dept_name, remark from alarm_assign_detail
     </sql>
 
-    <select id="selectAlarmAssignDetailList" parameterType="AlarmAssignDetail" resultMap="AlarmAssignDetailResult">
+    <select id="selectAlarmAssignDetailList" parameterType="com.zksy.base.domain.AlarmAssignDetail" resultMap="AlarmAssignDetailResult">
         <include refid="selectAlarmAssignDetailVo"/>
         <where>  
             <if test="alarmAssignDetailId != null "> and alarm_assign_detail_id = #{alarmAssignDetailId}</if>
@@ -31,7 +31,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where alarm_assign_detail_id = #{alarmAssignDetailId}
     </select>
 
-    <insert id="insertAlarmAssignDetail" parameterType="AlarmAssignDetail" useGeneratedKeys="true" keyProperty="alarmAssignDetailId">
+    <insert id="insertAlarmAssignDetail" parameterType="com.zksy.base.domain.AlarmAssignDetail" useGeneratedKeys="true" keyProperty="alarmAssignDetailId">
         insert into alarm_assign_detail
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="alarmAssignId != null">alarm_assign_id,</if>
@@ -51,7 +51,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
          </trim>
     </insert>
 
-    <update id="updateAlarmAssignDetail" parameterType="AlarmAssignDetail">
+    <update id="updateAlarmAssignDetail" parameterType="com.zksy.base.domain.AlarmAssignDetail">
         update alarm_assign_detail
         <trim prefix="SET" suffixOverrides=",">
             <if test="alarmAssignId != null">alarm_assign_id = #{alarmAssignId},</if>

+ 4 - 4
pipe-network-service/zksy-system/src/main/resources/mapper/base/AlarmAssignListMapper.xml

@@ -4,7 +4,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.zksy.base.mapper.AlarmAssignListMapper">
     
-    <resultMap type="AlarmAssignList" id="AlarmAssignListResult">
+    <resultMap type="com.zksy.base.domain.AlarmAssignList" id="AlarmAssignListResult">
         <result property="alarmAssignId"    column="alarm_assign_id"    />
         <result property="deviceCode"    column="device_code"    />
         <result property="deviceName"    column="device_name"    />
@@ -23,7 +23,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         select alarm_assign_id, device_code, device_name, manufacturer, alarm_status, hidden_danger_type, hidden_danger_remark, alarm_time, assign_status, assign_time, assign_user_id, assign_remark from alarm_assign_list
     </sql>
 
-    <select id="selectAlarmAssignListList" parameterType="AlarmAssignList" resultMap="AlarmAssignListResult">
+    <select id="selectAlarmAssignListList" parameterType="com.zksy.base.domain.AlarmAssignList" resultMap="AlarmAssignListResult">
         <include refid="selectAlarmAssignListVo"/>
         <where>  
             <if test="alarmAssignId != null "> and alarm_assign_id = #{alarmAssignId}</if>
@@ -40,7 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where alarm_assign_id = #{alarmAssignId}
     </select>
 
-    <insert id="insertAlarmAssignList" parameterType="AlarmAssignList" useGeneratedKeys="true" keyProperty="alarmAssignId">
+    <insert id="insertAlarmAssignList" parameterType="com.zksy.base.domain.AlarmAssignList" useGeneratedKeys="true" keyProperty="alarmAssignId">
         insert into alarm_assign_list
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="deviceCode != null and deviceCode != ''">device_code,</if>
@@ -70,7 +70,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
          </trim>
     </insert>
 
-    <update id="updateAlarmAssignList" parameterType="AlarmAssignList">
+    <update id="updateAlarmAssignList" parameterType="com.zksy.base.domain.AlarmAssignList">
         update alarm_assign_list
         <trim prefix="SET" suffixOverrides=",">
             <if test="deviceCode != null and deviceCode != ''">device_code = #{deviceCode},</if>

+ 0 - 1
pipe-network-service/zksy-system/src/main/resources/mapper/warning/AlarmWarningRelMapper.xml

@@ -10,7 +10,6 @@
             a.device_type,
             a.warning_type,
             a.warning_code,
-            a.warning_value,
             a.actual_value,
             a.alarm_status,
             a.alarm_time

+ 2 - 2
radar-service/src/main/java/com/zksy/radar/RadarApplication.java

@@ -8,12 +8,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
 @MapperScan({
         "com.zksy.radar.mapper",
         "com.zksy.base.mapper",
-        "com.zksy.system.mapper"
+        "com.zksy.system.mapper",
+        "com.zksy.api.mapper"
 })
 @SpringBootApplication(scanBasePackages = {
         "com.zksy.radar",
         "com.zksy.api",
-        "com.zksy.base.service",
         "com.zksy.system.service",
         "com.zksy.common",
         "com.zksy.utils"

+ 14 - 1
radar-service/src/main/java/com/zksy/radar/utils/MessageHandler.java

@@ -4,10 +4,10 @@ import com.zksy.api.domain.AlarmData;
 import com.zksy.api.domain.WarningThreshold;
 import com.zksy.api.service.AlarmDataService;
 import com.zksy.api.service.WarningThresholdService;
+import com.zksy.api.utils.AlarmLevelUtil;
 import com.zksy.common.exception.InvalidMessageException;
 import com.zksy.radar.domain.RadarData;
 import com.zksy.radar.service.RadarDataService;
-import com.zksy.radar.utils.DataParser;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelHandler;
@@ -277,11 +277,24 @@ public class MessageHandler extends ChannelInboundHandlerAdapter {
 			alarmData.setMinValue(minValue);
 			alarmData.setMaxValue(maxValue);
 			alarmData.setActualValue(actualValue);
+
+			// 计算告警等级和偏差率
+			int alarmLevel = AlarmLevelUtil.calculateLevel(actualValue, minValue, maxValue);
+			BigDecimal deviationRatio = AlarmLevelUtil.calculateDeviationRatio(actualValue, minValue, maxValue);
+			alarmData.setAlarmLevel(alarmLevel);
+			alarmData.setDeviationRatio(deviationRatio);
+
 			alarmData.setAlarmStatus(0);
 			alarmData.setAlarmTime(LocalDateTime.now());
 			alarmData.setRemark(remark);
 			alarmData.setCreateTime(LocalDateTime.now());
 			alarmDataService.saveAlarmData(alarmData);
+
+			// 1级告警特殊处理(TODO:调用短信接口或发送特殊通知)
+			if (alarmLevel == 1) {
+				logger.warn("【1级紧急告警】deviceCode={}, warningCode={}, deviationRatio={}%",
+						deviceCode, warningCode, deviationRatio);
+			}
 			logger.info("告警数据入库成功: deviceCode={}, warningCode={}", deviceCode, warningCode);
 		} catch (Exception e) {
 			logger.error("保存告警数据失败", e);

+ 2 - 2
telemetry-service/src/main/java/com/zksy/telemetry/TelemetryApplication.java

@@ -8,12 +8,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
 @MapperScan({
         "com.zksy.telemetry.mapper",
         "com.zksy.base.mapper",
-        "com.zksy.system.mapper"
+        "com.zksy.system.mapper",
+        "com.zksy.api.mapper"
 })
 @SpringBootApplication(scanBasePackages = {
         "com.zksy.telemetry",
         "com.zksy.api",
-        "com.zksy.base.service",
         "com.zksy.system.service",
         "com.zksy.common",
         "com.zksy.utils"

+ 2 - 2
water-level-service/src/main/java/com/zksy/water/WaterLevelApplication.java

@@ -8,12 +8,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
 @MapperScan({
         "com.zksy.water.mapper",
         "com.zksy.base.mapper",
-        "com.zksy.system.mapper"
+        "com.zksy.system.mapper",
+        "com.zksy.api.mapper"
 })
 @SpringBootApplication(scanBasePackages = {
         "com.zksy.water",
         "com.zksy.api",
-        "com.zksy.base.service",
         "com.zksy.system.service",
         "com.zksy.common",
         "com.zksy.utils"

+ 14 - 1
water-level-service/src/main/java/com/zksy/water/utils/MessageHandler.java

@@ -4,10 +4,10 @@ import com.zksy.api.domain.AlarmData;
 import com.zksy.api.domain.WarningThreshold;
 import com.zksy.api.service.AlarmDataService;
 import com.zksy.api.service.WarningThresholdService;
+import com.zksy.api.utils.AlarmLevelUtil;
 import com.zksy.common.exception.InvalidMessageException;
 import com.zksy.water.domain.WaterMonitorData;
 import com.zksy.water.service.WaterMonitorDataService;
-import com.zksy.water.utils.DataParser;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelHandler;
@@ -271,11 +271,24 @@ public class MessageHandler extends ChannelInboundHandlerAdapter {
 			alarmData.setMinValue(minValue);
 			alarmData.setMaxValue(maxValue);
 			alarmData.setActualValue(actualValue);
+
+			// 计算告警等级和偏差率
+			int alarmLevel = AlarmLevelUtil.calculateLevel(actualValue, minValue, maxValue);
+			BigDecimal deviationRatio = AlarmLevelUtil.calculateDeviationRatio(actualValue, minValue, maxValue);
+			alarmData.setAlarmLevel(alarmLevel);
+			alarmData.setDeviationRatio(deviationRatio);
+
 			alarmData.setAlarmStatus(0);
 			alarmData.setAlarmTime(LocalDateTime.now());
 			alarmData.setRemark(remark);
 			alarmData.setCreateTime(LocalDateTime.now());
 			alarmDataService.saveAlarmData(alarmData);
+
+			// 1级告警特殊处理(TODO:调用短信接口或发送特殊通知)
+			if (alarmLevel == 1) {
+				logger.warn("【1级紧急告警】deviceCode={}, warningCode={}, deviationRatio={}%",
+						deviceCode, warningCode, deviationRatio);
+			}
 			logger.info("告警数据入库成功: deviceCode={}, warningCode={}", deviceCode, warningCode);
 		} catch (Exception e) {
 			logger.error("保存告警数据失败", e);

+ 2 - 0
zk-api-service/src/main/java/com/zksy/api/config/AliyunSmsConfig.java

@@ -18,4 +18,6 @@ public class AliyunSmsConfig {
     private String templateCode;
     private String regionId;
 
+    /** 维修派单通知模板编码 */
+    private String maintainTemplateCode;
 }

+ 8 - 0
zk-api-service/src/main/java/com/zksy/api/domain/AlarmData.java

@@ -43,6 +43,14 @@ public class AlarmData implements Serializable {
     @ApiModelProperty(value = "实际值")
     private BigDecimal actualValue;
 
+    @TableField(value = "alarm_level")
+    @ApiModelProperty(value = "告警等级 1-4,1级最高")
+    private Integer alarmLevel;
+
+    @TableField(value = "deviation_ratio")
+    @ApiModelProperty(value = "偏差率(百分比)")
+    private BigDecimal deviationRatio;
+
     @TableField(value = "alarm_status")
     @ApiModelProperty(value = "报警状态 0-未处理 1-已处理")
     private Integer alarmStatus;

+ 2 - 0
zk-api-service/src/main/java/com/zksy/api/service/WarningThresholdService.java

@@ -3,6 +3,7 @@ package com.zksy.api.service;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.zksy.api.domain.WarningThreshold;
+import org.apache.ibatis.annotations.Mapper;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
@@ -12,6 +13,7 @@ import java.util.List;
 * @description 针对表【warning_threshold(预警阈值)】的数据库操作Service
 * @createDate 2025-09-01 09:59:43
 */
+@Mapper
 public interface WarningThresholdService extends IService<WarningThreshold> {
     /**
      * 根据设备编号查询该设备所有预警值

+ 52 - 9
zk-api-service/src/main/java/com/zksy/api/utils/SmsUtil.java

@@ -69,16 +69,31 @@ public class SmsUtil {
         }
     }
     /**
-     * 批量发送相同内容的短信给多个手机号
+     * 批量发送相同内容的短信给多个手机号(使用默认报警模板)
      * @param phoneNumbers 接收短信的手机号列表
      * @param templateParam 短信模板参数,JSON格式字符串
      * @return 发送结果,key为手机号,value为是否成功
      */
     public Map<String, Boolean> sendBatchSms(List<String> phoneNumbers, String templateParam) {
+        return sendBatchSms(phoneNumbers, templateParam, aliyunSmsConfig.getTemplateCode());
+    }
+
+    /**
+     * 批量发送相同内容的短信给多个手机号(可指定模板编码)
+     * @param phoneNumbers 接收短信的手机号列表
+     * @param templateParam 短信模板参数,JSON格式字符串
+     * @param templateCode 短信模板编码
+     * @return 发送结果,key为手机号,value为是否成功
+     */
+    public Map<String, Boolean> sendBatchSms(List<String> phoneNumbers, String templateParam, String templateCode) {
         if (phoneNumbers == null || phoneNumbers.isEmpty()) {
             log.warn("批量发送短信失败:手机号列表为空");
             return Collections.emptyMap();
         }
+        if (templateCode == null || templateCode.trim().isEmpty()) {
+            log.warn("批量发送短信失败:模板编码为空");
+            return phoneNumbers.stream().collect(Collectors.toMap(p -> p, p -> false));
+        }
 
         try {
             List<String> signNames = phoneNumbers.stream()
@@ -89,26 +104,24 @@ public class SmsUtil {
                     .map(phone -> templateParam)
                     .collect(Collectors.toList());
 
-            //构建批量发送请求
             SendBatchSmsRequest request = SendBatchSmsRequest.builder()
-                    .phoneNumberJson(new Gson().toJson(phoneNumbers)) // 手机号列表
-                    .signNameJson(new Gson().toJson(signNames)) // 签名列表
-                    .templateCode(aliyunSmsConfig.getTemplateCode())
-                    .templateParamJson(new Gson().toJson(templateParams)) // 参数列表
+                    .phoneNumberJson(new Gson().toJson(phoneNumbers))
+                    .signNameJson(new Gson().toJson(signNames))
+                    .templateCode(templateCode)
+                    .templateParamJson(new Gson().toJson(templateParams))
                     .build();
 
-            // 发送并处理响应
             CompletableFuture<SendBatchSmsResponse> response = client.sendBatchSms(request);
             SendBatchSmsResponse resp = response.get();
 
-            log.info("批量短信发送响应: {}",new Gson().toJson(resp.getBody()));
+            log.info("批量短信发送响应: templateCode={}, resp={}", templateCode, new Gson().toJson(resp.getBody()));
             return phoneNumbers.stream()
                     .collect(Collectors.toMap(
                             phone -> phone,
                             phone -> "OK".equals(resp.getBody().getCode())
                     ));
         } catch (Exception e) {
-            log.error("批量短信发送响应: {}",e.getMessage());
+            log.error("批量短信发送失败: templateCode={}, error={}", templateCode, e.getMessage());
             e.printStackTrace();
             return phoneNumbers.stream()
                     .collect(Collectors.toMap(
@@ -117,6 +130,36 @@ public class SmsUtil {
                     ));
         }
     }
+
+    /**
+     * 发送维修派单通知短信
+     * @param phoneNumbers 接收短信的手机号列表
+     * @param deviceName 设备名称
+     * @param deviceNo 设备编号
+     * @param location 设备位置
+     * @param faultDesc 故障描述
+     * @return 发送结果,key为手机号,value为是否成功
+     */
+    public Map<String, Boolean> sendMaintainNotify(List<String> phoneNumbers,
+                                                    String deviceName, String deviceNo,
+                                                    String location, String faultDesc) {
+        String maintainTemplateCode = aliyunSmsConfig.getMaintainTemplateCode();
+        if (maintainTemplateCode == null || maintainTemplateCode.trim().isEmpty()) {
+            log.warn("维修派单短信模板未配置(aliyun.sms.maintain-template-code),跳过发送");
+            return phoneNumbers != null
+                    ? phoneNumbers.stream().collect(Collectors.toMap(p -> p, p -> false))
+                    : Collections.emptyMap();
+        }
+
+        JSONObject params = new JSONObject();
+        params.put("deviceName", deviceName != null ? deviceName : "未知设备");
+        params.put("deviceNo", deviceNo != null ? deviceNo : "无");
+        params.put("location", location != null && !location.isEmpty() ? location : "暂无位置信息");
+        params.put("faultDesc", faultDesc != null && !faultDesc.isEmpty() ? faultDesc : "请查看工单详情");
+
+        return sendBatchSms(phoneNumbers, params.toJSONString(), maintainTemplateCode);
+    }
+
     /**
      * 检查设备数据是否触发报警并发送短信
      */