
简介这份疾病预防控制中心集中空调系统监测预警系统源码面向公共卫生信息化、暖通空调监控及计算机相关专业的课程设计与毕业设计场景适合需要快速搭建环境并理解监测预警流程的学习者。压缩包共740个文件约21.82MB包含HTML页面、JavaScript逻辑、CSS样式、JSON配置数据以及png/jpg/gif等界面素材其中HTML负责页面结构JavaScript与CSS实现交互和样式JSON存放配置数据图片素材提供图标与背景支持少量php脚本可处理服务端逻辑能支撑从页面展示到数据交互的完整演示。资源已有83人浏览学习可作为独立项目参考或二次开发基础项目结构清晰依赖简单便于初学者按模块拆解阅读。下载后可直接部署运行对需要掌握系统集成、预警机制设计以及前端展示实现的同学有实际帮助尤其适合作为课程设计或毕业设计的功能蓝本。1. 疾控中心集中空调监测预警系统一份值得拆解的经典Web课题疾控中心的集中空调通风系统里回风口的温湿度、新风量、PM10 这些指标平时看起来各自独立一旦连续超限往往意味着送风区域存在交叉污染风险。很多业务单位不是没有检测手段而是缺一套把数据采集、阈值比对、告警留痕串起来的系统。这份源码解决的正是这个环节它把集中空调的卫生学监测流程固化成一个可运行的 Java Web 工程前端用 Bootstrap 搭监测看板后端按监测点、监测记录、预警配置、预警日志四张核心表组织业务下载后可以直接部署运行。无论是做课程设计、期末大作业还是毕设它都是一个能快速上手的骨架尤其适合想弄清楚预警系统到底怎么判断该不该报警的人。2. 从 CSS 清单反推技术选型这套前端栈为什么这样搭2.1 先读一遍源码里的前端资产清单解压压缩包后第一眼看到的是一排 CSS 文件排在目录最前面。这个排列顺序本身就暴露了项目的技术取向Lc.css、bootstrap.css、bootstrap.min.css、font-awesome.css、font-awesome-ie7.css、bootstrap-responsive.css。把这些文件名拼在一起基本可以确定三件事页面框架用的是 Bootstrap 2.x 时代的响应式方案图标用的是 Font Awesome 3 系列字体库项目自身的业务样式独立放在了 Lc.css 里。文件作用使用阶段bootstrap.css / bootstrap.min.css栅格、按钮、表格、标签等基础组件页面全局bootstrap-responsive.css窄屏下栅格自动折行、隐藏侧栏页面全局font-awesome.css / font-awesome.min.css状态图标、功能图标按钮与告警标识font-awesome-ie7.css兼容 IE7 的图标字体修正仅老浏览器Lc.css / Lc.min.css项目自定义样式覆盖监控卡片、状态色业务页面这套组合放在今天看不算新但它有一个课设项目非常看重的优点不需要 Node 环境不需要 webpack静态资源直接拖到 webapp 目录就能跑。对于以业务逻辑和数据展示为主的监测系统来说Bootstrap 自带的栅格、表格、表单、按钮已经覆盖八成界面需求剩下的视觉差异交给 Lc.css 补齐即可。2.2 响应式栅格在监测看板里的实际用法监测系统的首页通常是“概览看板”核心是让值班人员一眼看到哪些点位出了问题。Bootstrap 2 的栅格是 12 列三个指标卡各占span4刚好一行放满。下面这段是这套系统里最典型的布局写法div classcontainer-fluid div classrow-fluid div classspan4 div classmetric-card h4回风温度/h4 p classmetric-value23.5℃/p span classstatus-label status-normal正常/span /div /div div classspan4 div classmetric-card h4回风湿度/h4 p classmetric-value52%/p span classstatus-label status-warning预警/span /div /div div classspan4 div classmetric-card h4PM10/h4 p classmetric-value0.18mg/m³/p span classstatus-label status-alarm超标/span /div /div /div /divcontainer-fluid让整个看板宽度随浏览器自适应row-fluid配合span4实现百分比宽度窗口缩小时指标卡会自动折行不需要另外写媒体查询。这个写法在现在的 Bootstrap 5 里已经改成了row-cols体系但对这份源码所属的 Bootstrap 2.x 项目来说row-fluid加span4就是它的标准布局方式。2.3 Lc.css 在业务页面上做了什么Bootstrap 只提供基础组件状态标签的颜色语义还得靠业务样式补这就是 Lc.css 存在的意义。它里面通常是一批按业务场景划分的类覆盖状态标签、表格行高亮、指标卡片三块.status-normal { background-color: #5cb85c; color: #fff; } .status-warning { background-color: #f0ad4e; color: #fff; } .status-alarm { background-color: #d9534f; color: #fff; } .metric-card { border: 1px solid #ddd; border-radius: 4px; padding: 16px; background: #fff; } .metric-value { font-size: 28px; font-weight: bold; }维护时有个容易踩的坑压缩包同时保留了Lc.css和Lc.min.css页面实际引用的往往是 min 版本。如果只改了Lc.css而没同步生成新的 min 文件刷新页面看不到任何变化。这类课设项目通常没有构建脚本所以改完Lc.css后要么手动压缩覆盖 min 文件要么直接把页面引用改成Lc.css开发阶段图省事用后者是常见做法。3. 监测数据怎么组织四张核心表与一条查询链路3.1 监测点与监测记录表先定“测什么”再谈“怎么判”集中空调监测和普通环境监测不一样每个监测点都挂在一套具体的空调机组或风管段上点位本身带有设备类型和所属区域属性离开这些描述信息一个温度值没有任何处置价值。所以第一张表要把点位固定下来第二张表存每次采集的实测数据CREATE TABLE monitor_point ( id INT PRIMARY KEY AUTO_INCREMENT, point_code VARCHAR(32) NOT NULL COMMENT 监测点编号如 AHU-01-001, point_name VARCHAR(64) NOT NULL COMMENT 监测点名称, area_name VARCHAR(64) DEFAULT NULL COMMENT 所属区域, device_type VARCHAR(32) DEFAULT 送风口 COMMENT 送风口/回风口/新风井, status TINYINT DEFAULT 1 COMMENT 1启用 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE monitor_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, point_id INT NOT NULL COMMENT 关联monitor_point.id, temperature DECIMAL(5,2) COMMENT 回风温度℃, humidity DECIMAL(5,2) COMMENT 相对湿度%, co2 DECIMAL(7,2) COMMENT CO2浓度ppm, pm10 DECIMAL(6,3) COMMENT PM10浓度mg/m3, fresh_air_volume DECIMAL(8,2) COMMENT 新风量m3/h, collect_time DATETIME COMMENT 采集时间, alarm_status TINYINT DEFAULT 0 COMMENT 0正常 1预警 2超标, KEY idx_point_time (point_id, collect_time) );monitor_record里的alarm_status是冗余字段存的是这条记录被写入时系统判定的状态作用是在概览页查询时直接按状态排序和筛选不用每次动态计算。而collect_time与point_id的联合索引是为了支撑按点位查历史曲线的 SQL这个查询在监测系统里出现频率最高。3.2 预警配置与预警日志规则与记录分离阈值不能写死在 Java 代码里。同一套系统里不同的空调区域、不同的季节对温度和 PM10 的要求可能完全不同业务上必须支持按点位单独调整阈值所以预警配置独立成表。同时还要回答“什么时候、谁、触发了几级告警”这就是预警日志表的作用CREATE TABLE alarm_config ( id INT PRIMARY KEY AUTO_INCREMENT, point_id INT NOT NULL COMMENT 关联monitor_point.id, item_code VARCHAR(32) NOT NULL COMMENT 指标编码temperature/humidity/co2/pm10/fresh_air, warning_min DECIMAL(10,2) DEFAULT NULL COMMENT 预警下限, warning_max DECIMAL(10,2) DEFAULT NULL COMMENT 预警上限, alarm_min DECIMAL(10,2) DEFAULT NULL COMMENT 超标下限, alarm_max DECIMAL(10,2) DEFAULT NULL COMMENT 超标上限, enabled TINYINT DEFAULT 1 COMMENT 1启用 0停用 ); CREATE TABLE alarm_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, point_id INT NOT NULL, item_code VARCHAR(32) NOT NULL, actual_value DECIMAL(10,2) NOT NULL COMMENT 触发时的实测值, alarm_level TINYINT NOT NULL COMMENT 1预警 2超标, status TINYINT DEFAULT 0 COMMENT 0待处置 1已派单 2已恢复, occur_time DATETIME COMMENT 触发时间, recover_time DATETIME DEFAULT NULL COMMENT 恢复时间, remark VARCHAR(255) DEFAULT NULL );warning_和alarm_两组字段的区别是处置级别不同预警代表接近限值提醒关注超标代表必须人工介入。把配置和记录拆成两张表好处是改阈值不影响历史告警记录审计时查到的是触发那一刻的实际数值而不是按今天的阈值重新推算的结果。3.3 概览页的实时数据查询一条 SQL 串起所有点位概览页拿到的是每个点位的最新一条记录最直接的写法是先按点位分组取最大采集时间再关联回监测记录表取整行数据SELECT p.id, p.point_name, r.temperature, r.humidity, r.co2, r.pm10, r.collect_time, CASE r.alarm_status WHEN 0 THEN 正常 WHEN 1 THEN 预警 WHEN 2 THEN 超标 END AS status_text FROM monitor_point p LEFT JOIN ( SELECT point_id, MAX(collect_time) AS max_time FROM monitor_record GROUP BY point_id ) t ON t.point_id p.id LEFT JOIN monitor_record r ON r.point_id t.point_id AND r.collect_time t.max_time WHERE p.status 1 ORDER BY r.alarm_status DESC, p.id;这里的两个LEFT JOIN保证了一个重要效果即使某个点位还没有任何监测记录点位本身仍然会出现在结果集里字段值为 NULL前端渲染成“暂无数据”而不是直接消失。ORDER BY r.alarm_status DESC让超标点位永远排在列表最前面这是监测看板里对值班人员最友好的排序方式。4. 预警判定引擎从阈值比较到告警落库4.1 先定状态再定规则预警状态值设计预警模块的核心是先定义清楚状态再写判定逻辑。这套系统里用的是三段式状态状态值含义处置要求0正常无需处理1预警提醒关注值班人员观察变化趋势2超标必须人工介入生成处置记录判定顺序上先判断超标再判断预警。因为超标状态的优先级高于预警如果反过来一条超标数据会被错误地标记成预警导致值班人员漏掉真正需要处置的事件。4.2 阈值判定的核心代码实现判定逻辑集中在AlarmRuleEngine里接收指标编码、实测值和该点位的配置项返回状态值Component public class AlarmRuleEngine { private static final int NORMAL 0; private static final int WARNING 1; private static final int ALARM 2; public int evaluate(String itemCode, double value, AlarmConfig cfg) { if (cfg null || cfg.getEnabled() 0) { return NORMAL; } // 温度、湿度是区间型指标上下限都需要比对 if (temperature.equals(itemCode) || humidity.equals(itemCode)) { if (outOfRange(value, cfg.getAlarmMin(), cfg.getAlarmMax())) { return ALARM; } if (outOfRange(value, cfg.getWarningMin(), cfg.getWarningMax())) { return WARNING; } return NORMAL; } // CO2、PM10 属于单上限指标只判断最大值 if (co2.equals(itemCode) || pm10.equals(itemCode)) { if (value cfg.getAlarmMax()) { return ALARM; } if (value cfg.getWarningMax()) { return WARNING; } } return NORMAL; } private boolean outOfRange(double value, Double min, Double max) { if (min ! null value min) return true; if (max ! null value max) return true; return false; } }outOfRange里的min和max都允许为 NULL因为温度配置的是区间而 CO2 只配上限不配下限NULL 表示不限制该方向。区间型指标必须先判alarm_区间再判warning_区间否则会出现“一条超标记录只触发预警”的错误。判定返回后由 Service 层写入alarm_logAlarmLog log new AlarmLog(); log.setPointId(point.getId()); log.setItemCode(itemCode); log.setActualValue(value); log.setAlarmLevel(level); log.setStatus(0); log.setOccurTime(new Date()); alarmLogMapper.insert(log);写入动作放在超标和预警两种状态下都会执行区别在于alarm_level字段。status0表示“待处置”等处置完成后由业务人员手动更新状态。4.3 前端定时刷新与告警提示后端负责判定前端负责把判定结果及时展示在页面上。这套系统用的是典型的 jQuery Ajax 轮询在页面加载后定时请求概览接口function loadOverview() { $.getJSON(contextPath /monitor/overview, function (res) { if (res.code ! 200) return; res.data.forEach(function (item) { var card document.getElementById(card- item.pointId); if (!card) return; card.querySelector(.metric-value).textContent item.value; card.querySelector(.status-label) .setAttribute(class, status-label status- item.alarmStatus); }); }); } setInterval(loadOverview, 30000);30 秒的轮询间隔在课设项目里是个合理的默认值既不会把测试服务器的连接池打满也能保证告警出现后 30 秒内被看到。需要注意前端轮询只负责展示预警判定必须由后端定时任务完成否则用户关掉浏览器预警就停了。如果页面切换到后台标签页现代浏览器会降低setInterval的执行频率可配合document.visibilitychange在页面重新可见时立刻刷新一次弥补定时器被节流造成的时间差。5. 拿到源码后建议先改这三处5.1 给历史数据补上 ECharts 曲线原系统查询页面多数是表格展示看单条记录没问题看趋势就很吃力。给历史数据加一条折线是性价比最高的改进。引入本地echarts.min.js后在同一个 query 页里读取监测记录接口把时间字段和温度字段映射成坐标点var chart echarts.init(document.getElementById(trendChart)); chart.setOption({ xAxis: { type: time }, yAxis: { type: value, name: 温度(℃) }, series: [{ type: line, name: 回风温度, data: res.data.map(function (d) { return [d.collectTime, d.temperature]; }) }] });xAxis.type设为time后后端返回的yyyy-MM-dd HH:mm:ss字符串会被自动解析成时间轴不需要手动转时间戳。这个改造只涉及前端页面和一个查询接口不动核心表结构。5.2 预警通知从页面弹窗升级为站内信原系统的告警只停留在页面上值班人员没盯屏幕就错过了。简单做法是把“写alarm_log”和“发通知”拆开在落库后触发一个通知接口public interface AlarmNotifier { void publish(AlarmLog alarmLog); }实现类里先写站内信即往通知表插一条记录列表页右上角显示未读红色角标。短信通道如果没有真实供应商就在实现类里打一条日志等对接时替换实现类业务代码不用动。这个接口抽象的代价几乎为零但对后续扩展很关键。5.3 部署时最容易踩的时区与编码坑打包部署时用 Maven 直接跳过测试mvn clean package -DskipTests java -jar target/monitor-system.jar --server.port8080老源码最容易出的问题不在业务代码而在环境数据库连接串里没设时区直接报Server returns invalid timezone字符集不对页面中文全部乱码。连接串至少写成下面这样jdbc:mysql://localhost:3306/cdc_ac?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai5. 拿到源码后建议先改这三处续5.1 给历史数据补上 ECharts 曲线续如果接口没有现成的历史查询补一个最简单的 Servlet 或者 Controller 方法即可参数传pointId和startTime、endTime页面在日期控件变更时重新请求并刷新图表。曲线图和下面的状态标签联动比单张表格直观得多。5.2 预警通知从页面弹窗升级为站内信续站内信表可以不新建直接复用alarm_log在remark里写“已通知值班员”把已读状态挂在status字段上。这样省去一张新表改动最小也保留了告警审计的原始记录。5.3 部署时最容易踩的时区与编码坑续Linux 服务器上如果系统时区不是 Asia/ShanghaiJVM 默认时区也会被带偏告警的occur_time会比真实时间差 8 小时。启动命令里显式指定时区是最稳妥的java -Duser.timezoneAsia/Shanghai -jar target/monitor-system.jar --server.port8080排查这类老项目的顺序应该是先确认数据库字符集和时区再核对连接串参数最后才动业务代码。顺序反了会先怀疑 Java 逻辑写错查半天才发现是底层环境的问题。端口被占用时Windows 用netstat -ano | findstr :8080Linux 用lsof -i:8080找到 PID 后直接结束进程不要盲目改端口否则前端页面上写死的接口地址又要跟着动一遍。本文还有配套的精品资源点击获取