Jelajahi Sumber

merge: 整合报警描述更新与管网契约审查补齐

Kazerin 1 Minggu lalu
induk
melakukan
0a6b1083e3
33 mengubah file dengan 178 tambahan dan 92 penghapusan
  1. 0 4
      audio-service/src/main/java/com/zksy/audio/service/NoiseLeakAlarmService.java
  2. 5 5
      environment-service/src/main/java/com/zksy/environment/config/RSServerService.java
  3. 0 1
      environment-service/src/main/java/com/zksy/environment/utils/AlarmUtil.java
  4. 0 2
      firefighting-pressure-service/src/main/java/com/zksy/pressure/utils/MessageHandler.java
  5. 0 1
      flammable-gas-service/src/main/java/com/zksy/gas/utils/AlarmUtil.java
  6. 0 1
      flammable-gas-service/src/main/java/com/zksy/gas/utils/MessageHandler.java
  7. 0 2
      manhole-service/src/main/java/com/zksy/manhole/utils/AlarmUtil.java
  8. 1 1
      pipe-network-service/CONTEXT.md
  9. 10 4
      pipe-network-service/sql/water_supply_alarm_test_data.sql
  10. 10 4
      pipe-network-service/sql/water_supply_monitoring_alarm_rebuild_test_data.sql
  11. 1 1
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GasAlarmController.java
  12. 1 1
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/PressureAlarmController.java
  13. 3 7
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/domain/AlarmData.java
  14. 6 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/mapper/AlarmDataMapper.java
  15. 13 4
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/service/impl/AlarmDataServiceImpl.java
  16. 18 10
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/support/AlarmLifecycleSupport.java
  17. 2 2
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/drill/service/DrillAlarmSyncService.java
  18. 0 1
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/drill/service/impl/DrillSimulationServiceImpl.java
  19. 2 2
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/DrillAlarmSyncService.java
  20. 0 1
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/DrillSimulationServiceImpl.java
  21. 1 3
      pipe-network-service/zksy-system/src/main/resources/mapper/base/AlarmOperationsMapper.xml
  22. 28 2
      pipe-network-service/zksy-system/src/test/java/com/zksy/base/alarm/service/AlarmLifecycleServiceTest.java
  23. 0 1
      radar-service/src/main/java/com/zksy/radar/utils/MessageHandler.java
  24. 0 1
      telemetry-service/src/main/java/com/zksy/telemetry/utils/MessageHandler.java
  25. 0 1
      water-level-service/src/main/java/com/zksy/water/utils/MessageHandler.java
  26. 3 3
      zk-api-service/src/main/java/com/zksy/api/domain/AlarmData.java
  27. 7 1
      zk-api-service/src/main/java/com/zksy/api/mapper/AlarmDataMapper.java
  28. 11 7
      zk-api-service/src/main/java/com/zksy/api/service/CollectionAlarmWriter.java
  29. 0 1
      zk-api-service/src/main/java/com/zksy/api/service/impl/EquipmentStatusApiServiceImpl.java
  30. 17 10
      zk-api-service/src/main/java/com/zksy/api/support/AlarmLifecycleSupport.java
  31. 3 4
      zk-api-service/src/main/java/com/zksy/api/utils/SmsUtil.java
  32. 2 2
      zk-api-service/src/main/resources/mapper/AlarmDataMapper.xml
  33. 34 2
      zk-api-service/src/test/java/com/zksy/api/service/CollectionAlarmLifecycleTest.java

+ 0 - 4
audio-service/src/main/java/com/zksy/audio/service/NoiseLeakAlarmService.java

@@ -199,10 +199,6 @@ public class NoiseLeakAlarmService {
         alarm.setAlarmLevel(1);
         alarm.setAlarmStatus(0);
         alarm.setCreateTime(LocalDateTime.now());
-        alarm.setRemark(alarmCount == 1
-                ? String.format("漏失监测单次独立采样确认:rms≥阈值且在漏失频段[%.0f,%.0f]Hz", lowerFreq, upperFreq)
-                : String.format("漏失监测确认漏失:最近%d分钟内%d次采样rms≥阈值且在漏失频段[%.0f,%.0f]Hz",
-                    windowMinutes, count, lowerFreq, upperFreq));
         try {
             requireSaved(alarmDataService.saveAlarmData(alarm));
             log.info("漏失告警入库成功: encode={}, count={}", encode, count);

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

@@ -338,7 +338,7 @@ public class RSServerService {
                     String temWarningType = "悬浮物预警";
                     String temRemark = "环境悬浮物报警";
                     if (alarmUtil.checkAndSaveAlarm(deviceId, temWarningType, temWarningCode,
-                            temMinValue, temMaxValue, BigDecimal.valueOf(finalValue), temRemark)) {
+                            temMinValue, temMaxValue, new BigDecimal(Float.toString(finalValue)), temRemark)) {
                         hasAlarm = true;
                     }
                 }
@@ -352,7 +352,7 @@ public class RSServerService {
                     String humWarningType = "COD预警";
                     String humRemark = "环境COD报警";
                     if (alarmUtil.checkAndSaveAlarm(deviceId, humWarningType, humWarningCode,
-                            humMinValue, humMaxValue, BigDecimal.valueOf(finalValue), humRemark)) {
+                            humMinValue, humMaxValue, new BigDecimal(Float.toString(finalValue)), humRemark)) {
                         hasAlarm = true;
                     }
                 }
@@ -364,7 +364,7 @@ public class RSServerService {
                     String humWarningType = "氨氮预警";
                     String humRemark = "环境氨氮报警";
                     if (alarmUtil.checkAndSaveAlarm(deviceId, humWarningType, humWarningCode,
-                            humMinValue, humMaxValue, BigDecimal.valueOf(finalValue), humRemark)) {
+                            humMinValue, humMaxValue, new BigDecimal(Float.toString(finalValue)), humRemark)) {
                         hasAlarm = true;
                     }
                 }
@@ -376,7 +376,7 @@ public class RSServerService {
                     String humWarningType = "电导率预警";
                     String humRemark = "环境电导率报警";
                     if (alarmUtil.checkAndSaveAlarm(deviceId, humWarningType, humWarningCode,
-                            humMinValue, humMaxValue, BigDecimal.valueOf(finalValue), humRemark)) {
+                            humMinValue, humMaxValue, new BigDecimal(Float.toString(finalValue)), humRemark)) {
                         hasAlarm = true;
                     }
                 }
@@ -388,7 +388,7 @@ public class RSServerService {
                     String humWarningType = "PH预警";
                     String humRemark = "环境PH报警";
                     if (alarmUtil.checkAndSaveAlarm(deviceId, humWarningType, humWarningCode,
-                            humMinValue, humMaxValue, BigDecimal.valueOf(finalValue), humRemark)) {
+                            humMinValue, humMaxValue, new BigDecimal(Float.toString(finalValue)), humRemark)) {
                         hasAlarm = true;
                     }
                 }

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

@@ -88,7 +88,6 @@ public class AlarmUtil {
 
             alarmData.setAlarmStatus(0);
             alarmData.setCreateTime(LocalDateTime.now());
-            alarmData.setRemark(remark);
             alarmData.setCreateTime(LocalDateTime.now());
 
             boolean saved = run(deviceCode, warningCode + ":报警入库", () -> requireSaved(alarmDataService.saveAlarmData(alarmData)));

+ 0 - 2
firefighting-pressure-service/src/main/java/com/zksy/pressure/utils/MessageHandler.java

@@ -290,7 +290,6 @@ public class MessageHandler extends ChannelInboundHandlerAdapter {
 
 			alarmData.setAlarmStatus(0);
 			alarmData.setCreateTime(LocalDateTime.now());
-			alarmData.setRemark(remark);
 			alarmData.setCreateTime(LocalDateTime.now());
 			boolean saved = run(deviceCode, warningCode + ":报警入库", () -> requireSaved(alarmDataService.saveAlarmData(alarmData)));
 
@@ -316,7 +315,6 @@ public class MessageHandler extends ChannelInboundHandlerAdapter {
 			alarmData.setAlarmLevel(1);
 			alarmData.setAlarmStatus(0);
 			alarmData.setCreateTime(LocalDateTime.now());
-			alarmData.setRemark(remark);
 			alarmData.setCreateTime(LocalDateTime.now());
 			boolean saved = run(deviceCode, warningCode + ":报警入库", () -> requireSaved(alarmDataService.saveAlarmData(alarmData)));
 			if (saved) { log.info("设备主动告警入库(1级): deviceCode={}, warningCode={}", deviceCode, warningCode); }

+ 0 - 1
flammable-gas-service/src/main/java/com/zksy/gas/utils/AlarmUtil.java

@@ -50,7 +50,6 @@ public class AlarmUtil {
             alarmData.setActualValue(actualValue);
             alarmData.setAlarmStatus(0);
             alarmData.setCreateTime(LocalDateTime.now());
-            alarmData.setRemark(remark);
             alarmData.setCreateTime(LocalDateTime.now());
 
             alarmDataService.saveAlarmData(alarmData);

+ 0 - 1
flammable-gas-service/src/main/java/com/zksy/gas/utils/MessageHandler.java

@@ -278,7 +278,6 @@ public class MessageHandler extends ChannelInboundHandlerAdapter {
 
 			alarmData.setAlarmStatus(0);
 			alarmData.setCreateTime(LocalDateTime.now());
-			alarmData.setRemark(remark);
 			alarmData.setCreateTime(LocalDateTime.now());
 			boolean saved = run(deviceCode, warningCode + ":报警入库", () -> requireSaved(alarmDataService.saveAlarmData(alarmData)));
 

+ 0 - 2
manhole-service/src/main/java/com/zksy/manhole/utils/AlarmUtil.java

@@ -85,7 +85,6 @@ public class AlarmUtil {
 
             alarmData.setAlarmStatus(0);
             alarmData.setCreateTime(LocalDateTime.now());
-            alarmData.setRemark(remark);
 
             boolean saved = run(deviceCode, warningCode + ":报警入库", () ->
                     requireSaved(alarmDataService.saveAlarmData(alarmData)));
@@ -125,7 +124,6 @@ public class AlarmUtil {
             alarmData.setDeviationRatio(BigDecimal.ZERO);
             alarmData.setAlarmStatus(0);
             alarmData.setCreateTime(LocalDateTime.now());
-            alarmData.setRemark(remark);
 
             boolean saved = run(deviceCode, warningCode + ":报警入库", () ->
                     requireSaved(alarmDataService.saveAlarmData(alarmData)));

+ 1 - 1
pipe-network-service/CONTEXT.md

@@ -161,7 +161,7 @@ _Avoid_:设备报警状态、设备异常
 _Avoid_:用有无实际值判断报警是否存在、把数值 0 当作空值
 
 **报警单号**:
-`alarm_data.alarm_no` 是报警事件的唯一业务号码,新建时由管理端或采集端程序生成,格式为 `BJyyyyMMddHHmmss` 加 4 位随机数(0000~9999),北京时间,总长 20 位。两端使用同一数据库事务锁保护取号、查重与入库,碰撞重试最多 100 次,唯一索引兜底;覆盖请求传入的号码,普通更新不改号,不回填历史。不使用单号生成触发器、默认值或序列。清单首列和报警转工单的创建日志使用该号,UUID 仅用于内部关联。新增、自动恢复和人工处理由业务代码在同一事务中主动写 `alarm_log`,不依赖任何自定义数据库函数或触发器;工单操作仅写 `work_order_log`,脚本删除旧日志镜像触发器及函数。`zksy-system` 与 `zk-api-service` 各保留一份 `AlarmLogOperTypeEnum`,不新增共享模块,两端值一致:0 报警触发、1 确认报警、2 误报警、3 报警处理,删除关联工单及工单操作类型。新增记录“系统触发报警”及报警描述(优先取非空备注,否则取类型/编码),恢复时记录“监测恢复,报警自动解除”;重复恢复不重复记录,日志写入失败则报警变更一同回滚,自动恢复不关闭工单。
+`alarm_data.alarm_no` 是报警事件的唯一业务号码,新建时由管理端或采集端程序生成,格式为 `BJyyyyMMddHHmmss` 加 4 位随机数(0000~9999),北京时间,总长 20 位。两端使用同一数据库事务锁保护取号、查重与入库,碰撞重试最多 100 次,唯一索引兜底;覆盖请求传入的号码,普通更新不改号,不回填历史。不使用单号生成触发器、默认值或序列。`alarm_data.alarm_content` 在新增时按 `warning_code` 关联的指标名称和触发值/上下限固化:触及下限追加“达到或低于下限”,触及上限追加“达到或超过上限”,否则只存指标名称;`remark` 列及两套 `AlarmData` 实体的 `remark`、`warningName` 属性均已删除,写入端不再通过报警对象传递指标名称。清单首列和报警转工单的创建日志使用该号,UUID 仅用于内部关联。新增、自动恢复和人工处理由业务代码在同一事务中主动写 `alarm_log`,不依赖任何自定义数据库函数或触发器;工单操作仅写 `work_order_log`,脚本删除旧日志镜像触发器及函数。`zksy-system` 与 `zk-api-service` 各保留一份 `AlarmLogOperTypeEnum`,不新增共享模块,两端值一致:0 报警触发、1 确认报警、2 误报警、3 报警处理,删除关联工单及工单操作类型。新增记录“系统触发报警”及已固化的报警内容,恢复时记录“监测恢复,报警自动解除”;重复恢复不重复记录,日志写入失败则报警变更一同回滚,自动恢复不关闭工单。
 _Avoid_:报警主键、报警类型编码、工单号
 
 **报警展示状态**:

+ 10 - 4
pipe-network-service/sql/water_supply_alarm_test_data.sql

@@ -4,9 +4,8 @@
 --   1. 测试数据使用 TEST- / test- 前缀,可安全重复执行(先删除再插入)。
 --   2. 若设备类别中已存在顶级类型"供水",则复用;否则自动创建测试顶级类型。
 --   3. 清理请执行本文件末尾的清理脚本,或单独执行 water_supply_alarm_test_cleanup.sql。
---   4. 部署顺序:须先执行 migration_alarm_case_work_order_id.sql(alarm_data/case_info/
---      hazard_hidden_account/work_order_log 的 order_id→work_order_id 改名 + alarm_data 删 alarm_time),
---      再执行本脚本;否则 work_order_id 列不存在会报错。
+--   4. 部署顺序:须先执行 migration_alarm_case_work_order_id.sql 和 migration_alarm_content.sql,
+--      再执行本脚本;否则 work_order_id/alarm_content 列不存在会报错。
 -- ============================================================
 
 -- ------------------------------------------------------------
@@ -89,7 +88,7 @@ END $$;
 --    注意:alarm_time 列已删除,统一使用 create_time。
 -- ------------------------------------------------------------
 INSERT INTO app_user.alarm_data
-    (id, device_code, warning_type, warning_code, actual_value, min_value, max_value, alarm_status, alarm_level, deviation_ratio, remark, create_time, update_time)
+    (id, device_code, warning_type, warning_code, actual_value, min_value, max_value, alarm_status, alarm_level, deviation_ratio, alarm_content, create_time, update_time)
 VALUES
     ('test-alarm-001', 'TEST-WL-001', '水位预警', 'WARN-WATER-LEVEL-TEST', 0.20, 0.50, 3.00, 0, 1, 40.00, '供水监测报警测试数据', now() - interval '180 minutes', now() - interval '180 minutes'),
     ('test-alarm-002', 'TEST-WL-001', '水位预警', 'WARN-WATER-LEVEL-TEST', 0.30, 0.50, 3.00, 0, 2, 35.00, '供水监测报警测试数据', now() - interval '150 minutes', now() - interval '150 minutes'),
@@ -100,6 +99,13 @@ VALUES
     ('test-alarm-007', 'TEST-FL-001', '流量预警', 'WARN-FLOW-TEST', 115.00, 10.00, 100.00, 0, 3, 15.00, '供水监测报警测试数据', now() - interval '1 day' - interval '30 minutes', now() - interval '1 day' - interval '30 minutes'),
     ('test-alarm-008', 'TEST-WL-002', '水位预警', 'WARN-WATER-LEVEL-TEST', 3.50, 0.50, 3.00, 1, 2, 18.00, '供水监测报警测试数据', now() - interval '300 minutes', now() - interval '290 minutes');
 
+UPDATE app_user.alarm_data
+SET alarm_content = warning_type || CASE
+    WHEN actual_value IS NOT NULL AND min_value IS NOT NULL AND actual_value <= min_value THEN '达到或低于下限'
+    WHEN actual_value IS NOT NULL AND max_value IS NOT NULL AND actual_value >= max_value THEN '达到或超过上限'
+    ELSE '' END
+WHERE id LIKE 'test-alarm-%';
+
 -- ------------------------------------------------------------
 -- 3. 阈值数据(warning_threshold,含分时阈值)
 -- ------------------------------------------------------------

+ 10 - 4
pipe-network-service/sql/water_supply_monitoring_alarm_rebuild_test_data.sql

@@ -22,9 +22,8 @@
 --      Z-TEST-*),以及 equipment_type_id 已指向不存在类型的脏数据设备;不带测试前缀的
 --      供水设备一律保留。删除清单可在执行前快照中追溯。
 --   4. 非供水设备按类型归位而非删除:燃气→3,排水→2,窨井→4。
---   5. 部署顺序:须先执行 migration_alarm_case_work_order_id.sql(alarm_data/case_info/
---      hazard_hidden_account/work_order_log 的 order_id→work_order_id 改名 + alarm_data 删 alarm_time),
---      再执行本脚本;否则 work_order_id 列不存在会报错。
+--   5. 部署顺序:须先执行 migration_alarm_case_work_order_id.sql 和 migration_alarm_content.sql,
+--      再执行本脚本;否则 work_order_id/alarm_content 列不存在会报错。
 --
 -- 执行后可对照的口径(本轮已在库中复算):
 --   设备:供水设备 15 台(流量 6 / 压力 5 / 漏失 4),全部挂在真实子类型 11/12/14
@@ -330,7 +329,7 @@ VALUES
 --    注意:alarm_time 列已删除,统一使用 create_time。
 -- ------------------------------------------------------------
 INSERT INTO app_user.alarm_data
-    (id, device_code, warning_type, warning_code, actual_value, min_value, max_value, alarm_status, alarm_level, deviation_ratio, remark, create_time, update_time)
+    (id, device_code, warning_type, warning_code, actual_value, min_value, max_value, alarm_status, alarm_level, deviation_ratio, alarm_content, create_time, update_time)
 VALUES
     ('wrb-alarm-01', 'CN-GS-202609160201', '流量预警', 'WRB-WARN-FLOW', 132.00, 10.00, 100.00, 0, 1, 32.00, '#56已转工单-WO-01 待派单', now(), now()),
     ('wrb-alarm-02', 'CB-GS-202609160202', '流量预警', 'WRB-WARN-FLOW', 128.00, 8.00, 120.00, 0, 2, 6.67, '#56已转工单-WO-02 待接单', now(), now()),
@@ -349,6 +348,13 @@ VALUES
     ('wrb-alarm-15', 'CN-GS-202609160224', '漏失预警', 'WRB-WARN-LEAK', 8.10, 0.00, 5.00, 1, 1, 62.00, '#56已解除-报警记录已处理', now(), now()),
     ('wrb-alarm-16', 'CN-GS-202609160201', '流量预警', 'WRB-WARN-FLOW', 124.00, 10.00, 100.00, 0, 2, 24.00, '#56待审核-同设备同类型已有 WO-01 未办结,转工单应被阻止', now(), now());
 
+UPDATE app_user.alarm_data
+SET alarm_content = warning_type || CASE
+    WHEN actual_value IS NOT NULL AND min_value IS NOT NULL AND actual_value <= min_value THEN '达到或低于下限'
+    WHEN actual_value IS NOT NULL AND max_value IS NOT NULL AND actual_value >= max_value THEN '达到或超过上限'
+    ELSE '' END
+WHERE id LIKE 'wrb-alarm-%';
+
 -- ------------------------------------------------------------
 -- 10. 工单(0..7 各一张;device_id 必须是供水设备 equipment_id,否则审核计数条取不到)
 -- ------------------------------------------------------------

+ 1 - 1
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GasAlarmController.java

@@ -77,7 +77,7 @@ public class GasAlarmController {
         m.put("createTime", a.getCreateTime() != null ? a.getCreateTime().format(FMT) : "");
         m.put("alarmStatus", a.getAlarmStatus());
         m.put("statusText", a.getAlarmStatus() != null && a.getAlarmStatus() == 0 ? "待处理" : "已处理");
-        m.put("remark", a.getRemark());
+        m.put("alarmContent", a.getAlarmContent());
         m.put("hasWorkOrder", order != null);
         m.put("workOrderId", order == null ? null : order.getOrderId());
         m.put("workOrderStatus", order == null ? null : order.getOrderStatus());

+ 1 - 1
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/PressureAlarmController.java

@@ -73,7 +73,7 @@ public class PressureAlarmController {
         m.put("createTime", a.getCreateTime() != null ? a.getCreateTime().format(FMT) : "");
         m.put("alarmStatus", a.getAlarmStatus());
         m.put("statusText", a.getAlarmStatus() != null && a.getAlarmStatus() == 0 ? "待处理" : "已处理");
-        m.put("remark", a.getRemark());
+        m.put("alarmContent", a.getAlarmContent());
         m.put("hasWorkOrder", order != null);
         m.put("workOrderId", order == null ? null : order.getOrderId());
         m.put("workOrderStatus", order == null ? null : order.getOrderStatus());

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

@@ -47,10 +47,6 @@ public class AlarmData implements Serializable {
     @ApiModelProperty(value = "预警编码")
     private String warningCode;
 
-    @TableField(exist = false)
-    @ApiModelProperty(value = "预警名称")
-    private String warningName;
-
     @TableField(value = "min_value")
     @ApiModelProperty(value = "预警最小值,小于等于该值视为报警")
     private BigDecimal minValue;
@@ -75,9 +71,9 @@ public class AlarmData implements Serializable {
     @ApiModelProperty(value = "报警状态 0-未处理 1-已处理")
     private Integer alarmStatus;
 
-    @TableField(value = "remark")
-    @ApiModelProperty(value = "备注")
-    private String remark;
+    @TableField(value = "alarm_content")
+    @ApiModelProperty(value = "报警内容")
+    private String alarmContent;
 
     @TableField(value = "create_time")
     @ApiModelProperty(value = "创建时间")

+ 6 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/mapper/AlarmDataMapper.java

@@ -21,6 +21,12 @@ public interface AlarmDataMapper extends BaseMapper<AlarmData> {
     @Options(useCache = false, flushCache = Options.FlushCachePolicy.TRUE)
     boolean existsAlarmNo(@Param("alarmNo") String alarmNo);
 
+    @Select("SELECT warning_name FROM app_user.warning_type WHERE warning_code=#{warningCode} ORDER BY id LIMIT 1")
+    String selectWarningName(@Param("warningCode") String warningCode);
+
+    @Select("SELECT warning_unit FROM app_user.warning_type WHERE warning_code=#{warningCode} ORDER BY id LIMIT 1")
+    String selectWarningUnit(@Param("warningCode") String warningCode);
+
     /** 人工处理也必须锁定原状态,重复处理不生成新日志。 */
     @Select("SELECT id, alarm_status, work_order_id FROM alarm_data WHERE id=#{id} FOR UPDATE")
     @Options(useCache = false, flushCache = Options.FlushCachePolicy.TRUE, timeout = 5)

+ 13 - 4
pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/service/impl/AlarmDataServiceImpl.java

@@ -266,6 +266,11 @@ public class AlarmDataServiceImpl extends ServiceImpl<AlarmDataMapper, AlarmData
             throw new IllegalStateException("未取得报警单号生成事务锁");
         }
         alarmData.setAlarmNo(AlarmLifecycleSupport.nextAlarmNo(baseMapper::existsAlarmNo));
+        String warningName = firstNonBlank(baseMapper.selectWarningName(alarmData.getWarningCode()),
+                alarmData.getWarningCode(), "监测报警");
+        alarmData.setAlarmContent(AlarmLifecycleSupport.alarmContent(warningName,
+                alarmData.getActualValue(), alarmData.getMinValue(), alarmData.getMaxValue(),
+                baseMapper.selectWarningUnit(alarmData.getWarningCode())));
         if (alarmData.getCreateTime() == null) {
             alarmData.setCreateTime(LocalDateTime.now());
         }
@@ -276,13 +281,17 @@ public class AlarmDataServiceImpl extends ServiceImpl<AlarmDataMapper, AlarmData
             alarmData.setAuditStatus(AlarmAuditStatusEnum.UNREVIEWED.getCode());
         }
         if (baseMapper.insert(alarmData) != 1) throw new IllegalStateException("报警入库失败");
-        writeLifecycleLog(alarmData.getId(), AlarmLogOperTypeEnum.CREATED,
-                AlarmLifecycleSupport.createdDescription(alarmData.getAlarmNo(), alarmData.getRemark(),
-                        null, alarmData.getWarningCode(), alarmData.getActualValue(),
-                        alarmData.getMinValue(), alarmData.getMaxValue()), "监测系统", alarmData.getCreateTime(), 0L);
+        writeLifecycleLog(alarmData.getId(), AlarmLogOperTypeEnum.CREATED, alarmData.getAlarmContent(), "监测系统", alarmData.getCreateTime(), 0L);
         return true;
     }
 
+    private String firstNonBlank(String... values) {
+        for (String value : values) {
+            if (value != null && !value.trim().isEmpty()) return value.trim();
+        }
+        return "监测报警";
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean save(AlarmData alarmData) {

+ 18 - 10
pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/support/AlarmLifecycleSupport.java

@@ -23,17 +23,25 @@ public final class AlarmLifecycleSupport {
         throw new IllegalStateException("报警单号生成冲突,请稍后重试");
     }
 
-    public static String createdDescription(String alarmNo, String remark, String warningType,
-                                            String warningCode, BigDecimal actual, BigDecimal min, BigDecimal max) {
-        String description = firstNonBlank(remark, warningType, warningCode, "监测报警");
-        StringBuilder text = new StringBuilder("系统触发报警;报警单号:")
-                .append(alarmNo).append(";报警描述:").append(description);
-        if (actual != null) {
-            text.append(";实际值:").append(actual.toPlainString());
-            if (min != null) text.append(";触发时下限:").append(min.toPlainString());
-            if (max != null) text.append(";触发时上限:").append(max.toPlainString());
+    public static String alarmContent(String warningName, BigDecimal actual, BigDecimal min, BigDecimal max, String unit) {
+        String description = firstNonBlank(warningName, "监测报警");
+        if (actual != null && min != null && actual.compareTo(min) <= 0) {
+            description += "达到或低于下限";
+        } else if (actual != null && max != null && actual.compareTo(max) >= 0) {
+            description += "达到或超过上限";
         }
-        return text.toString();
+        if (actual == null) return description;
+        String threshold;
+        if (min != null && max != null) threshold = min.stripTrailingZeros().toPlainString() + " ~ " + value(max, unit);
+        else if (min != null) threshold = "下限 " + value(min, unit);
+        else if (max != null) threshold = "上限 " + value(max, unit);
+        else threshold = "—";
+        return description + "\n实际值:" + value(actual, unit) + ";阈值:" + threshold;
+    }
+
+    private static String value(BigDecimal number, String unit) {
+        return number.stripTrailingZeros().toPlainString()
+                + (unit == null || unit.trim().isEmpty() ? "" : " " + unit.trim());
     }
 
     private static String firstNonBlank(String... values) {

+ 2 - 2
pipe-network-service/zksy-system/src/main/java/com/zksy/base/drill/service/DrillAlarmSyncService.java

@@ -54,8 +54,8 @@ public class DrillAlarmSyncService {
         boolean needCreateCase = EquipmentTypeEnum.MANHOLE_COVER_MONITOR.getId().equals(typeId);
         int caseLevel = alarmData.getAlarmLevel() != null ? alarmData.getAlarmLevel() : AlarmLevelEnum.LEVEL_FOUR.getCode();
         String desc = "设备告警";
-        if (alarmData.getRemark() != null) {
-            desc += "-" + alarmData.getRemark();
+        if (alarmData.getAlarmContent() != null) {
+            desc += "-" + alarmData.getAlarmContent();
         }
         CaseInfo caseInfo = null;
         if (needCreateCase) {

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

@@ -67,7 +67,6 @@ public class DrillSimulationServiceImpl implements DrillSimulationService {
                 alarm.setAlarmLevel(1);
                 alarm.setAlarmStatus(0);
                 alarm.setDeviationRatio(new BigDecimal("50"));
-                alarm.setRemark("演练模拟告警-" + category);
                 alarm.setCreateTime(now);
                 boolean saved = alarmDataService.saveAlarmData(alarm);
                 if (saved) {

+ 2 - 2
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/DrillAlarmSyncService.java

@@ -49,8 +49,8 @@ public class DrillAlarmSyncService {
         boolean needCreateCase = EquipmentTypeEnum.MANHOLE_COVER_MONITOR.getId().equals(typeId);
         int caseLevel = alarmData.getAlarmLevel() != null ? alarmData.getAlarmLevel() : AlarmLevelEnum.LEVEL_FOUR.getCode();
         String desc = "设备告警";
-        if (alarmData.getRemark() != null) {
-            desc += "-" + alarmData.getRemark();
+        if (alarmData.getAlarmContent() != null) {
+            desc += "-" + alarmData.getAlarmContent();
         }
         CaseInfo caseInfo = null;
         if (needCreateCase) {

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

@@ -67,7 +67,6 @@ public class DrillSimulationServiceImpl implements DrillSimulationService {
                 alarm.setAlarmLevel(1);
                 alarm.setAlarmStatus(0);
                 alarm.setDeviationRatio(new BigDecimal("50"));
-                alarm.setRemark("演练模拟告警-" + category);
                 alarm.setCreateTime(now);
                 boolean saved = alarmDataService.saveAlarmData(alarm);
                 if (saved) {

+ 1 - 3
pipe-network-service/zksy-system/src/main/resources/mapper/base/AlarmOperationsMapper.xml

@@ -15,9 +15,7 @@
         w.order_no, w.order_status, w.plan_finish_time, COALESCE(u.nick_name,u.user_name) AS receive_user_name,
         (SELECT wtt.warning_unit FROM app_user.equipment_warning_type ewt
             JOIN app_user.warning_type wtt ON wtt.id = ewt.warning_type_id
-            WHERE ewt.equipment_type_id = e.equipment_type_id AND wtt.warning_code = a.warning_code LIMIT 1) AS warning_unit,
-        (SELECT wtt.warning_name FROM app_user.warning_type wtt
-            WHERE wtt.warning_code = a.warning_code LIMIT 1) AS warning_name
+            WHERE ewt.equipment_type_id = e.equipment_type_id AND wtt.warning_code = a.warning_code LIMIT 1) AS warning_unit
     </sql>
     <resultMap id="alarm" type="com.zksy.base.alarm.dto.AlarmVO" autoMapping="true">
         <id property="id" column="id"/>

+ 28 - 2
pipe-network-service/zksy-system/src/test/java/com/zksy/base/alarm/service/AlarmLifecycleServiceTest.java

@@ -45,13 +45,39 @@ class AlarmLifecycleServiceTest {
         when(logs.insertLifecycleIfAbsent(anyString(),anyInt(),anyString(),anyString(),any(),anyLong(),nullable(Long.class))).thenReturn(1);
     }
     @Test void insertWritesLogBeforeCommittingWithoutTrigger() {
-        AlarmData alarm=new AlarmData(); alarm.setRemark("压力过低"); alarm.setActualValue(BigDecimal.ZERO);
+        AlarmData alarm=new AlarmData(); alarm.setWarningCode("PRESSURE"); alarm.setActualValue(BigDecimal.ZERO);
+        when(mapper.selectWarningName("PRESSURE")).thenReturn("压力过低");
         assertTrue(service.saveAlarmData(alarm));
+        assertEquals("压力过低\n实际值:0;阈值:—", alarm.getAlarmContent());
         var ordered=inOrder(mapper,logs,manager);
         ordered.verify(mapper).insert(alarm);
-        ordered.verify(logs).insertLifecycleIfAbsent(eq("a1"),eq(0),eq("系统触发报警;报警单号:"+alarm.getAlarmNo()+";报警描述:压力过低;实际值:0"),eq("监测系统"),any(),anyLong(),nullable(Long.class));
+        ordered.verify(logs).insertLifecycleIfAbsent(eq("a1"),eq(0),eq("系统触发报警;报警单号:"+alarm.getAlarmNo()+";报警描述:"+alarm.getAlarmContent()+";实际值:0"),eq("监测系统"),any(),anyLong(),nullable(Long.class));
         ordered.verify(manager).commit(any());
     }
+    @Test void insertBuildsContentFromWarningNameAndLowerThreshold() {
+        AlarmData alarm=new AlarmData(); alarm.setWarningCode("PRESSURE");
+        alarm.setActualValue(BigDecimal.ZERO); alarm.setMinValue(BigDecimal.ZERO);
+        when(mapper.selectWarningName("PRESSURE")).thenReturn("压力");
+        assertTrue(service.saveAlarmData(alarm));
+        assertEquals("压力达到或低于下限\n实际值:0;阈值:下限 0", alarm.getAlarmContent());
+    }
+    @Test void insertIncludesUpperThresholdAndUnit() {
+        AlarmData alarm=new AlarmData(); alarm.setWarningCode("COD");
+        alarm.setActualValue(new BigDecimal("60.750")); alarm.setMaxValue(new BigDecimal("60.00"));
+        when(mapper.selectWarningName("COD")).thenReturn("化学需氧量");
+        when(mapper.selectWarningUnit("COD")).thenReturn("mg/L");
+        assertTrue(service.saveAlarmData(alarm));
+        assertEquals("化学需氧量达到或超过上限\n实际值:60.75 mg/L;阈值:上限 60 mg/L", alarm.getAlarmContent());
+    }
+    @Test void insertIncludesRangeAndZeroValue() {
+        AlarmData alarm=new AlarmData(); alarm.setWarningCode("EC");
+        alarm.setActualValue(BigDecimal.ZERO); alarm.setMinValue(new BigDecimal("100"));
+        alarm.setMaxValue(new BigDecimal("5000"));
+        when(mapper.selectWarningName("EC")).thenReturn("电导率");
+        when(mapper.selectWarningUnit("EC")).thenReturn("μS/cm");
+        assertTrue(service.saveAlarmData(alarm));
+        assertEquals("电导率达到或低于下限\n实际值:0 μS/cm;阈值:100 ~ 5000 μS/cm", alarm.getAlarmContent());
+    }
     @Test void inheritedSaveAlsoInitializesLog() {
         assertTrue(service.save(new AlarmData()));
         verify(logs).insertLifecycleIfAbsent(eq("a1"),eq(0),anyString(),eq("监测系统"),any(),anyLong(),nullable(Long.class));

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

@@ -269,7 +269,6 @@ public class MessageHandler extends ChannelInboundHandlerAdapter {
 
 			alarmData.setAlarmStatus(0);
 			alarmData.setCreateTime(LocalDateTime.now());
-			alarmData.setRemark(remark);
 			alarmData.setCreateTime(LocalDateTime.now());
 			boolean saved = run(deviceCode, warningCode + ":报警入库", () -> requireSaved(alarmDataService.saveAlarmData(alarmData)));
 			if (saved) { logger.info("告警数据入库成功: deviceCode={}, warningCode={}, level={}级", deviceCode, warningCode, alarmLevel); }

+ 0 - 1
telemetry-service/src/main/java/com/zksy/telemetry/utils/MessageHandler.java

@@ -177,7 +177,6 @@ public class MessageHandler extends ChannelInboundHandlerAdapter {
                 alarm.setDeviationRatio(AlarmLevelUtil.calculateDeviationRatio(actual, min, max));
                 alarm.setAlarmStatus(0);
                 alarm.setCreateTime(java.time.LocalDateTime.now());
-                alarm.setRemark(null);
                 alarm.setCreateTime(java.time.LocalDateTime.now());
                 requireSaved(alarmDataService.saveAlarmData(alarm));
             });

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

@@ -268,7 +268,6 @@ public class MessageHandler extends ChannelInboundHandlerAdapter {
 
 			alarmData.setAlarmStatus(0);
 			alarmData.setCreateTime(LocalDateTime.now());
-			alarmData.setRemark(remark);
 			alarmData.setCreateTime(LocalDateTime.now());
 			boolean saved = run(deviceCode, warningCode + ":报警入库", () -> requireSaved(alarmDataService.saveAlarmData(alarmData)));
 			if (saved) { logger.info("告警数据入库成功: deviceCode={}, warningCode={}, level={}级", deviceCode, warningCode, alarmLevel); }

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

@@ -67,9 +67,9 @@ public class AlarmData implements Serializable {
     @ApiModelProperty(value = "审核结果:0-未审核 1-确认报警 2-误报警;由管理端统一审核")
     private Integer auditStatus;
 
-    @TableField(value = "remark")
-    @ApiModelProperty(value = "备注")
-    private String remark;
+    @TableField(value = "alarm_content")
+    @ApiModelProperty(value = "报警内容")
+    private String alarmContent;
 
     @TableField(value = "create_time")
     @ApiModelProperty(value = "创建时间")

+ 7 - 1
zk-api-service/src/main/java/com/zksy/api/mapper/AlarmDataMapper.java

@@ -18,6 +18,12 @@ public interface AlarmDataMapper extends BaseMapper<AlarmData> {
     @Options(useCache = false, flushCache = Options.FlushCachePolicy.TRUE)
     boolean existsAlarmNo(@Param("alarmNo") String alarmNo);
 
+    @Select("SELECT warning_name FROM app_user.warning_type WHERE warning_code=#{warningCode} ORDER BY id LIMIT 1")
+    String selectWarningName(@Param("warningCode") String warningCode);
+
+    @Select("SELECT warning_unit FROM app_user.warning_type WHERE warning_code=#{warningCode} ORDER BY id LIMIT 1")
+    String selectWarningUnit(@Param("warningCode") String warningCode);
+
     /** Kingbase事务锁:同一设备/报警编码的跨线程、跨实例写入串行,提交/回滚自动释放。 */
     @Select("SELECT 1 FROM pg_advisory_xact_lock(hashtext('collection-alarm:' || #{deviceCode}), hashtext(#{warningCode}))")
     @Options(timeout = 5)
@@ -30,7 +36,7 @@ public interface AlarmDataMapper extends BaseMapper<AlarmData> {
     AlarmData selectUnhandledAlarmForUpdate(@Param("deviceCode") String deviceCode, @Param("warningCode") String warningCode);
 
     /** 只更新本次等级对应的采集字段;max_value非空列在无阈值时写0,其余空指标可清空。 */
-    @Update("UPDATE alarm_data SET alarm_level=#{sample.alarmLevel}, actual_value=#{sample.actualValue,jdbcType=NUMERIC}, "
+    @Update("UPDATE alarm_data SET alarm_level=#{sample.alarmLevel}, alarm_content=#{sample.alarmContent}, actual_value=#{sample.actualValue,jdbcType=NUMERIC}, "
             + "min_value=#{sample.minValue,jdbcType=NUMERIC}, max_value=COALESCE(#{sample.maxValue,jdbcType=NUMERIC},0), "
             + "deviation_ratio=#{sample.deviationRatio,jdbcType=NUMERIC}, update_time=CURRENT_TIMESTAMP "
             + "WHERE id=#{id} AND device_code=#{sample.deviceCode} AND warning_code=#{sample.warningCode} AND alarm_status=0")

+ 11 - 7
zk-api-service/src/main/java/com/zksy/api/service/CollectionAlarmWriter.java

@@ -42,7 +42,11 @@ public class CollectionAlarmWriter {
     public boolean saveOrUpdateUnhandled(AlarmData alarm) {
         if (alarm == null) throw new IllegalArgumentException("告警不能为空");
         validateKey(alarm.getDeviceCode(), alarm.getWarningCode());
-        alarm.setRemark(alarmLabel(alarm.getRemark()));
+        String warningName = firstNonBlank(mapper.selectWarningName(alarm.getWarningCode()),
+                alarm.getWarningCode(), "监测报警");
+        alarm.setAlarmContent(AlarmLifecycleSupport.alarmContent(warningName,
+                alarm.getActualValue(), alarm.getMinValue(), alarm.getMaxValue(),
+                mapper.selectWarningUnit(alarm.getWarningCode())));
 
         if (alarm.getCreateTime() == null) alarm.setCreateTime(LocalDateTime.now());
         if (alarm.getAlarmStatus() == null) alarm.setAlarmStatus(0);
@@ -70,16 +74,16 @@ public class CollectionAlarmWriter {
             }
             alarm.setAlarmNo(AlarmLifecycleSupport.nextAlarmNo(mapper::existsAlarmNo));
             if (mapper.insert(alarm) != 1) throw new IllegalStateException("告警入库未成功");
-            writeLifecycleLog(alarm.getId(), AlarmLogOperTypeEnum.CREATED,
-                    AlarmLifecycleSupport.createdDescription(alarm.getAlarmNo(), alarm.getRemark(), null,
-                            alarm.getWarningCode(), alarm.getActualValue(), alarm.getMinValue(), alarm.getMaxValue()),
-                    "监测系统", alarm.getCreateTime(), 0L);
+            writeLifecycleLog(alarm.getId(), AlarmLogOperTypeEnum.CREATED, alarm.getAlarmContent(), "监测系统", alarm.getCreateTime(), 0L);
             return true;
         }));
     }
 
-    private String alarmLabel(String value) {
-        return value == null ? null : value.replace("预警", "告警");
+    private String firstNonBlank(String... values) {
+        for (String value : values) {
+            if (value != null && !value.trim().isEmpty()) return value.trim();
+        }
+        return "监测报警";
     }
 
     /**

+ 0 - 1
zk-api-service/src/main/java/com/zksy/api/service/impl/EquipmentStatusApiServiceImpl.java

@@ -196,7 +196,6 @@ public class EquipmentStatusApiServiceImpl implements EquipmentStatusApiService
         alarmData.setAlarmLevel(4);
         alarmData.setAlarmStatus(0);
         alarmData.setCreateTime(LocalDateTime.now());
-        alarmData.setRemark("设备超过设定时间未上报数据,判定为离线");
         alarmData.setCreateTime(LocalDateTime.now());
         if (collectionAlarmWriter.saveOrUpdateUnhandled(alarmData)) {
             log.info("设备离线报警数据入库成功: deviceCode={}, alarmId={}", deviceCode, alarmData.getId());

+ 17 - 10
zk-api-service/src/main/java/com/zksy/api/support/AlarmLifecycleSupport.java

@@ -23,19 +23,26 @@ public final class AlarmLifecycleSupport {
         throw new IllegalStateException("报警单号生成冲突,请稍后重试");
     }
 
-    public static String createdDescription(String alarmNo, String remark, String warningType,
-                                            String warningCode, BigDecimal actual, BigDecimal min, BigDecimal max) {
-        String description = firstNonBlank(remark, warningType, warningCode, "监测报警");
-        StringBuilder text = new StringBuilder("系统触发报警;报警单号:")
-                .append(alarmNo).append(";报警描述:").append(description);
-        if (actual != null) {
-            text.append(";实际值:").append(actual.toPlainString());
-            if (min != null) text.append(";触发时下限:").append(min.toPlainString());
-            if (max != null) text.append(";触发时上限:").append(max.toPlainString());
+    public static String alarmContent(String warningName, BigDecimal actual, BigDecimal min, BigDecimal max, String unit) {
+        String description = firstNonBlank(warningName, "监测报警");
+        if (actual != null && min != null && actual.compareTo(min) <= 0) {
+            description += "达到或低于下限";
+        } else if (actual != null && max != null && actual.compareTo(max) >= 0) {
+            description += "达到或超过上限";
         }
-        return text.toString();
+        if (actual == null) return description;
+        String threshold;
+        if (min != null && max != null) threshold = min.stripTrailingZeros().toPlainString() + " ~ " + value(max, unit);
+        else if (min != null) threshold = "下限 " + value(min, unit);
+        else if (max != null) threshold = "上限 " + value(max, unit);
+        else threshold = "—";
+        return description + "\n实际值:" + value(actual, unit) + ";阈值:" + threshold;
     }
 
+    private static String value(BigDecimal number, String unit) {
+        return number.stripTrailingZeros().toPlainString()
+                + (unit == null || unit.trim().isEmpty() ? "" : " " + unit.trim());
+    }
     private static String firstNonBlank(String... values) {
         for (String value : values) {
             if (value != null && !value.trim().isEmpty()) return value.trim();

+ 3 - 4
zk-api-service/src/main/java/com/zksy/api/utils/SmsUtil.java

@@ -229,7 +229,7 @@ public class SmsUtil {
             String remark = null;
 
             // 获取当前节点的指标数值
-            double currentValue = getCurrentValueByNodeId(nodeData, nodeId);
+            float currentValue = getCurrentValueByNodeId(nodeData, nodeId);
 
             // 判断是否触发报警
             boolean isOverThreshold = false;
@@ -252,7 +252,7 @@ public class SmsUtil {
                 saveAlarmData(deviceCode, warningType, warningCode,
                         minValue != null ? BigDecimal.valueOf(minValue) : null,
                         maxValue != null ? BigDecimal.valueOf(maxValue) : null,
-                        BigDecimal.valueOf(currentValue), remark);
+                        new BigDecimal(Float.toString(currentValue)), remark);
 
                 // 2. 再构造并发送短信
                 JSONObject params = new JSONObject();
@@ -292,7 +292,6 @@ public class SmsUtil {
             alarmData.setActualValue(actualValue);
             alarmData.setAlarmStatus(0);
             alarmData.setCreateTime(LocalDateTime.now());
-            alarmData.setRemark(remark);
             alarmData.setCreateTime(LocalDateTime.now());
             com.zksy.common.utils.CollectionSideEffects.requireSaved(alarmDataService.saveAlarmData(alarmData));
         } catch (Exception e) {
@@ -331,7 +330,7 @@ public class SmsUtil {
     /**
      * 根据nodeId获取对应的指标数值
      */
-    private double getCurrentValueByNodeId(NodeData nodeData, int nodeId) {
+    private float getCurrentValueByNodeId(NodeData nodeData, int nodeId) {
         if (nodeId == 1) {
             // 节点1需要特殊处理,取悬浮物字段(floatValue字段)
             return nodeData.getFloatValue();

+ 2 - 2
zk-api-service/src/main/resources/mapper/AlarmDataMapper.xml

@@ -17,7 +17,7 @@
             <result property="actualValue" column="actual_value" jdbcType="DECIMAL"/>
             <result property="alarmStatus" column="alarm_status" jdbcType="INTEGER"/>
 
-            <result property="remark" column="remark" jdbcType="VARCHAR"/>
+            <result property="alarmContent" column="alarm_content" jdbcType="VARCHAR"/>
             <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
             <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
     </resultMap>
@@ -26,6 +26,6 @@
         id,work_order_id,device_code,
         warning_code,min_value,max_value,alarm_level,deviation_ratio,audit_status,
         actual_value,alarm_status,
-        remark,create_time,update_time
+        alarm_content,create_time,update_time
     </sql>
 </mapper>

+ 34 - 2
zk-api-service/src/test/java/com/zksy/api/service/CollectionAlarmLifecycleTest.java

@@ -25,6 +25,7 @@ class CollectionAlarmLifecycleTest {
         when(mapper.lockAlarmKey("D1", "PRESSURE")).thenReturn(1);
         when(mapper.lockAlarmNumberGeneration()).thenReturn(1);
         when(mapper.markAlarmRecovered(anyString())).thenReturn(1);
+        when(mapper.selectWarningName("PRESSURE")).thenReturn("压力越界");
         when(mapper.selectRecoverableAlarmIdsForUpdate("D1", "PRESSURE")).thenReturn(Collections.singletonList("a1"));
         when(logs.insertLifecycleIfAbsent(anyString(),anyInt(),anyString(),anyString(),any(),anyLong())).thenReturn(1);
         when(mapper.insert(any(AlarmData.class))).thenAnswer(call -> {
@@ -34,10 +35,11 @@ class CollectionAlarmLifecycleTest {
     @Test void creationWritesDescriptionDirectlyWithoutAnyTrigger() {
         AlarmData alarm = alarm(); alarm.setActualValue(BigDecimal.ZERO); alarm.setMaxValue(BigDecimal.TEN);
         assertTrue(writer.saveOrUpdateUnhandled(alarm));
+        assertEquals("压力越界\n实际值:0;阈值:上限 10", alarm.getAlarmContent());
         var ordered = inOrder(mapper,logs,manager);
         ordered.verify(mapper).insert(alarm);
         ordered.verify(logs).insertLifecycleIfAbsent(eq("a1"),eq(0),
-                eq("系统触发报警;报警单号:"+alarm.getAlarmNo()+";报警描述:压力越界;实际值:0;触发时上限:10"),eq("监测系统"),any(),anyLong());
+                eq("系统触发报警;报警单号:"+alarm.getAlarmNo()+";报警描述:"+alarm.getAlarmContent()+";实际值:0;触发时上限:10"),eq("监测系统"),any(),anyLong());
         ordered.verify(manager).commit(any());
     }
     @Test void absentActualValueDoesNotIncludeThresholdPlaceholders() {
@@ -46,6 +48,36 @@ class CollectionAlarmLifecycleTest {
         verify(logs).insertLifecycleIfAbsent(eq("a1"),eq(0),
                 eq("系统触发报警;报警单号:"+alarm.getAlarmNo()+";报警描述:压力越界"),eq("监测系统"),any(),anyLong());
     }
+    @Test void contentMatchesFrontendThresholdDescriptionRule() {
+        AlarmData alarm=alarm(); alarm.setActualValue(BigDecimal.ZERO); alarm.setMinValue(BigDecimal.ZERO);
+        assertTrue(writer.saveOrUpdateUnhandled(alarm));
+        assertEquals("压力越界达到或低于下限\n实际值:0;阈值:下限 0", alarm.getAlarmContent());
+    }
+    @Test void contentIncludesUpperThresholdAndUnit() {
+        when(mapper.selectWarningUnit("PRESSURE")).thenReturn("mg/L");
+        AlarmData alarm = alarm();
+        alarm.setActualValue(new BigDecimal("60.750"));
+        alarm.setMaxValue(new BigDecimal("60.00"));
+        assertTrue(writer.saveOrUpdateUnhandled(alarm));
+        assertEquals("压力越界达到或超过上限\n实际值:60.75 mg/L;阈值:上限 60 mg/L", alarm.getAlarmContent());
+    }
+    @Test void contentIncludesLowerThresholdAndUnit() {
+        when(mapper.selectWarningUnit("PRESSURE")).thenReturn("mg/L");
+        AlarmData alarm = alarm();
+        alarm.setActualValue(new BigDecimal("25.78"));
+        alarm.setMinValue(new BigDecimal("5"));
+        assertTrue(writer.saveOrUpdateUnhandled(alarm));
+        assertEquals("压力越界\n实际值:25.78 mg/L;阈值:下限 5 mg/L", alarm.getAlarmContent());
+    }
+    @Test void contentIncludesRangeAndZeroValue() {
+        when(mapper.selectWarningUnit("PRESSURE")).thenReturn("μS/cm");
+        AlarmData alarm = alarm();
+        alarm.setActualValue(BigDecimal.ZERO);
+        alarm.setMinValue(new BigDecimal("100"));
+        alarm.setMaxValue(new BigDecimal("5000"));
+        assertTrue(writer.saveOrUpdateUnhandled(alarm));
+        assertEquals("压力越界达到或低于下限\n实际值:0 μS/cm;阈值:100 ~ 5000 μS/cm", alarm.getAlarmContent());
+    }
     @Test void everyRecoveredRowGetsLogBeforeCommit() {
         when(mapper.selectRecoverableAlarmIdsForUpdate("D1", "PRESSURE")).thenReturn(Arrays.asList("a1","a2"));
         assertEquals(2, writer.markRecovered("D1", "PRESSURE"));
@@ -98,6 +130,6 @@ class CollectionAlarmLifecycleTest {
     }
     private AlarmData alarm() {
         AlarmData alarm=new AlarmData(); alarm.setDeviceCode("D1"); alarm.setWarningCode("PRESSURE");
-        alarm.setRemark("压力越界"); return alarm;
+        return alarm;
     }
 }