系統(tǒng):從數(shù)據(jù)庫到部署的完整實(shí)踐)
1. 這個(gè)系統(tǒng)到底能做什么智慧熱力管理的前臺(tái)與后臺(tái)雙視角拿到這套JSP智慧熱力管理服務(wù)系統(tǒng)的時(shí)候我第一反應(yīng)是這不就是典型的一個(gè)頂三個(gè)課程設(shè)計(jì)作品嗎用戶側(cè)、管理側(cè)、數(shù)據(jù)側(cè)全在一個(gè)Web應(yīng)用里用的還是JSPServletMySQL這套經(jīng)典技術(shù)棧。但說實(shí)話把它完整跑起來之后我反而覺得這類項(xiàng)目在當(dāng)下比很多花里胡哨的前后端分離項(xiàng)目更有教學(xué)價(jià)值。先說清楚這個(gè)系統(tǒng)是什么。它屬于典型的MVC三層結(jié)構(gòu)的Java Web項(xiàng)目載體是JSP頁面運(yùn)行在Tomcat容器里數(shù)據(jù)庫用MySQL存儲(chǔ)。整個(gè)系統(tǒng)的業(yè)務(wù)范圍圍繞熱力管理展開熱力公司或者物業(yè)冷暖站需要實(shí)時(shí)掌握用戶采暖信息、設(shè)備運(yùn)行狀態(tài)、費(fèi)用繳納記錄而用戶需要一個(gè)界面查看自己家的供暖情況、在線報(bào)修、繳納供暖費(fèi)。這套系統(tǒng)拆開看前臺(tái)用戶端大概有這些功能模塊用戶注冊(cè)與登錄含驗(yàn)證碼校驗(yàn)個(gè)人信息查詢與修改供暖費(fèi)用查詢與在線繳納記錄報(bào)修工單提交與進(jìn)度查看公告通知瀏覽后臺(tái)管理端的功能則更偏數(shù)據(jù)治理方向管理員登錄認(rèn)證與權(quán)限攔截用戶信息管理增刪改查、條件模糊查詢供暖設(shè)備臺(tái)賬管理設(shè)備編號(hào)、運(yùn)行狀態(tài)、檢修記錄費(fèi)用標(biāo)準(zhǔn)配置與賬單生成報(bào)修工單審核、派單、歸檔數(shù)據(jù)統(tǒng)計(jì)按小區(qū)、按月份的費(fèi)用匯總這套系統(tǒng)適合什么人去研究我覺得有三類人最容易從中獲益第一類是正在做Java Web課設(shè)或畢設(shè)的在校學(xué)生需要一套功能完整、能跑通、能答辯的項(xiàng)目第二類是剛接觸JSP/Servlet/MySQL這套老技術(shù)棧想搞清楚傳統(tǒng)MVC到底怎么工作的自學(xué)者第三類是工作后需要維護(hù)老系統(tǒng)、或者要把老系統(tǒng)做二次升級(jí)的開發(fā)者這類人對(duì)傳統(tǒng)JSP項(xiàng)目的結(jié)構(gòu)一點(diǎn)都不陌生。和Spring Boot前后端分離那一套對(duì)比起來這套JSP系統(tǒng)最大的特點(diǎn)是一體化頁面、邏輯、數(shù)據(jù)訪問都在同一個(gè)Web應(yīng)用里協(xié)作沒有跨域、沒有前后端接口聯(lián)調(diào)啟動(dòng)一個(gè)Tomcat就能看到完整效果。這種結(jié)構(gòu)在2025年的今天看來很復(fù)古但它把請(qǐng)求從瀏覽器到Servlet到DAO再到數(shù)據(jù)庫的完整路徑暴露得一清二楚。你點(diǎn)一下登錄按鈕后臺(tái)代碼走過的每一條分支都能用斷點(diǎn)抓住這對(duì)理解Web應(yīng)用的本質(zhì)非常有幫助。更關(guān)鍵的是這套系統(tǒng)帶源碼、帶數(shù)據(jù)庫腳本、帶完整的部署調(diào)試步驟。它不是那種只給你看截圖、實(shí)際上缺東少西的演示項(xiàng)目而是一套導(dǎo)入就能跑的東西。下面我會(huì)從技術(shù)選型、數(shù)據(jù)庫設(shè)計(jì)、部署調(diào)試、二次開發(fā)幾個(gè)層面把這套系統(tǒng)的里里外外拆開講透。2. 技術(shù)選型為什么是JSP老四樣從架構(gòu)層面理解一個(gè)完整Web應(yīng)用的合理邊界很多剛接觸這個(gè)項(xiàng)目的人會(huì)問現(xiàn)在不都是Spring Boot Vue嗎為什么還要用JSP這套上古組合問出這個(gè)問題說明還沒理解這類項(xiàng)目的定位。這套系統(tǒng)用的是標(biāo)準(zhǔn)Java Web技術(shù)棧清點(diǎn)一下就是四樣?xùn)|西JSPJava Server Pages負(fù)責(zé)頁面展示支持Java代碼和EL表達(dá)式混寫本質(zhì)是運(yùn)行時(shí)編譯成ServletServlet負(fù)責(zé)接收請(qǐng)求、調(diào)度業(yè)務(wù)邏輯、控制頁面跳轉(zhuǎn)是MVC里的Controller層JavaBean/DAO負(fù)責(zé)封裝數(shù)據(jù)模型和數(shù)據(jù)庫操作傳統(tǒng)寫法下用JDBC連接MySQLMySQL數(shù)據(jù)落地的關(guān)系型數(shù)據(jù)庫提供用戶表、設(shè)備表、賬單表、工單表這些核心業(yè)務(wù)表這套技術(shù)棧組合在一起正好覆蓋了一個(gè)Web應(yīng)用的全部環(huán)節(jié)瀏覽器發(fā)起HTTP請(qǐng)求Tomcat把請(qǐng)求交給對(duì)應(yīng)的ServletServlet調(diào)用DAO層方法操作數(shù)據(jù)庫得到一個(gè)結(jié)果集再用forward或redirect把頁面帶到JSP渲染后響應(yīng)給瀏覽器。為什么選這套而不是Spring Boot答案有三層第一層是學(xué)習(xí)價(jià)值。JSPServlet是Java Web的底層路段Spring Boot再方便本質(zhì)上也是在Servlet規(guī)范之上做了封裝。如果你連最基本的request.getParameter()都沒寫過一上來就搞Spring Boot的注解滿天飛你根本不知道框架幫你干了什么。就好比你會(huì)開自動(dòng)擋卻不懂手動(dòng)擋是怎么換擋的碰到頓挫感只能干瞪眼。第二層是項(xiàng)目定位。課程設(shè)計(jì)和畢業(yè)設(shè)計(jì)不等于企業(yè)級(jí)生產(chǎn)項(xiàng)目評(píng)委和導(dǎo)師要看的不是你用了多少高深技術(shù)而是你對(duì)基本技術(shù)的掌握程度和完整工程的把控能力。JSP項(xiàng)目把每個(gè)環(huán)節(jié)都暴露在明面上答辯的時(shí)候隨便挑一個(gè)點(diǎn)都能深入講透這是Spring Boot黑盒封裝做不到的。第三層是運(yùn)行環(huán)境要求低。一臺(tái)普通的筆記本電腦裝好JDK 8、Tomcat 8/9、MySQL 5.7就能把這個(gè)系統(tǒng)完整跑起來。不需要Maven拉依賴、不需要Redis、不需要消息隊(duì)列對(duì)學(xué)校機(jī)房和老電腦特別友好。不過這里我要多說一句如果你未來要入行Java開發(fā)這套JSP項(xiàng)目只是地基而不是終點(diǎn)。但你在這個(gè)項(xiàng)目里積累的Servlet生命周期理解、Session管理經(jīng)驗(yàn)、JDBC事務(wù)處理能力、SQL編寫功底放到Spring Boot項(xiàng)目里照樣復(fù)用。反過來一個(gè)只會(huì)在Spring Boot里寫Autowired的人碰到傳統(tǒng)JSP項(xiàng)目反而會(huì)手足無措。3. 數(shù)據(jù)庫設(shè)計(jì)拆解五張核心表如何承載所有業(yè)務(wù)邏輯運(yùn)行這套系統(tǒng)前第一步是要把數(shù)據(jù)庫還原出來。拿到手里的SQL腳本一般包含建庫語句、建表語句和初始數(shù)據(jù)用Navicat或者命令行執(zhí)行即可。不要直接雙擊導(dǎo)入最好先打開腳本看一眼因?yàn)楹芏嗬享?xiàng)目的SQL文件里字符集設(shè)置五花八門直接導(dǎo)入容易出現(xiàn)中文亂碼。以我對(duì)同類熱力管理系統(tǒng)的了解這套項(xiàng)目的數(shù)據(jù)庫大概包含五張以上核心表每張表都有明確的業(yè)務(wù)指向3.1 用戶表t_user用戶表是最基礎(chǔ)也最關(guān)鍵的表承載三端數(shù)據(jù)個(gè)人基本信息、賬號(hào)信息、狀態(tài)信息。典型字段包括用戶ID自增主鍵、用戶名、密碼多數(shù)用MD5加密存儲(chǔ)、真實(shí)姓名、身份證號(hào)、手機(jī)號(hào)、小區(qū)ID、樓棟號(hào)、單元號(hào)、門牌號(hào)、供暖面積、用戶狀態(tài)正常/凍結(jié)、創(chuàng)建時(shí)間。這里有個(gè)值得注意的設(shè)計(jì)點(diǎn)供暖面積直接留在用戶表里而不是單獨(dú)放在房產(chǎn)表。因?yàn)楣┡M(fèi)用是按面積計(jì)算的選房時(shí)錄入一次面積后續(xù)生成賬單直接關(guān)聯(lián)這個(gè)字段邏輯簡單對(duì)課設(shè)項(xiàng)目來說夠用。真實(shí)商用系統(tǒng)當(dāng)然會(huì)拆出房產(chǎn)表、業(yè)主表但作為一套學(xué)習(xí)作品這種扁平化設(shè)計(jì)反而好懂。3.2 供暖設(shè)備表t_device設(shè)備表管理所有供熱設(shè)施臺(tái)賬。字段大概包括設(shè)備ID、設(shè)備編號(hào)唯一編碼如RL-2024-001、設(shè)備名稱如板式換熱器、循環(huán)水泵、所屬小區(qū)、安裝位置、設(shè)備狀態(tài)運(yùn)行/停機(jī)/檢修/報(bào)廢、供應(yīng)商、安裝日期、最近檢修日期、備注。管理端在這個(gè)表上做的事情基本就是增刪改查加條件搜索。這里我提示一個(gè)寫查詢的小技巧做設(shè)備搜索時(shí)狀態(tài)篩選用下拉框關(guān)鍵字搜索用模糊匹配兩個(gè)條件要?jiǎng)討B(tài)拼接SQL。很多初學(xué)JDBC的人容易踩一個(gè)坑——無條件查詢時(shí)SQL多出一個(gè)多余的WHERE解決方法是先拼SELECT * FROM t_device WHERE 11后續(xù)條件統(tǒng)一用AND銜接。3.3 計(jì)費(fèi)規(guī)則表t_fee_rule這張表決定供暖費(fèi)怎么算。核心字段規(guī)則ID、小區(qū)ID或區(qū)域、計(jì)費(fèi)方式按面積/按戶、單價(jià)元/平米/月或元/戶/月、生效開始日期、生效結(jié)束日期、備注說明。費(fèi)用生成邏輯并不復(fù)雜賬單金額 用戶表里的供暖面積 × 當(dāng)前生效規(guī)則里的單價(jià) × 供暖月份數(shù)。但實(shí)際寫代碼時(shí)要注意規(guī)則生效區(qū)間比如2024年11月到2025年3月執(zhí)行的是某一個(gè)單價(jià)如果用戶在12月變更了面積賬單一拆二就能講清楚合并算就不嚴(yán)謹(jǐn)。在這個(gè)項(xiàng)目里我建議規(guī)則配置做成前端可改、后端生成賬單時(shí)動(dòng)態(tài)讀取近期生效規(guī)則。3.4 賬單表t_bill賬單表是用戶和管理員都能看到的核心數(shù)據(jù)。字段包括賬單ID、用戶ID、賬期如2024-11、應(yīng)收金額、滯納金、應(yīng)繳總額、繳費(fèi)狀態(tài)未繳/已繳/已作廢、繳費(fèi)方式線上/線下/銀行代扣、繳費(fèi)時(shí)間、操作人ID。這套表設(shè)計(jì)里建議加一個(gè)唯一約束同一用戶同一賬期只能有一條賬單記錄。加這個(gè)約束的原因很實(shí)際——避免重復(fù)生成賬單。做批量生成賬單功能時(shí)先查一下目標(biāo)賬期是否已有數(shù)據(jù)有就提示該賬期賬單已生成不要重復(fù)生成避免直接往表里塞數(shù)據(jù)導(dǎo)致主鍵沖突。3.5 報(bào)修工單表t_repair工單表用于用戶報(bào)修和維修人員處理。字段包括工單ID、用戶ID、報(bào)修類型管道漏水/溫度不達(dá)標(biāo)/閥門故障等、報(bào)修描述、報(bào)修時(shí)間、報(bào)修圖片路徑、處理狀態(tài)待派單/處理中/已完成/已關(guān)閉、處理人ID、完成時(shí)間、用戶反饋滿意度。從業(yè)務(wù)閉環(huán)角度看工單系統(tǒng)是整個(gè)智慧熱力概念里最能體現(xiàn)交互深度的模塊。用戶在前臺(tái)提交報(bào)修管理員在后臺(tái)看到工單列表、分配維修人員、填寫處理結(jié)果用戶再回來查看進(jìn)度。三個(gè)角色圍繞一張表完成一次完整閉環(huán)這在答辯時(shí)是非常好的業(yè)務(wù)講解點(diǎn)。3.6 額外補(bǔ)充公告表和操作日志表如果源碼里帶公告管理還會(huì)有一張t_notice表簡單字段就是標(biāo)題、內(nèi)容、發(fā)布時(shí)間、發(fā)布人、置頂狀態(tài)。操作日志表可能叫t_log用來記錄管理員關(guān)鍵操作比如刪除用戶、修改價(jià)格、生成賬單。日志表容易被忽略但它能體現(xiàn)管理留痕的安全意識(shí)建議保留并講解給評(píng)委聽。數(shù)據(jù)庫還原的時(shí)候還需要注意一件事MySQL的sql_mode。新版MySQL 5.7以上默認(rèn)啟用ONLY_FULL_GROUP_BY導(dǎo)致老SQL里的GROUP BY語句會(huì)報(bào)錯(cuò)。如果你導(dǎo)入后運(yùn)行統(tǒng)計(jì)功能時(shí)看到關(guān)于only_full_group_by的報(bào)錯(cuò)處理方式是在連接字符串后面加上sessionVariablessql_modeSTRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION或者直接改MySQL的my.ini配置文件。這個(gè)問題我在很多老項(xiàng)目上都碰到過屬于典型的環(huán)境兼容性坑。4. 從導(dǎo)入工程到跑起來完整的調(diào)試部署實(shí)戰(zhàn)記錄拿到源碼后從零起步到頁面能正常打開需要經(jīng)過一整套環(huán)境準(zhǔn)備、項(xiàng)目導(dǎo)入、參數(shù)修改、啟動(dòng)驗(yàn)證的流程。很多人卡在這一步80%不是代碼問題而是環(huán)境變量、編譯版本、路徑轉(zhuǎn)義這些“隱形刺客”。4.1 環(huán)境版本搭配先說版本這是最容易出問題的部分。根據(jù)我的實(shí)操經(jīng)驗(yàn)這套JSP系統(tǒng)的兼容性組合大概是這樣的組件推薦版本原因JDK1.88u202以后版本均可JSP/Servlet老項(xiàng)目用JDK8最穩(wěn)新JDK對(duì)Tomcat8兼容性較差Tomcat8.5.x 或 9.0.x支持Servlet 3.1/4.0規(guī)范能跑JSP和ServletMySQL5.7.x5.7對(duì)老SQL腳本兼容最高8.0需注意字符集和加密插件變化IDEEclipse IDE for Enterprise Java or IDEA UltimateIDEA社區(qū)版不支持Java EE內(nèi)置極度推薦用企業(yè)版或Eclipse數(shù)據(jù)庫工具Navicat 15/DBeaver用于執(zhí)行SQL腳本、查看表數(shù)據(jù)有條件的建議直接下載JDK 8和Tomcat 8.5.9x別用太新的組件的核心原因是老項(xiàng)目沒有處理好API兼容和依賴JDK11以上運(yùn)行老Tomcat會(huì)遇到模塊化訪問報(bào)錯(cuò)。具體報(bào)錯(cuò)是java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException這是JDK11移除了Java EE模塊導(dǎo)致的解決辦法是手動(dòng)補(bǔ)jar包折騰起來遠(yuǎn)不如直接切JDK8省心。4.2 項(xiàng)目導(dǎo)入與配置詳解拿到工程后通常是整個(gè)文件夾包含src、WebRoot/WebContent、數(shù)據(jù)庫腳本、文檔用IDEA導(dǎo)入前先檢查build path里的jar包是否齊全。常見缺失的有mysql-connector-java-x.x.jarJDBC驅(qū)動(dòng)jstl.jar 和 standard.jarJSP標(biāo)準(zhǔn)標(biāo)簽庫servlet-api.jarTomcat自帶的不要打進(jìn)工程如果發(fā)現(xiàn)少驅(qū)動(dòng)去Maven中央倉庫搜mysql-connector-java下載jar放進(jìn)項(xiàng)目的WEB-INF/lib目錄下再Add to Library即可。嚴(yán)格來說servlet-api這類容器提供的jar不應(yīng)放lib里否則部署時(shí)可能出現(xiàn)類沖突老項(xiàng)目里倒是不太常見。接下來配置數(shù)據(jù)源連接信息。找到數(shù)據(jù)庫連接工具類通常是JDBCUtil.java或DBHelper.java或者db.properties配置文件修改三處private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/heating_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456;關(guān)鍵是URL里必須帶characterEncodingutf8否則頁面里的中文會(huì)在存取后亂碼。serverTimezone參數(shù)針對(duì)MySQL 8.0的時(shí)間區(qū)報(bào)錯(cuò)問題5.7環(huán)境不加也行。4.3 部署到Tomcat并啟動(dòng)在IDEA里配置Tomcat的方法我快速說一遍Run菜單 - Edit Configurations - 左上角 - Tomcat Server - LocalApplication server處選本機(jī)Tomcat目錄Deployment標(biāo)簽頁點(diǎn) 選Artifact - 選擇war exploded包Application context填/heating或者留空根路徑Server標(biāo)簽頁勾選After launch打開瀏覽器默認(rèn)URL填http://localhost:8080/然后點(diǎn)啟動(dòng)。第一次啟動(dòng)大概率會(huì)碰到端口占用問題。因?yàn)?080是Tomcat默認(rèn)端口被其它程序占用的幾率不低解決方式是在conf/server.xml里改Connector端口號(hào)比如改為8081改完重啟即可。啟動(dòng)成功后控制臺(tái)會(huì)看到類似信息信息: Server startup in [xxx] milliseconds。這時(shí)在瀏覽器地址欄輸入http://localhost:8080/或你配置的應(yīng)用路徑就能看到系統(tǒng)首頁。如果頁面直接彈出500錯(cuò)誤不要慌這是正常的按下面的鏈路排查。4.4 首次運(yùn)行500錯(cuò)誤排查鏈路我把實(shí)戰(zhàn)中遇到最多的啟動(dòng)問題整理成一張排查表現(xiàn)象原因解決辦法500 ClassNotFoundException: com.mysql.jdbc.DriverJDBC驅(qū)動(dòng)jar沒放到lib里下載mysql-connector-java并加入項(xiàng)目依賴500 Communications link failure數(shù)據(jù)庫沒啟動(dòng)或者URL端口/賬號(hào)錯(cuò)確認(rèn)MySQL服務(wù)已啟動(dòng)確認(rèn)連接串參數(shù)無誤數(shù)據(jù)庫導(dǎo)入中文亂碼SQL腳本編碼和執(zhí)行客戶端編碼不一致用UTF-8編碼重新導(dǎo)入導(dǎo)入前設(shè)置SET NAMES utf8mb4404找不到頁面app context路徑不對(duì)或servlet映射寫錯(cuò)檢查web.xml里的servlet-mapping檢查訪問URL是不是context映射路徑JSP只顯示代碼不渲染Tomcat沒把工程當(dāng)Web應(yīng)用加載確認(rèn)Artifact是war exploded并已部署其中404的錯(cuò)誤需要多說一句很多JSP項(xiàng)目里登錄表單的action寫的是UserServlet?actionlogin或user/login在web.xml里對(duì)應(yīng)配置了servlet和servlet-mapping。如果你改了項(xiàng)目部署名那訪問路徑也要跟著改。比如Application context設(shè)置成/heating那表單跳轉(zhuǎn)就應(yīng)該是/heating/UserServlet。老項(xiàng)目里寫死了絕對(duì)路徑的情況特別多這是404高發(fā)區(qū)。4.5 數(shù)據(jù)庫初始化腳本執(zhí)行順序拿到SQL腳本后按順序執(zhí)行三步建庫通常第一行是CREATE DATABASE IF NOT EXISTS heating_db DEFAULT CHARSET utf8;建表順序執(zhí)行多張CREATE TABLE語句導(dǎo)初始數(shù)據(jù)插入管理員賬號(hào)、測(cè)試用戶、若干設(shè)備、公告等初始數(shù)據(jù)里一般會(huì)有默認(rèn)管理員賬號(hào)比如admin/admin123。這組數(shù)據(jù)必須在數(shù)據(jù)庫里提前確認(rèn)存在否則后臺(tái)登錄永遠(yuǎn)進(jìn)不去??梢杂肗avicat雙擊打開表直接查看記錄。順帶提一個(gè)問題如果你在Navicat里執(zhí)行腳本時(shí)提示“Unknown Collation: utf8mb4_0900_ai_ci”說明腳本來源環(huán)境的MySQL版本是8.0而當(dāng)前MySQL是5.7。處理方法是把腳本里所有utf8mb4_0900_ai_ci替換成utf8mb4_general_ci不需要?jiǎng)悠渌鼉?nèi)容。5. 一次完整的功能走通從用戶注冊(cè)到后臺(tái)派單的業(yè)務(wù)鏈路代碼能跑起來之后最關(guān)鍵的事情是把業(yè)務(wù)流程完整走一遍。這一步不只是驗(yàn)證系統(tǒng)有沒有bug更重要的是幫你在答辯或者寫報(bào)告的時(shí)候能畫出一張完整的業(yè)務(wù)流轉(zhuǎn)圖。我建議按照下面這條鏈路去操作5.1 鏈路一用戶側(cè)自助辦理打開首頁點(diǎn)擊“注冊(cè)”填寫用戶名、密碼、姓名、小區(qū)、樓棟、面積信息注冊(cè)成功后自動(dòng)跳轉(zhuǎn)登錄頁用剛注冊(cè)的賬號(hào)登錄進(jìn)入用戶中心查看個(gè)人信息、關(guān)聯(lián)賬單、提交一條報(bào)修工單這條鏈路檢查的核心點(diǎn)包括注冊(cè)時(shí)表單非空校驗(yàn)是否生效、密碼MD5是否入庫、登錄后Session是否寫入、查詢賬單列表是否只顯示當(dāng)前用戶數(shù)據(jù)防止越權(quán)。這里濃縮一句經(jīng)驗(yàn)排查用戶數(shù)據(jù)越權(quán)是JSP項(xiàng)目評(píng)審時(shí)最容易被問到的點(diǎn)。如果賬單查詢SQL是SELECT * FROM t_bill而沒有按用戶ID過濾那么任何一個(gè)登錄用戶都能看所有人的賬單這就是嚴(yán)重的越權(quán)漏洞。拿到項(xiàng)目后第一件事就該打開對(duì)應(yīng)Servlet看DAO層SQL有沒有帶用戶ID條件。5.2 鏈路二后臺(tái)管理操作用admin賬號(hào)登錄后臺(tái)查看用戶列表測(cè)試模糊搜索按姓名或手機(jī)號(hào)新增一條設(shè)備記錄狀態(tài)選“運(yùn)行”進(jìn)入計(jì)費(fèi)規(guī)則頁配置一個(gè)單價(jià)點(diǎn)擊“生成賬單”在工單管理里查看用戶提交的報(bào)修單點(diǎn)擊“派單”填寫處理人在統(tǒng)計(jì)頁面查看費(fèi)用匯總這條鏈路檢查的核心點(diǎn)包括后臺(tái)所有Servlet是否做了登錄過濾用Filter實(shí)現(xiàn)、生成賬單金額是否等于面積×單價(jià)×月份數(shù)、工單狀態(tài)流轉(zhuǎn)是否符合預(yù)設(shè)邏輯。有一個(gè)非常普遍的問題很多JSP項(xiàng)目只做了界面上的登錄跳轉(zhuǎn)但Filter過濾器沒有寫好導(dǎo)致用戶在瀏覽器直接輸入后臺(tái)Servlet地址就能繞開登錄。驗(yàn)證方法是退出登錄后直接在地址欄輸入后臺(tái)列表頁的完整URL看能不能訪問。如果直接能進(jìn)說明過濾器沒生效或沒配置。修復(fù)方式在第五章詳述。5.3 鏈路三數(shù)據(jù)統(tǒng)計(jì)與報(bào)表統(tǒng)計(jì)功能是這類智慧管理系統(tǒng)的加分項(xiàng)。檢查時(shí)看一下實(shí)現(xiàn)方式常見有兩種SQL聚合查詢GROUP BY SUM或者Java內(nèi)存統(tǒng)計(jì)。推薦前者因?yàn)樗延?jì)算壓力放在數(shù)據(jù)庫層實(shí)現(xiàn)也更簡潔。例如查詢各小區(qū)當(dāng)月采暖費(fèi)總收入SQL大致長這樣SELECT u.area_name AS 小區(qū), SUM(b.total_amount) AS 總收入, COUNT(b.bill_id) AS 繳費(fèi)筆數(shù) FROM t_bill b LEFT JOIN t_user u ON b.user_id u.id WHERE b.pay_status 已繳費(fèi) AND b.period 2024-11 GROUP BY u.area_name ORDER BY 總收入 DESC;追加一句如果SQL執(zhí)行報(bào)錯(cuò)大概率就是MySQL 5.7的only_full_group_by模式問題按第三章提到的sql_mode配置處理就行。執(zhí)行成功后在JSP頁面里用一個(gè)JSTL的forEach標(biāo)簽循環(huán)輸出即可。6. JSP項(xiàng)目必須掌握的三個(gè)底層機(jī)制過濾器、會(huì)話管理和JDBC優(yōu)化很多跑通這個(gè)項(xiàng)目的同學(xué)都會(huì)卡在同一個(gè)問題代碼能跑但不知道自己寫的代碼為什么要這樣寫。其實(shí)這套系統(tǒng)里隱藏著三個(gè)Java Web核心機(jī)制搞懂了才算真正做過這個(gè)項(xiàng)目。6.1 過濾器Filter——登錄攔截的那道門管理員后臺(tái)必須做權(quán)限控制。常用的實(shí)現(xiàn)是寫一個(gè)AuthFilter類在web.xml里配置或者用WebFilter注解。核心邏輯說白了三句話從Session里取當(dāng)前登錄用戶如果用戶不存在重定向到登錄頁如果請(qǐng)求路徑是登錄相關(guān)的Servlet或者靜態(tài)資源放行實(shí)際開發(fā)時(shí)容易踩的坑是靜態(tài)資源被誤攔截。JSP頁面里引用的CSS、JS、圖片如果被過濾器攔了頁面樣式全丟。解決方法是排除路徑匹配比如url-pattern用/back/*并在過濾器里加白名單判斷isExcludedUrl方法。很多老項(xiàng)目直接把所有/*都攔了然后頁面丑成一坨原因就在這里。6.2 HttpSession會(huì)話管理——認(rèn)識(shí)用戶上下文Session是JSP里最常用的對(duì)象之一用戶登錄成功后設(shè)置session.setAttribute(user, user)后續(xù)頁面用session.getAttribute(user)讀取。它為什么有效因?yàn)榉?wù)端會(huì)生成一個(gè)唯一JSESSIONID通過Cookie保存在瀏覽器端每次請(qǐng)求自動(dòng)帶著這個(gè)ID服務(wù)端根據(jù)ID找到對(duì)應(yīng)的Session對(duì)象。需要提醒一個(gè)安全習(xí)慣用戶退出時(shí)不要只跳轉(zhuǎn)回登錄頁要執(zhí)行session.invalidate()銷毀會(huì)話否則Session還掛在服務(wù)端別人在同一瀏覽器繼續(xù)訪問后臺(tái)還會(huì)以你的身份登錄。這個(gè)細(xì)節(jié)在答辯時(shí)提到導(dǎo)師會(huì)認(rèn)為你具備安全意識(shí)。6.3 JDBC連接管理——?jiǎng)e用裸DriverManager項(xiàng)目里訪問數(shù)據(jù)庫如果直接在每個(gè)DAO里寫Class.forName和DriverManager.getConnection我見過大量老項(xiàng)目就是這種寫法那么每次請(qǐng)求都會(huì)創(chuàng)建一次數(shù)據(jù)庫連接。在低并發(fā)下問題不明顯但如果你自己模擬并發(fā)請(qǐng)求會(huì)發(fā)現(xiàn)連接遲遲釋放不掉最終導(dǎo)致數(shù)據(jù)庫端報(bào)Too many connections。合理的做法是寫一個(gè)ConnectionUtil工具類把連接、關(guān)閉、結(jié)果集釋放封裝成一個(gè)靜態(tài)方法每個(gè)DAO統(tǒng)一調(diào)用。更進(jìn)一步是引入數(shù)據(jù)庫連接池比如Druid或C3P0只需要在項(xiàng)目里加入對(duì)應(yīng)jar包、配置一個(gè)properties文件然后用DataSource獲取連接即可。這個(gè)改動(dòng)工作量不大但對(duì)接下來的答辯和代碼升級(jí)都有加分效果。給你一個(gè)最簡Druid配置參考Properties prop new Properties(); prop.load(new FileInputStream(src/druid.properties)); DataSource dataSource DruidDataSourceFactory.createDataSource(prop); Connection conn dataSource.getConnection();druid.properties內(nèi)容大概長這樣driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/heating_db?useUnicodetruecharacterEncodingutf8 usernameroot password123456 initialSize5 maxActive20 maxWait10000換成連接池之后DAO里的其余代碼可以完全不動(dòng)改動(dòng)成本非常低。而且這樣做帶來的好處是數(shù)量級(jí)的連接復(fù)用、獲取時(shí)間縮短、并發(fā)能力提高。答辯時(shí)問到系統(tǒng)并發(fā)能力怎么優(yōu)化你就可以拿這個(gè)當(dāng)論據(jù)。7. 源碼細(xì)節(jié)上的常見坑與對(duì)應(yīng)修復(fù)方案把系統(tǒng)完整跑通只是開始。如果你準(zhǔn)備在這個(gè)項(xiàng)目基礎(chǔ)上寫論文、改功能或者給客戶做一個(gè)演示版下面幾類源碼層面的坑你遲早會(huì)遇到。我把比較有代表性的挑出來逐個(gè)說。7.1 頁面中文亂碼的三層排查法亂碼問題在JSP老項(xiàng)目里是最高頻問題。出現(xiàn)亂碼你需要從上到下排查三層第一層JSP頁面頭部是否寫了pageEncoding和contentType% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %第二層Servlet和過濾器里取參數(shù)前是否設(shè)置了請(qǐng)求編碼request.setCharacterEncoding(UTF-8);重點(diǎn)說這個(gè)方法的生效條件必須在調(diào)用request.getParameter之前的代碼執(zhí)行而且只對(duì)POST請(qǐng)求體有效。如果是GET請(qǐng)求帶中文參數(shù)需要在Tomcat的conf/server.xml里給Connector加URLEncoding屬性Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /第三層數(shù)據(jù)庫連接URL是否帶characterEncodingutf8以及表結(jié)構(gòu)本身字符集是否為utf8。三層逐項(xiàng)核對(duì)后九成亂碼能解決。7.2 數(shù)據(jù)庫連接密碼明文問題源碼里數(shù)據(jù)庫賬號(hào)密碼都是明文寫在Java類里的。這個(gè)做法在真實(shí)項(xiàng)目里很危險(xiǎn)。做演示或交作業(yè)沒關(guān)系但如果你要掛到公網(wǎng)或者部署到服務(wù)器上強(qiáng)烈建議至少把密碼寫在外部配置文件中比如config.properties然后用代碼讀取避免源碼泄露時(shí)數(shù)據(jù)庫也跟著暴露。7.3 頁面間傳參request.setAttribute與session.setAttribute的區(qū)別很多初學(xué)JSP的程序員在兩個(gè)方法之間來回切換換來換去就亂了。我一句話講明白request.setAttribute(name, obj) 是一次請(qǐng)求內(nèi)有效設(shè)置后調(diào)request.getRequestDispatcher(xxx.jsp).forward(request, response)轉(zhuǎn)發(fā)目標(biāo)JSP頁面能讀到session.setAttribute(name, obj) 是同一瀏覽器的整個(gè)會(huì)話內(nèi)有效即使跳到別的Servlet再跳回來只要Session沒失效數(shù)據(jù)都在實(shí)際項(xiàng)目里查詢列表頁進(jìn)入修改頁時(shí)需要攜帶記錄ID用request傳參就夠。而登錄用戶的完整信息適合保存在Session里因?yàn)楹芏囗撁娑家x取當(dāng)前登錄人。這個(gè)選擇沒有絕對(duì)標(biāo)準(zhǔn)但遵循一個(gè)原則只在這一個(gè)頁面用的數(shù)據(jù)用request跨越多個(gè)請(qǐng)求都要用的數(shù)據(jù)才能用session。Session里塞太多數(shù)據(jù)會(huì)占用服務(wù)器內(nèi)存并發(fā)大時(shí)很容易爆。7.4 SQL注入與防注入老項(xiàng)目最怕的就是SQL注入。如果源碼里的登錄查詢是直接拼接字符串String sql SELECT * FROM t_user WHERE username name AND password pwd ;那么只要在用戶名框輸入admin --就能繞過密碼登錄。這個(gè)漏洞在課設(shè)答辯時(shí)被評(píng)委逮住會(huì)直接影響評(píng)分。修復(fù)方案是改為PreparedStatementString sql SELECT * FROM t_user WHERE username? AND password?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, name); ps.setString(2, pwd); ResultSet rs ps.executeQuery();改完之后特殊字符只作為參數(shù)值傳遞不再參與SQL語句拼接注入路徑就斷了。我見過很多老項(xiàng)目在演示時(shí)一切正常但答辯老師隨手一個(gè)單引號(hào)就能把系統(tǒng)打掛。趁早檢查一遍自己的工程。7.5 密碼MD5加密存儲(chǔ)如果用戶密碼在數(shù)據(jù)庫里是全明文需要改成加密存儲(chǔ)。用MD5最簡單的做法是在注冊(cè)Servlet里調(diào)用String encoded DigestUtils.md5Hex(password);登錄時(shí)也把輸入密碼做一次同樣處理然后再查詢比對(duì)。MD5本身不夠安全但這套項(xiàng)目畢竟不是金融系統(tǒng)做基本的不可逆轉(zhuǎn)換已經(jīng)能說明安全意識(shí)。時(shí)間充裕的話可以把鹽值salt拼接進(jìn)去比如salt是用戶名md5(username password)防彩虹表效果更好。8. 如何在這個(gè)項(xiàng)目上做二次開發(fā)三個(gè)有亮點(diǎn)的擴(kuò)展方向基礎(chǔ)跑通之后給項(xiàng)目加點(diǎn)自己的東西會(huì)讓它在答辯或者展示時(shí)完全不一樣。我按難度從低到高給三個(gè)擴(kuò)展方向每個(gè)都是這類系統(tǒng)常見的真實(shí)痛點(diǎn)。8.1 擴(kuò)展一數(shù)據(jù)可視化統(tǒng)計(jì)頁面比起甩一張干巴巴的數(shù)字表把費(fèi)用趨勢(shì)、工單處理時(shí)長、設(shè)備狀態(tài)分布做成圖表觀感完全不同。最簡單的方式是集成ECharts只要在JSP頁面里引入ECharts的CDN然后在前端用AJAX調(diào)用后臺(tái)一個(gè)返回JSON的Servlet接口即可。后臺(tái)可以新增一個(gè)StatsServlet接收參數(shù)比如last6Months返回當(dāng)月費(fèi)用的json數(shù)組response.setContentType(application/json;charsetUTF-8); response.getWriter().write(jsonStr);注意如果用原生Servlet輸出JSON建議引入fastjson或Gson庫不用自己手動(dòng)拼字符串因?yàn)槠醋址畷r(shí)一個(gè)引號(hào)沒轉(zhuǎn)義就可能讓前端報(bào)錯(cuò)。8.2 擴(kuò)展二管理員操作日志審計(jì)前文提過t_log表現(xiàn)在把它的價(jià)值補(bǔ)上增加一個(gè)LogFilter或者在增刪改的方法里統(tǒng)一記錄操作者、操作時(shí)間、操作內(nèi)容、操作IP。這個(gè)功能實(shí)現(xiàn)起來門檻很低卻是政企類系統(tǒng)最基礎(chǔ)的安全要求。具體做法是寫一個(gè)LogUtil工具類封裝一個(gè)record(操作人、模塊、動(dòng)作、描述)方法然后在管理員刪除用戶、生成賬單、修改單價(jià)這些操作后面各調(diào)一次。連同記錄登錄成功/失敗次數(shù)演示時(shí)給評(píng)委展示非法登錄嘗試留痕說服力很強(qiáng)。8.3 擴(kuò)展三微信小程序或移動(dòng)端H5對(duì)接如果你想把項(xiàng)目升級(jí)得更現(xiàn)代可以考慮在不動(dòng)JSP后臺(tái)的前提下給系統(tǒng)增加一個(gè)移動(dòng)端入口。做法是把后臺(tái)的Servlet接口按照J(rèn)SON協(xié)議重寫一套或者新增一套a/api/前綴的Servlet返回?cái)?shù)據(jù)格式統(tǒng)一成{code:0, data:xxx, msg:ok}然后前端小程序或H5頁面只負(fù)責(zé)渲染。這個(gè)擴(kuò)展的工程量主要在前端后端改動(dòng)其實(shí)很小。之前用request和response做頁面跳轉(zhuǎn)的地方只需要改成write JSON即可。如果你熟悉微信小程序值班表、費(fèi)用查詢、報(bào)修跟蹤這些頁面可以做得很精美整套系統(tǒng)立刻有了移動(dòng)互聯(lián)網(wǎng)的味道。9. 我的幾點(diǎn)實(shí)操體會(huì)項(xiàng)目從解壓到完全調(diào)通我自己折騰了兩天中間踩過的坑寫出來給大家參考最費(fèi)時(shí)間的不是環(huán)境搭建而是搞清楚初始數(shù)據(jù)都存了什么。管理員賬號(hào)藏在SQL腳本第幾行、哪個(gè)菜單背后調(diào)用了哪張表這些內(nèi)容光看代碼不如實(shí)際跑一遍記得牢。建議拿到項(xiàng)目后把幾張核心表導(dǎo)出成Excel隨時(shí)對(duì)照。第二JSP項(xiàng)目調(diào)試時(shí)不要只靠System.out.println要學(xué)會(huì)用DEBUG模式打斷點(diǎn)。IDEA里把Tomcat配成Debug模式后在Servlet的doPost方法里打上斷點(diǎn)瀏覽器點(diǎn)登錄光標(biāo)就會(huì)停在斷點(diǎn)處request里的參數(shù)、連接狀態(tài)、SQL執(zhí)行結(jié)果一步到位全部可見。這個(gè)技能對(duì)排查復(fù)雜bug價(jià)值極大。第三修改任何JSP或Java文件后如果不是熱部署模式一定要重啟Tomcat再刷新頁面。曾經(jīng)因?yàn)闆]重啟反復(fù)看到未更新的老頁面平白多花了半小時(shí)排查一個(gè)根本不存在的問題。第四備份數(shù)據(jù)庫腳本。每次改完數(shù)據(jù)結(jié)構(gòu)導(dǎo)出一份新的SQL文件存到bak目錄里。這是我吃了多次虧之后的習(xí)慣性動(dòng)作沒有備份的情況下一個(gè)DELETE不加WHERE的失誤就能讓整個(gè)演示泡湯。