操指南:從部署到事務(wù)驗(yàn)證)
簡介本資源是一套高質(zhì)量JavaWeb課程設(shè)計(jì)級(jí)仿小米在線商城系統(tǒng)源碼面向高校計(jì)算機(jī)相關(guān)專業(yè)學(xué)生及JavaWeb初學(xué)者用于完成大作業(yè)、課程實(shí)訓(xùn)或項(xiàng)目實(shí)踐。項(xiàng)目已通過嚴(yán)格調(diào)試評(píng)審得分95分以上具備完整前后端功能與可直接運(yùn)行的數(shù)據(jù)庫支持涵蓋用戶中心、商品瀏覽、購物車、訂單管理等核心電商模塊。壓縮包共467個(gè)文件包含39個(gè)Java業(yè)務(wù)邏輯類、28個(gè)JSP頁面、25個(gè)JavaScript交互腳本、19個(gè)CSS樣式文件及大量圖片資源143個(gè)PNG、109個(gè)JPG、65個(gè)GIF另有2個(gè)SQL建庫腳本與19個(gè)Jar依賴庫整體大小23.98MB結(jié)構(gòu)清晰、注釋規(guī)范便于理解MVC分層架構(gòu)與Web開發(fā)全流程。目前已有1554人學(xué)習(xí)下載配套資源含響應(yīng)式前端樣式如bootstrap.min.css、index.css等、后臺(tái)管理基礎(chǔ)界面及多媒體素材適合開展代碼閱讀、功能擴(kuò)展與二次開發(fā)訓(xùn)練。1. JavaWeb仿小米在線商城源碼95分大作業(yè)項(xiàng)目實(shí)測(cè)能跑通但別急著部署到Tomcat就完事你剛下載完這個(gè)標(biāo)著“95分以上大作業(yè)項(xiàng)目”的JavaWeb仿小米商城壓縮包雙擊解壓后看到一堆.css文件style.css.bak、bootstrap-responsive(1).css、tinymce.css……還有WEB-INF/web.xml和src/下密密麻麻的Servlet、DAO、JSP——第一反應(yīng)可能是“這不就是個(gè)標(biāo)準(zhǔn)MVC三層結(jié)構(gòu)導(dǎo)入IDEA點(diǎn)運(yùn)行不就完了”錯(cuò)。我去年幫3個(gè)同學(xué)調(diào)試過同源項(xiàng)目2人卡在數(shù)據(jù)庫連接池初始化失敗1人登錄成功但購物車數(shù)據(jù)始終為空查了6小時(shí)才發(fā)現(xiàn)是web.xml里filter順序?qū)懛戳藢?dǎo)致SessionFilter沒攔住請(qǐng)求。這不是一個(gè)“能跑就行”的Demo而是一個(gè)嚴(yán)格遵循高校JavaWeb課程評(píng)分細(xì)則的完整閉環(huán)含用戶注冊(cè)/登錄、商品分類展示、搜索、加入購物車、訂單生成、后臺(tái)管理含商品CRUD、訂單狀態(tài)更新、MySQL事務(wù)控制下單扣庫存生成訂單必須原子性甚至包含TinyMCE富文本編輯器用于商品詳情頁——這些細(xì)節(jié)恰恰是95分和80分項(xiàng)目的分水嶺。適合正在趕JavaWeb課設(shè) deadline 的本科生也適合想補(bǔ)全傳統(tǒng)Web開發(fā)鏈路JSPServletJDBCBootstrap的轉(zhuǎn)行者。它不炫技但每一步都踩在教學(xué)考核點(diǎn)上比如用DBUtils而非MyBatis強(qiáng)制手寫SQL防止學(xué)生抄框架比如所有JSP頁面都帶% page contentTypetext/html;charsetUTF-8 %連字符集都幫你寫死——這不是偷懶是老師批改時(shí)一眼就能確認(rèn)你懂基礎(chǔ)規(guī)范。2. 從解壓到可訪問首頁五步走通本地運(yùn)行鏈路含IDEATomcat配置細(xì)節(jié)2.1 解壓后目錄結(jié)構(gòu)解析識(shí)別核心模塊與隱藏依賴解壓后的根目錄結(jié)構(gòu)如下已剔除無關(guān)臨時(shí)文件JavaWeb-XiaoMi/ ├── WebContent/ # 標(biāo)準(zhǔn)WebRoot含JSP、CSS、JS、圖片 │ ├── css/ # 你看到的那些.css文件全在這里 │ ├── js/ │ ├── images/ │ ├── WEB-INF/ │ │ ├── web.xml # 關(guān)鍵注意servlet-mapping順序 │ │ ├── lib/ # 包含mysql-connector-java-5.1.47.jar等 │ │ └── classes/ # 編譯后的.class文件若未編譯則為空 │ └── index.jsp # 首頁入口 ├── src/ # Java源碼按包名組織 │ ├── dao/ │ ├── entity/ │ ├── service/ │ ├── servlet/ │ └── util/ # DBUtil工具類、PageBean分頁類等 ├── db/ # 數(shù)據(jù)庫腳本重點(diǎn) │ ├── xiaomi.sql # 完整建庫建表初始數(shù)據(jù)含管理員賬號(hào) │ └── README.md # 說明字符集必須為utf8mb4 └── pom.xml? # 注意此項(xiàng)目無pom.xml純傳統(tǒng)Web項(xiàng)目勿用Maven導(dǎo)入提示bootstrap-responsive(1).css是Bootstrap 2.x響應(yīng)式樣式非3.x或4.x對(duì)應(yīng)前端適配邏輯寫在index.jsp的meta nameviewport和媒體查詢中tinymce.css僅在后臺(tái)商品編輯頁加載前臺(tái)無需關(guān)心。2.2 數(shù)據(jù)庫準(zhǔn)備執(zhí)行SQL腳本前必須確認(rèn)的三個(gè)字符集陷阱項(xiàng)目使用MySQL 5.7兼容8.0db/xiaomi.sql腳本內(nèi)建庫語句為CREATE DATABASE xiaomi DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;但實(shí)際執(zhí)行時(shí)90%的翻車發(fā)生在以下環(huán)節(jié)MySQL服務(wù)端默認(rèn)字符集不是utf8mb4檢查命令SHOW VARIABLES LIKE character_set_server;若返回utf8非utf8mb4需修改MySQL配置文件my.cnf[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci重啟MySQL服務(wù)后再執(zhí)行CREATE DATABASE才生效。否則即使建庫時(shí)指定了utf8mb4表字段仍會(huì)繼承server默認(rèn)值??蛻舳诉B接未指定字符集src/util/DBUtil.java中JDBC URL為jdbc:mysql://localhost:3306/xiaomi?useUnicodetruecharacterEncodingutf8此處必須改為utf8mb4否則中文插入會(huì)亂碼jdbc:mysql://localhost:3306/xiaomi?useUnicodetruecharacterEncodingutf8mb4serverTimezoneGMT%2B8SQL腳本本身含BOM頭導(dǎo)致執(zhí)行失敗用Notepad打開xiaomi.sql→ 編碼 → 轉(zhuǎn)為UTF-8無BOM格式 → 保存。否則MySQL Workbench執(zhí)行時(shí)報(bào)錯(cuò)ERROR 1064 (42000)。執(zhí)行成功后驗(yàn)證USE xiaomi; SHOW CREATE TABLE user; -- 確認(rèn)user表engineInnoDB且charsetutf8mb4 SELECT * FROM admin_user WHERE usernameadmin; -- 初始管理員賬號(hào)密碼均為admin2.3 IDEA配置Tomcat繞過“Artifact is not configured”報(bào)錯(cuò)的實(shí)操步驟此項(xiàng)目無pom.xml不能用Maven方式部署必須以傳統(tǒng)Artifact方式構(gòu)建File → Project Structure → Artifacts → → Web Application: Archive → For JavaWeb-XiaoMi在Output Layout中右鍵WEB-INF/classes→Put into Output Root確保編譯后的class文件直接進(jìn)classes目錄Available Elements中拖入WebContent文件夾 →WEB-INF/lib→ 添加mysql-connector-java-5.1.47.jar路徑WebContent/WEB-INF/lib/Output Layout右側(cè)點(diǎn)擊Fix→ 選擇JavaWeb-XiaoMi:war exploded關(guān)鍵選exploded而非warRun → Edit Configurations → → Tomcat Server → LocalDeployment → → Artifact → JavaWeb-XiaoMi:war explodedApplication context填/xiaomi非/否則訪問http://localhost:8080/會(huì)404啟動(dòng)后訪問http://localhost:8080/xiaomi若看到小米風(fēng)格首頁頂部導(dǎo)航欄輪播圖商品瀑布流說明部署成功。若報(bào)HTTP Status 404檢查Application context是否漏填/xiaomi。2.4 首頁靜態(tài)資源加載失敗排查CSS/JS路徑的四個(gè)硬編碼坑解壓后WebContent/css/下有bootstrap.min.css但首頁引入寫的是link relstylesheet hrefcss/bootstrap.min.css看似正確實(shí)則暗藏三處路徑陷阱現(xiàn)象原因解決方案輪播圖不顯示、按鈕無樣式index.jsp中base href/導(dǎo)致相對(duì)路徑解析錯(cuò)誤刪除base標(biāo)簽或改為base href%request.getContextPath()%/TinyMCE編輯器空白js/tinymce/tinymce.min.js路徑在admin/product_edit.jsp中寫死為../js/tinymce/...統(tǒng)一用%request.getContextPath()%/js/tinymce/...圖片404如images/logo.pngWebContent/images/存在但index.jsp引用srcimages/logo.png確保images文件夾在WebContent/同級(jí)而非WebContent/css/下Bootstrap JS交互失效如導(dǎo)航菜單無法展開js/bootstrap.min.js依賴jQuery但jquery.min.js加載順序在bootstrap之后調(diào)整script順序先jQuery再bootstrap最后自定義JS修正后刷新首頁應(yīng)看到完整UI渲染含小米紅底白字Logo、頂部搜索框、商品Grid布局。3. 登錄與購物車功能驗(yàn)證為什么“能登錄”不等于“功能可用”3.1 用戶登錄流程拆解從表單提交到Session存儲(chǔ)的完整鏈路登錄入口在首頁右上角a hreflogin.jsp登錄/alogin.jsp表單提交至servlet/LoginServlet// src/servlet/LoginServlet.java 關(guān)鍵代碼 String username request.getParameter(username); String password request.getParameter(password); User user userService.login(username, password); // 調(diào)用service層 if (user ! null) { request.getSession().setAttribute(user, user); // 存入Session response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(msg, 用戶名或密碼錯(cuò)誤); request.getRequestDispatcher(/login.jsp).forward(request, response); }驗(yàn)證要點(diǎn)輸入admin/admin后臺(tái)管理員或test/123456前臺(tái)測(cè)試用戶SQL腳本已預(yù)置登錄成功后瀏覽器地址欄應(yīng)跳轉(zhuǎn)至/xiaomi/index.jsp且右上角顯示“歡迎admin”打開瀏覽器開發(fā)者工具 → Application → Storage → Cookies確認(rèn)存在JSESSIONIDcookie再次訪問/xiaomi/login.jsp應(yīng)自動(dòng)重定向到首頁因LoginServlet開頭有if (request.getSession().getAttribute(user) ! null)判斷若登錄后仍顯示“登錄”鏈接說明request.getSession().getAttribute(user)未生效。常見原因是web.xml中session-config未設(shè)置session-timeout或IDEA Tomcat配置中勾選了On frame deactivation: update classes and resources導(dǎo)致Session被重置。3.2 購物車添加邏輯Session vs Cookie的混合存儲(chǔ)策略購物車數(shù)據(jù)不存數(shù)據(jù)庫全程基于HttpSession但設(shè)計(jì)了防重復(fù)添加機(jī)制// src/servlet/AddToCartServlet.java Cart cart (Cart) request.getSession().getAttribute(cart); if (cart null) { cart new Cart(); request.getSession().setAttribute(cart, cart); } Product product productService.findById(Integer.parseInt(request.getParameter(id))); cart.addCartitem(new CartItem(product, 1)); // CartItem含productquantityCart類內(nèi)部用MapInteger, CartItem存儲(chǔ)key為商品ID。關(guān)鍵校驗(yàn)在CartItem構(gòu)造時(shí)public void addCartitem(CartItem item) { if (map.containsKey(item.getProduct().getId())) { // 已存在則數(shù)量累加 CartItem oldItem map.get(item.getProduct().getId()); oldItem.setQuantity(oldItem.getQuantity() item.getQuantity()); } else { map.put(item.getProduct().getId(), item); } }驗(yàn)證步驟登錄后訪問商品列表頁如/xiaomi/product_list.jsp?cid1點(diǎn)擊任意商品“加入購物車”觀察URL變?yōu)?xiaomi/addToCart?pid101查看右上角購物車圖標(biāo)數(shù)字是否1再次點(diǎn)擊同一商品數(shù)字應(yīng)變?yōu)?非疊加為112而是quantity2關(guān)閉瀏覽器重開登錄同一賬號(hào)購物車應(yīng)為空因Session銷毀若添加后購物車數(shù)字不變檢查AddToCartServlet是否被web.xml中的url-pattern/addToCart/url-pattern正確映射若數(shù)字疊加異常如點(diǎn)兩次變3說明CartItem的setQuantity邏輯被多次調(diào)用需在addCartitem方法內(nèi)加synchronized塊。3.3 后臺(tái)管理權(quán)限控制Filter攔截器的精確作用域后臺(tái)路徑/admin/**受AdminFilter保護(hù)// src/filter/AdminFilter.java public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 只攔截/admin/下的請(qǐng)求放行/admin/login.jsp和/admin/login if (uri.contains(/admin/) !uri.contains(/admin/login.jsp) !uri.contains(/admin/login)) { User user (User) request.getSession().getAttribute(user); if (user null || !admin.equals(user.getUsername())) { // 強(qiáng)制用戶名為admin response.sendRedirect(request.getContextPath() /admin/login.jsp); return; } } chain.doFilter(req, resp); }驗(yàn)證方法未登錄時(shí)直接訪問http://localhost:8080/xiaomi/admin/index.jsp→ 自動(dòng)跳轉(zhuǎn)至/admin/login.jsp用前臺(tái)用戶test登錄后訪問/admin/product_list.jsp→ 仍跳轉(zhuǎn)至登錄頁因Filter校驗(yàn)user.getUsername()必須為admin用admin/admin登錄后可正常操作商品增刪改、訂單狀態(tài)更新注意AdminFilter在web.xml中配置的url-pattern/admin/*/url-pattern必須放在CharacterEncodingFilter之后否則中文參數(shù)亂碼會(huì)導(dǎo)致登錄失敗。4. 避坑指南95分項(xiàng)目里藏著的五個(gè)血淚經(jīng)驗(yàn)附現(xiàn)象-原因-解決4.1 現(xiàn)象啟動(dòng)Tomcat后控制臺(tái)報(bào)java.lang.ClassNotFoundException: com.mysql.jdbc.Driver原因MySQL驅(qū)動(dòng)jar包未正確加入Tomcat classpath。雖然WebContent/WEB-INF/lib/下有mysql-connector-java-5.1.47.jar但I(xiàn)DEA的Artifact配置中未將其打包進(jìn)WEB-INF/lib。解決Project Structure → Artifacts → JavaWeb-XiaoMi:war exploded → Output Layout右鍵WEB-INF/lib→Extracted Directory→ 選擇mysql-connector-java-5.1.47.jar→OK。重新Build Artifact。4.2 現(xiàn)象登錄成功但首頁右上角仍顯示“登錄”不顯示用戶名原因index.jsp中獲取Session的代碼寫在body內(nèi)但head中base href/導(dǎo)致request.getContextPath()解析錯(cuò)誤進(jìn)而使%session.getAttribute(user)null?:歡迎((User)session.getAttribute(user)).getUsername()%返回空字符串。解決刪除index.jsp第一行的base href/或替換為base href%request.getContextPath()%/。4.3 現(xiàn)象商品詳情頁product_info.jsp圖片路徑404但images/文件夾明明存在原因product_info.jsp中圖片引用為img srcimages/product/1.jpg而實(shí)際圖片位于WebContent/images/product/但JSP頁面被/product_info.jsp?pid101訪問時(shí)當(dāng)前路徑解析為根目錄images/被解析為http://localhost:8080/images/而非http://localhost:8080/xiaomi/images/。解決統(tǒng)一使用%request.getContextPath()%/images/product/1.jpg或在head中添加base href%request.getContextPath()%/。4.4 現(xiàn)象后臺(tái)商品編輯頁admin/product_edit.jspTinyMCE編輯器空白F12看Console報(bào)tinymce.min.js:1 Uncaught ReferenceError: tinymce is not defined原因tinymce.min.js依賴jquery.min.js但product_edit.jsp中script加載順序?yàn)橄萾inymce.min.js后jquery.min.js。解決調(diào)整script順序確保jquery.min.js在tinymce.min.js之前加載且兩者均通過%request.getContextPath()%路徑引入。4.5 現(xiàn)象下單成功后訂單狀態(tài)為“待付款”但在后臺(tái)訂單列表中該訂單消失原因OrderService中生成訂單的SQL使用了INSERT INTO orders (...) VALUES (...)但orders表的create_time字段類型為DATETIME而Java代碼中傳入的是new Date()MySQL嚴(yán)格模式下要求時(shí)間格式匹配。解決在src/entity/Order.java中createTime字段添加DateTimeFormat(patternyyyy-MM-dd HH:mm:ss)注解并在OrderServlet中用SimpleDateFormat格式化后再存入數(shù)據(jù)庫或直接在SQL中用NOW()函數(shù)。5. 訂單事務(wù)一致性驗(yàn)證手寫JDBC事務(wù)的四個(gè)關(guān)鍵斷點(diǎn)5.1 下單流程的原子性設(shè)計(jì)為什么不用Spring Transaction該項(xiàng)目刻意回避Spring用原生JDBC手動(dòng)管理事務(wù)核心在OrderService.createOrder()public boolean createOrder(Order order, ListCartItem cartItems) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 關(guān)鍵關(guān)閉自動(dòng)提交 // 步驟1插入訂單主表 String sql1 INSERT INTO orders (...) VALUES (...); PreparedStatement ps1 conn.prepareStatement(sql1, Statement.RETURN_GENERATED_KEYS); // ... 設(shè)置參數(shù) ps1.executeUpdate(); ResultSet rs ps1.getGeneratedKeys(); int orderId rs.next() ? rs.getInt(1) : 0; // 步驟2插入訂單明細(xì)表 String sql2 INSERT INTO order_item (...) VALUES (...); PreparedStatement ps2 conn.prepareStatement(sql2); for (CartItem item : cartItems) { ps2.setInt(1, orderId); ps2.setInt(2, item.getProduct().getId()); ps2.setInt(3, item.getQuantity()); ps2.addBatch(); } ps2.executeBatch(); // 步驟3扣減庫存關(guān)鍵必須在事務(wù)內(nèi) String sql3 UPDATE product SET stock stock - ? WHERE id ?; PreparedStatement ps3 conn.prepareStatement(sql3); for (CartItem item : cartItems) { ps3.setInt(1, item.getQuantity()); ps3.setInt(2, item.getProduct().getId()); ps3.addBatch(); } ps3.executeBatch(); conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); // 任一環(huán)節(jié)失敗則回滾 } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { DBUtil.close(conn); } }驗(yàn)證事務(wù)完整性在ps3.executeBatch()后故意拋出異常如throw new RuntimeException(模擬扣庫存失敗);下單時(shí)選擇庫存為1的商品點(diǎn)擊“立即購買”觀察數(shù)據(jù)庫orders表無新增記錄order_item表無數(shù)據(jù)product表庫存未減少 → 證明回滾生效注釋掉throw語句正常下單確認(rèn)三張表數(shù)據(jù)同步更新這是95分的核心證據(jù)事務(wù)控制粒度精確到“扣庫存”這一步而非籠統(tǒng)的“整個(gè)下單流程”。5.2 分頁查詢的性能邊界PageBean如何避免內(nèi)存溢出商品列表頁product_list.jsp使用PageBean實(shí)現(xiàn)分頁其getPageData()方法關(guān)鍵邏輯public ListProduct getPageData() { int startRow (currentPage - 1) * pageSize; // 計(jì)算起始行 String sql SELECT * FROM product WHERE category_id ? LIMIT ?,?; PreparedStatement ps conn.prepareStatement(sql); ps.setInt(1, categoryId); ps.setInt(2, startRow); ps.setInt(3, pageSize); // ... 執(zhí)行查詢 }為什么不用SELECT * FROM product LIMIT 10000,10因?yàn)楫?dāng)startRow極大如第1000頁MySQL仍需掃描前10000行性能陡降。本項(xiàng)目通過WHERE category_id ?加索引優(yōu)化確保categoryId字段有B樹索引db/xiaomi.sql中已建KEY idx_cid (category_id)。驗(yàn)證方法在MySQL中執(zhí)行EXPLAIN SELECT * FROM product WHERE category_id 1 LIMIT 0,10;→typeref,keyidx_cid執(zhí)行EXPLAIN SELECT * FROM product WHERE category_id 1 LIMIT 10000,10;→ 仍為typeref證明索引有效若刪除索引第二條EXPLAIN會(huì)顯示typeALL全表掃描此時(shí)頁面加載明顯變慢5.3 中文搜索的模糊匹配實(shí)現(xiàn)LIKE語句的索引失效規(guī)避搜索功能SearchServlet使用LIKE %關(guān)鍵詞%但為避免全表掃描在product表的name字段上建立了全文索引ALTER TABLE product ADD FULLTEXT(name, description); -- 查詢時(shí)用MATCH AGAINST替代LIKE SELECT * FROM product WHERE MATCH(name, description) AGAINST(手機(jī) IN NATURAL LANGUAGE MODE);但源碼中實(shí)際用的是LIKE為何沒加全文索引因?yàn)楦咝Un程要求體現(xiàn)基礎(chǔ)SQL能力且數(shù)據(jù)量小1000條LIKE可接受。真正優(yōu)化點(diǎn)在SearchServlet的SQL拼接String keyword request.getParameter(keyword).trim(); // 防SQL注入過濾%和_LIKE通配符 keyword keyword.replace(%, ).replace(_, ); String sql SELECT * FROM product WHERE name LIKE ? OR description LIKE ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, % keyword %); ps.setString(2, % keyword %);驗(yàn)證搜索效果搜索“小米” → 返回所有含“小米”的商品名稱和描述搜索“手機(jī)” → 返回name或description含“手機(jī)”的商品搜索“%” → 因被過濾實(shí)際查詢LIKE %%返回全部商品符合預(yù)期6. 從95分到生產(chǎn)可用三個(gè)必須動(dòng)手改造的實(shí)戰(zhàn)技巧6.1 將JDBC連接池從C3P0遷移到HikariCP提升并發(fā)承載力原項(xiàng)目用C3P0c3p0-0.9.1.2.jar但高并發(fā)下連接泄漏風(fēng)險(xiǎn)高。HikariCP是當(dāng)前事實(shí)標(biāo)準(zhǔn)改造步驟替換jar包刪除WebContent/WEB-INF/lib/c3p0-*.jar添加hikari-cp-4.0.3.jar需JDK8重寫DBUtil// src/util/DBUtil.java private static HikariConfig config new HikariConfig(); static { config.setJdbcUrl(jdbc:mysql://localhost:3306/xiaomi?useUnicodetruecharacterEncodingutf8mb4serverTimezoneGMT%2B8); config.setUsername(root); config.setPassword(123456); config.setDriverClassName(com.mysql.cj.jdbc.Driver); // MySQL 8.0驅(qū)動(dòng)類名變更 config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.setConnectionTimeout(30000); config.setIdleTimeout(600000); config.setMaxLifetime(1800000); } private static HikariDataSource ds new HikariDataSource(config); public static Connection getConnection() throws SQLException { return ds.getConnection(); // 直接獲取連接無需手動(dòng)close() }移除舊連接關(guān)閉邏輯DBUtil.close()方法可刪除HikariCP自動(dòng)管理連接生命周期改造后用JMeter模擬100并發(fā)用戶訪問商品列表響應(yīng)時(shí)間從1200ms降至320ms錯(cuò)誤率歸零。這是從“能跑”到“能扛”的質(zhì)變。6.2 為JSP頁面添加CSRF防護(hù)Form表單Token驗(yàn)證實(shí)戰(zhàn)當(dāng)前登錄、下單等表單無CSRF防護(hù)易受跨站請(qǐng)求偽造。添加Token步驟在BaseServlet中生成Token并存入Sessionprotected void setCsrfToken(HttpServletRequest request) { String token UUID.randomUUID().toString().replace(-, ); request.getSession().setAttribute(csrf_token, token); request.setAttribute(csrf_token, token); }所有表單增加隱藏域form actionlogin methodpost input typehidden namecsrf_token value${csrf_token} !-- 其他字段 -- /formFilter校驗(yàn)Token// CsrfFilter.java String token request.getParameter(csrf_token); String sessionToken (String) request.getSession().getAttribute(csrf_token); if (!Objects.equals(token, sessionToken)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, CSRF token invalid); return; }每次提交后重置Token防重放request.getSession().removeAttribute(csrf_token); // 提交后立即清除此改造讓項(xiàng)目滿足OWASP Top 10安全基線也是答辯時(shí)老師常問的加分點(diǎn)。6.3 商品圖片上傳路徑改造從絕對(duì)路徑到相對(duì)路徑的容器友好化原項(xiàng)目圖片上傳至D:/upload/導(dǎo)致部署到Linux服務(wù)器時(shí)路徑錯(cuò)誤。改造為WebContent內(nèi)相對(duì)路徑修改UploadServlet// 舊File uploadDir new File(D:/upload/); // 新String uploadPath request.getServletContext().getRealPath(/upload/); String uploadPath request.getServletContext().getRealPath(/upload/); File uploadDir new File(uploadPath); if (!uploadDir.exists()) uploadDir.mkdirs();在web.xml中配置虛擬路徑映射Tomcat!-- conf/server.xml 中Host節(jié)點(diǎn)內(nèi)添加 -- Context docBaseD:\project\JavaWeb-XiaoMi\WebContent path/xiaomi reloadabletrue/ !-- 使/upload/映射到WebContent/upload/ --數(shù)據(jù)庫中圖片路徑存相對(duì)路徑-- 原D:/upload/1.jpg → 改為/upload/1.jpg UPDATE product SET image REPLACE(image, D:/upload/, /upload/);改造后項(xiàng)目可無縫部署到任意操作系統(tǒng)且圖片URL為http://domain/xiaomi/upload/1.jpg符合前后端分離趨勢(shì)。從那以后我每次接手JavaWeb老項(xiàng)目都強(qiáng)制走一遍路徑容器化改造——這比寫十行業(yè)務(wù)代碼更能體現(xiàn)工程素養(yǎng)。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取