監(jiān)測預警系統(tǒng)Java Web源碼拆解:從數(shù)據(jù)庫到預警引擎)
簡介這份疾病預防控制中心集中空調(diào)系統(tǒng)監(jiān)測預警系統(tǒng)源碼面向公共衛(wèi)生信息化、暖通空調(diào)監(jiān)控及計算機相關專業(yè)的課程設計與畢業(yè)設計場景適合需要快速搭建環(huán)境并理解監(jiān)測預警流程的學習者。壓縮包共740個文件約21.82MB包含HTML頁面、JavaScript邏輯、CSS樣式、JSON配置數(shù)據(jù)以及png/jpg/gif等界面素材其中HTML負責頁面結構JavaScript與CSS實現(xiàn)交互和樣式JSON存放配置數(shù)據(jù)圖片素材提供圖標與背景支持少量php腳本可處理服務端邏輯能支撐從頁面展示到數(shù)據(jù)交互的完整演示。資源已有83人瀏覽學習可作為獨立項目參考或二次開發(fā)基礎項目結構清晰依賴簡單便于初學者按模塊拆解閱讀。下載后可直接部署運行對需要掌握系統(tǒng)集成、預警機制設計以及前端展示實現(xiàn)的同學有實際幫助尤其適合作為課程設計或畢業(yè)設計的功能藍本。1. 疾控中心集中空調(diào)監(jiān)測預警系統(tǒng)一份值得拆解的經(jīng)典Web課題疾控中心的集中空調(diào)通風系統(tǒng)里回風口的溫濕度、新風量、PM10 這些指標平時看起來各自獨立一旦連續(xù)超限往往意味著送風區(qū)域存在交叉污染風險。很多業(yè)務單位不是沒有檢測手段而是缺一套把數(shù)據(jù)采集、閾值比對、告警留痕串起來的系統(tǒng)。這份源碼解決的正是這個環(huán)節(jié)它把集中空調(diào)的衛(wèi)生學監(jiān)測流程固化成一個可運行的 Java Web 工程前端用 Bootstrap 搭監(jiān)測看板后端按監(jiān)測點、監(jiān)測記錄、預警配置、預警日志四張核心表組織業(yè)務下載后可以直接部署運行。無論是做課程設計、期末大作業(yè)還是畢設它都是一個能快速上手的骨架尤其適合想弄清楚預警系統(tǒng)到底怎么判斷該不該報警的人。2. 從 CSS 清單反推技術選型這套前端棧為什么這樣搭2.1 先讀一遍源碼里的前端資產(chǎn)清單解壓壓縮包后第一眼看到的是一排 CSS 文件排在目錄最前面。這個排列順序本身就暴露了項目的技術取向Lc.css、bootstrap.css、bootstrap.min.css、font-awesome.css、font-awesome-ie7.css、bootstrap-responsive.css。把這些文件名拼在一起基本可以確定三件事頁面框架用的是 Bootstrap 2.x 時代的響應式方案圖標用的是 Font Awesome 3 系列字體庫項目自身的業(yè)務樣式獨立放在了 Lc.css 里。文件作用使用階段bootstrap.css / bootstrap.min.css柵格、按鈕、表格、標簽等基礎組件頁面全局bootstrap-responsive.css窄屏下柵格自動折行、隱藏側(cè)欄頁面全局font-awesome.css / font-awesome.min.css狀態(tài)圖標、功能圖標按鈕與告警標識font-awesome-ie7.css兼容 IE7 的圖標字體修正僅老瀏覽器Lc.css / Lc.min.css項目自定義樣式覆蓋監(jiān)控卡片、狀態(tài)色業(yè)務頁面這套組合放在今天看不算新但它有一個課設項目非常看重的優(yōu)點不需要 Node 環(huán)境不需要 webpack靜態(tài)資源直接拖到 webapp 目錄就能跑。對于以業(yè)務邏輯和數(shù)據(jù)展示為主的監(jiān)測系統(tǒng)來說Bootstrap 自帶的柵格、表格、表單、按鈕已經(jīng)覆蓋八成界面需求剩下的視覺差異交給 Lc.css 補齊即可。2.2 響應式柵格在監(jiān)測看板里的實際用法監(jiān)測系統(tǒng)的首頁通常是“概覽看板”核心是讓值班人員一眼看到哪些點位出了問題。Bootstrap 2 的柵格是 12 列三個指標卡各占span4剛好一行放滿。下面這段是這套系統(tǒng)里最典型的布局寫法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/m3/p span classstatus-label status-alarm超標/span /div /div /div /divcontainer-fluid讓整個看板寬度隨瀏覽器自適應row-fluid配合span4實現(xiàn)百分比寬度窗口縮小時指標卡會自動折行不需要另外寫媒體查詢。這個寫法在現(xiàn)在的 Bootstrap 5 里已經(jīng)改成了row-cols體系但對這份源碼所屬的 Bootstrap 2.x 項目來說row-fluid加span4就是它的標準布局方式。2.3 Lc.css 在業(yè)務頁面上做了什么Bootstrap 只提供基礎組件狀態(tài)標簽的顏色語義還得靠業(yè)務樣式補這就是 Lc.css 存在的意義。它里面通常是一批按業(yè)務場景劃分的類覆蓋狀態(tài)標簽、表格行高亮、指標卡片三塊.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開發(fā)階段圖省事用后者是常見做法。3. 監(jiān)測數(shù)據(jù)怎么組織四張核心表與一條查詢鏈路3.1 監(jiān)測點與監(jiān)測記錄表先定“測什么”再談“怎么判”集中空調(diào)監(jiān)測和普通環(huán)境監(jiān)測不一樣每個監(jiān)測點都掛在一套具體的空調(diào)機組或風管段上點位本身帶有設備類型和所屬區(qū)域?qū)傩噪x開這些描述信息一個溫度值沒有任何處置價值。所以第一張表要把點位固定下來第二張表存每次采集的實測數(shù)據(jù)CREATE TABLE monitor_point ( id INT PRIMARY KEY AUTO_INCREMENT, point_code VARCHAR(32) NOT NULL COMMENT 監(jiān)測點編號如 AHU-01-001, point_name VARCHAR(64) NOT NULL COMMENT 監(jiān)測點名稱, area_name VARCHAR(64) DEFAULT NULL COMMENT 所屬區(qū)域, 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 關聯(lián)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是冗余字段存的是這條記錄被寫入時系統(tǒng)判定的狀態(tài)作用是在概覽頁查詢時直接按狀態(tài)排序和篩選不用每次動態(tài)計算。而collect_time與point_id的聯(lián)合索引是為了支撐按點位查歷史曲線的 SQL這個查詢在監(jiān)測系統(tǒng)里出現(xiàn)頻率最高。3.2 預警配置與預警日志規(guī)則與記錄分離閾值不能寫死在 Java 代碼里。同一套系統(tǒng)里不同的空調(diào)區(qū)域、不同的季節(jié)對溫度和 PM10 的要求可能完全不同業(yè)務上必須支持按點位單獨調(diào)整閾值所以預警配置獨立成表。同時還要回答“什么時候、誰、觸發(fā)了幾級告警”這就是預警日志表的作用CREATE TABLE alarm_config ( id INT PRIMARY KEY AUTO_INCREMENT, point_id INT NOT NULL COMMENT 關聯(lián)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 觸發(fā)時的實測值, alarm_level TINYINT NOT NULL COMMENT 1預警 2超標, status TINYINT DEFAULT 0 COMMENT 0待處置 1已派單 2已恢復, occur_time DATETIME COMMENT 觸發(fā)時間, recover_time DATETIME DEFAULT NULL COMMENT 恢復時間, remark VARCHAR(255) DEFAULT NULL );warning_和alarm_兩組字段的區(qū)別是處置級別不同預警代表接近限值提醒關注超標代表必須人工介入。把配置和記錄拆成兩張表好處是改閾值不影響歷史告警記錄審計時查到的是觸發(fā)那一刻的實際數(shù)值而不是按今天的閾值重新推算的結果。3.3 概覽頁的實時數(shù)據(jù)查詢一條 SQL 串起所有點位概覽頁拿到的是每個點位的最新一條記錄最直接的寫法是先按點位分組取最大采集時間再關聯(lián)回監(jiān)測記錄表取整行數(shù)據(jù)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保證了一個重要效果即使某個點位還沒有任何監(jiān)測記錄點位本身仍然會出現(xiàn)在結果集里字段值為 NULL前端渲染成“暫無數(shù)據(jù)”而不是直接消失。ORDER BY r.alarm_status DESC讓超標點位永遠排在列表最前面這是監(jiān)測看板里對值班人員最友好的排序方式。4. 預警判定引擎從閾值比較到告警落庫4.1 先定狀態(tài)再定規(guī)則預警狀態(tài)值設計預警模塊的核心是先定義清楚狀態(tài)再寫判定邏輯。這套系統(tǒng)里用的是三段式狀態(tài)狀態(tài)值含義處置要求0正常無需處理1預警提醒關注值班人員觀察變化趨勢2超標必須人工介入生成處置記錄判定順序上先判斷超標再判斷預警。因為超標狀態(tài)的優(yōu)先級高于預警如果反過來一條超標數(shù)據(jù)會被錯誤地標記成預警導致值班人員漏掉真正需要處置的事件。4.2 閾值判定的核心代碼實現(xiàn)判定邏輯集中在AlarmRuleEngine里接收指標編碼、實測值和該點位的配置項返回狀態(tài)值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; } // 溫度、濕度是區(qū)間型指標上下限都需要比對 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因為溫度配置的是區(qū)間而 CO2 只配上限不配下限NULL 表示不限制該方向。區(qū)間型指標必須先判alarm_區(qū)間再判warning_區(qū)間否則會出現(xiàn)“一條超標記錄只觸發(fā)預警”的錯誤。判定返回后由 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);寫入動作放在超標和預警兩種狀態(tài)下都會執(zhí)行區(qū)別在于alarm_level字段。status0表示“待處置”等處置完成后由業(yè)務人員手動更新狀態(tài)。4.3 前端定時刷新與告警提示后端負責判定前端負責把判定結果及時展示在頁面上。這套系統(tǒng)用的是典型的 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 秒的輪詢間隔在課設項目里是個合理的默認值既不會把測試服務器的連接池打滿也能保證告警出現(xiàn)后 30 秒內(nèi)被看到。需要注意前端輪詢只負責展示預警判定必須由后端定時任務完成否則用戶關掉瀏覽器預警就停了。如果頁面切換到后臺標簽頁現(xiàn)代瀏覽器會降低setInterval的執(zhí)行頻率可配合document.visibilitychange在頁面重新可見時立刻刷新一次彌補定時器被節(jié)流造成的時間差。5. 拿到源碼后建議先改這三處5.1 給歷史數(shù)據(jù)補上 ECharts 曲線原系統(tǒng)查詢頁面多數(shù)是表格展示看單條記錄沒問題看趨勢就很吃力。給歷史數(shù)據(jù)加一條折線是性價比最高的改進。引入本地echarts.min.js后在同一個 query 頁里讀取監(jiān)測記錄接口把時間字段和溫度字段映射成坐標點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字符串會被自動解析成時間軸不需要手動轉(zhuǎn)時間戳。這個改造只涉及前端頁面和一個查詢接口不動核心表結構。5.2 預警通知從頁面彈窗升級為站內(nèi)信原系統(tǒng)的告警只停留在頁面上值班人員沒盯屏幕就錯過了。簡單做法是把“寫alarm_log”和“發(fā)通知”拆開在落庫后觸發(fā)一個通知接口public interface AlarmNotifier { void publish(AlarmLog alarmLog); }實現(xiàn)類里先寫站內(nèi)信即往通知表插一條記錄列表頁右上角顯示未讀紅色角標。短信通道如果沒有真實供應商就在實現(xiàn)類里打一條日志等對接時替換實現(xiàn)類業(yè)務代碼不用動。這個接口抽象的代價幾乎為零但對后續(xù)擴展很關鍵。5.3 部署時最容易踩的時區(qū)與編碼坑打包部署時用 Maven 直接跳過測試mvn clean package -DskipTests java -jar target/monitor-system.jar --server.port8080老源碼最容易出的問題不在業(yè)務代碼而在環(huán)境數(shù)據(jù)庫連接串里沒設時區(qū)直接報Server returns invalid timezone字符集不對頁面中文全部亂碼。連接串至少寫成下面這樣jdbc:mysql://localhost:3306/cdc_ac?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai5. 拿到源碼后建議先改這三處續(xù)5.1 給歷史數(shù)據(jù)補上 ECharts 曲線續(xù)如果接口沒有現(xiàn)成的歷史查詢補一個最簡單的 Servlet 或者 Controller 方法即可參數(shù)傳pointId和startTime、endTime頁面在日期控件變更時重新請求并刷新圖表。曲線圖和下面的狀態(tài)標簽聯(lián)動比單張表格直觀得多。5.2 預警通知從頁面彈窗升級為站內(nèi)信續(xù)站內(nèi)信表可以不新建直接復用alarm_log在remark里寫“已通知值班員”把已讀狀態(tài)掛在status字段上。這樣省去一張新表改動最小也保留了告警審計的原始記錄。5.3 部署時最容易踩的時區(qū)與編碼坑續(xù)Linux 服務器上如果系統(tǒng)時區(qū)不是 Asia/ShanghaiJVM 默認時區(qū)也會被帶偏告警的occur_time會比真實時間差 8 小時。啟動命令里顯式指定時區(qū)是最穩(wěn)妥的java -Duser.timezoneAsia/Shanghai -jar target/monitor-system.jar --server.port8080排查這類老項目的順序應該是先確認數(shù)據(jù)庫字符集和時區(qū)再核對連接串參數(shù)最后才動業(yè)務代碼。順序反了會先懷疑 Java 邏輯寫錯查半天才發(fā)現(xiàn)是底層環(huán)境的問題。端口被占用時Windows 用netstat -ano | findstr :8080Linux 用lsof -i:8080找到 PID 后直接結束進程不要盲目改端口否則前端頁面上寫死的接口地址又要跟著動一遍。本文還有配套的精品資源點擊獲取