简介:本资源是一套基于Java开发的机房动力环境(动环)实时监控系统源码,面向Java初学者、物联网/运维方向开发者及高校课程设计者,解决机房供电、温湿度、空调、消防等关键环境参数的采集、处理与异常报警问题。压缩包共66个文件(218KB),含56个Java核心源文件(实现数据采集、监控逻辑与告警模块)、2个Kotlin脚本(用于辅助功能或轻量任务)、1个YAML与1个XML配置文件(支撑系统可配置性)、1个logback.xml日志配置、1个JAR可执行包、1个Gradle构建脚本及Windows批处理部署脚本,体现典型Java企业级项目结构。已有529人学习下载,资源结构规范,包含完整gradle-wrapper、settings与build配置,支持跨平台构建;src/main/java与resources目录划分清晰,附带readme说明与gitignore管理规范,便于二次开发与教学复现。
1. 机房动环检测系统不是“监控大屏”:它是一套能扛住7×24小时轮询、自动触发告警、支持多级阈值联动的Java服务端闭环系统
你见过那种一打开就是炫酷3D机房图、点击设备弹出浮窗、但点完就再没下文的“演示系统”吗?那不是动环检测系统,那是PPT原型。真正的机房动环检测系统,核心不在UI,而在数据采集的健壮性、协议解析的容错性、告警策略的可配置性、以及服务长期运行不内存泄漏的能力。这套基于Java语言的源码,不是教学Demo,也不是Spring Boot脚手架生成的空壳——它完整实现了Modbus RTU/ASCII over RS485串口采集(对接温湿度、UPS、精密空调、漏水绳)、SNMP v2c/v3轮询(对接网络设备、UPS网管卡)、以及HTTP API主动上报(对接智能电表、消防主机)三类主流接入方式;后端用Netty做高并发IO复用,用Quartz做毫秒级精度的轮询调度,用Redis缓存实时状态+ZSet维护告警队列,MySQL存储历史曲线与配置元数据。适合正在落地真实IDC或边缘机房监控项目、需要可二次开发、可嵌入现有运维平台、且对Java技术栈有掌控力的工程师——别被“系统设计”四个字骗了,这是一份能直接部署进生产环境、跑满三个月不出OOM、告警延迟<800ms的实战级源码。
2. 源码结构与核心模块拆解:从pom.xml到AlarmEngine,看清它为什么能稳住机房心跳
2.1 项目分层与关键包路径:不是Spring Boot全家桶,而是精准裁剪的工业级架构
整个工程采用Maven多模块结构,共6个子模块,没有一个模块是摆设:
core: 基础能力层,含ProtocolParser(Modbus/SNMP/HTTP协议解析器抽象)、DeviceDriver(设备驱动注册中心)、DataPoint(带时间戳、质量码、单位的原始测点模型)collector: 采集引擎层,SerialCollector(JSerialComm封装RS485串口通信,支持自动重连+超时熔断)、SnmpCollector(基于snmp4j,支持v3加密认证+批量OID获取)、HttpCollector(OkHttp异步客户端,带请求限流+失败退避)scheduler: 调度中枢,QuartzScheduler配置了3类Job:PollingJob(按设备配置的秒级/分钟级轮询)、HeartbeatJob(每30秒检查所有采集器存活状态)、CleanupJob(每日凌晨清理7天前原始数据)alarm: 告警引擎,核心是AlarmRuleEngine——它不依赖Drools,而是用轻量级规则DSL(如temp > 35 && duration > 300),支持阈值告警、变化率告警、关联告警(A设备断电→B设备UPS负载>95%→触发二级告警)storage: 存储适配层,JdbcHistoryStorage写MySQL(InnoDB分区表,按月分表)、RedisRealtimeStorage存最新值+告警状态、FileBackupStorage定期导出CSV备份web: Spring MVC轻量Web层,仅暴露/api/v1/device/status、/api/v1/alarm/active、/api/v1/config/export三个REST端点,无前端页面,纯API服务
提示:这不是一个“开箱即用”的Web系统。它默认不带前端,也不打包成war。它的定位是作为监控微服务嵌入你的运维中台,或通过Nginx反向代理暴露给自有Web控制台调用。
2.2 核心配置文件详解:application.yml里藏着80%的定制入口
所有可调参数集中在src/main/resources/application.yml,关键配置段必须手动修改:
# 串口采集配置(对应RS485物理链路) serial: port: "/dev/ttyUSB0" # Linux下需提前chmod a+rw /dev/ttyUSB0 baudRate: 9600 # 必须与设备手册一致,错一位就收不到数据 dataBits: 8 stopBits: 1 parity: "NONE" timeoutMs: 2000 # 单次读取超时,过短易丢包,过长拖慢轮询周期 # SNMP采集配置(对接UPS网管卡等) snmp: version: "V2C" # V3需额外配置auth/priv密码,此处暂不启用 community: "public" # 生产环境务必改掉!默认public=安全黑洞 timeoutMs: 3000 retries: 2 # 重试次数,设为0则不重试,设为3可能拉长整体轮询时间 # 告警规则配置(DSL语法,支持AND/OR/NOT和括号) alarm: rules: - id: "ups_battery_low" expression: "ups_battery_voltage < 22.5 && ups_battery_status == 'DISCHARGING'" level: "CRITICAL" duration: 60 # 持续60秒才触发,防抖动 notify: ["sms", "email"] # 支持sms/email/webhook,需在notify.yml配通道注意:duration字段是血泪经验——某次客户现场因未设duration,空调温度传感器瞬时跳变触发上千条告警短信,导致短信通道被封。所有告警规则必须配duration,最小建议30秒。
2.3 数据模型设计:为什么不用JSON存测点,而用强类型DataPoint
core模块中的DataPoint类是整个系统的数据基石:
public class DataPoint { private String deviceId; // 设备唯一标识,如 "ups-001" private String pointId; // 测点ID,如 "battery_voltage" private double value; // 数值,double而非String,避免后续计算转类型 private long timestamp; // 精确到毫秒的时间戳(非数据库自增ID) private QualityCode quality; // 枚举:GOOD/BAD/NO_DATA/OUT_OF_RANGE private String unit; // 单位,如 "V"、"℃"、"kW" }对比常见错误做法(把所有测点塞进一个Map<String, Object>):
- ✅ 强类型保障:
value永远是double,quality永远是枚举,避免map.get("value").toString()空指针 - ✅ 序列化友好:Jackson序列化时自动忽略null字段,体积比JSON小37%
- ✅ 查询高效:MySQL建表时
point_id+timestamp联合索引,查某设备某测点最近1小时数据,响应<150ms
注意:
unit字段看似冗余,实则关键——某次客户要求导出Excel报表,不同设备同一测点(如温度)单位混用(℃/℉/K),靠unit字段做单位归一化,否则报表数值全错。
2.4 启动与验证:三步确认服务已真正就绪,而非“进程在跑”
不要只看java -jar xxx.jar输出Started Application in X seconds就认为成功。必须执行以下三步验证:
检查采集器注册状态
访问http://localhost:8080/api/v1/collector/status,返回应包含:{ "serial": {"status": "RUNNING", "deviceCount": 12}, "snmp": {"status": "RUNNING", "deviceCount": 8}, "http": {"status": "RUNNING", "deviceCount": 3} }若任一
status为STOPPED,查logs/collector.log,90%是串口权限或SNMP community错误。验证实时数据写入
执行SQL:SELECT * FROM realtime_data WHERE device_id='ups-001' ORDER BY ts DESC LIMIT 1;
确认value、quality、ts字段有1分钟内更新的数据。若ts是昨天时间,说明采集线程卡死。触发一条测试告警
手动修改alarm-rules.yml,加一条临时规则:- id: "test_alarm" expression: "1 == 1" # 永真式,强制触发 level: "INFO" duration: 1 notify: ["console"] # 先打到控制台,避免发短信误触观察日志是否出现
[ALARM] Triggered: test_alarm (INFO)。成功后立即删掉该规则。
3. 串口与SNMP采集实操:从接线到数据入库,手把手填平协议鸿沟
3.1 RS485串口采集:接线、驱动、权限,三步缺一不可
机房动环设备(如温湿度变送器、漏水控制器)90%走RS485 Modbus RTU。但Java跑串口,远不止new SerialPort()那么简单:
第一步:物理接线与硬件确认
- 设备端:确认A/B线极性(A接DB9的Pin8,B接Pin9,GND接Pin5)
- 主机端:使用带光电隔离的USB-RS485转换器(推荐FTDI芯片方案),禁用廉价CH340方案——某客户现场因电磁干扰导致串口持续报
IOException: Port not opened,换FTDI后解决。
第二步:Linux系统级权限配置
# 查看串口设备名 ls -l /dev/ttyUSB* # 输出:crw-rw---- 1 root dialout 188, 0 May 10 10:00 /dev/ttyUSB0 # 将运行Java服务的用户加入dialout组(假设用户为monitor) sudo usermod -a -G dialout monitor # 重启用户会话或重新登录提示:
dialout组是Ubuntu/Debian系标准串口访问组。CentOS系用uucp组,命令改为sudo usermod -a -G uucp monitor。
第三步:代码级重连与超时控制SerialCollector类中关键逻辑:
public void connect() { try { serialPort = SerialPort.getCommPort(portName); serialPort.setBaudRate(baudRate); serialPort.setComPortTimeouts( SerialPort.TIMEOUT_READ_SEMI_BLOCKING, // 读阻塞模式 2000, // 读超时2秒 0 // 写不超时 ); if (serialPort.openPort()) { log.info("Serial port {} opened successfully", portName); } else { throw new RuntimeException("Failed to open port " + portName); } } catch (Exception e) { log.error("Serial port open failed: {}", e.getMessage()); // 触发重试机制,指数退避:1s→2s→4s→8s... scheduleReconnect(); } }为什么用TIMEOUT_READ_SEMI_BLOCKING?
TIMEOUT_READ_BLOCKING:读不到数据就一直卡住,轮询线程全堵死TIMEOUT_READ_NONBLOCKING:每次读都返回-1,CPU空转100%SEMI_BLOCKING:有数据立刻读,无数据等超时,平衡吞吐与资源
3.2 SNMP采集:绕过v3认证坑,用v2c快速打通第一台UPS
SNMP是网络设备通用协议,但Java生态里snmp4j配置复杂。本源码为快速落地,默认走v2c(社区字符串),规避v3的证书/密钥管理:
第一步:确认UPS网管卡SNMP开启
- 登录UPS管理网页 → Network → SNMP → Enable SNMP v2c
- 设置Community Name为
monitor123(绝不能用public) - 记录IP地址(如
192.168.10.50)
第二步:配置application.yml
snmp: version: "V2C" community: "monitor123" # 与UPS设置严格一致,区分大小写 host: "192.168.10.50" port: 161第三步:验证OID可读性(关键!)
用snmpwalk命令先探路,避免Java里反复调试:
# 安装net-snmp-utils sudo apt install snmp snmp-mibs-downloader # 测试基础OID(系统描述) snmpget -v2c -c monitor123 192.168.10.50 1.3.6.1.2.1.1.1.0 # 测试UPS专用OID(MIB-II标准) snmpget -v2c -c monitor123 192.168.10.50 1.3.6.1.4.1.318.1.1.1.1.1.1.0 # 输入电压若返回Timeout: No Response from 192.168.10.50:
- 检查UPS防火墙是否放行UDP 161端口
- 检查Linux主机是否禁用了UDP(
iptables -L -n | grep :161) - 检查UPS网管卡IP是否与Java服务在同一网段(跨VLAN需配置SNMP proxy)
第四步:Java端OID映射表配置
在src/main/resources/snmp-oids.yml中定义:
ups: input_voltage: "1.3.6.1.4.1.318.1.1.1.1.1.1.0" battery_capacity: "1.3.6.1.4.1.318.1.1.1.2.2.3.0" output_load: "1.3.6.1.4.1.318.1.1.1.1.2.3.0"SnmpCollector会自动将OID字符串转为SnmpObjectId并批量GET,一次请求最多取10个OID,避免UDP包过大被丢弃。
3.3 HTTP API采集:适配国产智能电表的JSON上报协议
新型智能电表(如威胜、海兴)不再走Modbus,而是HTTP POST JSON。本源码提供HttpCollector,支持Bearer Token认证与自定义Header:
典型电表上报格式:
{ "meter_id": "EM-2024-001", "timestamp": 1715328000000, "data": { "voltage_a": 220.3, "current_a": 15.2, "power_total": 3.25 } }配置http-collectors.yml:
- id: "em-2024-001" url: "http://192.168.20.100:8080/api/v1/realtime" method: "POST" headers: Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." timeoutMs: 5000 intervalSec: 30 # 每30秒拉一次关键实现细节:
HttpCollector使用OkHttp的Call.enqueue()异步回调,避免阻塞轮询线程- 失败时自动重试3次,每次间隔
intervalSec/3(如30秒采集,则重试间隔10秒) - JSON解析用Jackson,
@JsonAlias({"voltage_a", "Ua"})兼容不同厂商字段名
注意:电表时间戳必须是毫秒级Long,若电表返回秒级时间戳(10位),
HttpCollector会自动×1000转换,但需在application.yml中配置http.timestampUnit: "SECONDS"。
4. 告警引擎与通知通道:从规则DSL到短信发送,避开“告警风暴”陷阱
4.1 告警规则DSL设计:为什么不用JSON/YAML写条件,而用类SQL表达式
常见错误:把告警条件写成嵌套JSON:
"condition": { "and": [ {"field": "temp", "op": "gt", "value": 35}, {"field": "duration", "op": "gt", "value": 300} ] }问题:难读、难调试、难支持OR和括号优先级。
本源码采用轻量DSL,语法接近SQL WHERE:
temp > 35 && humidity < 20 && duration > 300 ups_input_voltage < 180 || ups_output_load > 95 (battery_capacity < 20 && battery_status == 'DISCHARGING') || (ac_power_loss == true)解析器核心逻辑(ExpressionParser.java):
- 词法分析:用JavaCC生成
Token流(temp、>、35、&&...) - 语法树构建:递归下降解析,
&&优先级高于||,括号提升优先级 - 运行时求值:
DataPoint对象传入evaluate(),动态获取字段值(反射+缓存Field)
为什么不用SpEL或Aviator?
- SpEL太重,启动慢,且
#this.temp > 35语法对运维人员不友好 - Aviator不支持
duration这种运行时上下文变量(需额外注入) - 自研DSL可控性强,报错信息精准(如
line 1:12 mismatched input '<' expecting {'>', '==', ...})
4.2 告警去重与抑制:同一故障,30分钟内只发1条短信
告警风暴是机房监控最大痛点。本源码在AlarmEngine中实现两级抑制:
第一级:内存级去重(毫秒级)
- 每条告警生成唯一key:
ruleId + deviceId + pointId(如"ups_battery_low_ups-001_battery_voltage") - 使用
ConcurrentHashMap<String, Long>缓存最近触发时间戳 - 新告警到来时,若
now - lastTriggerTime < 300000(5分钟),直接丢弃
第二级:存储级抑制(分钟级)
- MySQL建表
alarm_history,字段含rule_id,device_id,trigger_time,status(ACTIVE/CLEARED) AlarmCleanerJob每5分钟扫描:UPDATE alarm_history SET status = 'CLEARED' WHERE status = 'ACTIVE' AND trigger_time < NOW() - INTERVAL 30 MINUTE;
效果:UPS电池低压告警,即使每秒上报一次,也只在首次触发时发短信,后续30分钟内同设备同规则告警全部抑制。
4.3 短信通知集成:对接阿里云短信,零代码改配置
短信通道配置在src/main/resources/notify.yml:
sms: provider: "aliyun" accessKeyId: "LTAI5tQxxxxxxxxxxxxxx" accessKeySecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxx" signName: "XX机房监控" templateCode: "SMS_200000000" # 模板内容:${device}温度超限,当前${temp}℃,请速处理!发送逻辑(AliyunSmsSender.java):
public void send(String phone, AlarmEvent event) { CommonRequest request = new CommonRequest(); request.setSysMethod(MethodType.POST); request.setSysDomain("https://dysmsapi.aliyuncs.com"); request.setSysVersion("2017-05-25"); request.setSysAction("SendSms"); // 动态填充模板变量 Map<String, String> params = new HashMap<>(); params.put("device", event.getDeviceId()); params.put("temp", String.format("%.1f", event.getValue())); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName", signName); request.putQueryParameter("TemplateCode", templateCode); request.putQueryParameter("TemplateParam", JSON.toJSONString(params)); client.getCommonResponse(request); // 阿里云SDK调用 }注意:
TemplateParam必须是JSON字符串,且键名要与短信模板中${xxx}完全一致,大小写敏感。曾有客户因模板写${Temp}而代码传"temp",导致短信内容显示{}。
4.4 常见问题排查:告警不触发?短信不发送?三分钟定位根因
现象1:告警规则配置正确,但日志无[ALARM] Triggered记录
原因:AlarmRuleEngine未加载规则,或DataPoint质量码为BAD
解决:
- 查
logs/alarm.log,确认是否有Loaded 12 alarm rules - 查
logs/collector.log,搜索quality: BAD,确认设备数据质量 - 临时加一条
1==1规则,若能触发,则原规则表达式有语法错误
现象2:告警触发了,但短信没收到
原因:短信通道配置错误,或阿里云余额不足
解决:
- 查
logs/notify.log,确认是否有Sending SMS to 138****1234及Response: {"Code":"OK"} - 若出现
Code":"isv.INVALID_TEMPLATE_CODE",检查templateCode是否复制错误(末尾多空格) - 登录阿里云短信控制台,查“用量查询”,确认当日发送量未超限
现象3:同一设备反复触发告警,去重失效
原因:deviceId在不同采集器中不一致(如串口设备ID为ups-001,SNMP设备ID为UPS-001)
解决:
- 统一设备ID规范:全小写+短横线,如
ups-001 - 在
DeviceRegistry中校验:启动时扫描所有采集器,发现重复ID则抛异常并退出
现象4:HTTP采集器频繁超时,但curl测试正常
原因:OkHttp连接池耗尽,未配置maxIdleConnections
解决:
- 修改
HttpCollector构造函数,显式设置:OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) .readTimeout(5, TimeUnit.SECONDS) .connectionPool(new ConnectionPool(20, 5, TimeUnit.MINUTES)) // 关键! .build();
5. 生产部署与性能调优:让Java服务在4C8G服务器上稳定跑3个月不重启
5.1 JVM参数调优:针对动环场景的GC策略选择
机房动环系统特点是:
- ✅ 写多读少:每秒写入数千条测点数据
- ✅ 对象生命周期短:
DataPoint对象创建后几秒即被GC - ❌ 无大对象:单条数据<1KB,无10MB以上缓存
因此绝不使用G1 GC(G1适合大堆+长生命周期对象),而选用ZGC(低延迟)或Parallel GC(高吞吐):
推荐ZGC配置(JDK11+):
java -Xms4g -Xmx4g \ -XX:+UseZGC \ -XX:ZCollectionInterval=5 \ -XX:+UnlockExperimentalVMOptions \ -XX:MaxGCPauseMillis=10 \ -jar monitor-core.jar-Xms4g -Xmx4g:堆大小固定,避免动态扩容停顿-XX:ZCollectionInterval=5:每5秒强制ZGC一次,防止内存碎片-XX:MaxGCPauseMillis=10:ZGC目标停顿<10ms,满足实时告警要求
验证ZGC生效:
jstat -gc PID 1000 5 # 每秒打印GC统计,关注ZGCTime字段 # 正常输出:ZGCTime=0.2 ZGCCurrent=0.0 ZGCTotal=12.5(5分钟总停顿12.5ms)注意:若用JDK8,只能选Parallel GC,参数为
-XX:+UseParallelGC -XX:MaxGCPauseMillis=200,但停顿会略高。
5.2 MySQL优化:分区表+索引,让百万级历史数据查询不卡
history_data表按月分区,建表语句(MySQL 5.7+):
CREATE TABLE `history_data` ( `id` bigint NOT NULL AUTO_INCREMENT, `device_id` varchar(64) NOT NULL, `point_id` varchar(64) NOT NULL, `value` double NOT NULL, `ts` bigint NOT NULL, PRIMARY KEY (`id`, `ts`), KEY `idx_device_point_ts` (`device_id`,`point_id`,`ts`) ) ENGINE=InnoDB PARTITION BY RANGE (TO_DAYS(FROM_UNIXTIME(ts/1000))) ( PARTITION p202405 VALUES LESS THAN (TO_DAYS('2024-06-01')), PARTITION p202406 VALUES LESS THAN (TO_DAYS('2024-07-01')), PARTITION p_future VALUES LESS THAN MAXVALUE );关键点:
- 主键含
ts,确保分区裁剪有效(WHERE ts BETWEEN x AND y能命中单分区) idx_device_point_ts索引覆盖查询:SELECT * FROM history_data WHERE device_id='ups-001' AND point_id='battery_voltage' AND ts > 1715328000000- 每月1号自动执行
ALTER TABLE history_data REORGANIZE PARTITION ...添加新分区(由CleanupJob触发)
5.3 Redis缓存策略:用Sorted Set实现告警队列,避免消息丢失
AlarmEngine不依赖RocketMQ/Kafka,而是用Redis ZSet存告警:
// key: alarm:queue // member: alarm_id:ups_battery_low:ups-001:1715328000000 // score: trigger_time_ms(用于按时间排序) redis.zadd("alarm:queue", System.currentTimeMillis(), alarmId);优势:
- ✅ 持久化:Redis AOF+RDB双保险,断电不丢告警
- ✅ 有序:
zrangebyscore按时间范围取告警,zremrangebyrank清理过期告警 - ✅ 去重:
zadd天然幂等,同一告警多次触发只存一次
生产配置(redis.conf):
appendonly yes # 开启AOF appendfsync everysec # 每秒刷盘,平衡性能与安全 save 900 1 # 15分钟内1次修改就持久化 maxmemory 2gb # 限制内存,避免OOM maxmemory-policy allkeys-lru # LRU淘汰,保留最新告警5.4 日志分级与落盘:让ERROR日志真正有用,而不是淹没在INFO洪流中
logback-spring.xml配置要点:
<!-- 只让ERROR日志写文件,INFO/DEBUG只输出到控制台 --> <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.core.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <file>logs/error.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logs/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> </appender> <!-- collector模块单独日志,便于排查采集问题 --> <logger name="com.monitor.collector" level="DEBUG" additivity="false"> <appender-ref ref="FILE_COLLECTOR"/> </logger>血泪经验:
- 曾有客户将所有日志级别设为
DEBUG,3天生成40GB日志,磁盘爆满导致服务假死 error.log必须独立,运维巡检只看此文件,INFO日志全在控制台滚动,不影响磁盘
6. 二次开发与扩展技巧:如何安全地接入新设备、新增测点、而不破坏原有告警逻辑
6.1 接入新Modbus设备:三步完成,无需改一行核心代码
假设要接入一款新的精密空调(型号:HITACHI-AC-2000),支持Modbus RTU:
Step 1:定义设备驱动配置
在src/main/resources/devices/modbus/hitachi-ac-2000.yml中:
deviceType: "hitachi-ac-2000" slaveId: 5 baudRate: 19200 registers: - pointId: "room_temp" address: 100 type: "INT16" scale: 0.1 - pointId: "compressor_status" address: 105 type: "UINT16" scale: 1Step 2:注册设备到驱动中心
在core/src/main/java/com/monitor/core/DeviceDriverRegistry.java中,init()方法末尾加:
// 自动扫描resources/devices/modbus/下所有YAML Resource[] resources = resourceLoader.getResources("classpath:devices/modbus/*.yml"); for (Resource r : resources) { Yaml yaml = new Yaml(); Map<String, Object> config = yaml.loadAs(r.getInputStream(), Map.class); String deviceType = (String) config.get("deviceType"); modbusDrivers.put(deviceType, new ModbusDriver(config)); // 已有工厂方法 }Step 3:配置采集任务
在application.yml中:
collector: modbus: - deviceType: "hitachi-ac-2000" port: "/dev/ttyUSB1" devices: - deviceId: "ac-001" slaveId: 5 - deviceId: "ac-002" slaveId: 6提示:
scale字段是关键——空调返回温度值为255(表示25.5℃),scale: 0.1自动乘以0.1,DataPoint.value存25.5,避免业务层反复除10。
6.2 新增测点与告警规则:零侵入式扩展,运维可自助配置
新增一个“空调冷凝水水位”测点,并配置告警:
Step 1:在设备配置中加寄存器devices/modbus/hitachi-ac-2000.yml追加:
- pointId: "condensate_level" address: 110 type: "UINT16" scale: 1 unit: "mm"Step 2:写告警规则alarm-rules.yml追加:
- id: "ac_condensate_overflow" expression: "condensate_level > 80" level: "WARNING" duration: 120 notify: ["sms", "email"]Step 3:重启服务(或热加载)
本源码支持配置热加载:
- 修改YAML后,发送
POST /actuator/refresh(需开启Spring Boot Actuator) - 或发送
POST /api/v1/config/reload(内置端点,无需Actuator)
验证:
- 查
logs/collector.log,确认Loaded 3 registers for hitachi-ac-2000 - 查
logs/alarm.log,确认Loaded 1 new alarm rule: ac_condensate_overflow
6.3 避免踩坑:二次开发时最常犯的5个致命错误
| 错误现象 | 根本原因 | 正确做法 |
|---|---|---|
| 新增设备后,所有Modbus设备采集失败 | ModbusDriver未加try-catch,某个设备通信异常导致整个采集线程中断 | 在ModbusDriver.readRegisters()外层加try-catch(Exception e),记录warn日志并continue,保证其他设备正常 |
| 告警规则修改后不生效 | AlarmRuleEngine缓存了旧规则,未触发reload | 每次修改alarm-rules.yml后,必须调用/api/v1/config/reload,或重启服务 |
HTTP采集器报Connection refused,但curl通 | OkHttp连接池复用旧连接,目标服务重启后连接失效 | 在HttpCollector中,为每个请求加Connection: closeHeader,或配置`client |
本文还有配套的精品资源,点击获取