生家教課程訂購平臺實戰(zhàn):權(quán)限控制與訂單狀態(tài)機設(shè)計)
1. 一個家教課程訂購項目背后真正的難點角色權(quán)限與訂單狀態(tài)流轉(zhuǎn)1.1 為什么選擇SSM而不是Spring Boot先說結(jié)論如果你是為了快速交差或者生產(chǎn)環(huán)境落地我肯定會推薦Spring Boot但如果你拿到的題目明確是java_ssm52大學(xué)生家教課程訂購平臺那大概率是教學(xué)體系或者畢業(yè)設(shè)計框架里對SSM有硬性要求。這個項目源碼我在IDEA里完整跑通過也帶著幾個學(xué)生從零搭建過SSM這套東西放在今天看確實有點老派但它對理解Java Web底層的幫助是Spring Boot給不了的。SSM指的是Spring Spring MVC MyBatis三件套。Spring負(fù)責(zé)控制反轉(zhuǎn)和面向切面編程Spring MVC負(fù)責(zé)請求路由和參數(shù)綁定MyBatis負(fù)責(zé)SQL與Java對象的映射。這三個框架各管一段鏈路非常清晰瀏覽器發(fā)請求到DispatcherServletHandlerMapping找到對應(yīng)ControllerController調(diào)用Service層Service層通過Mapper接口操作數(shù)據(jù)庫最后ModelAndView或者JSON返回給前端。每一條請求你都看得見摸得著不像Spring Boot那樣把很多東西自動配置掉了出了問題反而不好定位。對于學(xué)習(xí)階段的人來說SSM的麻煩恰恰是它的教學(xué)價值。1.2 項目的三層角色模型拆解這個家教課程訂購平臺涉及三種核心角色大學(xué)生學(xué)生端、教師授課端、管理員平臺端。我見過很多人做這種多角色項目一上來就寫一堆if判斷最后代碼亂成一鍋粥。正確的做法是先想清楚每種角色能干什么、不能干什么再把權(quán)限規(guī)則落到Spring MVC的攔截器里去。學(xué)生端核心訴求是瀏覽課程、按科目或者價格篩選、下單購買、查看已購課程、對教師進行評價。教師端核心訴求是申請成為教師、發(fā)布課程、查看自己被訂購的情況、管理課程上下架。管理員端核心訴求是審核教師入駐申請、審核課程內(nèi)容、處理訂單異常、統(tǒng)計平臺數(shù)據(jù)。這三種角色的菜單和操作邊界完全不同我建議在菜單層面就用session里的role字段做區(qū)分而不是讓前端通過路由隱藏頁面。原因很簡單前端隱藏只是UI層面的后端如果不做攔截懂點技術(shù)的人直接拼接URL就能訪問管理接口這是真實項目中特別常見的漏洞。SSM里用HandlerInterceptor統(tǒng)一處理非常合適。2. IDEA中搭建SSM聚合工程的具體步驟2.1 工程結(jié)構(gòu)與Maven依賴配置我在IDEA里推薦用Maven的多模塊結(jié)構(gòu)雖然單模塊也能跑但多模塊更貼近真實團隊協(xié)作習(xí)慣。建議拆成三個模塊ssm-common通用工具類和常量、ssm-dao數(shù)據(jù)訪問層Mapper接口和XML、ssm-webController層、Service層、前端頁面和配置文件。Maven依賴這塊有幾個關(guān)鍵點Spring核心依賴、Spring MVC依賴、MyBatis和MyBatis-Spring橋接包、數(shù)據(jù)庫驅(qū)動、連接池commonly用的是Druid、Jackson處理JSON、JSP標(biāo)準(zhǔn)標(biāo)簽庫JSTL。版本選擇上我踩過一次坑Spring 4.x和Spring 5.x對javax.servlet-api的依賴版本要求不一樣如果你用的是Tomcat 8.5以上建議直接上Spring 5.2.x否則會出現(xiàn)ClassNotFound異常排查起來很折磨人。一個可以直接參考的pom.xml核心片段dependencies dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version5.2.15.RELEASE/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.2.15.RELEASE/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.6/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.6/version /dependency /dependencies2.2 配置文件里最容易踩的坑數(shù)據(jù)庫連接與事務(wù)SSM的配置文件比Spring Boot多常見的有spring-mvc.xml、spring-mybatis.xml、web.xml、jdbc.properties、mybatis-config.xml。大多數(shù)初學(xué)者都會在這一步卡住原因往往是配置文件名稱不匹配或者掃描包路徑寫錯。我給大家一個排查思路先拆開驗證不要一次性合起來跑。先啟動一個只帶spring-context的最小配置確保Bean能創(chuàng)建再引入MyBatis確保Mapper接口能被掃描到且能創(chuàng)建代理對象最后加入Spring MVC的DispatcherServlet配置。每一步單獨驗證出了問題就知道是哪一層的。jdbc.properties部分注意數(shù)據(jù)庫連接串編碼要和數(shù)據(jù)庫一致jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/tutor_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.passwordyourpassword事務(wù)配置極其重要尤其是訂單這種涉及多表寫入的操作不配事務(wù)就是給自己埋雷。在spring-mybatis.xml里用DataSourceTransactionManager加上tx:advice聲明service層所有方法都受事務(wù)控制默認(rèn)timeout不要設(shè)太短下單流程要校驗庫存、寫訂單表、寫訂單詳情表三步下來很容易超過3秒默認(rèn)值。我的建議是設(shè)為15秒并且把只讀查詢單獨放一個事務(wù)屬性。3. 數(shù)據(jù)庫設(shè)計圍繞課程訂購這條主線的核心表3.1 用戶體系表設(shè)計很多學(xué)生項目會把學(xué)生和教師放在同一張user表里用type字段區(qū)分。這個選擇本身沒錯但要注意教師比學(xué)生多了很多屬性比如教學(xué)科目、教學(xué)年限、個人簡介、審核狀態(tài)。如果全塞在user表里字段會很雜。我用的是基礎(chǔ)用戶表 角色擴展表模式user表只保存登錄憑證和公共資料用戶名、密碼、手機號、頭像、注冊時間、角色類型teacher_info表單獨存教師的擴展信息兩張表通過user_id關(guān)聯(lián)。密碼存儲這塊我多說一句。雖然這是一個課程設(shè)計級別的項目但用MD5直接存明文密碼的做法真的別再用了網(wǎng)上隨便一個在線MD5庫都能撞出來。至少用Spring的BCryptPasswordEncoder加鹽加密。這個類在spring-security-crypto包里單獨引入這個依賴就能用不需要拉入整個Spring Security框架很輕量。3.2 課程與訂單表的約束設(shè)計課程表course的核心字段包括課程名稱、所屬科目分類、教師ID外鍵關(guān)聯(lián)teacher_info、課程簡介、課時數(shù)、單價、封面圖、上架狀態(tài)、創(chuàng)建時間。這里有個細節(jié)很多人沒注意到課程表的價格字段最好用decimal(10,2)不要用float或者double。因為涉及訂單金額計算二進制浮點數(shù)在累加時會出現(xiàn)精度丟失比如0.1加0.2在Java里用double計算結(jié)果是0.30000000000000004這在金額場景是絕對不允許的。訂單主表orders和訂單詳情表order_item是按業(yè)務(wù)需要設(shè)計的。一個訂單可以包含多個課程所以主表管訂單整體狀態(tài)、總金額、下單用戶ID、下單時間、支付狀態(tài)、支付時間詳情表管具體購買了哪門課程、單價、購買數(shù)量、對應(yīng)教師ID。外鍵關(guān)系是orders一對多order_item。訂單表盡量不做物理刪除只用狀態(tài)字段標(biāo)記取消或完成這樣后續(xù)統(tǒng)計報表才有真實數(shù)據(jù)可用。3.3 訂單狀態(tài)機的實現(xiàn)思路訂單狀態(tài)是這個項目最核心的業(yè)務(wù)邏輯。我設(shè)計的狀態(tài)字段是一個int值從0到40表示待支付1表示支付成功2表示已取消3表示已完成4表示申請退款。狀態(tài)變化遵循嚴(yán)格的方向約束待支付可以走支付成功也可以走取消支付成功可以走申請退款也可以走課程完結(jié)已完成申請退款只能走到退款成功或者拒絕退款。如果你不想讓狀態(tài)像野馬一樣亂跳就在Service層做一個狀態(tài)流轉(zhuǎn)校驗方法每次更新訂單之前先判斷當(dāng)前狀態(tài)是否允許跳轉(zhuǎn)到目標(biāo)狀態(tài)。我試過在項目里加一張order_status_log表專門記錄每一次狀態(tài)變更的操作人、操作時間和備注。這個表看起來不起眼但對調(diào)試幫助非常大尤其是訂單狀態(tài)和用戶反饋對不上時查這張表就能知道到底哪一步出了問題。這個設(shè)計在答辯環(huán)節(jié)也是加分項。4. 核心功能實現(xiàn)課程檢索、下單、支付模擬4.1 課程分頁搜索PageHelper還是手寫SQL課程列表頁的核心是分頁搜索條件通常包含科目分類、價格區(qū)間、教師姓名關(guān)鍵詞、上架狀態(tài)。如果只對管理員顯示課程管理列表還要加上審核狀態(tài)。這里我推薦用PageHelper插件配合Mapper XML手寫SQL。雖然MyBatis-Plus等增強框架有更好的內(nèi)置分頁能力但在SSM項目里PageHelper已經(jīng)是社區(qū)驗證過的成熟方案。用法很簡單在Service層查詢前調(diào)用PageHelper.startPage(pageNum, pageSize)后面緊跟的第一次Mapper查詢就會自動拼上LIMIT。返回的結(jié)果用PageInfo包裝這樣前端就能拿到總記錄數(shù)、總頁數(shù)、當(dāng)前頁數(shù)據(jù)這些完整分頁信息。需要提醒的是PageHelper的startPage只作用于緊接著的一次查詢?nèi)绻阍谡{(diào)用startPage之后做了兩次Mapper查詢第二次查詢也會被意外分頁這是一個非常經(jīng)典的誤用場景。我的建議是startPage和查詢方法一定要貼在一起寫中間不要插入任何其他Mapper調(diào)用。4.2 下單流程的并發(fā)控制與防重提學(xué)生點擊購買按鈕后端下單流程是這樣的校驗用戶是否登錄、校驗課程是否上架、校驗課程庫存、創(chuàng)建訂單主表記錄、創(chuàng)建訂單詳情記錄、扣減課程剩余名額、跳轉(zhuǎn)支付。并發(fā)問題出現(xiàn)在校驗庫存到扣減庫存這個時間窗口。兩個用戶同時下單同一門只剩1個名額的課程都通過了校驗然后各自扣減最后庫存變成-1。解決這種問題常規(guī)方案有樂觀鎖和悲觀鎖在SSM項目里我用的是比較務(wù)實的做法在課程表增加一個version字段扣庫存的SQL里帶上version條件影響行數(shù)為0就說明版本沖突讓用戶重新下單或者提示庫存不足。實際執(zhí)行的更新語句大概是這樣的UPDATE course SET stock stock - 1, version version 1 WHERE id #{courseId} AND stock 0 AND version #{version}這句SQL本身就把庫存大于0的判斷放在條件里效果等同于樂觀鎖即使沒有version字段stock 0這個條件也能兜住超賣問題。防重提則靠一個簡單的下單方法冪等控制在訂單主表加user_id和create_time的聯(lián)合索引下單前先查一下最近30秒內(nèi)是否已有同用戶待支付訂單如果有直接返回請先處理已有訂單。4.3 模擬支付與訂單狀態(tài)回調(diào)真實項目接入微信或支付寶支付需要商戶號、證書、回調(diào)域名一堆東西學(xué)生項目通常不需要也不可能有這些條件。這個項目我建議做兩個入口一個是模擬支付跳轉(zhuǎn)頁展示訂單金額和支付二維碼樣式的占位圖點擊模擬支付成功按鈕直接調(diào)支付成功回調(diào)接口另一個是訂單列表里的模擬支付失敗按鈕用來測試支付失敗后訂單保持待支付狀態(tài)的處理邏輯?;卣{(diào)接口的核心動作是更新訂單狀態(tài)為已支付并且記錄支付時間。這里有個很容易被忽視的點回調(diào)接口要做冪等校驗。因為用戶可能因為網(wǎng)絡(luò)原因多次點擊模擬支付成功如果回調(diào)接口不做判斷直接覆蓋狀態(tài)就會把已取消的訂單誤標(biāo)成已支付。我的做法是先根據(jù)訂單號查訂單當(dāng)前狀態(tài)只有待支付狀態(tài)的訂單才允許改為已支付否則直接返回操作失敗。5. 管理端與統(tǒng)計功能怎么做得不雞肋5.1 教師審核與課程上下架管理端最核心的兩個流程是教師入駐審核和課程上下架管理。教師注冊后狀態(tài)默認(rèn)是待審核只有管理員審核通過后教師才能登錄系統(tǒng)發(fā)布課程。審核流程的業(yè)務(wù)邏輯其實就是一個狀態(tài)更新操作但真正的難點在于狀態(tài)改了以后相關(guān)頁面要能實時感知。比如教師登錄后被提示審核中暫無法發(fā)布課程他無法立刻看到自己已經(jīng)通過審核因為登錄時把角色狀態(tài)寫死在session里了。我用了一個變通方案不再把審核狀態(tài)常駐session而是每次教師進入發(fā)布課程頁面時實時從數(shù)據(jù)庫查一份最新狀態(tài)這樣管理員那邊審核通過教師刷新頁面就能生效不用重復(fù)登錄。課程上下架管理更簡單直接管理員列表里展示所有課程的當(dāng)前狀態(tài)點上架/下架按鈕時調(diào)一個接口切換狀態(tài)字段。這里注意課程下架時要順帶判斷有沒有未完成的訂單正在關(guān)聯(lián)該課程如果有提示管理員先處理訂單不要粗暴下架否則用戶體驗會很差。5.2 數(shù)據(jù)統(tǒng)計的簡單實現(xiàn)近7日訂單量每個管理后臺都要有一個數(shù)據(jù)看板學(xué)生項目一般不需要做復(fù)雜的圖表但至少要有幾個核心數(shù)字注冊用戶總數(shù)、付費訂單總數(shù)、平臺總流水、近7日訂單趨勢。近7日訂單量的SQL寫法有一定的代表性我直接給出思路以日期為分組維度統(tǒng)計每天已支付訂單的數(shù)量和金額。有人會用GROUP BY DATE_FORMAT(create_time, %Y-%m-%d)這沒問題但會有一個坑某天沒有訂單時這一天的數(shù)據(jù)就是空的圖表上會斷檔。更專業(yè)的做法是先在Java代碼里生成一個近7天的日期列表然后逐天查訂單量沒有訂單的日期補0。SELECT DATE(create_time) AS order_date, COUNT(*) AS order_count, SUM(total_amount) AS total_amount FROM orders WHERE status 1 GROUP BY DATE(create_time) ORDER BY order_date DESC6. 從IDEA啟動到部署Tomcat配置與常見問題排查6.1 本地運行環(huán)境準(zhǔn)備我強烈建議統(tǒng)一環(huán)境版本不然真的會很折磨人。JDK用1.8Maven用3.6.3Tomcat用8.5.xMySQL用5.7或者8.0都可以。IDEA版本不太挑2019及以上版本都能正常跑SSM項目。導(dǎo)入項目到IDEA時注意不要直接Open整個文件夾建議用Maven方式導(dǎo)入File - Open選擇pom.xmlIDEA會識別出這是一個Maven項目并自動下載依賴。如果網(wǎng)絡(luò)狀況不好第一次構(gòu)建會卡很長時間這也是很多學(xué)生一上來就放棄的坎。解決辦法是給Maven配置阿里云鏡像。Tomcat配置方面IDEA里點Run - Edit Configurations添加Tomcat Server - Local設(shè)置好Tomcat的安裝目錄后在Deployment頁簽里添加Artifact選擇war exploded。用war exploded模式的好處是支持熱部署改完JSP或者Java類后不用頻繁重啟。訪問路徑建議設(shè)置成/否則每次打開都是http://localhost:8080/項目名/頁面里干的相對路徑還容易出問題。6.2 我實測遇到過的三個典型運行問題第一個典型問題是數(shù)據(jù)庫連接串沒有加serverTimezone參數(shù)MySQL 8.0以上會直接報CLIENT_PLUGIN_AUTH或者Communications link failure。解決辦法是在jdbc.url后面加上serverTimezoneAsia/Shanghai記得把驅(qū)動換成com.mysql.cj.jdbc.Driver。第二個典型問題是訪問首頁出現(xiàn)了Tomcat默認(rèn)的404錯誤頁面排查半天發(fā)現(xiàn)是因為web.xml里的DispatcherServlet攔截了/請求但沒有配置HomeController的RequestMapping為/index。這個問題的本質(zhì)是Controller映射和服務(wù)端配置對不上建議把首頁請求映射到Controller里一個顯示課程列表的方法而不是直接找靜態(tài)index.jsp。因為首頁需要從數(shù)據(jù)庫加載課程數(shù)據(jù)走Controller才合理。第三個典型問題是JSP頁面無法顯示JSTL標(biāo)簽頁面上直接輸出大段代碼。原因是pom.xml里引入JSTL的坐標(biāo)不對或者Tomcat運行時沒有把jstl-api和jstl-impl都帶上。更常見的坑是引入的方式混淆了Java EE的舊包和Jakarta的新包。SSM項目里用javax.servlet:jstl版本1.2就行別引成2.x的新坐標(biāo)。6.3 項目源碼如何使用與二次開發(fā)方向如果你拿到的是完整源碼第一步不要急著跑先看README或者項目結(jié)構(gòu)說明。把數(shù)據(jù)庫腳本導(dǎo)入MySQL檢查jdbc.properties里的賬號密碼是否和自己本地一致。然后用IDEA以Maven項目方式打開等待依賴下載完成后配置好Tomcat啟動即可。這個項目可以擴展的方向其實很多我梳理了幾個比較接地氣的方向第一是引入Redis做課程熱榜緩存減少高頻課程列表對數(shù)據(jù)庫的壓力第二是在線支付接入真實沙箱環(huán)境把模擬支付替換成支付寶的沙箱API這個在簡歷上是實打?qū)嵉牧咙c第三是增加站內(nèi)信功能教師收到新訂單提醒、管理員收到新入駐申請?zhí)嵝训谒氖钦n程評價體系細化為多維度評分比如講解清晰度、課程難度、時間守時性。我個人的建議是如果要用這個項目參加面試一定要能講清楚兩件事一是訂單并發(fā)控制是怎么做的二是權(quán)限攔截是怎么實現(xiàn)的。這兩塊是面試官最愛深入追問的點你理解越透徹項目就越有說服力。如果只是做課程設(shè)計作業(yè)把狀態(tài)機和統(tǒng)計功能做扎實配合一份清晰的文檔和截圖拿到一個理想的成績基本沒有問題。