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