案例 9:IoT 设备告警分级
本案例对应 JQuick-Java 仓库 USE-CASES.md 的场景 9:IoT 设备告警分级,属于物联网/设备运维领域,解决对传感器、网关、摄像头、工业机器人等设备做「六维实时指标独立打分 → 告警等级/类型 → 派单/通知/自动处置/升级时限」的实时告警决策问题。
IoT 平台需要对海量在线设备做实时告警分级。设备运行快照包含:设备编号、名称、类型(SENSOR/GATEWAY/CAMERA/ROBOT)、设备重要性(HIGH/MEDIUM/LOW)、当前温度、CPU 使用率、内存使用率、窗口期错误次数、是否断连、最近心跳间隔(秒)、电池电量与振动强度。
评分从六个维度独立进行:温度按 90/80/70/60℃ 四档记 35/25/15/8 分;错误次数按 20/10/5/1 次四档记 25/18/10/4 分;CPU 与内存取峰值后按 95%/85%/75% 记 20/12/6 分;电量不高于 5% 记 15 分、不高于 20% 记 8 分;断连直接记 25 分,心跳间隔达到 300 秒记 15 分、达到 120 秒记 8 分;振动强度按 8/5/3 三档记 20/12/5 分。六维汇总后按 70/45/20 三条线映射 CRITICAL/MAJOR/MINOR/NORMAL。
告警类型按「网络 > 温度 > 性能 > 电量 > 振动」的业务优先级唯一归类:断连或心跳超过 300 秒归为 NETWORK,温度达到 70℃ 归为 TEMPERATURE,CPU 或内存达到 85% 归为 PERFORMANCE,电量不高于 20% 归为 POWER,振动达到 5 归为 VIBRATION。在等级与类型之上,再联动产出派单结论与 P0-P3 优先级(45 分以上必派单,20-44 分的关键 HIGH 设备也要派单,优先级 P2)、通知渠道(CRITICAL 电话、MAJOR 短信、MINOR IM、其余邮件)、自动处置动作(70 分以上或网络类隔离 ISOLATE,性能类限流 RATE_LIMIT,温度/振动类降频 THROTTLE)与升级时限(CRITICAL 5 分钟、MAJOR 15 分钟、MINOR 60 分钟、NORMAL 240 分钟)。
决策同时附带命中指标明细与处置动作清单,便于告警复盘与审计。场景 9 覆盖 6 组形态:炼钢炉温控传感器严重过热 80 分 P0、园区闸机网关断连 51 分 P1、图像识别节点性能告警 22 分 MINOR 不派单、关键设备低电量 25 分 P2 派单、健康设备 0 分 NORMAL、传送带电机振动异常 38 分降频。
- 六维阈值(温度 90/80/70/60℃、错误 20/10/5/1 次、心跳 300/120 秒、电量 5%/20% 等)硬编码在网关脚本、流式作业与告警平台中,调一次阈值要重新上线整条链路。
- 告警类型按优先级归类与六维评分是两套逻辑,散落在不同组件里,新增一个告警维度就要同时改多处。
- 派单不仅看分数还要叠加设备重要性(20-44 分的关键设备 P2 派单),用普通
if-else堆叠后迅速腐化、无人敢改。 - 通知渠道、自动处置(隔离/限流/降频)、升级时限都与告警等级联动,改一个升级时限可能影响派单与通知,牵一发而动全身。
- 告警风暴之下,运维需要每条告警附带命中了哪些指标、执行了哪些处置动作,黑盒告警无法复盘和定责。
- 边缘网关与设备平台多为 JDK 8 与国产化环境,资源受限,无法部署重型规则中间件。
本案例采用标准「六件套」:入参模型承载设备实时快照,决策结果模型承载等级、类型、派单、通知、处置与升级时限,规则接口声明 13 个规则方法,XML 脚本外置全部打分口径与阈值,Service 负责六维汇总封顶与指标/动作组装,JUnit 以 6 组告警形态回归。
| 层次 | 职责 | 对应文件 |
|---|---|---|
| 领域模型 | 设备运行快照、告警决策结果 | DeviceAlarm.java、AlarmDecision.java |
| 规则集与 XML | 六维评分 + 类型归类 + 等级/派单/通知/处置/升级共 13 个方法 | DeviceAlarmRules.java、device-alarm.xml |
| 编排服务 | 六维汇总并 clamp 到 0-100、指标明细与处置动作清单组装 | DeviceAlarmService.java |
| 测试 | 6 组告警形态(过热/断连/性能/低电量/正常/振动) | DeviceAlarmServiceTest.java |
规则方法拆分
Section titled “规则方法拆分”| 规则方法 | 口径 | 分值/输出 |
|---|---|---|
scoreTemperature |
≥90℃ 35 / ≥80 25 / ≥70 15 / ≥60 8 / 否则 0 | 0-35 |
scoreError |
错误次数 ≥20 记 25 / ≥10 记 18 / ≥5 记 10 / ≥1 记 4 / 否则 0 | 0-25 |
scoreResource |
CPU 与内存取峰值,≥95 记 20 / ≥85 记 12 / ≥75 记 6 / 否则 0 | 0-20 |
scoreBattery |
电量 ≤5% 记 15 / ≤20% 记 8 / 否则 0 | 0-15 |
scoreConnection |
断连记 25 / 心跳 ≥300s 记 15 / ≥120s 记 8 / 否则 0 | 0-25 |
scoreVibration |
振动 ≥8 记 20 / ≥5 记 12 / ≥3 记 5 / 否则 0 | 0-20 |
alarmLevel |
≥70 CRITICAL / ≥45 MAJOR / ≥20 MINOR / 否则 NORMAL | CRITICAL/MAJOR/MINOR/NORMAL |
alarmType |
网络(断连或心跳≥300s)> 温度(≥70℃)> 性能(CPU/内存≥85)> 电量(≤20%)> 振动(≥5),否则 NONE | NETWORK/TEMPERATURE/PERFORMANCE/POWER/VIBRATION/NONE |
needDispatch |
≥45 派单;20-44 且关键设备(HIGH)派单;否则不派 | true / false |
dispatchPriority |
≥70 P0 / ≥45 P1 / 20-44 且 HIGH 为 P2 / 否则 P3 | P0/P1/P2/P3 |
notifyChannel |
CRITICAL PHONE_CALL / MAJOR SMS / MINOR IM / 其余 EMAIL | PHONE_CALL/SMS/IM/EMAIL |
autoRemediation |
≥70 或 NETWORK → ISOLATE;PERFORMANCE → RATE_LIMIT;TEMPERATURE/VIBRATION → THROTTLE;否则 NONE | ISOLATE/RATE_LIMIT/THROTTLE/NONE |
escalateMinutes |
CRITICAL 5 / MAJOR 15 / MINOR 60 / NORMAL 240 | 升级时限(分钟) |
代码实现(完整可运行)
Section titled “代码实现(完整可运行)”将六个文件按仓库原有包结构放入 Maven 工程:两个模型放入 src/test/java/com/github/paohaijiao/demo/model/,规则接口与测试放入 com.github.paohaijiao.demo 包,编排服务放入 com.github.paohaijiao.demo.service 包,XML 脚本放入 src/test/resources/demo/device-alarm.xml,包名全部保持源码原样。工程根目录执行 mvn test -Dtest=DeviceAlarmServiceTest 即可运行。
DeviceAlarm.java
Section titled “DeviceAlarm.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * IoT 设备运行快照(告警分级所需的实时指标) */@Data@NoArgsConstructor@AllArgsConstructorpublic class DeviceAlarm {
/** * 设备编号 */ private String deviceId;
/** * 设备名称 */ private String deviceName;
/** * 设备类型:SENSOR / GATEWAY / CAMERA / ROBOT */ private String deviceType;
/** * 设备重要性:HIGH / MEDIUM / LOW */ private String criticality;
/** * 温度(摄氏度) */ private double temperature;
/** * CPU 使用率(0-100) */ private int cpuUsage;
/** * 内存使用率(0-100) */ private int memoryUsage;
/** * 窗口期内错误次数 */ private int errorCount;
/** * 是否断连 */ private boolean connectionLost;
/** * 最近心跳间隔(秒) */ private int lastHeartbeatSeconds;
/** * 电池电量(0-100) */ private int batteryLevel;
/** * 振动强度 */ private double vibrationLevel;}AlarmDecision.java
Section titled “AlarmDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * IoT 设备告警决策结果 */@Datapublic class AlarmDecision {
/** * 设备编号 */ private String deviceId;
/** * 告警分(0-100) */ private int alarmScore;
/** * 告警等级:CRITICAL / MAJOR / MINOR / NORMAL */ private String alarmLevel;
/** * 告警类型:TEMPERATURE / PERFORMANCE / NETWORK / POWER / VIBRATION / NONE */ private String alarmType;
/** * 是否派发工单 */ private boolean needDispatch;
/** * 派单优先级:P0 / P1 / P2 / P3 */ private String dispatchPriority;
/** * 通知渠道:PHONE_CALL / SMS / IM / EMAIL */ private String notifyChannel;
/** * 自动处置动作:ISOLATE / RATE_LIMIT / THROTTLE / NONE */ private String autoRemediation;
/** * 升级时限(分钟) */ private int escalateMinutes;
/** * 命中指标明细 */ private List<String> indicators = new ArrayList<String>();
/** * 处置动作建议 */ private List<String> actions = new ArrayList<String>();
public void addIndicator(String indicator) { this.indicators.add(indicator); }
public void addAction(String action) { this.actions.add(action); }}DeviceAlarmRules.java
Section titled “DeviceAlarmRules.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/** * 场景 9:IoT 设备告警分级 * <p> * 各维度独立打分(温度/错误/资源/电量/连接/振动),再由综合分决定等级、派单、通知与自动处置。 */public interface DeviceAlarmRules {
int scoreTemperature(@Param("temperature") double temperature);
int scoreError(@Param("errorCount") int errorCount);
int scoreResource(@Param("cpuUsage") int cpuUsage, @Param("memoryUsage") int memoryUsage);
int scoreBattery(@Param("batteryLevel") int batteryLevel);
int scoreConnection(@Param("connectionLost") boolean connectionLost, @Param("lastHeartbeatSeconds") int lastHeartbeatSeconds);
int scoreVibration(@Param("vibrationLevel") double vibrationLevel);
String alarmLevel(@Param("alarmScore") int alarmScore);
String alarmType(@Param("connectionLost") boolean connectionLost, @Param("lastHeartbeatSeconds") int lastHeartbeatSeconds, @Param("temperature") double temperature, @Param("cpuUsage") int cpuUsage, @Param("memoryUsage") int memoryUsage, @Param("batteryLevel") int batteryLevel, @Param("vibrationLevel") double vibrationLevel);
boolean needDispatch(@Param("alarmScore") int alarmScore, @Param("criticality") String criticality);
String dispatchPriority(@Param("alarmScore") int alarmScore, @Param("criticality") String criticality);
String notifyChannel(@Param("alarmLevel") String alarmLevel, @Param("criticality") String criticality);
String autoRemediation(@Param("alarmType") String alarmType, @Param("alarmScore") int alarmScore);
int escalateMinutes(@Param("alarmLevel") String alarmLevel);}device-alarm.xml
Section titled “device-alarm.xml”<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE javas PUBLIC "-//PAOHAIJIAO//DTD API JAVA 1.0//EN" "classpath:paohaijiao/dtd/Jquick-java.dtd"><javas namespace="com.github.paohaijiao.demo.DeviceAlarmRules">
<!-- 温度评分:0~35 --> <java name="scoreTemperature" returnClass="int"> <![CDATA[ int def scoreTemperature(double:temperature) { if (temperature >= 90.0) { return 35; } else if (temperature >= 80.0) { return 25; } else if (temperature >= 70.0) { return 15; } else if (temperature >= 60.0) { return 8; } else { return 0; } } ]]> </java>
<!-- 错误次数评分:0~25 --> <java name="scoreError" returnClass="int"> <![CDATA[ int def scoreError(int:errorCount) { if (errorCount >= 20) { return 25; } else if (errorCount >= 10) { return 18; } else if (errorCount >= 5) { return 10; } else if (errorCount >= 1) { return 4; } else { return 0; } } ]]> </java>
<!-- 资源占用评分(取 CPU/内存峰值):0~20 --> <java name="scoreResource" returnClass="int"> <![CDATA[ int def scoreResource(int:cpuUsage, int:memoryUsage) { int peak = cpuUsage; if (memoryUsage > peak) { peak = memoryUsage; } if (peak >= 95) { return 20; } else if (peak >= 85) { return 12; } else if (peak >= 75) { return 6; } else { return 0; } } ]]> </java>
<!-- 电量评分:0~15 --> <java name="scoreBattery" returnClass="int"> <![CDATA[ int def scoreBattery(int:batteryLevel) { if (batteryLevel <= 5) { return 15; } else if (batteryLevel <= 20) { return 8; } else { return 0; } } ]]> </java>
<!-- 连接异常评分:0~25 --> <java name="scoreConnection" returnClass="int"> <![CDATA[ int def scoreConnection(boolean:connectionLost, int:lastHeartbeatSeconds) { if (connectionLost) { return 25; } else if (lastHeartbeatSeconds >= 300) { return 15; } else if (lastHeartbeatSeconds >= 120) { return 8; } else { return 0; } } ]]> </java>
<!-- 振动评分:0~20 --> <java name="scoreVibration" returnClass="int"> <![CDATA[ int def scoreVibration(double:vibrationLevel) { if (vibrationLevel >= 8.0) { return 20; } else if (vibrationLevel >= 5.0) { return 12; } else if (vibrationLevel >= 3.0) { return 5; } else { return 0; } } ]]> </java>
<!-- 告警等级 --> <java name="alarmLevel" returnClass="java.lang.String"> <![CDATA[ str def alarmLevel(int:alarmScore) { if (alarmScore >= 70) { return "CRITICAL"; } else if (alarmScore >= 45) { return "MAJOR"; } else if (alarmScore >= 20) { return "MINOR"; } else { return "NORMAL"; } } ]]> </java>
<!-- 告警类型(按业务优先级归类) --> <java name="alarmType" returnClass="java.lang.String"> <![CDATA[ str def alarmType(boolean:connectionLost, int:lastHeartbeatSeconds, double:temperature, int:cpuUsage, int:memoryUsage, int:batteryLevel, double:vibrationLevel) { if (connectionLost || lastHeartbeatSeconds >= 300) { return "NETWORK"; } else if (temperature >= 70.0) { return "TEMPERATURE"; } else if (cpuUsage >= 85 || memoryUsage >= 85) { return "PERFORMANCE"; } else if (batteryLevel <= 20) { return "POWER"; } else if (vibrationLevel >= 5.0) { return "VIBRATION"; } else { return "NONE"; } } ]]> </java>
<!-- 是否派单 --> <java name="needDispatch" returnClass="boolean"> <![CDATA[ boolean def needDispatch(int:alarmScore, str:criticality) { if (alarmScore >= 45) { return true; } else if (alarmScore >= 20 && criticality == "HIGH") { return true; } else { return false; } } ]]> </java>
<!-- 派单优先级 --> <java name="dispatchPriority" returnClass="java.lang.String"> <![CDATA[ str def dispatchPriority(int:alarmScore, str:criticality) { if (alarmScore >= 70) { return "P0"; } else if (alarmScore >= 45) { return "P1"; } else if (alarmScore >= 20 && criticality == "HIGH") { return "P2"; } else { return "P3"; } } ]]> </java>
<!-- 通知渠道 --> <java name="notifyChannel" returnClass="java.lang.String"> <![CDATA[ str def notifyChannel(str:alarmLevel, str:criticality) { if (alarmLevel == "CRITICAL") { return "PHONE_CALL"; } else if (alarmLevel == "MAJOR") { return "SMS"; } else if (alarmLevel == "MINOR") { return "IM"; } else { return "EMAIL"; } } ]]> </java>
<!-- 自动处置动作 --> <java name="autoRemediation" returnClass="java.lang.String"> <![CDATA[ str def autoRemediation(str:alarmType, int:alarmScore) { if (alarmScore >= 70) { return "ISOLATE"; } else if (alarmType == "NETWORK") { return "ISOLATE"; } else if (alarmType == "PERFORMANCE") { return "RATE_LIMIT"; } else if (alarmType == "TEMPERATURE") { return "THROTTLE"; } else if (alarmType == "VIBRATION") { return "THROTTLE"; } else { return "NONE"; } } ]]> </java>
<!-- 升级时限(分钟) --> <java name="escalateMinutes" returnClass="int"> <![CDATA[ int def escalateMinutes(str:alarmLevel) { if (alarmLevel == "CRITICAL") { return 5; } else if (alarmLevel == "MAJOR") { return 15; } else if (alarmLevel == "MINOR") { return 60; } else { return 240; } } ]]> </java>
</javas>DeviceAlarmService.java
Section titled “DeviceAlarmService.java”package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.DeviceAlarmRules;import com.github.paohaijiao.demo.model.AlarmDecision;import com.github.paohaijiao.demo.model.DeviceAlarm;
/** * 场景 9:IoT 设备告警分级服务 * <p> * 职责划分: * <ul> * <li><b>Java 侧</b>:六维评分汇总与封顶、告警类型与等级装配、指标与动作组装</li> * <li><b>规则侧</b>:各维度打分口径、类型归类优先级、等级/派单/通知阈值,全部外置在 XML 中</li> * </ul> */public class DeviceAlarmService {
private static final int SCORE_FLOOR = 0; private static final int SCORE_CEIL = 100;
private final DeviceAlarmRules rules;
public DeviceAlarmService(DeviceAlarmRules rules) { this.rules = rules; }
/** * 计算设备告警决策 * * @param device 设备运行快照 * @return 告警决策 */ public AlarmDecision evaluate(DeviceAlarm device) { AlarmDecision decision = new AlarmDecision(); decision.setDeviceId(device.getDeviceId());
int rawScore = rules.scoreTemperature(device.getTemperature()) + rules.scoreError(device.getErrorCount()) + rules.scoreResource(device.getCpuUsage(), device.getMemoryUsage()) + rules.scoreBattery(device.getBatteryLevel()) + rules.scoreConnection(device.isConnectionLost(), device.getLastHeartbeatSeconds()) + rules.scoreVibration(device.getVibrationLevel()); int alarmScore = clamp(rawScore);
String alarmLevel = rules.alarmLevel(alarmScore); String alarmType = rules.alarmType(device.isConnectionLost(), device.getLastHeartbeatSeconds(), device.getTemperature(), device.getCpuUsage(), device.getMemoryUsage(), device.getBatteryLevel(), device.getVibrationLevel());
decision.setAlarmScore(alarmScore); decision.setAlarmLevel(alarmLevel); decision.setAlarmType(alarmType); decision.setNeedDispatch(rules.needDispatch(alarmScore, device.getCriticality())); decision.setDispatchPriority(rules.dispatchPriority(alarmScore, device.getCriticality())); decision.setNotifyChannel(rules.notifyChannel(alarmLevel, device.getCriticality())); decision.setAutoRemediation(rules.autoRemediation(alarmType, alarmScore)); decision.setEscalateMinutes(rules.escalateMinutes(alarmLevel));
buildIndicators(device, decision); buildActions(device, decision); return decision; }
private int clamp(int score) { if (score < SCORE_FLOOR) { return SCORE_FLOOR; } if (score > SCORE_CEIL) { return SCORE_CEIL; } return score; }
private void buildIndicators(DeviceAlarm device, AlarmDecision decision) { if (device.getTemperature() >= 60.0) { decision.addIndicator("温度 " + device.getTemperature() + "℃"); } if (device.getCpuUsage() >= 75) { decision.addIndicator("CPU 使用率 " + device.getCpuUsage() + "%"); } if (device.getMemoryUsage() >= 75) { decision.addIndicator("内存使用率 " + device.getMemoryUsage() + "%"); } if (device.getErrorCount() >= 1) { decision.addIndicator("错误次数 " + device.getErrorCount()); } if (device.isConnectionLost()) { decision.addIndicator("设备断连"); } else if (device.getLastHeartbeatSeconds() >= 120) { decision.addIndicator("心跳间隔 " + device.getLastHeartbeatSeconds() + " 秒"); } if (device.getBatteryLevel() <= 20) { decision.addIndicator("电量 " + device.getBatteryLevel() + "%"); } if (device.getVibrationLevel() >= 3.0) { decision.addIndicator("振动强度 " + device.getVibrationLevel()); } }
private void buildActions(DeviceAlarm device, AlarmDecision decision) { if (decision.isNeedDispatch()) { decision.addAction("按 " + decision.getDispatchPriority() + " 优先级派发运维工单"); } else { decision.addAction("暂不派单,持续观察指标趋势"); } decision.addAction("通过 " + decision.getNotifyChannel() + " 通知责任人"); if (!"NONE".equals(decision.getAutoRemediation())) { decision.addAction("执行自动处置:" + decision.getAutoRemediation()); } if ("HIGH".equals(device.getCriticality()) && decision.getAlarmScore() >= 20) { decision.addAction("关键设备告警,同步通知设备负责人"); } decision.addAction(decision.getEscalateMinutes() + " 分钟内未恢复则升级"); }}DeviceAlarmServiceTest.java
Section titled “DeviceAlarmServiceTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.AlarmDecision;import com.github.paohaijiao.demo.model.DeviceAlarm;import com.github.paohaijiao.demo.service.DeviceAlarmService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 9 生产级样板:IoT 设备告警分级 * <p> * 覆盖 6 组告警形态:严重过热、网络断连、性能资源耗尽、低电量、 * 设备正常、振动异常。 */public class DeviceAlarmServiceTest {
private DeviceAlarmService service;
@Before public void setUp() { DeviceAlarmRules rules = JQuickJava.create() .createApi(DeviceAlarmRules.class, "demo/device-alarm.xml"); this.service = new DeviceAlarmService(rules); }
/** * 严重过热:温度 92℃ + 高频错误 22 次 + CPU 96% → 80 分 CRITICAL */ @Test public void shouldEscalateOverheatDeviceToCritical() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9001"); device.setDeviceName("炼钢炉温控传感器"); device.setDeviceType("SENSOR"); device.setCriticality("HIGH"); device.setTemperature(92.0); device.setCpuUsage(96); device.setMemoryUsage(70); device.setErrorCount(22); device.setConnectionLost(false); device.setLastHeartbeatSeconds(60); device.setBatteryLevel(80); device.setVibrationLevel(1.0);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(80, decision.getAlarmScore()); Assert.assertEquals("CRITICAL", decision.getAlarmLevel()); Assert.assertEquals("TEMPERATURE", decision.getAlarmType()); Assert.assertTrue(decision.isNeedDispatch()); Assert.assertEquals("P0", decision.getDispatchPriority()); Assert.assertEquals("PHONE_CALL", decision.getNotifyChannel()); Assert.assertEquals("ISOLATE", decision.getAutoRemediation()); Assert.assertEquals(5, decision.getEscalateMinutes()); Assert.assertEquals(3, decision.getIndicators().size()); Assert.assertTrue(decision.getIndicators().contains("温度 92.0℃")); Assert.assertTrue(decision.getIndicators().contains("CPU 使用率 96%")); Assert.assertTrue(decision.getIndicators().contains("错误次数 22")); Assert.assertTrue(decision.getActions().contains("按 P0 优先级派发运维工单")); Assert.assertTrue(decision.getActions().contains("执行自动处置:ISOLATE")); }
/** * 网络断连:断连 + 错误 10 次 + 低电量 15% → 51 分 MAJOR */ @Test public void shouldClassifyNetworkOutage() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9002"); device.setDeviceName("园区闸机网关"); device.setDeviceType("GATEWAY"); device.setCriticality("HIGH"); device.setTemperature(40.0); device.setCpuUsage(50); device.setMemoryUsage(50); device.setErrorCount(10); device.setConnectionLost(true); device.setLastHeartbeatSeconds(600); device.setBatteryLevel(15); device.setVibrationLevel(0.5);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(51, decision.getAlarmScore()); Assert.assertEquals("MAJOR", decision.getAlarmLevel()); Assert.assertEquals("NETWORK", decision.getAlarmType()); Assert.assertTrue(decision.isNeedDispatch()); Assert.assertEquals("P1", decision.getDispatchPriority()); Assert.assertEquals("SMS", decision.getNotifyChannel()); Assert.assertEquals("ISOLATE", decision.getAutoRemediation()); Assert.assertEquals(15, decision.getEscalateMinutes()); Assert.assertTrue(decision.getIndicators().contains("设备断连")); Assert.assertTrue(decision.getIndicators().contains("电量 15%")); }
/** * 性能资源耗尽:内存 92% + 错误 6 次,但设备重要性 MEDIUM → 22 分 MINOR 且不派单 */ @Test public void shouldFlagPerformanceIssueWithoutDispatch() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9003"); device.setDeviceName("图像识别节点"); device.setDeviceType("CAMERA"); device.setCriticality("MEDIUM"); device.setTemperature(45.0); device.setCpuUsage(88); device.setMemoryUsage(92); device.setErrorCount(6); device.setConnectionLost(false); device.setLastHeartbeatSeconds(30); device.setBatteryLevel(60); device.setVibrationLevel(1.0);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(22, decision.getAlarmScore()); Assert.assertEquals("MINOR", decision.getAlarmLevel()); Assert.assertEquals("PERFORMANCE", decision.getAlarmType()); Assert.assertFalse(decision.isNeedDispatch()); Assert.assertEquals("P3", decision.getDispatchPriority()); Assert.assertEquals("IM", decision.getNotifyChannel()); Assert.assertEquals("RATE_LIMIT", decision.getAutoRemediation()); Assert.assertEquals(60, decision.getEscalateMinutes()); Assert.assertTrue(decision.getIndicators().contains("内存使用率 92%")); }
/** * 低电量:电量 3% + 错误 6 次,关键设备 → 25 分 MINOR 且按 P2 派单 */ @Test public void shouldAlertLowBatteryOnCriticalDevice() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9004"); device.setDeviceName("冷库温湿度记录仪"); device.setDeviceType("SENSOR"); device.setCriticality("HIGH"); device.setTemperature(50.0); device.setCpuUsage(40); device.setMemoryUsage(40); device.setErrorCount(6); device.setConnectionLost(false); device.setLastHeartbeatSeconds(50); device.setBatteryLevel(3); device.setVibrationLevel(0.2);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(25, decision.getAlarmScore()); Assert.assertEquals("MINOR", decision.getAlarmLevel()); Assert.assertEquals("POWER", decision.getAlarmType()); Assert.assertTrue(decision.isNeedDispatch()); Assert.assertEquals("P2", decision.getDispatchPriority()); Assert.assertEquals("IM", decision.getNotifyChannel()); Assert.assertEquals("NONE", decision.getAutoRemediation()); Assert.assertTrue(decision.getIndicators().contains("电量 3%")); }
/** * 设备正常:各项指标健康 → 0 分 NORMAL */ @Test public void shouldKeepHealthyDeviceNormal() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9005"); device.setDeviceName("办公区环境传感器"); device.setDeviceType("SENSOR"); device.setCriticality("MEDIUM"); device.setTemperature(42.0); device.setCpuUsage(30); device.setMemoryUsage(40); device.setErrorCount(0); device.setConnectionLost(false); device.setLastHeartbeatSeconds(30); device.setBatteryLevel(85); device.setVibrationLevel(0.5);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(0, decision.getAlarmScore()); Assert.assertEquals("NORMAL", decision.getAlarmLevel()); Assert.assertEquals("NONE", decision.getAlarmType()); Assert.assertFalse(decision.isNeedDispatch()); Assert.assertEquals("P3", decision.getDispatchPriority()); Assert.assertEquals("EMAIL", decision.getNotifyChannel()); Assert.assertEquals("NONE", decision.getAutoRemediation()); Assert.assertEquals(240, decision.getEscalateMinutes()); Assert.assertTrue(decision.getIndicators().isEmpty()); Assert.assertTrue(decision.getActions().contains("暂不派单,持续观察指标趋势")); }
/** * 振动异常:振动强度 9.5 + 错误 10 次 → 38 分 MINOR,自动限流 */ @Test public void shouldDetectAbnormalVibration() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9006"); device.setDeviceName("传送带电机"); device.setDeviceType("ROBOT"); device.setCriticality("MEDIUM"); device.setTemperature(55.0); device.setCpuUsage(70); device.setMemoryUsage(65); device.setErrorCount(10); device.setConnectionLost(false); device.setLastHeartbeatSeconds(40); device.setBatteryLevel(70); device.setVibrationLevel(9.5);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(38, decision.getAlarmScore()); Assert.assertEquals("MINOR", decision.getAlarmLevel()); Assert.assertEquals("VIBRATION", decision.getAlarmType()); Assert.assertFalse(decision.isNeedDispatch()); Assert.assertEquals("P3", decision.getDispatchPriority()); Assert.assertEquals("IM", decision.getNotifyChannel()); Assert.assertEquals("THROTTLE", decision.getAutoRemediation()); Assert.assertTrue(decision.getIndicators().contains("振动强度 9.5")); Assert.assertTrue(decision.getActions().contains("执行自动处置:THROTTLE")); }}在工程根目录执行:
mvn test -Dtest=DeviceAlarmServiceTest测试覆盖形态
Section titled “测试覆盖形态”| 测试方法 | 构造的业务形态 | 关键断言/期望决策 |
|---|---|---|
shouldEscalateOverheatDeviceToCritical |
炼钢炉温控传感器:92℃、错误 22 次、CPU 96%、HIGH 设备 | 80 分(35+25+20)、CRITICAL、TEMPERATURE、派单 P0、PHONE_CALL、ISOLATE、5 分钟升级、指标 3 条 |
shouldClassifyNetworkOutage |
园区闸机网关:断连、心跳 600s、错误 10 次、电量 15% | 51 分(25+18+8)、MAJOR、NETWORK、派单 P1、SMS、ISOLATE、15 分钟 |
shouldFlagPerformanceIssueWithoutDispatch |
图像识别节点:CPU 88%、内存 92%、错误 6 次、MEDIUM 设备 | 22 分(12+10)、MINOR、PERFORMANCE、不派单、P3、IM、RATE_LIMIT、60 分钟 |
shouldAlertLowBatteryOnCriticalDevice |
冷库记录仪:电量 3%、错误 6 次、HIGH 关键设备 | 25 分(15+10)、MINOR、POWER、关键设备 P2 派单、IM、自动处置 NONE |
shouldKeepHealthyDeviceNormal |
办公区环境传感器:全部指标健康 | 0 分、NORMAL、NONE、不派单、P3、EMAIL、NONE、240 分钟、指标为空 |
shouldDetectAbnormalVibration |
传送带电机:振动 9.5、错误 10 次、MEDIUM 设备 | 38 分(20+18)、MINOR、VIBRATION、不派单、P3、IM、THROTTLE 降频 |
USE-CASES.md 场景 9 收录的两组实测输出:
炼钢炉温控传感器严重过热(92℃、22 次错误、CPU 96%):
告警分: 80 (CRITICAL) 类型: TEMPERATURE 派单: P0通知: PHONE_CALL 自动处置: ISOLATE 升级时限: 5 分钟园区闸机网关断连(断连 + 心跳 600s + 10 次错误 + 低电量 15%):
告警分: 51 (MAJOR) 类型: NETWORK 派单: P1通知: SMS 自动处置: ISOLATE 升级时限: 15 分钟- 六个维度各为一个独立评分方法,13 个规则方法覆盖评分、归类、派单、通知、处置、升级时限,全部阈值(70/45/20、300/120 秒、5%/20% 等)外置 XML,调阈值不必重启业务系统。
- Service 在 Java 侧把六维原始分统一 clamp 到 0-100,并按指标实际命中情况组装明细清单,每条告警都可复盘「为什么是这个等级、执行了什么动作」。
scoreResource在脚本内先取 CPU 与内存的峰值再分档,避免两个资源维度重复计分;alarmType以一条正向if-else链表达「网络 > 温度 > 性能 > 电量 > 振动」的归类优先级。- 派单规则把设备重要性作为第二条件:20-44 分区间只有 HIGH 关键设备才 P2 派单,普通设备只观察不派单,显著抑制告警风暴。
autoRemediation前置「总分 ≥70 一律隔离」,再按 NETWORK/PERFORMANCE/TEMPERATURE/VIBRATION 分流隔离、限流、降频;@Param按名绑定支持alarmType多达 7 个入参,代理实例可复用,底层为 ASM 调用链。- Java 8 即可运行、无重型规则平台依赖,适合部署在资源受限的边缘网关与信创环境。