設計:學生疫情信息管理系統(tǒng)設計與實現(xiàn))
關于畢業(yè)設計很多同學一上來就問選什么題目但真正的問題其實是選一個什么技術棧的題目。JSP學生疫情信息管理系統(tǒng)這算是我見過最典型的畢設題目之一了。它有明確的管理系統(tǒng)特征有用戶角色劃分有數(shù)據(jù)流轉(zhuǎn)能展示增刪改查基本功又能掛上統(tǒng)計圖表這類加分項。更重要的是它完全可以用JSPServletMySQL這套老三樣實現(xiàn)不需要你臨時去啃Spring Boot和Vue門檻適中工作量可控非常適合用來完成本科畢設。這篇文章不打算給你推倒重來的代碼也不會列一長串第一步干嘛、第二步干嘛那種假大空的清單。我想以一個帶過多年畢設、也親自手寫過這類系統(tǒng)的視角把這個題目從選題邏輯、數(shù)據(jù)庫設計、代碼分層到答辯準備的全過程拆開講一遍。你會知道哪些地方值得認真做哪些地方是大多數(shù)人的通病以及那些只有在實際動手時才會踩到的坑。1. 為什么JSP學生疫情信息管理這個題目經(jīng)久不衰選題邏輯與技術棧分析1.1 核心需求拆解這個系統(tǒng)到底要管什么先別急著想我要不要用別的框架。畢業(yè)設計的第一件事是把題目的業(yè)務需求拆清楚。學生疫情信息管理系統(tǒng)看起來是個管理系統(tǒng)但它的數(shù)據(jù)模型和普通的學生管理系統(tǒng)、圖書管理系統(tǒng)有明顯區(qū)別。普通的CRUD系統(tǒng)重點在增刪改查而疫情信息管理系統(tǒng)的核心其實是兩個詞收集和核查。學生的健康信息是每天或每周產(chǎn)生的它不是一次性的靜態(tài)數(shù)據(jù)而是一條持續(xù)更新的時間序列。比如體溫記錄、所在地、行程軌跡、健康碼狀態(tài)這些信息在不同時間點會產(chǎn)生不同值。所以設計數(shù)據(jù)庫時如果沿用用戶表健康表這種一錘子模式后面做統(tǒng)計和按日期查詢時會極其痛苦。我一般建議把該系統(tǒng)拆成四個業(yè)務域基礎信息域?qū)W生檔案、院系班級、輔導員信息。這是管理系統(tǒng)的地基。健康上報域?qū)W生的健康打卡、體溫記錄、位置信息、健康狀態(tài)。這對應一張高頻寫入的表必須以日期和學號做聯(lián)合約束。行程與外出登記域離校申請、返校申請、行程軌跡登記對應審批流程。統(tǒng)計與預警域按班級、院系維度的未上報統(tǒng)計、異常體溫篩選、全校上報趨勢圖。很多同學做畢設時容易把能登錄、能增刪改查當成全部結果系統(tǒng)做出來功能點是不少但數(shù)據(jù)之間是孤立的沒有一條主線把信息串起來。如果你在開題階段就把上面這四個域理清楚后面無論是寫代碼還是畫ER圖思路都會清晰很多。1.2 JSP技術棧的過時與不過時每次帶畢設我都會被問同一個問題老師JSP是不是太老了我是不是該用Spring Boot我的回答通常分兩方面。如果你有半年以上的Java Web基礎用Spring Boot做畢設當然更時髦簡歷上也更好看。但問題是很多同學的實際情況是Java基礎剛過考試線Maven都還沒玩熟Spring Boot的自動配置和依賴管理反而成為攔路虎。JSPServletJavaBean這套老組合雖然不夠現(xiàn)代但它把Web開發(fā)的底層邏輯暴露得清清楚楚——HTTP請求怎么進來、Servlet如何匹配、請求如何轉(zhuǎn)發(fā)到JSP頁面、JDBC如何操作數(shù)據(jù)庫。把這一套走通你對Java Web的理解反而是扎實的。再從畢設驗收的角度說學生疫情信息管理系統(tǒng)的核心價值在業(yè)務邏輯和數(shù)據(jù)組織不在技術架構。JSP頁面可以直接內(nèi)嵌Java代碼雖然不推薦寫起來直觀改起來也快特別適合需要快速出活的畢業(yè)設計節(jié)奏。而且JSP天然支持JavaBean用EL表達式和JSTL標簽就能把頁面展示做得干凈整潔和Servlet配合非常順手。所以我的結論很直接如果你的目標是順利完成畢設并真正理解一個Web系統(tǒng)的運轉(zhuǎn)過程JSPServletMySQL完全夠用而且是一條穩(wěn)妥的路線。2. 從數(shù)據(jù)表設計到用戶權限先把系統(tǒng)骨架搭扎實2.1 分表邏輯推演哪些表必須拆哪些字段必須冗余很多同學建表時的習慣是想到哪兒建到哪兒這是大忌。對于學生疫情信息管理系統(tǒng)我建議一開始就規(guī)劃好6張核心表sys_user用戶表存放用戶ID、賬號、密碼MD5加密存儲、姓名、角色類型學生/輔導員/管理員、關聯(lián)學號或工號。t_student學生信息表學號、姓名、性別、院系ID、班級ID、聯(lián)系電話、家庭所在地。用戶表和學生表分開是為了以后擴展其他角色時不必改動學生結構。t_department、t_class院系表和班級表保持兩級組織結構方便做按院系和班級的下鉆統(tǒng)計。t_health_report健康上報表主鍵ID、學號、上報日期、體溫、當前所在地、健康碼狀態(tài)、行程碼截圖路徑、上報時間。這里一定要加一個聯(lián)合唯一索引(student_id, report_date)保證一個學生一天只能上報一條。t_travel_record行程登記表主鍵、學號、出發(fā)地、目的地、出發(fā)日期、返回日期、交通方式、審批狀態(tài)、審批意見。有一個容易忽略的點學生姓名和班級名稱要不要冗余到健康上報表里我推薦冗余。雖然有學號可以關聯(lián)查詢但在高頻查詢比如按班級篩選未上報名單時多表JOIN的代價遠大于冗余字段占用的空間。畢設系統(tǒng)數(shù)據(jù)量不大性能問題不明顯但冗余字段能極大簡化JSP頁面的查詢邏輯屬于以空間換時間的典型做法。另外一張重要的表是t_health_report里的健康碼狀態(tài)字段。這項數(shù)據(jù)在實際業(yè)務里可能有紅、黃、綠三種狀態(tài)設計時不要用VARCHAR隨便存字符串可以用TINYINT存0/1/2然后在Java代碼里做枚舉映射。這樣不僅存儲更緊湊也方便后續(xù)寫統(tǒng)計SQL。2.2 三種角色三條權限線登錄后各看各的學生疫情信息管理系統(tǒng)最怕做成所有登錄用戶看到同一個菜單。如果輔導員登錄后也能看到管理員的系統(tǒng)設置頁面那這個系統(tǒng)在答辯時基本是送人頭。所以權限控制要在一開始就規(guī)劃好。建議用最經(jīng)典的攔截器思路自定義一個LoginFilter在doFilter里檢查session中是否存在登錄用戶再根據(jù)roleType判斷是否放行。比如/student/*路徑只允許角色為2學生的賬號訪問/teacher/*路徑只允許角色為1輔導員的賬號訪問/admin/*路徑只允許角色為0管理員的賬號訪問。三種角色的核心操作分別是學生端每日健康上報、查看歷史上報記錄、提交行程登記申請、查看審批結果。這個界面要盡量簡潔一個表單搞定不要堆砌無關信息。輔導員端查看本班學生上報情況、查看未上報名單、審批學生的行程申請、按日期導出本班健康信息Excel。這里最核心的需求是查漏所以要重點設計按日期篩選未上報列表。管理員端維護院系班級信息、管理用戶賬號、查看全校上報統(tǒng)計圖表、管理所有學生的上報記錄。統(tǒng)計圖表是整個系統(tǒng)的亮點建議用ECharts展示趨勢折線和柱狀圖。2.3 健康上報的狀態(tài)流轉(zhuǎn)這條鏈子怎么串起來健康上報本身是簡單的提交動作但如果加上輔導員審核環(huán)節(jié)就需要一套流轉(zhuǎn)邏輯。我建議在t_health_report表中加一個status字段0表示待審核、1表示正常通過、2表示異常待復核。學生提交后默認狀態(tài)為0輔導員在列表中看到待審核記錄判斷體溫是否正常、健康碼狀態(tài)如何然后選擇通過或標記異常。這個狀態(tài)流轉(zhuǎn)看起來簡單但有個小細節(jié)很重要學生端修改當天上報記錄的權限應當被限制。比如學生提交后如果狀態(tài)已經(jīng)是正常通過那就不能再修改當天記錄只能通過補充說明功能留言。如果狀態(tài)還是待審核或異常待復核則允許在規(guī)定時間內(nèi)編輯一次。這個邏輯用代碼實現(xiàn)也就是一兩個if判斷但體現(xiàn)了業(yè)務流程的完整性答辯時是可以拿出來講的產(chǎn)品細節(jié)。3. Servlet / DAO / JSP三層實戰(zhàn)核心功能與踩坑記錄3.1 從JDBC到DBUtil不要再在Servlet里寫SQL了代碼結構問題我見過太多反面教材了。有的同學把數(shù)據(jù)庫連接、SQL查詢、前端跳轉(zhuǎn)全部寫在Servlet的doPost里一個方法幾百行看著很唬人但一旦需求改動改一個地方崩三處。JSP項目的標準做法是三層架構Servlet控制層→ Service業(yè)務邏輯層→ DAO數(shù)據(jù)訪問層。JSP只負責渲染數(shù)據(jù)。先寫一個標準的數(shù)據(jù)庫連接工具類。這里強烈推薦用c3p0連接池而不是傳統(tǒng)的DriverManager每次新建連接。連接池的好處是復用數(shù)據(jù)庫連接資源在高頻提交健康上報時不會因為頻繁創(chuàng)建連接導致Tomcat卡頓。配置起來也不復雜import com.mchange.v2.c3p0.ComboPooledDataSource; public class DBUtil { private static ComboPooledDataSource dataSource new ComboPooledDataSource(); static { try { dataSource.setDriverClass(com.mysql.jdbc.Driver); dataSource.setJdbcUrl(jdbc:mysql://localhost:3306/epidemic_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai); dataSource.setUser(root); dataSource.setPassword(your_password); dataSource.setInitialPoolSize(5); dataSource.setMaxPoolSize(20); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); if (ps ! null) ps.close(); if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }這里有個非常常見的坑MySQL 8.0之后的驅(qū)動類是com.mysql.cj.jdbc.Driver不是com.mysql.jdbc.Driver。如果你用的是舊驅(qū)動類啟動時不會立刻報錯但第一次查詢數(shù)據(jù)庫時會拋出ClassNotFoundException。另外連接URL一定要配置serverTimezoneAsia/Shanghai否則MySQL 8.0的時間類型處理會報錯。這兩個問題我在指導畢設時幾乎每年都會遇到。3.2 登錄模塊與密碼加密如何優(yōu)雅地處理Session登錄模塊是每個管理系統(tǒng)的門面也是最容易被問你這里安全嗎的地方。明文存儲密碼在畢設里很常見但實在不該出現(xiàn)在一個2020年代的系統(tǒng)里。建議使用MD5加鹽的方式處理。public static String md5Encrypt(String input, String salt) { String value input salt; try { MessageDigest md MessageDigest.getInstance(MD5); byte[] bytes md.digest(value.getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : bytes) { String hex Integer.toHexString(b 0xff); if (hex.length() 1) sb.append(0); sb.append(hex); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }登錄成功后把用戶對象放進session然后重定向到對應角色的首頁。這里有一個用戶體驗的細節(jié)登錄頁面用驗證碼。雖然增加了一點實現(xiàn)成本但能體現(xiàn)完整性也是答辯時的一個加分項。驗證碼可以用Java的BufferedImage動態(tài)生成一個包含4位數(shù)字的圖片把驗證碼字符串存到session中登錄時比對即可。Session的管理也有講究。很多同學在退出登錄時只寫一句session.removeAttribute(user)這是不夠的。正確的做法是session.invalidate()讓整個會話失效同時FrontController里處理Session超時的跳轉(zhuǎn)邏輯避免用戶會話過期后點擊提交報出空指針異常。3.3 健康上報流程實現(xiàn)事務與并發(fā)約束是核心考驗健康上報是整個系統(tǒng)里最重要的功能也是容易出問題的地方。在t_health_report表中我們設置了(student_id, report_date)的聯(lián)合唯一索引。如果學生瘋狂點擊提交按鈕第二個請求會被數(shù)據(jù)庫拒絕并拋出DuplicateKeyException。代碼層面要在Service層做攔截public int submitReport(HealthReport report) throws Exception { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); String checkSql SELECT COUNT(*) FROM t_health_report WHERE student_id? AND report_date?; ps conn.prepareStatement(checkSql); ps.setInt(1, report.getStudentId()); ps.setDate(2, report.getReportDate()); rs ps.executeQuery(); if (rs.next() rs.getInt(1) 0) { throw new BusinessException(今日已上報無需重復提交); } rs.close(); ps.close(); String insertSql INSERT INTO t_health_report(student_id, report_date, temperature, location, health_code, travel_code_img, status, create_time) VALUES(?,?,?,?,?,?,?,NOW()); ps conn.prepareStatement(insertSql); ps.setInt(1, report.getStudentId()); ps.setDate(2, report.getReportDate()); ps.setBigDecimal(3, report.getTemperature()); ps.setString(4, report.getLocation()); ps.setInt(5, report.getHealthCode()); ps.setString(6, report.getTravelCodeImg()); ps.setInt(7, 0); ps.executeUpdate(); conn.commit(); return 1; } catch (Exception e) { if (conn ! null) conn.rollback(); throw e; } finally { DBUtil.close(conn, ps, rs); } }注意這個事務控制。上報操作涉及先查后插兩步之間如果不在同一事務里就可能出現(xiàn)并發(fā)下的重復插入。雖然聯(lián)合唯一索引兜底了但加上事務后流程才嚴謹這也是答辯時導師會關注的點。3.4 文件上傳與ECharts圖表讓頁面豐富起來行程碼截圖上傳是很多同學到后期才補的功能做得比較隨意。JSP里實現(xiàn)文件上傳最穩(wěn)妥的方式是用Apache的Commons FileUpload組件。需要引入commons-fileupload-1.4.jar和commons-io-2.11.0.jar兩個依賴然后在Servlet中解析MultipartRequest。文件存儲路徑有個細節(jié)不要把文件存在Web應用根目錄下的一個固定文件夾里否則項目重新部署時文件會丟。建議存放在Tomcat的外部路徑比如D:/upload/數(shù)據(jù)庫里保存的是相對路徑或者文件名頁面展示時通過一個專門的Servlet流式讀取返回。這樣Demo期間即使清了Tomcat緩存圖片也還在。統(tǒng)計圖表的實現(xiàn)強烈推薦用ECharts。它不用你寫任何后端圖表代碼只要在JSP頁面里引入ECharts的JS庫然后通過Ajax請求后端接口返回JSON數(shù)據(jù)再渲染到div容器中。后端返回JSON可以用Gson庫非常方便。script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script div idtrendChart stylewidth: 100%;height:400px;/div script var chart echarts.init(document.getElementById(trendChart)); $.getJSON(admin/reportTrend.do, function(data) { chart.setOption({ title: { text: 近14天全校上報人數(shù)趨勢 }, xAxis: { type: category, data: data.dates }, yAxis: { type: value }, series: [{ type: line, data: data.counts, smooth: true }] }); }); /script這塊的難度在于寫統(tǒng)計SQL。比如近14天每天上報人數(shù)對應的SQL是SELECT report_date, COUNT(*) FROM t_health_report WHERE report_date DATE_SUB(CURDATE(), INTERVAL 13 DAY) GROUP BY report_date;這里要注意沒有上報的那幾天這條SQL查出來是沒有結果行的需要后端的Java代碼補0否則ECharts的坐標軸會缺數(shù)據(jù)。這個補零的細節(jié)很典型平時不寫統(tǒng)計功能的人第一次處理時一定會漏。4. 組裝JSP頁面從前端布局到后臺路徑管理4.1 用include把頁面骨架統(tǒng)一起來很多同學的JSP頁面是CtrlC、CtrlV出來的一個項目里幾十個頁面每個頁面都有重復的導航欄和側邊欄。不僅代碼冗余后期想改菜單標題得改幾十個文件。這里推薦用jsp:include或者JSTL的c:import做公共布局。以輔導員端為例可以把頁面拆成三個部分header.jsp頂部導航、menu.jsp側邊欄菜單、footer.jsp頁腳每個業(yè)務頁面只需要寫主體內(nèi)容。% page contentTypetext/html;charsetUTF-8 languagejava % html head title班級上報情況/title jsp:include pagecommon/header.jsp/ /head body jsp:include pagecommon/menu.jsp/ div classmain-content h2本班今日上報情況/h2 table classtable table-bordered thead trth學號/thth姓名/thth體溫/thth所在地/thth健康狀態(tài)/thth上報時間/th/tr /thead tbody c:forEach items${reportList} varitem tr td${item.studentNo}/td td${item.studentName}/td td${item.temperature}/td td${item.location}/td td${item.healthCodeName}/td td${item.createTime}/td /tr /c:forEach /tbody /table /div jsp:include pagecommon/footer.jsp/ /body /html這里要多說一句JSP頁面本身的編碼問題。項目里所有JSP文件都要在頭部聲明pageEncodingUTF-8同時Tomcat的server.xml中Connector需要設置URIEncodingUTF-8。否則你會遇到中文亂碼的問題而且是最難排查的那種——數(shù)據(jù)庫存的是正常中文頁面上顯示卻是問號。排查過程通常要花掉半天時間其實源頭就是POST請求的編碼沒設置。4.2 Servlet路徑設計的取舍約定優(yōu)于配置Servlet的路徑映射看起來只是個細節(jié)但做好了能省很多事。建議采用REST風格比如LoginServlet映射到/login.doLogoutServlet映射到/logout.doStudentReportServlet映射到/student/report.doTeacherAuditServlet映射到/teacher/audit.doAdminStatisticServlet映射到/admin/reportTrend.do這么做的好處是路徑本身就是功能模塊的語義。前端JSP里寫表單的action時一眼就能看出這個請求屬于哪個角色、干什么用的。用了REST風格后還可以給每個角色一個獨立的前綴目錄再和權限攔截器的路徑判斷配合起來非常絲滑。我還建議在項目中使用一個BaseServlet作為基類。它繼承HttpServlet重寫doGet和doPost統(tǒng)一解決請求編碼、登錄狀態(tài)校驗和異常處理三個問題。業(yè)務Servlet再繼承這個BaseServlet每個業(yè)務方法只需要關注自己的邏輯。這是非常典型的經(jīng)驗模式也是答辯時展示工程能力的加分項。4.3 前端控件的細節(jié)優(yōu)化讓Demo顯得更完整疫情影響系統(tǒng)的前端不需要炫技但要有意識地做幾件小事第一個是日期控件默認值。健康上報表單里的日期默認應該是今天不需要用戶手動選擇。用input typedate value${today}即可后端在Model中傳入今天的日期字符串。第二個是表格的分頁。雖然畢設系統(tǒng)數(shù)據(jù)量小但上報歷史記錄這種表會隨著時間累積變長不做分頁就顯得不專業(yè)??梢杂肞ageBean類封裝頁碼、總記錄數(shù)、每頁大小和數(shù)據(jù)列表DAO層用LIMIT ?, ?配合預編譯實現(xiàn)JSP頁面用c:forEach渲染頁碼按鈕。第三個是表單校驗。不要只依賴JavaScript彈窗提示后端Servlet里也要做非空和格式校驗。體溫如果超過42度就拒絕寫入這也是一個簡單的業(yè)務規(guī)則幾個if就能搞定。5. 答辯前一晚功能自查清單與演示腳本5.1 完整走一遍核心流程別在臺上翻車一個常見的答辯翻車場景是學生現(xiàn)場演示上報流程結果發(fā)現(xiàn)報了十幾條數(shù)據(jù)后輔導員端列表里一條都沒顯示。原因多半是學號關聯(lián)查詢寫錯了或者新增的學生沒有被分配班級。所以我一直強調(diào)答辯前至少完整走三遍核心流程。建議你的演示腳本按下面這個順序走用一個學生賬號登錄提交一條健康上報體溫36.5所在地填一個正常城市健康碼狀態(tài)選綠碼。切到輔導員賬號登錄查看今日上報列表確認剛才那條記錄出現(xiàn)。輔導員對這條記錄執(zhí)行審核通過。切回學生賬號查看歷史記錄確認狀態(tài)變?yōu)檎?。在輔導員端點按班級統(tǒng)計確認上報率圖表更新。切成管理員賬號查看全校近14天趨勢圖切到院系列表。這套腳本覆蓋了所有角色和核心數(shù)據(jù)流。只要這六個環(huán)節(jié)都能走通你的系統(tǒng)在功能層面已經(jīng)能打過大部分畢業(yè)設計了。5.2 準備幾個主動回答的技術亮點答辯時不是等老師問你要主動把亮點講出來。我建議你在最后準備好三個可以主動展示的點亮點一事務控制與防重復提交。說明你在上報模塊用到了事務和聯(lián)合唯一索引的雙重保護在處理重復點擊時不會產(chǎn)生臟數(shù)據(jù)。亮點二分層設計與可維護性。直接展示你的項目目錄結構說明基于三層架構業(yè)務代碼和頁面展示分離后期擴展角色和功能不需要大規(guī)模重構。亮點三密碼加密與權限控制。告訴評委登錄密碼使用了加鹽MD5并且通過Filter實現(xiàn)了基于角色的URL訪問控制未授權用戶直接繞過路徑會被攔截。5.3 老師們愛問的問題提前想好答案我在答辯現(xiàn)場見過的老師問題高度集中在下面幾個方向。建議每道題都準備一口頭答案你的系統(tǒng)如何保證一個學生不會重復上報答聯(lián)合唯一索引加業(yè)務判斷在事務里先查詢當日記錄再插入體溫異常的數(shù)據(jù)你是怎么處理的答體溫超過閾值時狀態(tài)自動標為異常待復核并推送給輔導員審核如果你變成千級并發(fā)用戶JSP這套方案會遇到什么瓶頸答數(shù)據(jù)庫連接池限制、Session在分布式下不共享、Servlet容器單機吞吐量有限可以橫向擴展或改造SSM/Spring Boot你數(shù)據(jù)庫里用什么字段存日期讀取時候時區(qū)問題怎么處理答用DATETIMEURL配置serverTimezone統(tǒng)一使用Asia/Shanghai時區(qū)最后這個問題很容易被問到因為很多同學在這個細節(jié)上栽過跟頭。能主動說出時區(qū)配置的處理方式比你背十個名詞解釋都管用。收尾一些副產(chǎn)物資料每次做完這類項目我都會順手把下面這幾樣東西整理成獨立文檔答辯后直接裝進畢設歸檔文件夾里SQL腳本.sql包含建庫、建表、插入初始賬號的完整SQL語句。方便評委機器上直接建環(huán)境。README.md寫清楚環(huán)境要求JDK版本、Tomcat版本、MySQL版本、部署步驟、初始賬號密碼。這個文檔的價值在答辯當天體現(xiàn)得淋漓盡致——評委換成自己的電腦時一份好的README能省大量解釋時間。操作說明.doc配好安裝截圖的用戶手冊給不使用軟件系統(tǒng)的人看足以證明工程能力有條理。這些副產(chǎn)物不需要花太多時間但它們能讓你的畢設從能跑的程序變成完整的項目交付物。評委看的不只是功能還有一位畢業(yè)生是否具備了基本的軟件工程交付意識——說實在話很多職場新人連這點都做不好。如果時間允許把這三樣東西準備好你的JSP學生疫情信息管理系統(tǒng)就不僅僅是一份合格畢業(yè)設計還是一件能拿出去給面試官看的真實作品了。