简介:这份疾病预防控制中心集中空调系统监测预警系统源码,面向公共卫生信息化、暖通空调监控及计算机相关专业的课程设计与毕业设计场景,适合需要快速搭建环境并理解监测预警流程的学习者。压缩包共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 class="container-fluid"> <div class="row-fluid"> <div class="span4"> <div class="metric-card"> <h4>回风温度</h4> <p class="metric-value">23.5℃</p> <span class="status-label status-normal">正常</span> </div> </div> <div class="span4"> <div class="metric-card"> <h4>回风湿度</h4> <p class="metric-value">52%</p> <span class="status-label status-warning">预警</span> </div> </div> <div class="span4"> <div class="metric-card"> <h4>PM10</h4> <p class="metric-value">0.18mg/m³</p> <span class="status-label status-alarm">超标</span> </div> </div> </div> </div>container-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_log:
AlarmLog 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字段。status=0表示“待处置”,等处置完成后由业务人员手动更新状态。
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.port=8080老源码最容易出的问题不在业务代码,而在环境:数据库连接串里没设时区,直接报Server returns invalid timezone;字符集不对,页面中文全部乱码。连接串至少写成下面这样:
jdbc:mysql://localhost:3306/cdc_ac?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai5. 拿到源码后建议先改这三处(续)
5.1 给历史数据补上 ECharts 曲线(续)
如果接口没有现成的历史查询,补一个最简单的 Servlet 或者 Controller 方法即可,参数传pointId和startTime、endTime,页面在日期控件变更时重新请求并刷新图表。曲线图和下面的状态标签联动,比单张表格直观得多。
5.2 预警通知从页面弹窗升级为站内信(续)
站内信表可以不新建,直接复用alarm_log,在remark里写“已通知值班员”,把已读状态挂在status字段上。这样省去一张新表,改动最小,也保留了告警审计的原始记录。
5.3 部署时最容易踩的时区与编码坑(续)
Linux 服务器上如果系统时区不是 Asia/Shanghai,JVM 默认时区也会被带偏,告警的occur_time会比真实时间差 8 小时。启动命令里显式指定时区是最稳妥的:
java -Duser.timezone=Asia/Shanghai -jar target/monitor-system.jar --server.port=8080排查这类老项目的顺序应该是:先确认数据库字符集和时区,再核对连接串参数,最后才动业务代码。顺序反了,会先怀疑 Java 逻辑写错,查半天才发现是底层环境的问题。端口被占用时,Windows 用netstat -ano | findstr :8080,Linux 用lsof -i:8080,找到 PID 后直接结束进程,不要盲目改端口,否则前端页面上写死的接口地址又要跟着动一遍。
本文还有配套的精品资源,点击获取