拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java+Vue智慧蜂场实战:MQTT多源感知、时序异常检测与病害风险预警全链路 从设备接入、时序治理到可解释风险,再到巡检反馈与模型评估

Java+Vue智慧蜂场实战:MQTT多源感知、时序异常检测与病害风险预警全链路 从设备接入、时序治理到可解释风险,再到巡检反馈与模型评估

Java+Vue智慧蜂场实战:MQTT多源感知、时序异常检测与病害风险预警全链路

从设备接入、时序治理到可解释风险,再到巡检反馈与模型评估

【Java】 【Vue 3】 【Spring Boot】 【MQTT】 【智慧蜂场】 【物联网】 【时序异常检测】 【EWMA】 【病害预警】 【ECharts】

蜂箱里最危险的变化,往往不是某个传感器突然越限,而是多个信号在一段时间内同时偏离自身基线:湿度持续升高、二氧化碳累积、巢门活动下降、重量变化异常,甚至声音特征也开始漂移。本文以 Java + Vue 智慧蜂场系统为主线,完整拆解温湿度、CO₂、称重、声音、巢门计数与气象数据的采集接入,重点实现 MQTT 消息治理、设备去重与离线补传、EWMA 平滑、滑动窗口 Z-Score、多因子规则与逻辑回归融合、告警抑制、人工巡检闭环及 Precision、Recall、F1 评估。文章同时给出 Spring Boot、Vue 3、MySQL、Redis、ECharts 的工程落地方式,并用 5 万条可复现模拟数据验证数据链路。系统输出的是可解释的风险线索,而不是替代现场诊断的“自动确诊”。

先看一个问题:凌晨连续阴雨,H-027 到底该不该报警?

凌晨 02:10,蜂箱 H-027 的湿度升到 84.6%,CO₂ 接近 1900 ppm,巢门活动量比自身近期基线明显下降。单看任何一个指标,都可能找到“正常解释”:降雨会推高湿度,夜间活动本来就低,CO₂ 也会受通风条件影响。

但如果这些变化不是一个采样点,而是连续 6 个周期共同偏离;同时蜂箱重量也在下降,那么问题就从“一个传感器越限”变成了“蜂群状态值得优先巡检”。

图1多因子风险场景:系统判断的是联合异常,而不是用单点阈值替代诊断

这也是本文设计的核心:系统不尝试根据一条湿度数据给蜂群“确诊”,而是把环境、行为、趋势、持续时间和人工巡检结果串成一条可解释的风险链。

1. 系统要解决的不是“看曲线”,而是四个连续问题

问题

普通监控系统

本文方案

数据从哪里来

只显示当前值

设备、蜂箱、采集时间、接收时间全部关联

异常是不是噪声

单点越限即告警

物理范围 + 中值/平滑 + 连续异常

为什么判高风险

只给红色状态

返回模型概率、规则命中与异常原因

告警之后怎么办

推送结束

接单、巡检、结论、标签回流、模型评估

2. 全链路架构:从蜂箱传感器一直走到人工巡检

图2系统总体架构:感知、边缘、数据治理、风险分析与处置闭环分层

感知层采集箱内温湿度、CO₂、重量、声音、巢门活动量和外部气象。边缘端先做物理范围校验、尖峰过滤和离线缓存;网关通过 MQTT 上报。Spring Boot 服务完成设备身份、消息去重、时序入库、特征计算、风险评分和告警状态管理。Redis 保存最新状态、设备心跳和告警抑制窗口,MySQL 保存历史监测、告警、巡检和模型版本。Vue 3 + ECharts 负责把趋势、异常原因和处置状态呈现给管理人员。

3. MQTT 主题树:先把消息语义设计对

图3MQTT主题树:周期遥测、设备状态和紧急事件分开

建议不要把所有消息都塞进一个 topic。遥测数据量大、频率固定;在线状态需要 retained/LWT 语义;紧急事件则更关注及时性和至少一次送达。主题分开后,服务端可以按消息类型设置不同 QoS、消费策略和告警通道。

bee/{farmId}/hive/{hiveId}/telemetry
bee/{farmId}/hive/{hiveId}/status
bee/{farmId}/hive/{hiveId}/alarm

// telemetry payload 示例
{
"deviceId": "D-THCO2-027",
"sampleTime": "2026-09-27T02:10:00",
"insideTemp": 34.1,
"insideHumidity": 84.6,
"co2": 1912,
"weight": 31.8,
"activity": 106,
"soundDb": 58.2
}

QoS 1 并不意味着业务层不会重复。网络重连、客户端重发都可能产生重复消息,因此还需要业务唯一键。可以用 hiveId + deviceId + sampleTime,或者由设备产生稳定 messageId。

4. 数据治理:模型之前最重要的一层

图4数据治理流水线:格式、身份、时间、去重、物理范围、缺失标记依次处理

检查

示例

处理

JSON完整性

缺少 sampleTime

拒绝并记录设备异常

设备身份

deviceId 未注册

进入非法设备日志

绑定关系

设备未绑定蜂箱

拒绝进入业务时序

时间漂移

采集时间晚于接收时间 2 小时

保留原值并标记 timeDrift

重复消息

唯一键已存在

幂等忽略

物理范围

湿度 <0 或 >100

标记无效,不参与模型

缺失数据

CO₂ 缺失

保留 missing 标记,不伪造关键病害特征

采集时间 sampleTime 和服务端 receiveTime 必须同时保存。转场蜂场、山区网络和移动网关都可能出现延迟补传;只保存入库时间,会把网络问题误判成环境变化。

5. Spring Boot MQTT 消费:不要在回调里塞满所有业务

@Service
@RequiredArgsConstructor
public class BeeTelemetryConsumer {

private final TelemetryApplicationService applicationService;

public void onMessage(String topic, String payload) {
TelemetryMessage message = TelemetryMessage.parse(payload);

applicationService.accept(
topic,
message.deviceId(),
message.hiveId(),
message.sampleTime(),
message);
}
}

消息回调只负责解析和转交。设备校验、幂等、过滤、存储、特征计算与告警应该拆到应用服务中,否则 MQTT 客户端线程一旦被慢 SQL 或模型计算阻塞,就会放大消息堆积。

@Transactional
public void accept(String topic,
String deviceId,
String hiveId,
LocalDateTime sampleTime,
TelemetryMessage message) {

deviceService.requireActiveBinding(deviceId, hiveId);

String uniqueKey = hiveId + "|" + deviceId + "|" + sampleTime;
if (!dedupService.tryAcquire(uniqueKey)) {
return;
}

ValidationResult result = telemetryValidator.validate(message);
telemetryRepository.save(result.toRecord());

if (result.canEnterRiskModel()) {
featureQueue.publish(result.recordId());
}
}

6. 边缘过滤与离线补传:网络不稳定不能等于数据丢失

原始方案已经提出中值滤波、滑动平均和离线缓存。实际工程中建议给本地缓存记录增加 sequence、sampleTime 和 sent 状态。网络恢复后按采集时间补传,而不是把补传时刻当作采集时刻。

场景

错误做法

推荐做法

短时尖峰

直接触发高风险

保留原值,同时计算过滤值并标记质量

断网 1 小时

丢弃数据

本地缓存,恢复后顺序补传

补传重复

重复入库

服务端业务唯一键去重

传感器恒值

当成稳定正常

检测长时间零方差,产生设备健康告警

设备离线

沉默不处理

LWT/心跳超时产生设备告警

7. EWMA:让趋势比噪声更容易被看见

图5时序特征工程:原始读数经过平滑与历史基线比较后再进入风险模型

指数加权移动平均的优势是无需保存很长窗口,同时能通过 α 控制“相信最新值”还是“相信历史”。原稿给出的实现非常适合作为在线流式特征。

public final class EwmaCalculator {
private final double alpha;
private Double state;

public EwmaCalculator(double alpha) {
if (alpha <= 0.0 || alpha > 1.0) {
throw new IllegalArgumentException("alpha范围错误");
}
this.alpha = alpha;
}

public double update(double value) {
if (!Double.isFinite(value)) {
throw new IllegalArgumentException("观测值必须为有限数");
}
state = state == null
? value
: alpha * value + (1.0 - alpha) * state;
return state;
}
}

α 不是越大越好。温湿度这种缓慢变化信号可以取相对平稳的系数;活动量若需要更快响应,则可提高 α。正式系统应按指标分别配置,并把参数版本写入模型配置,而不是硬编码散落在业务代码中。

8. Z-Score:同样的数值,对不同蜂箱含义可能完全不同

固定阈值解决的是“绝对异常”,Z-Score 更适合回答“这个蜂箱是否偏离自己的历史”。例如两个蜂箱湿度都为 82%,若 A 长期在 78%~83%,B 长期在 62%~68%,二者风险含义显然不同。

public double score(double value) {
if (values.size() < 3) {
add(value);
return 0.0;
}

double mean = values.stream()
.mapToDouble(Double::doubleValue)
.average().orElse(0.0);

double variance = values.stream()
.mapToDouble(v -> (v - mean) * (v - mean))
.average().orElse(0.0);

double sd = Math.sqrt(variance);
double z = sd < 0.000001 ? 0.0 : (value - mean) / sd;

add(value);
return z;
}

要注意冷启动:历史样本不足时不能把 Z=0 误解为“正常”,更合理的状态是 baselineReady=false。蜂箱转场、换王、合群等重大管理动作后,历史基线也可能失效,需要重新建立或分阶段建模。

9. 多因子风险模型:规则与逻辑回归各司其职

图6风险融合模型:逻辑回归处理联合特征,规则处理明确业务边界

public double predict(double humidityZ,
double co2Z,
double weightDropKg,
double activityDropRate) {
double intercept = -2.20;

double z = intercept
+ 0.72 * humidityZ
+ 0.95 * co2Z
+ 0.48 * weightDropKg
+ 1.15 * activityDropRate;

z = Math.max(-30.0, Math.min(30.0, z));
return 1.0 / (1.0 + Math.exp(-z));
}

这里的系数来自项目示例模型,应视为演示参数,而不是经过真实蜂场标注数据训练得到的通用医学或兽医结论。正式应用需要使用真实巡检标签重新拟合、验证并按季节或蜂场条件校准。

double score = probability * 60.0;

if (co2 >= 1800.0) score += 15.0;
if (humidity >= 82.0) score += 10.0;
if (activityDropRate >= 0.45) score += 10.0;
if (consecutiveAbnormalCount >= 6) score += 15.0;

if (score >= 70.0) return "高风险";
if (score >= 40.0) return "中风险";
return "低风险";

最终接口还应该返回 reasons,例如“CO₂≥1800 ppm”“湿度连续 6 个周期偏高”“活动量下降 48%”。这比只返回 HIGH 更适合现场人员判断优先级。

10. 告警状态机:解决“传感器每十分钟提醒一次”的告警风暴

图7告警状态机:观察、确认、告警、处理、复核、关闭形成完整生命周期

如果采样周期为 10 分钟,高湿状态持续 4 小时,简单阈值法可能产生 24 条相似通知。正确做法是把异常状态与通知动作分开:连续异常负责升级风险,通知则受冷却窗和状态机控制。

规则

示例

首次异常

进入观察,不立即高等级通知

连续 N 次

进入待确认或告警

冷却窗

同一蜂箱同类告警 60 分钟内不重复通知

风险升级

中→高不受冷却窗限制

指标恢复

连续正常后关闭,记录恢复时间

再次异常

关闭后重新累计,形成新告警事件

11. Redis 在这里真正适合存什么

Key

内容

为什么适合Redis

bee:hive:{id}:latest

最新监测状态

可重建、高频读取

bee:device:{id}:heartbeat

最后心跳

TTL 可直接判断离线

bee:alert:{hive}:{rule}:cooldown

告警抑制状态

天然适合过期窗口

bee:feature:{hive}:{name}

短窗口特征状态

在线计算频繁访问

bee:dedup:{messageId}

消息去重标记

短 TTL 幂等

历史监测、巡检结论、告警生命周期等不可丢的事实仍应落到持久化数据库。Redis 是实时状态层,不应该成为唯一事实来源。

12. MySQL 核心表:把原始值、质量标记和模型版本都留下

CREATE TABLE bee_telemetry (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
farm_id VARCHAR(32) NOT NULL,
hive_id VARCHAR(32) NOT NULL,
device_id VARCHAR(64) NOT NULL,
sample_time DATETIME(3) NOT NULL,
receive_time DATETIME(3) NOT NULL,
inside_temp DECIMAL(6,2),
inside_humidity DECIMAL(6,2),
co2 DECIMAL(10,2),
weight_kg DECIMAL(8,2),
activity_count INT,
sound_db DECIMAL(6,2),
quality_flag VARCHAR(32) NOT NULL,
message_id VARCHAR(96) NOT NULL,
UNIQUE KEY uk_message (message_id),
KEY idx_hive_time (hive_id, sample_time)
);

CREATE TABLE bee_alert (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
hive_id VARCHAR(32) NOT NULL,
risk_score DECIMAL(6,2) NOT NULL,
risk_level VARCHAR(16) NOT NULL,
model_version VARCHAR(32) NOT NULL,
reasons JSON NOT NULL,
status VARCHAR(24) NOT NULL,
triggered_at DATETIME(3) NOT NULL,
acknowledged_at DATETIME(3),
closed_at DATETIME(3),
KEY idx_hive_triggered (hive_id, triggered_at)
);

13. Vue 3 + ECharts:首屏不要堆十张图,先回答“先去看哪箱”

图8监控看板示意:首屏突出在线状态、高风险、待巡检和风险趋势

看板第一层是行动信息,而不是图表数量。建议把“高风险蜂箱、待巡检、设备离线、今日新告警”放在首屏,再让用户进入蜂箱详情查看温湿度、CO₂、重量、活动量、声音和风险变化。

<template>
<div class="risk-card" v-for="item in highRiskHives" :key="item.hiveId">
<strong>{{ item.hiveId }} · {{ item.score }} 分</strong>

<ul>
<li v-for="reason in item.reasons" :key="reason.code">
{{ reason.message }}
</li>
</ul>

<button @click="openHive(item.hiveId)">查看趋势与巡检记录</button>
</div>
</template>

风险卡片一定要把原因放出来。否则前端只是把后端的一个数字涂成红色,无法帮助养蜂人员判断是先检查通风、补饲,还是先排查传感器故障。

14. 模拟时序:如何验证“多因子同步偏离”

图9基于项目生成逻辑构造的模拟时序示意;阴影区域为人为注入的异常窗口

图中的数据用于验证算法链路,不代表真实蜂场观测结果。测试时可以人为注入“连续降雨 + 通风异常 + 活动下降”,检查 EWMA、Z-Score、风险融合和告警状态机是否按预期响应。

这种故障注入比只展示一条漂亮曲线更有价值,因为它能回答:异常从第几个周期开始被识别?冷却窗是否抑制重复通知?恢复后告警是否自动关闭?

15. 5 万条模拟数据:可复现,但不能拿来证明真实准确率

项目原始数据生成器设置 50,000 条记录、100 个蜂箱,并使用固定随机种子 20250308L。外部温湿度包含昼夜与季节波动,同时注入降雨、通风异常和病害风险趋势,再生成箱内湿度、CO₂、活动量、重量、声音与风险标签。

private static final int RECORD_COUNT = 50000;
private static final int HIVE_COUNT = 100;
private static final Random RANDOM = new Random(20250308L);

boolean rain = RANDOM.nextDouble() < 0.12;
boolean ventilationFault = RANDOM.nextDouble() < 0.045;
boolean diseaseTrend = RANDOM.nextDouble() < 0.035;

double riskScore = clamp(
(insideHumidity - 70.0) * 1.2
+ (co2 - 1300.0) / 22.0
+ (220.0 - activity) / 4.0
+ (diseaseTrend ? 22.0 : 0.0),
0.0, 100.0);

固定随机种子的价值是回归测试可重复,而不是让模拟数据“更真实”。如果标签本身由同一套风险公式生成,再用同类特征去预测,很容易得到过于漂亮的指标。因此模拟数据适合测试数据管道、可视化、性能和异常响应,不适合声称模型在真实蜂场达到某个准确率。

16. 模型评估:Precision、Recall、F1 分别回答什么

图10混淆矩阵:预警系统必须同时关注误报与漏报

double precision = tp + fp == 0
? 0.0 : (double) tp / (tp + fp);

double recall = tp + fn == 0
? 0.0 : (double) tp / (tp + fn);

double f1 = precision + recall == 0.0
? 0.0
: 2.0 * precision * recall / (precision + recall);

指标

回答的问题

蜂场中的含义

Precision

发出的告警有多少是真的?

过低会造成告警疲劳

Recall

真实异常有多少被抓到?

过低意味着漏掉应巡检蜂箱

F1

Precision 与 Recall 是否平衡?

便于比较不同阈值/版本

混淆矩阵

错在哪里?

区分误报 FP 与漏报 FN

真实标签应来自现场巡检、送检结果和设备故障排查,而不是直接把模型自己的规则分数当作“真值”。否则评估会形成自证循环。

17. 巡检闭环:模型真正需要的是高质量反馈标签

图11风险预警闭环:人工复核结果回流后,模型才具备持续校准条件

巡检结论

建议标签

后续用途

确认蜂群状态异常

VALID_ALERT

正样本

现场正常,模型误报

FALSE_ALERT

误报分析与阈值校准

传感器故障

DEVICE_FAULT

排除出病害训练标签

需要送检

PENDING_LAB

暂不作为确定标签

管理操作导致变化

MANAGEMENT_EVENT

作为上下文特征

尤其要把“设备故障”和“模型误报”分开。CO₂ 传感器漂移导致的高值,如果直接记成病害误报,会污染模型训练数据;先识别数据质量问题,再讨论风险模型。

18. 一条完整链路:H-027 从异常到关闭发生了什么

02:10,H-027 上传一条 telemetry。服务端完成设备绑定校验和 messageId 去重,发现数据处于物理合法范围,于是写入历史表并更新 Redis 最新状态。

02:20~03:00,多条记录显示湿度、CO₂ 与活动量持续偏离。EWMA 抑制单点抖动,Z-Score 反映相对历史基线的偏离程度;逻辑回归输出联合风险概率,规则层因为 CO₂、湿度和连续异常次数命中而继续加权。

达到高风险后,系统只创建一个 alertId,并推送给责任人员;后续 10 分钟采样继续更新同一告警的证据窗口,而不是不断创建新通知。技术员接单后检查通风、蜂群状态与传感器,填写现场结论。

若确认是通风问题并完成处理,后续指标连续恢复,告警进入已关闭;巡检结论成为模型评估标签。若最终发现是 CO₂ 传感器漂移,则标记 DEVICE_FAULT,并触发设备维护,而不是把这次事件计入病害模型误报。

19. 故障注入:正常运行截图不能证明系统可靠

图12可复现验证矩阵:重复投递、断网、尖峰、告警风暴和设备故障都应主动测试

测试

操作

预期

重复投递

同一 messageId 连续发布 2 次

历史表只新增 1 条

离线补传

断网缓存 6 个周期再恢复

按 sampleTime 补传,receiveTime 保留真实接收时间

单点尖峰

湿度从 68 突变 96 后恢复

质量标记/滤波抑制,不直接形成持续高风险

持续异常

湿度+CO₂异常 6 个周期

风险逐步升级并形成一个告警事件

告警风暴

同规则持续命中

冷却窗内不重复通知

设备恒值

温度长时间完全不变

产生设备质量告警

人工误报

巡检标记 FALSE_ALERT

进入模型评估数据集

20. 性能:5 分钟采样看起来不快,乘上蜂箱数量就不同了

如果 1,000 个蜂箱每 5 分钟上报一次,每天约产生 288,000 条蜂箱级采样;若每个蜂箱由多个独立设备分别上报,消息量还会继续增加。因此数据表需要按 hive_id + sample_time 建索引,统计查询避免每次扫描全部原始数据。

压力点

风险

优化

MQTT瞬时重连

大量补传同时进入

消费解耦、批量入库、背压

历史曲线

长时间范围查询慢

按小时/日预聚合

特征计算

重复扫描窗口

Redis/内存维护短窗口状态

告警扫描

全表定时轮询

事件驱动增量计算

ECharts大曲线

浏览器点数过多

服务端降采样/聚合

长期数据

单表持续膨胀

按时间分区或冷热归档

21. 安全与设备可信:设备接入不能只靠一个 hiveId

生产环境至少需要设备身份、凭证轮换、Topic ACL、TLS、消息大小限制和异常频率控制。否则任何能连到 Broker 的客户端都可能伪造某个蜂箱的 telemetry。

风险

控制

伪造设备

每设备独立凭证或证书

跨蜂场发布

Broker Topic ACL

明文传输

TLS

重放旧消息

messageId + sampleTime + 时间窗

异常高频

设备级限流

凭证泄漏

可吊销、可轮换,不写死在前端

22. 病害预警的科学边界:风险线索不是自动诊断

湿度升高、CO₂ 累积、重量下降、活动量或声音异常,都可能与蜂群健康风险相关,但也可能由天气、通风、采蜜、饲喂、转场、设备漂移等因素造成。系统最合理的定位是“连续监测 + 风险排序 + 巡检辅助”。

因此页面文案应使用“高风险、建议优先巡检、异常指标”而不是“已患某病”。涉及具体病害确认时,应依赖现场检查、专业检测或实验室结果。这样的边界不会削弱系统价值,反而能避免把相关性误写成因果或诊断。

23. 常见误区:这些做法很容易让系统看起来智能、实际不可用

误区

问题

改进

一个阈值判断病害

天气和噪声导致误报

连续异常 + 多因子联合

MQTT QoS 1 就认为不重复

至少一次可能重复

业务幂等

只保存入库时间

补传数据时间错位

采集时间 + 接收时间

缺失值一律均值填充

可能虚构关键病害信号

关键字段保留 missing 标记

风险只返回分数

无法解释

返回 reasons + modelVersion

模拟数据算出高准确率

存在标签泄漏/自证

模拟用于链路测试,真实标签用于评估

告警一触发就结束

没有反馈数据

巡检、复核、标签回流

24. 最终落地:从“传感器大屏”升级为真正的风险管理系统

一个成熟的智慧蜂场系统,不应该以“能看到温湿度曲线”为终点。真正有价值的链路是:设备可靠采集,MQTT 可追踪接入,数据经过质量治理,时序特征反映相对基线,多因子模型输出可解释风险,告警状态机控制通知噪声,技术人员完成巡检,现场结论再回到模型评估。

Java/Spring Boot 负责设备接入、数据治理、特征与告警业务,Redis 承担短期实时状态,MySQL 保存可追溯历史,Vue 3 + ECharts 把风险原因和处置过程呈现出来。EWMA、Z-Score 和逻辑回归并不复杂,真正困难的是让它们在设备掉线、消息重复、网络补传、季节变化、传感器漂移和人工反馈不完整的情况下仍然保持清晰的工程语义。

当系统能够明确区分“数据异常、设备异常、环境风险和现场确认结果”,智慧蜂场才真正从数据展示走向辅助决策;长期积累的高质量巡检标签,也才有资格支撑后续更复杂的时序模型和蜂群健康研究。

技术栈与运行环境

前端:Vue 3、Pinia、Axios、ECharts;后端:Java 17+、Spring Boot 3.x、MyBatis Plus;消息与数据:MQTT Broker(QoS / ACL / LWT)、MySQL 8.x、Redis 7.x;模型:EWMA、滑动窗口 Z-Score、规则评分、逻辑回归;部署与验证:Linux、Docker、Nginx、JUnit、MQTT 测试客户端与固定随机种子模拟数据。

返回列表