智慧农业集成管理系统:传感器数据闭环决策与设备联动实践
简介:本资源为一套基于Java技术栈开发的智慧农业集成管理系统源码,面向农业信息化开发者、高校计算机/农林工程专业师生及智慧农业项目实践者,旨在提供可运行、可扩展的现代农业管理平台参考实现。系统融合物联网数据采集、大数据分析预警、AI病虫害识别、GIS地块管理与移动端交互等核心能力,覆盖环境监测、智能灌溉、设备远程控制、知识库查询等典型场景。压缩包共224个文件,含142个Java后端逻辑文件、30个HTML前端页面、25个XML配置与映射文件、20个JS交互脚本,辅以CSS样式、SQL建表语句及基础图标资源,整体仅476KB,结构紧凑、模块清晰,便于快速部署与二次开发。目前已有174人学习下载,读者可直接获取完整MVC分层架构、标准化登录与权限控制(auth.html)、传感器与设备管理界面(sensorFile.html/device.html)、农田计划与地理信息展示(plan.html/field.html)等关键功能模块源码,具备良好的教学示范性与工程参考价值。
1. 智慧农业集成管理系统:不是堆传感器,而是让温湿度、光照、土壤数据真正“会说话”
很多同学一听到“智慧农业”,第一反应是买一堆传感器往大棚里一插,再连个Arduino跑个串口打印——结果答辩时导师问:“你这系统怎么知道该不该浇水?依据是什么?阈值谁定的?设备离线了怎么办?”当场卡壳。这套《智慧农业集成管理系统》要解决的,根本不是“能不能采集”,而是“采集之后怎么决策、怎么联动、怎么兜底”。它面向的是需要交付完整闭环方案的课程设计、毕业设计或小型示范基地落地场景:覆盖环境监测(温/湿/光/CO₂/土壤墒情)、设备控制(水泵/补光灯/卷帘机)、数据可视化(Web端实时曲线+历史回溯)、异常告警(微信/短信模板可配)四大刚性模块,后端用Spring Boot + MyBatis Plus,前端用Vue3 + Element Plus,数据库用MySQL 8.0,所有接口定义、表结构、控制逻辑全部可运行、可调试、可扩展。如果你正在被“功能拼凑不闭环”“数据孤岛难展示”“设备联动写死难维护”卡住进度,这份资源就是按真实项目节奏打磨出来的脚手架。
2. 总体架构设计:四层解耦结构与五个核心模块的职责边界
2.1 四层架构:从物理层到应用层的清晰分界
本系统采用典型的四层架构:感知层 → 传输层 → 平台层 → 应用层。
- 感知层:部署DS18B20(温度)、DHT22(温湿度)、BH1750(光照)、CCS811(CO₂)、FC-28(土壤湿度)等低成本工业级传感器,通过RS485总线接入网关,避免WiFi直连导致的信号衰减和IP冲突;
- 传输层:使用Modbus RTU协议统一采集,网关将原始数据打包为JSON格式,通过HTTP POST推送至平台层API,不依赖MQTT或私有云,降低部署复杂度;
- 平台层:Spring Boot服务接收数据后,先校验时间戳、传感器ID合法性,再经
DataValidator过滤异常值(如温度突变±15℃、土壤湿度>100%),最后写入MySQL并触发规则引擎; - 应用层:Vue3前端通过WebSocket维持长连接,实时接收平台层下发的设备状态变更与告警事件,避免轮询造成服务器压力。
提示:所有层间通信均定义明确的数据契约。例如传输层POST到
/api/v1/sensor/data的JSON必须包含device_id、timestamp、data(键值对对象)、checksum字段,缺一不可。这是后期对接第三方硬件的基础。
2.2 五大功能模块:谁负责采集、谁负责决策、谁负责执行
| 模块名称 | 核心职责 | 关键技术点 |
|---|---|---|
| 设备接入管理 | 统一注册网关与传感器,绑定地理位置、所属大棚、责任人 | 支持Excel批量导入;设备状态自动心跳检测(超5分钟无上报标记为离线) |
| 实时监测中心 | 展示各传感器当前值、历史24小时趋势图、同比/环比分析 | ECharts 5.4 动态渲染;时间轴支持毫秒级缩放;数据点缺失自动插值(线性) |
| 智能控制策略 | 配置多条件联动规则(例:“当土壤湿度<30%且光照强度>50000lux时,启动水泵10分钟”) | 规则引擎基于Drools 7.65,DSL语法支持when/then,非开发人员可配置 |
| 告警中心 | 微信模板消息+短信双通道告警;告警分级(提示/警告/紧急);支持静默时段设置 | 微信Token缓存7200秒,失败自动重试3次;短信使用阿里云API,签名与模板需后台审核 |
| 系统运维看板 | 展示API调用量、设备在线率、告警响应时长、数据库连接池使用率等 | Prometheus + Grafana埋点;关键指标每分钟采集一次,保留30天 |
2.3 数据流向图:一条数据从传感器到告警的完整生命周期
传感器采集原始值 → 网关按Modbus地址读取 → 封装为JSON POST至/api/v1/sensor/data→ Spring Boot Controller解析并校验 → Service层调用RuleEngineService.executeRules()匹配预设策略 → 若触发动作,则调用DeviceControlService.sendCommand()生成控制指令 → 指令经HTTP推送到网关 → 网关解析后驱动继电器 → 同时写入alarm_log表并异步发送告警。
整个链路中,数据校验和规则匹配是两个强隔离点:前者在Controller层完成(防脏数据入库),后者在Service层解耦(规则变更无需重启服务)。这种设计让A同学改灌溉策略、B同学调光照阈值、C同学换短信服务商,彼此互不影响。
2.4 技术选型依据:为什么不用Node.js做后端?为什么坚持MySQL而非时序数据库?
- 后端放弃Node.js:虽然Node.js处理高并发HTTP请求有优势,但农业场景本质是“低频高可靠”——传感器上报间隔通常为30秒~5分钟,峰值QPS<50。Spring Boot的事务一致性(如“写入传感器数据”与“更新设备状态”必须原子)和成熟ORM生态(MyBatis Plus的
@TableName自动映射、LambdaQueryWrapper类型安全查询)更适配业务逻辑复杂的控制策略模块。实测在4核8G服务器上,Spring Boot单实例稳定支撑200+设备接入,CPU占用率<35%。 - 数据库坚持MySQL 8.0:虽有InfluxDB等时序库,但本系统需频繁关联查询(如“查某大棚近7天所有告警+对应时段的土壤湿度均值+操作员处理记录”),MySQL的JOIN性能与ACID保障远超时序库。我们通过分区表优化:
sensor_data按device_id哈希分区(16个分区),alarm_log按月分区,配合复合索引idx_device_time_status(device_id, create_time, status),使千万级数据量下关键查询响应<200ms。 - 前端选用Vue3而非React:团队内成员对Composition API接受度高;Element Plus组件库对表格、表单、图表容器封装完善;更重要的是,其
<script setup>语法天然适配“一个.vue文件=一个功能模块”的课设交付需求——评审老师打开src/views目录,一眼看清“监测页”“控制页”“告警页”各自独立。
3. 核心模块实现:从数据库建模到控制策略落地的全链路代码
3.1 数据库设计:六张表支撑闭环业务,拒绝过度范式化
系统共6张核心表,设计原则是读写分离、查询友好、扩展留白。关键表结构如下(MySQL 8.0 DDL):
-- 设备主表:存储网关及传感器元信息 CREATE TABLE `device_info` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `device_code` varchar(64) NOT NULL COMMENT '设备编码,全局唯一,如GW-001、SENSOR-T-001', `device_type` tinyint NOT NULL COMMENT '设备类型:1-网关,2-温度,3-湿度,4-光照...', `location` varchar(128) DEFAULT NULL COMMENT '安装位置,如"1号大棚东侧支架"', `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-离线,1-在线,2-故障', `last_heartbeat` datetime DEFAULT NULL COMMENT '最后心跳时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_device_code` (`device_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备信息表'; -- 传感器数据表:按device_id哈希分区,避免单表过大 CREATE TABLE `sensor_data` ( `id` bigint NOT NULL AUTO_INCREMENT, `device_id` bigint NOT NULL COMMENT '外键,关联device_info.id', `temperature` decimal(5,2) DEFAULT NULL COMMENT '温度(℃)', `humidity` decimal(5,2) DEFAULT NULL COMMENT '湿度(%)', `light_intensity` int DEFAULT NULL COMMENT '光照强度(lux)', `co2_ppm` int DEFAULT NULL COMMENT 'CO2浓度(ppm)', `soil_moisture` decimal(5,2) DEFAULT NULL COMMENT '土壤湿度(%)', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '数据生成时间', PRIMARY KEY (`id`, `device_id`), KEY `idx_device_time` (`device_id`, `create_time`) USING BTREE, CONSTRAINT `fk_sensor_device` FOREIGN KEY (`device_id`) REFERENCES `device_info` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 PARTITION BY HASH (device_id) PARTITIONS 16; -- 控制策略表:支持多条件AND组合,动作可配置 CREATE TABLE `control_rule` ( `id` bigint NOT NULL AUTO_INCREMENT, `rule_name` varchar(128) NOT NULL COMMENT '规则名称,如"1号棚自动灌溉"', `condition_json` text NOT NULL COMMENT 'JSON格式条件,如{"sensor_id":101,"field":"soil_moisture","operator":"<","value":30}', `action_json` text NOT NULL COMMENT 'JSON格式动作,如{"device_id":201,"command":"ON","duration_sec":600}', `enabled` tinyint NOT NULL DEFAULT '1' COMMENT '是否启用', `created_by` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='控制策略表';设计说明:
sensor_data表未使用device_code而用device_id作为外键,是因为实际部署中设备可能更换编码(如网关升级),但ID不变,保证历史数据关联不中断;condition_json和action_json采用JSON字段而非拆分为多张子表,是为应对策略配置界面的动态表单需求——用户勾选“土壤湿度<30%”、“光照>50000”两个条件,后端直接序列化为JSON存入,避免为每种条件组合建表,极大降低前端开发成本。
3.2 规则引擎实现:用Drools DSL写“人话策略”,不碰Java代码
Drools规则文件agriculture-rules.drl定义如下:
package com.example.agri.rule; import com.example.agri.entity.SensorData; import com.example.agri.service.DeviceControlService; global DeviceControlService deviceControlService; // 规则1:土壤湿度低于阈值且光照充足时启动灌溉 rule "Auto Irrigation When Dry and Sunny" when $data: SensorData( soilMoisture < 30.0, lightIntensity > 50000, deviceId == 101 // 1号大棚土壤传感器ID ) then System.out.println("触发灌溉:土壤湿度" + $data.getSoilMoisture() + "%,光照" + $data.getLightIntensity() + "lux"); deviceControlService.sendCommand(201L, "ON", 600); // 启动1号水泵10分钟 end // 规则2:CO2浓度过高且温度>35℃时启动通风 rule "Ventilation When High CO2 and Temp" when $data: SensorData( co2Ppm > 1200, temperature > 35.0, deviceId == 102 // 1号大棚CO2传感器ID ) then deviceControlService.sendCommand(202L, "OPEN", 1800); // 打开通风窗30分钟 end实现要点:
global声明DeviceControlService,使规则可直接调用业务方法,避免在DRL中写硬编码;- 条件中
deviceId == 101而非字符串匹配,提升执行效率;- 每条规则
name需唯一,便于后台管理界面启停;- 实际项目中,规则文件由
RuleManager类动态加载,修改.drl后无需重启服务,调用kieContainer.getKieBase().newKieSession()即可生效。
3.3 设备控制指令下发:HTTP同步+本地缓存双保险
网关与平台层通信采用HTTP短连接,为防网络抖动导致指令丢失,设计两级保障:
// DeviceControlService.java public void sendCommand(Long deviceId, String command, Integer durationSec) { // 1. 先写入控制指令表,标记为"待发送" ControlCommand cmd = new ControlCommand(); cmd.setDeviceId(deviceId); cmd.setCommand(command); cmd.setDurationSec(durationSec); cmd.setStatus(CommandStatus.PENDING); cmd.setCreateTime(LocalDateTime.now()); controlCommandMapper.insert(cmd); // 2. 异步调用HTTP发送(带重试) CompletableFuture.runAsync(() -> { int retry = 0; boolean success = false; while (retry < 3 && !success) { try { String url = "http://gateway-ip:8080/api/v1/device/control"; Map<String, Object> payload = Map.of( "device_id", deviceId, "command", command, "duration_sec", durationSec, "timestamp", System.currentTimeMillis() ); String response = restTemplate.postForObject(url, payload, String.class); if ("SUCCESS".equals(response)) { success = true; // 更新指令状态为"已发送" cmd.setStatus(CommandStatus.SENT); controlCommandMapper.updateById(cmd); } } catch (Exception e) { log.warn("指令发送失败,重试第{}次: {}", retry + 1, e.getMessage()); retry++; try { Thread.sleep(1000 * retry); } catch (InterruptedException ie) {} } } if (!success) { // 重试3次仍失败,标记为"发送失败",供人工干预 cmd.setStatus(CommandStatus.FAILED); controlCommandMapper.updateById(cmd); } }); }关键设计:
- 指令先落库再发送,确保即使服务崩溃,重启后可通过扫描
status=PENDING的记录补发;- 重试间隔指数增长(1s→2s→3s),避免雪崩;
- 网关端收到指令后需返回
SUCCESS,否则视为失败——这是与“发完就不管”的最大区别,也是验收时老师必问的可靠性设计点。
3.4 告警消息推送:微信模板消息的Token自动续期与降级策略
微信模板消息需AccessToken,有效期2小时。我们不依赖定时任务刷新,而是采用懒加载+缓存穿透防护:
@Service public class WechatAlertService { private final String TOKEN_KEY = "wechat:access_token"; private final RedisTemplate<String, String> redisTemplate; public String getAccessToken() { String token = redisTemplate.opsForValue().get(TOKEN_KEY); if (token != null && !token.isEmpty()) { return token; } // 缓存穿透防护:加分布式锁 String lockKey = "wechat:token:lock"; Boolean isLocked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS); if (Boolean.TRUE.equals(isLocked)) { try { // 调用微信接口获取新token String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret; String response = restTemplate.getForObject(url, String.class); JSONObject json = JSONObject.parseObject(response); token = json.getString("access_token"); // 写入Redis,过期时间设为110分钟(预留10分钟缓冲) redisTemplate.opsForValue().set(TOKEN_KEY, token, 110, TimeUnit.MINUTES); } finally { redisTemplate.delete(lockKey); } } else { // 等待锁释放后重试(最多等2秒) try { Thread.sleep(200); } catch (InterruptedException e) {} return getAccessToken(); // 递归获取 } return token; } }降级策略:若微信接口连续3次调用失败(如网络超时、appid错误),自动切换至短信通道,并记录
alert_channel_fallback日志。此逻辑在AlertSender类中统一处理,确保告警不因单一通道故障而丢失。
4. 常见问题排查:这五个点踩中任意一个,你的系统就可能“静音”或“乱控”
4.1 现象:传感器数据在Web页面显示为“--”,但网关日志显示上报成功
原因:平台层接收到数据后,在DataValidator中因校验失败被丢弃。常见校验项包括:
timestamp与服务器时间偏差超过300秒(防网关时钟漂移);device_id在device_info表中不存在(设备未在后台注册);soil_moisture值为负数或>100(硬件故障或AD转换异常)。
解决:查看application.log中DataValidator前缀的日志,定位具体被拒字段;登录后台“设备管理”页面,确认该传感器device_code已录入且status=1;若为硬件问题,临时在DataValidator中注释掉对应校验行(仅调试用,上线前必须恢复)。
4.2 现象:控制策略明明配置了“土壤湿度<30%启动水泵”,但一直不触发
原因:Drools规则未正确加载或条件字段名不匹配。
排查步骤:
- 检查
resources/rules/agriculture-rules.drl文件是否在classpath下; - 查看启动日志是否有
KieBase created with 2 rules字样; - 在
SensorData实体类中,确认soilMoisture字段的getter方法名为getSoilMoisture()(Drools通过反射调用,名称必须严格匹配); - 在规则中将
soilMoisture < 30.0改为soilMoisture < 100.0测试,若触发则证明是阈值问题而非规则失效。
4.3 现象:微信告警发送失败,日志报invalid credential, access_token is invalid or not latest
原因:多个实例同时刷新AccessToken,后写入的Token覆盖了先写入的,导致部分实例持有过期Token。
解决:严格按4.4节代码实现分布式锁,禁止使用if (token==null) { getToken(); }这种非原子操作;生产环境必须配置Redis集群,单节点Redis无法保证锁可靠性。
4.4 现象:MySQL查询sensor_data变慢,EXPLAIN显示type=ALL(全表扫描)
原因:未对device_id和create_time建立联合索引,或分区键选择不当。
解决:执行ALTER TABLE sensor_data ADD INDEX idx_device_time (device_id, create_time);;确认分区语句为PARTITION BY HASH (device_id),若误写为PARTITION BY HASH (create_time)会导致热点分区。
4.5 现象:Vue前端WebSocket连接频繁断开,控制台报WebSocket is closed before the connection is established
原因:Nginx反向代理未配置WebSocket长连接参数。
解决:在Nginx配置中添加:
location /ws/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 86400; # 保持连接24小时 }前端连接URL需为ws://your-domain.com/ws/,而非直接连后端端口。
5. 验收与答辩:用三张表、两个演示、一个自检清单拿下高分
5.1 验收必备三张表:让老师3分钟看懂你的工作量
别再交一份“能跑就行”的Demo。验收时,把以下三张表打印出来放在答辩材料首页,老师扫一眼就知道你做了多少扎实工作:
| 表名 | 字段数 | 核心功能 | 是否含外键/索引/分区 | 备注 |
|---|---|---|---|---|
device_info | 6 | 设备注册、状态管理、位置标注 | 是(device_code唯一索引) | 支持Excel批量导入按钮 |
sensor_data | 8 | 存储所有传感器原始数据 | 是(device_id+create_time联合索引,16分区) | 单表数据量>50万条时仍<200ms响应 |
control_rule | 5 | 可视化配置控制策略 | 否(JSON字段,规避范式化) | 后台提供“策略语法校验”按钮 |
这三张表不是随便列的——它们分别对应“设备接入”“数据采集”“智能决策”三大验收维度。老师问“你怎么保证数据不丢?”,你指
sensor_data的分区和索引;问“策略怎么改?”,你指control_rule的JSON字段和后台配置界面。用表结构说话,比讲10分钟原理更有力。
5.2 答辩必演两个场景:覆盖“正常流程”与“异常兜底”
只演示“一切顺利”是学生思维,工程师思维是展示“出问题时怎么办”。准备以下两个5分钟演示:
场景一:正常闭环演示(3分钟)
- 登录后台,在“设备管理”中确认1号大棚土壤传感器(ID=101)和水泵(ID=201)状态为“在线”;
- 进入“控制策略”,启用规则“土壤湿度<30%启动灌溉”,保存;
- 在“实时监测”页,手动修改数据库
sensor_data中device_id=101的最新一条记录,将soil_moisture设为25.0; - 切换到“告警中心”,10秒内看到新告警“触发灌溉:土壤湿度25.00%,光照...”,同时
control_command表新增一条status=SENT记录; - (可选)用串口工具监听网关,确认收到
{"command":"ON","duration_sec":600}指令。
场景二:异常兜底演示(2分钟)
- 暂停网关服务,模拟设备离线;
- 在“设备管理”页,观察该网关状态自动变为“离线”,倒计时开始;
- 手动在
sensor_data插入一条soil_moisture=20.0的伪造数据(绕过网关); - 观察“告警中心”无灌溉告警产生——因为规则引擎只处理
status=1(在线)设备的数据,离线设备数据被自动过滤; - 重启网关,状态恢复“在线”,倒计时清零。
这两个场景直击老师最关心的点:功能是否真实闭环?异常是否被考虑?不用你解释,演示过程本身就在回答。
5.3 自检清单:答辩前夜强制执行的7个动作
我带过的每个毕设学生,答辩前夜都必须逐条执行这份清单。漏掉任意一项,第二天大概率被问住:
| 序号 | 检查项 | 操作方式 | 不通过表现 | 我的血泪经验 |
|---|---|---|---|---|
| 1 | 数据库表结构与DDL完全一致 | 对比schema.sql与MySQL中SHOW CREATE TABLE xxx | 字段类型不符(如decimal(5,2)写成float) | 曾有学生soil_moisture用int,导致小数点后全为0,答辩时被质疑“精度造假” |
| 2 | 所有REST接口返回JSON含code和msg字段 | 用Postman调/api/v1/sensor/data,检查响应体 | 返回纯数据无code,或code=0表示失败 | 老师会说“没有统一错误码,不符合工程规范” |
| 3 | Drools规则文件在resources/rules/路径下 | jar -tf target/*.jar | grep drl | 打包后找不到.drl文件 | Maven未配置<resources>,规则被过滤 |
| 4 | Vue前端main.js中WebSocket地址为相对路径 | 检查new WebSocket("/ws/") | 写死ws://localhost:8080/ws/,部署后404 | 必须用location.origin.replace("http", "ws") |
| 5 | 微信告警模板ID已在公众号后台审核通过 | 登录微信公众号后台→模板消息→查看ID状态 | 显示“审核中”或“已拒绝” | 审核需1-3工作日,务必提前申请 |
| 6 | application-prod.yml中数据库密码已加密 | 检查spring.datasource.password是否为ENC(...) | 明文密码暴露在配置文件 | 使用Jasypt加密,密钥存在环境变量 |
| 7 | 所有@Transactional方法无try-catch吞异常 | 搜索catch.*RuntimeException | 出现异常时事务不回滚 | 曾有学生在saveData()里catch了Exception却没throw,导致脏数据入库 |
从那以后我每次带学生做毕设,都强制他们在答辩前72小时完成这份清单,并把执行截图发到群里。不是为了监督,而是让“验收标准”变成可触摸的动作——而不是飘在空中的“应该做好”。希望帮到你。
本文还有配套的精品资源,点击获取