:大學生兼職家教網(wǎng)項目全解析與部署指南)
簡介這是一份基于JSP技術(shù)的大學生兼職家教網(wǎng)站項目源碼包面向初學Java Web的開發(fā)者或需要完成課程設(shè)計的學生定位于展示從零搭建信息發(fā)布、查詢、申請與后臺管理平臺的完整流程。壓縮包共包含79個文件體積僅4.24MB主要由19個JSP頁面、22個class編譯文件、3個Java源文件、4個jar庫、gif/jpg圖像素材以及js/css等前端素材組成可快速導入IDE進行二次學習與部署。項目完整覆蓋JSP基本語法、Servlet控制器、MVC分層、JDBC數(shù)據(jù)庫操作、AJAX異步交互及Session會話保持等關(guān)鍵知識點并提供登錄注冊、家教信息管理等常見業(yè)務(wù)模塊有助于理解前后端數(shù)據(jù)流轉(zhuǎn)與數(shù)據(jù)庫表設(shè)計。目前已有140人學習下載適合用于畢業(yè)設(shè)計參考或Web開發(fā)初級項目實戰(zhàn)按包內(nèi)目錄即可梳理頁面、類與資源文件的對應(yīng)關(guān)系便于對照調(diào)試。總體而言這份源碼包結(jié)構(gòu)緊湊適合快速上手并深入體會Java Web項目的完整開發(fā)鏈路。1. 從信息登記表到管理后臺JSP-324 大學生兼職家教網(wǎng)能解決什么一到寒暑假高校附近的家教需求就成倍增長。JSP-324 大學生兼職家教網(wǎng)這類項目最初就是為了解決這種場景出現(xiàn)的某社團負責人手里的紙質(zhì)登記表沒幾天就攢成一沓想找初三數(shù)學一對一的家長、能帶小學英語的大二學生、談好的課時費和上課地點全擠在同一張表上改一處就亂三處。它把家教信息的發(fā)布、查詢、申請和狀態(tài)管理搬進了瀏覽器學生注冊后發(fā)布可授課科目家長發(fā)布需求雙方在系統(tǒng)里完成匹配并一路流轉(zhuǎn)到成交。整個項目按 JSP Servlet JDBC MySQL 的技術(shù)棧組織服務(wù)端用 JSP 動態(tài)輸出頁面用 Servlet 接收前端請求并把業(yè)務(wù)結(jié)果轉(zhuǎn)發(fā)回視圖數(shù)據(jù)庫操作走 JDBC 標準接口。涉及的模塊包括用戶注冊登錄、家教信息發(fā)布與多條件查詢、預約申請、接單與完成狀態(tài)管理。相比現(xiàn)在流行的前后端分離架構(gòu)這套玩法重一些、渲染在服務(wù)端完成但正因為這樣它把 Java Web 開發(fā)最核心的鏈路完整展現(xiàn)了一遍。適合誰剛學完 JSP 和 Servlet 語法、想用完整項目把知識點串起來的人在做課程設(shè)計選題、需要一套能跑通全流程參考代碼的人以及想快速了解 MVC 分層在實際項目里怎么落地的開發(fā)者。下面按從環(huán)境到運行、從功能到坑位的順序把它拆開講。2. 工程技術(shù)棧與部署環(huán)境MVC 三層分工與本地跑通的前提2.1 為什么是 JSP Servlet JDBC選型邏輯與適用邊界先回答一個很多人會問的問題都什么年代了為什么還要做一個 JSP 項目這個問題的答案藏在項目的業(yè)務(wù)形態(tài)里。大學生兼職家教網(wǎng)的核心操作是信息流的增刪改查——發(fā)布一條家教信息、瀏覽列表、提交申請、更新接單狀態(tài)。它的頁面數(shù)量不大、交互不復雜、并發(fā)量也不高用 JSP 在服務(wù)端直接渲染頁面反而比前后端分離少一層接口約定部署方式也簡單打一個 WAR 包丟進容器就能跑。從學習角度看JSP 項目迫使你在同一套代碼里同時處理視圖、控制、數(shù)據(jù)三個層次理解請求在瀏覽器與服務(wù)器之間怎么走。這個項目的分層方式很典型JSP 頁面只負責展示和收集參數(shù)Servlet 負責把請求轉(zhuǎn)給業(yè)務(wù)邏輯并決定跳轉(zhuǎn)到哪個頁面DAO 類負責數(shù)據(jù)庫讀寫中間的 service 包放業(yè)務(wù)規(guī)則。視圖層拿到的所有數(shù)據(jù)都來自控制器塞進 request 或 session 的屬性不直接訪問數(shù)據(jù)庫。適用邊界也要說清楚。如果業(yè)務(wù)再復雜一點比如加了實時聊天、復雜權(quán)限矩陣JSP 這套就會因為頁面與 Java 代碼耦合而變得難維護。它的優(yōu)勢區(qū)間是中小型信息管理類應(yīng)用。將來做畢業(yè)設(shè)計或者接小型外包項目這個技術(shù)組合依然夠用這也是我建議把項目跑通的原因——它的每個模塊都能遷移到其他管理系統(tǒng)中去。2.2 環(huán)境準備清單JDK、Tomcat、MySQL 的版本匹配在導入項目之前先把環(huán)境對齊。這個項目基于 JSP/Servlet 傳統(tǒng)架構(gòu)對版本的要求不算苛刻但版本差太多會出很隱蔽的問題。建議按下面這張表準備組件建議版本說明JDK8 或 118 最穩(wěn)11 也能跑注意 IDE 編譯級別要與之一致Tomcat8.5 / 9.x支持 Servlet 3.1/4.0匹配常見 JSP 版本MySQL5.7 或 8.05.7 配 connector 5.1.x8.0 必須換 8.x 驅(qū)動IDE任意支持 Java Web 的 IDE導入時選擇 Dynamic Web Project 結(jié)構(gòu)準備完成后先在命令行確認基礎(chǔ)環(huán)境正常java -version mysql --version看到版本號輸出后再確認 Tomcat 的 CATALINA_HOME 環(huán)境變量或者 IDE 里的 Server Runtime 配置。這里有一個常見坑IDE 里配置的 Tomcat 和命令行啟動的 Tomcat 不是同一個實例。如果你在 IDE 外手工把項目丟進 webapps 啟動過一次項目的編譯產(chǎn)物可能殘留在 work 目錄里后面在 IDE 里啟動會加載到過期內(nèi)容。所以我一般會讓 IDE 管理一個獨立的 Tomcat 運行時避免混用。2.3 數(shù)據(jù)庫初始化三張核心表與角色設(shè)計家教平臺的數(shù)據(jù)量不大但表之間的關(guān)系值得認真設(shè)計。項目里最基本的有三張表用戶表、家教信息表、申請記錄表。用下面的 SQL 初始化數(shù)據(jù)庫CREATE DATABASE IF NOT EXISTS tutor_db DEFAULT CHARACTER SET utf8mb4; CREATE TABLE sys_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(30), phone VARCHAR(20), role TINYINT DEFAULT 0 COMMENT 0-學生 1-家長, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE job_info ( job_id INT PRIMARY KEY AUTO_INCREMENT, publisher_id INT NOT NULL, title VARCHAR(100) NOT NULL, subject VARCHAR(30) COMMENT 科目, grade VARCHAR(20) COMMENT 年級, teach_type TINYINT DEFAULT 0 COMMENT 0-線上 1-線下, price DECIMAL(6,2) COMMENT 課時費, address VARCHAR(200), detail TEXT, status TINYINT DEFAULT 0 COMMENT 0-待接單 1-已接單 2-已完成 3-已關(guān)閉, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE apply_record ( apply_id INT PRIMARY KEY AUTO_INCREMENT, job_id INT NOT NULL, apply_user_id INT NOT NULL, message VARCHAR(200), status TINYINT DEFAULT 0 COMMENT 0-待處理 1-已同意 2-已拒絕, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );注意幾個設(shè)計意圖。第一role 用整數(shù)而不是字符串避免數(shù)據(jù)庫里出現(xiàn)學生家長studentparent等多種寫法導致查詢條件對不上。第二status 采用從 0 開始的數(shù)字枚舉每個值在代碼里用常量或注釋明確對應(yīng)關(guān)系后面做狀態(tài)流轉(zhuǎn)時只需要比較數(shù)值不用解析字符串。第三字符字段都用 utf8mb4 而不是 utf8否則 emoji 或生僻姓名寫入時會報 Incorrect string value 錯誤。這三張表是項目跑起來的最小集合實際項目可能還會加管理員表但核心鏈路依賴的就是它們。2.4 導入項目后的目錄閱讀順序拿到項目壓縮包后解壓導入 IDE先不要急著點運行花十分鐘把目錄結(jié)構(gòu)讀一遍。典型目錄長這樣src/ ├── com/tutor/controller/ # Servlet 控制器 ├── com/tutor/service/ # 業(yè)務(wù)邏輯層 ├── com/tutor/dao/ # JDBC 數(shù)據(jù)訪問層 ├── com/tutor/entity/ # 實體類對應(yīng)數(shù)據(jù)表 └── com/tutor/util/ # 工具類DBUtil、MD5Util 等 WebContent/ ├── index.jsp # 入口頁/首頁 ├── page/ │ ├── login.jsp │ ├── register.jsp │ ├── release.jsp # 發(fā)布家教信息 │ └── list.jsp # 家教信息列表 └── WEB-INF/ ├── web.xml # Servlet 映射與過濾器注冊 └── lib/ # mysql 驅(qū)動等 JAR 包我推薦的閱讀順序是先看 WEB-INF/web.xml它告訴你請求路徑與 Servlet 的對應(yīng)關(guān)系是整個應(yīng)用的路由表再看 util/DBUtil.java了解數(shù)據(jù)庫連接怎么建立和關(guān)閉然后挑一個完整鏈路看比如從 list.jsp 往下追到對應(yīng) Servlet 再到 DAO。順著 controller → service → dao 的順序追代碼比從首頁 F5 開始點速度快得多原因是視圖層 JSP 語法對新手干擾大而控制層和數(shù)據(jù)層更接近純 Java邏輯更清楚。提示這個項目解壓后如果打算用 Git 管理記得把 .settings、build、target 這類 IDE 與編譯產(chǎn)物目錄加進 .gitignore否則倉庫會被無意義文件撐大隊友拉下來還容易因為本地配置不同產(chǎn)生一堆沖突。3. 家教信息發(fā)布與管理從表單到數(shù)據(jù)庫的閉環(huán)實現(xiàn)3.1 發(fā)布表單JSP 頁面的字段設(shè)計與參數(shù)對應(yīng)關(guān)系家教信息發(fā)布頁面是項目的核心入口。它的職責只有一個把用戶在瀏覽器里填寫的表單數(shù)據(jù)收集起來交給后端 Servlet 處理。先看頁面片段form action${pageContext.request.contextPath}/job methodpost input typehidden nameaction valuerelease/ label標題/label input typetext nametitle required maxlength100/ label科目/label select namesubject option value數(shù)學數(shù)學/option option value英語英語/option option value物理物理/option /select label年級/label input typetext namegrade placeholder如初三/ label課時費元/小時/label input typenumber nameprice min0 step0.01/ label授課方式/label input typeradio nameteachType value0 checked/線上 input typeradio nameteachType value1/線下 label詳細描述/label textarea namedetail rows5/textarea button typesubmit發(fā)布/button /form表單里每個控件的 name 屬性就是后端 Servlet 用 getParameter 取參數(shù)時的鍵名前后端必須嚴格一致這是最常見也最難排查的 bug 來源之一。action 用 ${pageContext.request.contextPath}/job 而不是寫死路徑是因為項目部署時上下文路徑可能變化寫死會導致 404。隱藏字段 actionrelease 是讓同一個 Servlet 處理多種操作時的分發(fā)標記——發(fā)布、修改、刪除都走 /job 這個 URL靠 action 區(qū)分。3.2 實體類與 DAO 層PreparedStatement 的參數(shù)化寫法后端的第一個落點是實體類和 DAO。實體類 JobInfo 的字段與 job_info 表一一對應(yīng)這里不再貼全部 getter/setter重點看 DAO 的插入方法public int insert(JobInfo job) throws SQLException { String sql INSERT INTO job_info (publisher_id, title, subject, grade, teach_type, price, detail) VALUES (?, ?, ?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, job.getPublisherId()); ps.setString(2, job.getTitle()); ps.setString(3, job.getSubject()); ps.setString(4, job.getGrade()); ps.setInt(5, job.getTeachType()); ps.setBigDecimal(6, job.getPrice()); ps.setString(7, job.getDetail()); return ps.executeUpdate(); } }這里用 PreparedStatement 而不是 Statement是 JDBC 編程里一條不能破的規(guī)矩。第一參數(shù)通過 ? 占位符傳遞數(shù)據(jù)庫驅(qū)動會把它們當作純數(shù)據(jù)而不是 SQL 片段從源頭堵住 SQL 注入第二PreparedStatement 有預編譯緩存同一條 SQL 執(zhí)行多次時性能更好。代碼里的 try-with-resources 是 Java 7 引入的寫法Connection、PreparedStatement 實現(xiàn)了 AutoCloseable會在 try 塊結(jié)束后自動關(guān)閉不必再手寫 finally 里的一堆 close()。參數(shù)類型需要注意price 在 MySQL 里是 DECIMAL(6,2)在 Java 側(cè)對應(yīng) BigDecimal用 setBigDecimal 傳入而不是 setDouble否則小數(shù)精度可能出問題teach_type 是 TINYINT對應(yīng) setInt。如果某個 DAO 方法里看到 setString 傳整數(shù)那多半會觸發(fā)隱式轉(zhuǎn)換MySQL 能接收但存在隱患。3.3 Servlet 控制器接收參數(shù)、設(shè)置編碼、決定跳轉(zhuǎn)Servlet 是 MVC 里的 C 層負責把請求參數(shù)整理成實體類調(diào)用 service/DAO最后決定返回哪個視圖??春诵奶幚磉壿媝rotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String action request.getParameter(action); if (release.equals(action)) { JobInfo job new JobInfo(); job.setPublisherId((Integer) request.getSession() .getAttribute(userId)); job.setTitle(request.getParameter(title)); job.setSubject(request.getParameter(subject)); job.setGrade(request.getParameter(grade)); job.setTeachType(Integer.parseInt(request.getParameter(teachType))); job.setPrice(new BigDecimal(request.getParameter(price))); boolean ok jobService.addJob(job); if (ok) { response.sendRedirect(request.getContextPath() /job?actionlist); } else { request.setAttribute(error, 發(fā)布失敗); request.getRequestDispatcher(/page/release.jsp) .forward(request, response); } } }第一行 setCharacterEncoding(UTF-8) 必須放在讀取任何參數(shù)之前因為 POST 請求體的解碼在第一次 getParameter 時已完成之后再設(shè)置就無效了。userId 從 session 取而不是信任前端傳值這是防止越權(quán)的關(guān)鍵設(shè)計——發(fā)布者身份必須由服務(wù)端會話決定。成功后用 sendRedirect 走一次新的 GET 請求到列表頁符合POST 后重定向的習慣避免刷新頁面時重復提交失敗則用 forward 回到發(fā)布頁并攜帶 error 信息此時瀏覽器地址欄不變用戶可以馬上修改重試。注意sendRedirect 會丟 request 里的屬性所以錯誤提示必須換成 session 或者在重定向 URL 后面帶參數(shù)而 forward 保留 request 作用域適合表單校驗失敗回顯的場景。兩者別混用。3.4 列表查詢與分頁條件組合和 LIMIT 偏移量家教信息列表需要支持按科目、年級和授課方式篩選同時做分頁。查詢 SQL 是動態(tài)拼裝的但拼的是條件骨架值仍然用占位符StringBuilder sql new StringBuilder( SELECT * FROM job_info WHERE status 0 ); if (subject ! null !subject.isEmpty()) { sql.append(AND subject ? ); } if (grade ! null !grade.isEmpty()) { sql.append(AND grade LIKE ? ); } sql.append(ORDER BY create_time DESC LIMIT ?, ?);對應(yīng)參數(shù)設(shè)置要注意順序先設(shè)置前面條件里的 subject、grade最后再設(shè)置 LIMIT 的兩個參數(shù)。LIMIT 第一個參數(shù)是偏移量 offset第二個是每頁條數(shù) pageSize計算公式是 offset (pageNum - 1) * pageSize。比如每頁 10 條第三頁的 offset 就是 20。很多人在這個計算上翻車把 pageNum 直接當 offset 傳進去結(jié)果第二頁開始數(shù)據(jù)錯位。分頁還要向前端返回總頁數(shù)一般先 COUNT(*) 查總記錄數(shù)再除以 pageSize 向上取整。這個 COUNT 查詢和列表查詢條件必須完全一致否則會出現(xiàn)列表顯示 3 頁但分頁條只有 2 頁的尷尬。我一般會把條件拼裝抽成一個公共方法兩個查詢共用從根上避免條件不一致。4. 登錄認證與申請流程Session 管理與狀態(tài)機的實現(xiàn)4.1 登錄成功后的會話設(shè)計服務(wù)端存什么、過期時間設(shè)多少登錄是整個應(yīng)用的身份基礎(chǔ)。用戶提交用戶名和密碼后Servlet 用用戶名查庫、比對密碼成功后把用戶標識存進 Session而不是把整個用戶對象隨便亂放。推薦的做法是只存 userId、realName、role 三個字段夠用且減少 session 體積。代碼示意public User login(String username, String password) { // 先按用戶名查用戶再比對密碼 User user userDao.findByUsername(username); if (user ! null user.getPassword().equals(MD5Util.md5(password))) { return user; } return null; }登錄成功后控制器里這樣存會話HttpSession session request.getSession(); session.setAttribute(userId, user.getUserId()); session.setAttribute(realName, user.getRealName()); session.setAttribute(role, user.getRole()); session.setMaxInactiveInterval(30 * 60);setMaxInactiveInterval(30 * 60) 把會話閑置超時設(shè)為 30 分鐘單位是秒超過這個時間用戶再操作就要重新登錄。這個值需要根據(jù)使用場景調(diào)整——家教平臺的用戶不會一直盯著頁面20~30 分鐘是合理區(qū)間設(shè)太短會被頻繁踢下線設(shè)太長又有會話被劫持的風險。這里還要理解 Session 和 Cookie 的關(guān)系Session 數(shù)據(jù)在服務(wù)端瀏覽器端只保存一個 JSESSIONID 的 CookieCookie 失效或清除后服務(wù)端就找不到對應(yīng)的用戶數(shù)據(jù)。所以 Session 里存狀態(tài)Cookie 只是拿鑰匙鑰匙丟了狀態(tài)就找不回來。4.2 登錄過濾器統(tǒng)一攔截未登錄請求與靜態(tài)資源放行頁面一多如果每個 Servlet 都手動判斷 session 是否有效代碼會非常啰嗦而且容易漏。項目里正確的做法是寫一個 Filter在 web.xml 里注冊為 /*對所有請求先做身份檢查。核心邏輯public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); String uri request.getRequestURI(); if (uri.endsWith(.css) || uri.endsWith(.js) || uri.endsWith(.png) || uri.contains(/page/login.jsp)) { chain.doFilter(req, resp); return; } if (session ! null session.getAttribute(userId) ! null) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /page/login.jsp); } }注意兩點。第一request.getSession(false) 傳 false 表示不新建 Session——如果用戶本來沒有會話這里會返回 null而不是自動創(chuàng)建一個空的避免每個未登錄請求都白白產(chǎn)生 Session 對象。第二靜態(tài)資源和登錄頁本身必須放行否則會出現(xiàn)登錄頁加載不出 CSS、jQuery 全被攔的問題。放行規(guī)則用 URI 后綴判斷比較直觀也可以用前綴匹配如 /static/、/assets/。如果項目里用了驗證碼圖片同樣要放進放行清單。4.3 申請接單的狀態(tài)流轉(zhuǎn)四個狀態(tài)的權(quán)限邊界家教信息發(fā)布之后核心業(yè)務(wù)就是申請與接單。一個家教訂單在系統(tǒng)里經(jīng)歷的狀態(tài)序列如下狀態(tài)值狀態(tài)名觸發(fā)動作誰可以操作0待接單發(fā)布成功學生申請1已接單家長同意申請家長2已完成雙方確認學生/家長3已關(guān)閉發(fā)布者撤銷發(fā)布者狀態(tài)機的實現(xiàn)并不復雜關(guān)鍵在于每個狀態(tài)變更操作開始時都要校驗當前狀態(tài)和操作者身份。以家長同意申請為例Servlet 里需要先查出 job 的 publisher_id跟 session 里的 userId 比較不一致直接拒絕再檢查 job.status 必須是 0否則提示該家教信息已接單。這兩步校驗缺一不可只校驗狀態(tài)不校驗身份就會出現(xiàn) A 用戶能操作 B 用戶信息的越權(quán)漏洞。對應(yīng)的一條更新 SQL 涉及兩張表UPDATE apply_record SET status 1 WHERE apply_id ? AND status 0; UPDATE job_info SET status 1 WHERE job_id ? AND status 0;這兩條 UPDATE 都帶有 status 0 的條件既是狀態(tài)校驗也起到樂觀鎖的作用如果記錄已經(jīng)被別人改過執(zhí)行影響行數(shù)為 0程序就能感知到并發(fā)沖突并提示用戶而不是盲目覆蓋。4.4 密碼存儲MD5 加鹽與防拖庫思路用戶表里的 password 字段不能存明文這是 Java Web 項目的底線。項目里一般會用 MD5 做摘要但裸 MD5 對彩虹表幾乎沒抵抗力所以必須加鹽。思路是生成一個隨機鹽值拼到密碼后面再算摘要鹽值隨用戶記錄一起存儲。實現(xiàn)通常收斂在一個工具類里public static String md5WithSalt(String rawPassword, String salt) { String input rawPassword salt; return md5(input); }注冊時生成鹽值String salt UUID.randomUUID().toString().substring(0, 8); 登錄時從庫里取出該用戶的鹽值重新拼接計算再比對。這樣即使數(shù)據(jù)庫泄露攻擊者拿到的也是加鹽摘要想反推原文需要為每個用戶單獨跑字典成本高很多。MD5 在這個項目階段夠用如果做生產(chǎn)系統(tǒng)建議直接換 BCrypt算法自帶隨機鹽而且計算慢更抗暴力破解。5. 部署與運行避坑最常見五個異常及排查路徑這個項目從導入到跑通最容易踩的坑集中在五組問題里。每條按現(xiàn)象、原因、解決三個步驟寫照著排查能省下大量時間。5.1 Tomcat 啟動失敗8080 端口被占用現(xiàn)象啟動 Tomcat 時控制臺報 Port 8080 required by Tomcat v9.0 Server at localhost is already in use服務(wù)起不來或者起來后訪問不到頁面。原因本機有其他程序占用了 8080。最常見的是之前手工啟動的 Tomcat 實例沒關(guān)干凈或者其他開發(fā)工具的調(diào)試端口恰好落在 8080。解決先查占用進程Windows 用 netstat -ano | findstr 8080看到 PID 后在任務(wù)管理器里結(jié)束對應(yīng)進程或者直接改端口在 conf/server.xml 里把 Connector 的 port 改成 8081同時注意項目里所有 contextPath 相關(guān)路徑不受影響因為改的是服務(wù)器監(jiān)聽端口而不是上下文路徑。改完端口別忘了 IDE 里的 server 配置也要同步更新。5.2 啟動即 ClassNotFoundExceptionMySQL 驅(qū)動缺失或版本不匹配現(xiàn)象訪問任意需要操作數(shù)據(jù)庫的頁面拋 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver或者啟動時就報驅(qū)動類加載失敗。原因mysql-connector-java 的 JAR 沒有放進 WEB-INF/lib。很多人把驅(qū)動 JAR 加到了 IDE 的 Classpath 里但部署時 IDE 沒有把它打包進 WEB-INF/libTomcat 運行時自然找不到。驅(qū)動版本不匹配導致的啟動翻車十次里有八次是這類原因。解決把 mysql-connector-java-x.x.x.jar 直接復制到 WebContent/WEB-INF/lib 目錄下然后右鍵項目刷新確認構(gòu)建路徑里能看到它。另外注意驅(qū)動類名變了MySQL 8.x 驅(qū)動是 com.mysql.cj.jdbc.Driver老代碼里寫 com.mysql.jdbc.Driver 會報錯需要同步改掉。5.3 數(shù)據(jù)庫中文亂碼連接 URL 和頁面編碼不一致現(xiàn)象從數(shù)據(jù)庫查出來的中文顯示為問號或者往庫里寫中文后讀出來是亂碼。原因問題往往不是單點而是鏈路里多個環(huán)節(jié)的編碼不一致。常見組合是連接 URL 少了 characterEncoding 參數(shù)而數(shù)據(jù)庫、表、JSP 頁面各自用了不同編碼。解決在 JDBC URL 上顯式聲明編碼URL 形如 jdbc:mysql://localhost:3306/tutor_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai。同時把建庫語句的 DEFAULT CHARACTER SET utf8mb4 手動執(zhí)行一次確認表也是 utf8mb4。JSP 頁面頂部加上 % page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %三處對齊后亂碼基本絕跡。注意MySQL 8.x 不加 serverTimezone 會直接報時區(qū)錯誤這個參數(shù)不是可選項連接 URL 里必須帶上。5.4 修改 JSP 后頁面不更新Tomcat 工作目錄緩存舊編譯結(jié)果現(xiàn)象改了 JSP 里的標題或樣式刷新瀏覽器還是舊頁面清瀏覽器緩存也沒用。原因JSP 第一次被訪問時 Tomcat 會把它編譯成 Servlet class 存到 work 目錄之后每次請求默認先檢查 JSP 文件是否有改動。但在 IDE 的增量部署場景下有時候文件時間戳沒有變化或者修改發(fā)生在 IDE 外部Tomcat 沒感知到更新一直用緩存里的舊 class。解決先停掉 Tomcat找到 Tomcat 安裝目錄下的 work/Catalina 目錄或者 IDE 里配置的 catalina.base 對應(yīng) work 目錄整體刪除再重新啟動也可以在 IDE 的 Server 面板里右鍵 Clean。從那以后我只要改了 JSP 或 web.xml都會順手 Clean 一次再啟動二十秒的事但能省掉改了沒反應(yīng)的半小時排查。5.5 登錄頁樣式全丟過濾器把 CSS/JS 誤傷現(xiàn)象進入 login.jsp 頁面HTML 結(jié)構(gòu)正常但所有樣式和腳本都沒生效F12 看到 .css 請求返回 302 跳轉(zhuǎn)到了登錄頁。原因過濾器注冊為 /* 對所有請求做登錄校驗靜態(tài)資源路徑也被攔截未登錄狀態(tài)下 CSS/JS 請求被重定向走頁面自然裸奔。解決在過濾器里放行靜態(tài)資源前面 4.2 的代碼已經(jīng)寫了按后綴放行的方式更規(guī)范的做法是把靜態(tài)資源統(tǒng)一放在 /static、/assets 目錄下過濾器按路徑前綴放行。還要注意一個隱藏點放行條件里如果用了 contains(/page/login.jsp)那么登錄頁里的 CSS 是放行了但 CSS 本身請求的 URL 是 /css/style.css必須同時有后綴判斷否則依然拿不到。6. 上線前的最后兩步SQL 審計與操作日志6.1 全面檢查 SQL 拼裝點在項目收尾演示之前我會全局搜索所有 DAO 類里是否還有 Statement 或字符串拼接 SQL 的地方。搜索特征是 String sql SELECT ... 變量只要出現(xiàn)一律改成 PreparedStatement 的參數(shù)化寫法。判斷標準很簡單任何來自 request.getParameter 的值都不允許直接拼進 SQL。用 IDE 的全局搜索把范圍限定在 dao 包和 service 包逐條過一遍這個動作花不了十分鐘但能堵住最危險的注入口。6.2 給關(guān)鍵操作補一行日志發(fā)布信息、申請接單、修改狀態(tài)這三類操作是業(yè)務(wù)里最容易出糾紛的地方。我會在 Servlet 處理完成后加一行日志輸出記錄操作人、操作對象和結(jié)果。用最樸素的 java.util.logging 或者 Log4j 都行關(guān)鍵是日志里要有 userId、jobId 和操作類型格式類似 release|user12|job78|success。這樣演示或者測試時一旦出問題可以直接從日志里還原操作鏈路不需要再去數(shù)據(jù)庫里猜是誰改的數(shù)據(jù)。最后說一個自己的習慣每次部署前我都會拿兩個瀏覽器賬號把完整流程走一遍——學生賬號注冊、發(fā)布家教信息、退出家長賬號登錄、搜索到目標信息、提交申請切回學生賬號同意申請狀態(tài)從待接單變成已接單。這個流程能跑通項目就達到了可驗收的狀態(tài)。JSESSIONID 跨瀏覽器互不影響這點正好是 Session 機制可靠性的實際驗證。希望這組拆解能幫你在導入這個項目時少走幾步彎路順利把它跑起來。本文還有配套的精品資源點擊獲取