Java实现环境监测系统:从数据采集到WebSocket实时可视化
简介这是一份面向Java初学者与毕业设计学生的环境监测系统完整源码涵盖空气、温度、湿度等多类环境数据的采集、展示与管理流程。项目采用Spring、Servlet/JSP与DAO分层架构覆盖业务处理、数据持久化与请求响应链路适合课程设计、毕业设计或Java Web入门练习。压缩包共46个文件核心为35个Java源文件按controller、service、dao、daoimpl分包另含数据库设计脚本、数据库更新脚本、监控管理方案及README说明整体约29KB。目前已有701人学习下载。通过阅读源码可快速理解Java Web项目从实体建模、持久层操作到控制器响应的完整链路掌握环境监测相关模块的接口写法与数据流转方式也能参考其中用户登录与权限管理、数据实时展示等模块进行二次开发。1. 环境监测软件选Java本质是选一条能落地的数据链路一套环境监测系统真正难的不是画曲线而是处理“数据从哪来、怎么存、怎么在页面上接近实时地出现”这条链路。以这套Java实现的环境监测系统源码为例核心流程是设备端按周期上报温湿度、PM2.5、VOC等指标服务端先做校验再写入MySQL前端页面通过WebSocket订阅增量数据独立的报警模块按预设规则判断是否触发告警。对正在做环境监测软件相关项目的Java开发者来说这套源码的价值在于把采集任务、数据模型、消息推送、规则判断拆成独立的模块既容易讲清楚设计思路也方便后续替换成生产级组件。下面把这条链路逐段拆开。2. 表结构与实体映射环境监测系统的数据底座2.1 设备、指标、规则、日志四张表的职责边界如果按业务的一时冲动建表很容易陷入字段冗余。覆盖主流程只需要四张核心表具体的表结构如下。表名作用关键字段索引建议device采集终端信息id、device_code、name、location、statusdevice_code 唯一索引env_record环境指标明细id、device_code、temperature、humidity、pm25、voc、collect_time(device_code, collect_time) 联合索引alarm_rule设备报警规则id、device_code、metric、operator、threshold、notify_typedevice_code 索引alarm_log报警触发记录id、rule_id、device_code、metric、real_value、alarm_time、handle_statushandle_status 索引我一般会强调一个容易被忽略的设计点在env_record里存逻辑外键device_code而不是device_id。原因有两个一是设备编号在排查问题时可以直接从日志里看出来不用再做一次ID映射二是InnoDB的外键约束在写入量大时会放大锁开销尤其是采集周期短、批量插入频繁的场景。既然本项目选择用Java直连MySQL就没必要增加这道约束。env_record表的联合索引(device_code, collect_time)是整个查询性能的关键。页面上最常见的请求是“查某个设备某段时间的温度曲线”没有这个联合索引时MySQL会走全表扫描数据量超过一万条就已经有可感知的延迟。反过来如果查询条件只有collect_time而没有device_code这个联合索引也无效所以接口层必须保证两个条件同时传入。2.2 实体类与MyBatis批量写入Java侧采用标准的Entity、Mapper、Service三层结构。环境指标实体类如下。public class EnvRecord { private Long id; private String deviceCode; private BigDecimal temperature; private BigDecimal humidity; private BigDecimal pm25; private BigDecimal voc; private LocalDateTime collectTime; public static EnvRecord of(String deviceCode, BigDecimal temperature, BigDecimal humidity, BigDecimal pm25, BigDecimal voc, LocalDateTime collectTime) { EnvRecord record new EnvRecord(); record.deviceCode deviceCode; record.temperature temperature; record.humidity humidity; record.pm25 pm25; record.voc voc; record.collectTime collectTime; return record; } // getter、setter 省略 }这里保留BigDecimal而不是double是有实际考量的。温湿度在后续计算体感温度、空气质量指数时需要做乘除运算double的累积误差会以小数点后第三位的形式暴露在报表里。MySQL侧对应字段用DECIMAL(5,2)、DECIMAL(5,2)、DECIMAL(7,1)、DECIMAL(6,2)分别对应温度、湿度、PM2.5和VOC。Mapper层逐条insert会有明显的网络往返成本单次采集周期如果有几百个设备这个开销是叠加放大的。推荐用批量插入insert idbatchInsert parameterTypelist INSERT INTO env_record (device_code, temperature, humidity, pm25, voc, collect_time) VALUES foreach collectionlist itemr separator, (#{r.deviceCode}, #{r.temperature}, #{r.humidity}, #{r.pm25}, #{r.voc}, #{r.collectTime}) /foreach /insertMyBatis的foreach批量拼接是最常见的做法。需要注意MySQL的max_allowed_packet默认是64MB单次batch超过上千条记录时由于每条记录都带字符串和时间戳预估包大小后按500条一批拆分更稳妥否则会出现类似“PacketTooBigException”的连接中断。采集频率越高这个参数越值得确认不能只在小数据量下跑通就认为没问题。2.3 Service层的事务边界Service层建议拆成三个入口采集入口、查询入口、报警入口。采集入口只负责写入查询入口只负责读取报警入口在采集完成后异步触发。这样无论环境监测软件的需求怎么演进核心读写路径都不会互相干扰。事务边界是一个容易踩坑的地方。env_record写入不应该和alarm_rule查询放在同一个REQUIRED事务里报警规则属于读多写少的配置数据每轮采集都重新开启大事务会放大锁等待时间。把报警判断放到事务提交之后执行既保证环境数据先落库又不会让规则查询拖累写入性能。3. 定时采集、数据校验与报警环境监测服务端的核心链路3.1 定时任务的防重入与失败补偿Java环境监测系统的采集任务通常用Spring的Scheduled驱动。下面的代码是一个带防重入保护的采集骨架。Component public class CollectTask { private final DeviceMapper deviceMapper; private final EnvRecordService recordService; private final AtomicBoolean running new AtomicBoolean(false); Scheduled(cron 0 */5 * * * ?) public void collect() { if (!running.compareAndSet(false, true)) { return; } try { ListDevice devices deviceMapper.selectOnlineDevices(); for (Device device : devices) { // 实际工程中通过 Modbus、MQTT 或 HTTP 上报获取指标 MapString, BigDecimal metrics readMetrics(device.getDeviceCode()); recordService.save(device.getDeviceCode(), metrics); } } finally { running.set(false); } } }防重入的核心是AtomicBoolean的compareAndSet它能保证同一时刻只有一个采集线程在执行。Spring Boot默认调度线程池大小是1但如果手动调整过线程池参数或者未来改成多实例部署任务就可能重叠执行。用running标志可以避免双写和重复报警。失败补偿的策略要分场景。设备离线导致采集异常时我会在readMetrics里把失败原因写入device_status_log表下一次定时任务再尝试连接连续三次失败就把device表的状态改为offline前端显示“设备掉线”而不是继续画一条假曲线。采集间隔建议用cron表达式控制它能精确到整点执行而fixedRate是任务结束后才开始计时任务耗时一旦不稳定采集节奏就会漂移。3.2 数据校验与异常指标标记从设备侧拿到的数据不能直接入库要先做范围校验。public class MetricValidator { private static final MapString, double[] RANGES new HashMap() {{ put(temperature, new double[]{-40, 60}); put(humidity, new double[]{0, 100}); put(pm25, new double[]{0, 2000}); put(voc, new double[]{0, 60000}); }}; public static boolean check(String metric, BigDecimal value) { double[] range RANGES.get(metric); if (range null) { return false; } return value.doubleValue() range[0] value.doubleValue() range[1]; } }参数说明temperature范围按户外设备定义室内场景可以收窄到050pm25上限取2000是考虑到沙尘等极端天气时实际值可能超过999。每个指标的范围要和传感器量程匹配这是环境监测软件开发里经常被忽略的环节。校验失败的数据写入alarm_log并标记quality_flag0而不是静默丢弃。这个设计后续在评估数据质量时很有价值因为它保留了原始异常记录而不只是丢了一行数据。3.3 规则判断与通知解耦报警模块不推荐把规则硬编码在if-else里。用alarm_rule表存储规则判断逻辑只依赖操作符和阈值即可。public boolean judge(String metric, BigDecimal value, AlarmRule rule) { int cmp value.compareTo(rule.getThreshold()); return switch (rule.getOperator()) { case - cmp 0; case - cmp 0; case - cmp 0; case - cmp 0; case EQ - cmp 0; default - false; }; }关键点是compareTo而不是equals。BigDecimal的compareTo忽略精度差异2.0和2.00在它看来相等equals反而会判不等。规则判断如果直接用doubleValue做比较在阈值是小数的场景容易出错。报警通知建议用Spring事件机制解耦采集完成后发布AlarmCheckEvent监听器里做规则判断命中后异步发送通知。这样采集主链路不会被通知耗时阻塞。通知方式平均耗时可靠性适用场景站内信入库小于10ms高默认必选邮件200ms~2s中值班通知短信/Webhook300ms~5s依赖第三方紧急告警4. 实时监测面板WebSocket推送与曲线可视化4.1 WebSocket推送增量数据环境监测软件最影响体验的地方是数据刷新方式。传统方案是前端轮询接口每秒请求一次页面看着还行但服务端会积累大量无效请求。更合适的方式是建立WebSocket长连接服务端在数据落库后主动推送。ServerEndpoint(/ws/env/{deviceCode}) public class EnvWebSocket { private static final MapString, Session SESSIONS new ConcurrentHashMap(); OnOpen public void onOpen(Session session, PathParam(deviceCode) String deviceCode) { session.getUserProperties().put(deviceCode, deviceCode); SESSIONS.put(session.getId(), session); } OnMessage public void onMessage(String message, Session session) { if (ping.equals(message)) { session.getAsyncRemote().sendText(pong); } } public static void sendToDevice(String deviceCode, String payload) { SESSIONS.values().forEach(s - { if (deviceCode.equals(s.getUserProperties().get(deviceCode))) { s.getAsyncRemote().sendText(payload); } }); } }这段代码有两个需要留意的点。第一用session.getId()做key可以避免同一个浏览器开多个标签页时相互误关第二sendToDevice遍历所有会话在设备数几十到上百的监测场景是够用的设备量到千级后就应该改成deviceCode映射到SetSession的结构。推送时机要与采集任务错开。采集任务在整点前后执行推送则在数据完成校验和入库之后触发。这里用Spring的ApplicationEvent做解耦比较合适采集完成后发布EnvRecordInsertedEventWebSocket监听器收到事件后把最新的指标推给对应设备的订阅端点。这样采集线程和推送线程各自独立互不阻塞。4.2 曲线查询与聚合SQL前端曲线图请求的数据通常不是原始明细而是聚合后的采样点。接口用一个参数控制聚合粒度。GetMapping(/api/env/trend) public ListEnvTrendVO trend(String deviceCode, RequestParam String start, RequestParam String end, RequestParam(defaultValue raw) String interval) { if (hour.equals(interval)) { return recordService.selectHourly(deviceCode, start, end); } return recordService.selectRaw(deviceCode, start, end); }interval的作用是控制前端拿到的数据点数。查询一天的数据时按小时聚合查询一周的数据时按天聚合否则前端要接收上千个原始点ECharts渲染时会明显卡顿。聚合SQL与明细查询走的是不同路径SELECT DATE_FORMAT(collect_time, %Y-%m-%d %H:00) AS bucket, ROUND(AVG(temperature), 2) AS avg_temp, ROUND(AVG(humidity), 2) AS avg_hum, MAX(pm25) AS max_pm25 FROM env_record WHERE device_code #{deviceCode} AND collect_time BETWEEN #{start} AND #{end} GROUP BY bucket ORDER BY bucket;注意聚合口径不能一刀切。温度可以算平均值PM2.5应该看日均值和峰值VOC则需要看超标频次这些指标的业务含义不同。聚合周期和指标维度要一起传给前端前端按照指标类型渲染不同形式的图表才能分辨哪些是持续性问题、哪些是瞬时超标。4.3 ECharts页面集成前端用原生HTML加ECharts足矣环境监测的曲线图没有用到特别复杂的图表类型。典型做法是页面加载后请求一次接口然后通过WebSocket增量更新最后几个点。const chart echarts.init(document.getElementById(trend)); fetch(/api/env/trend?deviceCodeDEV-001start2025-06-01 00:00end2025-06-01 23:59intervalhour) .then(res res.json()) .then(data { chart.setOption({ xAxis: { type: time }, yAxis: { type: value, name: 温度(℃) }, toolbox: { feature: { dataZoom: {}, saveAsImage: {} } }, series: [{ type: line, data: data.map(item [item.bucket, item.avgTemp]), smooth: true, symbol: none, areaStyle: { opacity: 0.15 } }] }); });接口字段与图表的映射放在VO层完成Controller只返回VO对象。VO对应EnvTrendVO和AlertVO两个结构化类型这样实体的字段变更不会直接波及前端契约。图表下方的指标卡片直接复用同一条接口数据展示当前值、超标时长和设备状态卡片信息与曲线数据保持一致避免出现前端各组件改一处漏一处的情况。5. 部署、性能优化与常见坑位5.1 设备掉线检测的实现技巧设备是否在线不能只看最后一条记录的时间。环境监测系统里传感器偶发断连是常态断电、网线松动、网关重启都会造成采集中断。我的做法是单独维护device.last_heartbeat_time字段每一条采集记录写入时同步更新心跳时间同时用一个清理任务把心跳超过三倍采集周期的设备标记为离线。UPDATE device SET status offline WHERE status online AND last_heartbeat_time DATE_SUB(NOW(), INTERVAL 15 MINUTE);这段SQL里的15分钟来自采集周期5分钟乘以3。这个倍率不是拍脑袋定的而是考虑到网络抖动、设备重启、服务端GC停顿等情况2倍周期可能误判3倍是一个相对合理的值。执行频率设为一分钟一次即可带来的开销可以忽略。状态变更后通过WebSocket推送一条设备离线事件前端把这个设备的所有指标卡片置灰避免用户看到一条断掉的时间线还以为是服务端出了问题。5.2 Linux部署环境检查清单Java环境监测系统部署到服务器时最先要确认的是JDK版本和编码格式。源码在Windows本地开发代码用了中文注释Linux服务器上如果不加JVM参数日志和数据库读写会出现乱码。在启动脚本中显式指定UTF-8nohup java -jar env-monitor.jar \ -Dfile.encodingUTF-8 \ -Xms512m -Xmx1024m \ -Dspring.datasource.urljdbc:mysql://localhost:3306/env_monitor?useUnicodetruecharacterEncodingutf8rewriteBatchedStatementstrue \ --spring.profiles.activeprod app.log 21 注意rewriteBatchedStatementstrue这个参数。JDBC如果没有开启它rewriteBatchedStatement不会把多个insert改写为多值insertMyBatis的批量插入会退化成逐条执行。很多环境监测软件源码在本地跑没问题一上服务器就慢原因往往就在数据库连接串少这个参数。排查时同时检查MySQL的max_allowed_packet确保两者匹配。5.3 排查报警延迟的路径报警延迟出现时不要先怀疑规则引擎按“采集任务是否准时执行 - 数据是否落库 - 事件是否发布 - 监听器是否消费”的链路逐段排查。第一步看定时任务日志里有没有超时记录第二步确认env_record的collect_time和当前时间是否吻合第三步看事件发布日志与消费日志的时间差。90%以上延迟问题出在采集任务被前一批数据拖住而不是推送或判断逻辑有问题。# 查看最近一次采集任务的时间与当前时间差 mysql -e SELECT MAX(collect_time), NOW() FROM env_monitor.env_record;这条SQL几毫秒就能确认数据链路是否在流转。环境监测系统的实时性不是靠某个单点组件保证的而是采集、入库、推送、展示四个环节的时间差总和任何一个环节出现漂移前端都会表现为数据陈旧。把这个排查路径固化下来比反复看代码更高效。本文还有配套的精品资源点击获取

相关新闻

水下生物目标检测实战:YOLOv8训练与避坑指南

水下生物目标检测实战:YOLOv8训练与避坑指南

简介:面向水下生物目标检测的Python开发者,资源提供基于YOLO与PyTorch的完整目标检测方案,覆盖数据集格式转换、模型训练与PyQt可视化识别流程,适合深度学习入门者与计算机视觉实践者参考学习。压缩包共1830个文件,大小…

2026/9/23 16:46:45 阅读更多 →
3步搞定在线脑图源码解析,拒绝只会抄代码

3步搞定在线脑图源码解析,拒绝只会抄代码

3步搞定在线脑图源码解析,拒绝只会抄代码 看了一堆教程还是不会写项目,这是大多数转行开发者最真实的痛点。很多人觉得只要把框架跑起来,项目就算完成了,但真正上线后才发现,数据同步、性能瓶颈和交互细节全是坑。今天我们要做的不是简单的页面拼接,而…

2026/9/23 16:46:45 阅读更多 →
YOLOv11+ROS2多模态交互系统:机器人视觉导航方案实战

YOLOv11+ROS2多模态交互系统:机器人视觉导航方案实战

简介:这份PDF文档面向机器人视觉导航方向的开发者与研究者,系统讲解如何将YOLOv11目标检测算法与ROS2框架结合,构建多模态交互的机器人视觉导航方案。文档共45页,支持目录章节跳转与阅读器左侧大纲快速定位,内容完整、…

2026/9/23 16:46:45 阅读更多 →

最新新闻

3步搞定阿里云邮箱企业版配置,一文搞懂避坑指南

3步搞定阿里云邮箱企业版配置,一文搞懂避坑指南

3步搞定阿里云邮箱企业版配置,一文搞懂避坑指南 配置SMTP环境就卡半天?别急,很多人卡在认证方式或端口设置上。本文旨在 一文搞懂 阿里云邮箱企业版的核心配置逻辑。…

2026/9/23 17:56:12 阅读更多 →
上海游戏培训避坑:5道高频面试题拆解证书含金量

上海游戏培训避坑:5道高频面试题拆解证书含金量

上海游戏培训避坑:5道高频面试题拆解证书含金量 面试被问原理答不上来,心里慌不慌?在【上海游戏培训】圈子里,这种尴尬太常见了。很多学员花大几万学费,回去一问证书怎么查、和别的岗位有啥区别,支支吾吾说不出个所以然。这不仅是面子问题,更是硬伤。…

2026/9/23 17:56:12 阅读更多 →
基于LSTM的淘宝商品评论分析系统:从评论文本到情感判定的完整落地路径

基于LSTM的淘宝商品评论分析系统:从评论文本到情感判定的完整落地路径

简介:这份资源是面向NLP入门者与电商数据分析学习者的完整项目包,以LSTM循环神经网络为核心,解决淘宝商品评论的情感倾向识别与文本分类问题。项目从词向量、序列建模到模型训练与前端展示形成闭环,适合课程设计、毕业设计或算法练…

2026/9/23 17:56:12 阅读更多 →
手写数字抽奖系统避坑指南 搞定随机算法不翻车

手写数字抽奖系统避坑指南 搞定随机算法不翻车

手写数字抽奖系统避坑指南 搞定随机算法不翻车 面对屏幕上那串红色的 StackTrace,是不是脑子直接宕机了?明明照着教程敲的代码,一运行就抛出 IndexOutOfBoundsException 或者…

2026/9/23 17:56:12 阅读更多 →
中彩网双色球预测手写实现性能优化实战

中彩网双色球预测手写实现性能优化实战

中彩网双色球预测手写实现性能优化实战 看了一堆教程还是不会写项目?别慌,这不是你的问题,是教程没教你怎么把代码跑快。 很多应届生做 中彩网双色球预测 这种数据处理项目,上来就无脑 for 循环。数据量一上来,程序卡死,CPU…

2026/9/23 17:56:11 阅读更多 →
安卓界面设计避坑指南:解决布局错乱与性能卡顿

安卓界面设计避坑指南:解决布局错乱与性能卡顿

安卓界面设计避坑指南:解决布局错乱与性能卡顿 配置环境卡半天,代码一跑界面就崩,这种绝望感谁懂?刚接手的安卓项目,XML 写得再漂亮,真机一预览全是错位、重叠或者白屏。别急着怀疑自己水平不行,大概率是掉进了布局引擎的陷阱。这份避坑指南不是讲…

2026/9/23 17:55:11 阅读更多 →

日新闻

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A…

2026/9/23 0:00:23 阅读更多 →
2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我 刚把开发环境的显示器从1080P换到2K,跑老项目直接报错,版本升级后 API…

2026/9/23 0:01:25 阅读更多 →
3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点 官方文档翻了三遍还是云里雾里?别急,美眉图在实战项目中常被用来做数据可视化,但它的原理比你想的简单。今天咱们直接上手,用一个完整的小项目把美眉图跑通,不再死磕那些冗长的理论说明。…

2026/9/23 0:01:25 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/23 9:53:41 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/23 9:53:40 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/23 9:53:40 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/23 9:53:40 阅读更多 →