旅行一站式定制平臺核心設(shè)計)
1. 畢業(yè)季旅游平臺的項目定位與業(yè)務(wù)邊界每年六七月大學的班級群和宿舍群里總會冒出大量畢業(yè)旅行求組隊求推薦路線的消息。畢業(yè)旅行和普通旅游的最大區(qū)別在于這不是一次說走就走的個人行程而是一個涉及多人統(tǒng)籌、預算分攤、時間協(xié)調(diào)的集體項目。有人管錢有人管路線有人管拍照還有人負責對付那些都可以型選手——你問他想去哪他說隨便你定了方案他又說不好。做過一次班級畢業(yè)旅行組織工作的人應(yīng)該都能體會這種需求采集靠接龍、路線規(guī)劃靠百度、費用核算靠備忘錄的原始狀態(tài)。這個項目的出發(fā)點就是把上面這一堆亂糟糟的線下協(xié)作搬到一個Web平臺上。系統(tǒng)的角色模型很清晰普通學生用戶能夠瀏覽和報名畢業(yè)旅行路線、提交定制需求班級或團體組織者可以創(chuàng)建團體行程、發(fā)起成員邀請、分攤費用并統(tǒng)一管理報名名單旅游服務(wù)商或管理員則負責發(fā)布路線方案、處理定制訂單、維護景點和酒店資源。整個系統(tǒng)背后有一個核心業(yè)務(wù)邏輯它服務(wù)的是一群人一起定制一次旅行而不是一個人自己訂一張票。這個定位差異直接影響了好幾個設(shè)計決策。比如訂單表必須區(qū)分個人訂單和團體訂單團隊訂單的狀態(tài)流轉(zhuǎn)比個人訂單復雜得多——有人繳費了有人還沒繳有人臨時退出需要退款有人想改行程方案。再比如路線推薦不能只按景點熱度排序還要考慮一個班級幾十號人的集合地點、預算區(qū)間、出行天數(shù)。我當時做這個項目的時候最深的體會是業(yè)務(wù)邊界想清楚了表結(jié)構(gòu)自然就出來了業(yè)務(wù)邊界含糊后邊所有代碼都在打補丁。這個項目適合兩類人參考一類是準備做畢業(yè)設(shè)計的Java方向?qū)W生它涵蓋的技術(shù)面比較均衡——Spring Boot、MyBatis、前端頁面、權(quán)限管理、訂單狀態(tài)機、文件上傳下載足夠撐起一個合格畢設(shè)的體量另一類是工作中需要快速搭建一個中小型業(yè)務(wù)系統(tǒng)的人可以重點看我后面講到的訂單狀態(tài)設(shè)計和團體資金分攤思路。2. 為什么選Spring Boot做底座時序、依賴與工程結(jié)構(gòu)現(xiàn)在Spring Boot幾乎成了Java Web項目的默認起點但做這個項目時還是有幾個人問我為什么不用傳統(tǒng)的SSM分層再加一堆XML配置為什么不用現(xiàn)成的若依框架直接二開2.1 Spring Boot解決的核心痛點傳統(tǒng)SSM時代一個項目從零搭建要經(jīng)歷引入十多個Maven依賴、配置web.xml、配置spring-mvc.xml、配置mybatis-config.xml、配置數(shù)據(jù)源、處理靜態(tài)資源映射……光是把環(huán)境跑起來就能耗掉一兩天。而Spring Boot的自動配置機制把大部分重復勞動收掉了它的核心邏輯可以理解為classpath里有Spring MVC的依賴我就自動幫你配好DispatcherServlet有HikariCP和mysql驅(qū)動我就自動幫你配好數(shù)據(jù)源。項目里只需要保留那些我這套系統(tǒng)特有的配置——比如自定義攔截器要注冊、Redis連接參數(shù)要指定、文件上傳大小要放開。我用的是Spring Boot 2.x系列Java 8這個組合今天看起來不算最前沿但作為框架學習項目非常穩(wěn)。Spring Boot 3.x要求Java 17起步換了javax到j(luò)akarta的包名很多教程和案例還停留在老寫法對于畢設(shè)來說如果導師給的參考代碼、學校機房環(huán)境都偏向老一套用3.x反而容易把自己卡住。我的建議是如果不追求版本號好看2.7.x是當下兼容性最好、踩坑信息最全的選擇。2.2 關(guān)于要不要自己寫前端這個平臺的前端部分我最終采用了服務(wù)端模板加少量原生JavaScript的方案。原因有三第一這是畢設(shè)核心評分點在業(yè)務(wù)邏輯和技術(shù)棧覆蓋把大量精力花在Vue或React腳手架搭建上性價比不高第二后端渲染對于權(quán)限控制、登錄態(tài)校驗這些場景天然友好頁面跳轉(zhuǎn)時后端攔截器直接把關(guān)不容易出現(xiàn)接口裸露的越權(quán)問題第三Thymeleaf和JSP這類模板技術(shù)的學習曲線平緩改起來快出現(xiàn)問題時腦子里能完整還原請求到響應(yīng)的鏈路。如果你前端能力比較強用前后端分離加Vue會是個加分項API文檔寫到Swagger里答辯時展示接口測試截圖也好看。但要注意一個現(xiàn)實問題前后端分離意味著你在處理跨域、Token刷新、接口鑒權(quán)這些環(huán)節(jié)都要額外花時間而且答辯時老師可能會盯著前端源碼問雙向綁定原理。我自己見過不少同學前后端分離做了兩個月最后卡在nginx部署環(huán)節(jié)反而得不償失。2.3 我的標準工程結(jié)構(gòu)清單一個清晰的分層結(jié)構(gòu)是這個項目后面不失控的關(guān)鍵。我的目錄組織如下com.example.travel ├── controller // 控制層只做參數(shù)接收與結(jié)果封裝 │ ├── admin // 后臺管理接口 │ ├── user // 用戶端接口 │ └── order // 訂單相關(guān)接口 ├── service // 業(yè)務(wù)層核心邏輯全部寫在這里 │ ├── impl // 接口實現(xiàn) ├── mapper // MyBatis的Mapper接口 ├── entity // 數(shù)據(jù)庫實體類也可叫pojo/domain ├── dto // 數(shù)據(jù)傳輸對象接收前端參數(shù) ├── vo // 視圖對象返回前端數(shù)據(jù) ├── config // 配置類攔截器、跨域、上傳配置等 ├── common // 通用返回結(jié)果、異常處理、工具類 └── TravelApplication.java這里我特別想強調(diào)一點不要為了圖省事把業(yè)務(wù)邏輯直接寫在Controller里。我見過很多人寫一人端項目所有代碼堆在Controller里兩三行搞定一個方法看起來很快但等你寫訂單狀態(tài)流轉(zhuǎn)、退款邏輯、多人報名費用分攤這些復雜業(yè)務(wù)時沒有Service層兜底代碼會迅速膨脹到完全無法維護。一個實用的判斷標準是Controller方法里如果出現(xiàn)了if-else分支超過三層或者需要連續(xù)調(diào)用三次以上mapper方法這段邏輯就該下沉到Service。3. 一站式定制的核心鏈路從需求采集到訂單落地這個平臺叫一站式定制那它到底定制了什么口頭說一個概念很容易但落到系統(tǒng)功能上必須把定制流程拆成一條用戶能感知到的主干鏈路。我最終把它拆成了四個環(huán)節(jié)發(fā)起需求、智能推薦、方案確認、支付成團。3.1 需求采集模塊的業(yè)務(wù)模型畢業(yè)旅行需求不是一張簡單的姓名手機號想去哪表單。我的需求表travel_demand字段設(shè)計為出行天數(shù)、出發(fā)城市、預算范圍、偏好主題海邊/古鎮(zhèn)/爬山/城市漫游、成團人數(shù)、期望出行日期段、特殊要求備注。之所以把出發(fā)城市單獨拎出來是因為畢業(yè)旅行的集合出發(fā)地高度集中在學校所在城市服務(wù)商在設(shè)計路線時必須從指定城市出發(fā)才能談成團。這個模塊最容易忽略的是成團人數(shù)字段。個人可以一個人發(fā)起定制但服務(wù)商能否接單取決于人數(shù)是否達到可盈利的成團線。所以我給系統(tǒng)加入了一個規(guī)則需求提交后進入待匹配狀態(tài)后臺服務(wù)商可以瀏覽需求池符合條件則報價接單如果需求在72小時內(nèi)無人接單狀態(tài)自動變?yōu)槲雌ヅ溆脩艨梢允盏教崾静⒄{(diào)整參數(shù)重新提交。這個邏輯讓定制服務(wù)不是一句空話而是變成了系統(tǒng)里真實跑得通的狀態(tài)流轉(zhuǎn)。3.2 路線推薦權(quán)重評分而不是猜你喜歡很多同學做推薦功能一上來就寫協(xié)同過濾但畢業(yè)旅行平臺的場景下數(shù)據(jù)稀疏是硬傷——新用戶沒有行為歷史新路線沒有評分數(shù)據(jù)你拿什么算相似度這個平臺上我更傾向于做一個基于規(guī)則的路線匹配引擎。具體做法是給每條路線打上屬性標簽主題、天數(shù)、預算檔位、出發(fā)城市、人均價格用戶提交需求后按以下規(guī)則計分主題匹配權(quán)重50分天數(shù)落在期望區(qū)間內(nèi)權(quán)重30分預算差值最小權(quán)重15分價格比期望高太多直接淘汰近30天下單人數(shù)權(quán)重5分最后按總分排序推薦Top5。這個方案實現(xiàn)簡單、解釋性強答辯的時候你能跟老師講清楚每一分是怎么來的比黑盒式推薦算法更容易獲得認可。而且畢業(yè)旅行本來就帶有很強的社交屬性用戶更看重跟誰去而不是平臺推什么算法在這里的位置本來就應(yīng)該輕一些。3.3 團體行程的組裝邏輯團體項目和個人項目的根本區(qū)別在于個人項目選好路線直接下單即可團體項目則需要先有團再往團里加人最后才談路線定制和費用。我在系統(tǒng)里設(shè)計了旅行團表travel_group和成員表group_member前者存團長ID、成團人數(shù)上限、當前狀態(tài)招募中/定制中/已成團/已出行/已完成后者記錄每個成員的報名狀態(tài)和繳費狀態(tài)。這里有個細節(jié)值得展開團體報名是允許部分成員先報名、其他人后續(xù)加入的所以成員表里必須有個獨立的報名字段而不是用訂單狀態(tài)去隱式推斷。我把每個成員的參與狀態(tài)做成獨立枚舉待確認、已確認、已繳費、已退款、已退出。這樣團長看到的數(shù)據(jù)就是一張誰還沒繳費列表系統(tǒng)的催繳通知可以根據(jù)這張表精準發(fā)送到人而不是群發(fā)全團造成困惑。3.4 訂單狀態(tài)機與實際金額計算訂單表我把狀態(tài)設(shè)計成待付款PENDING、已付款PAID、定制中CUSTOMIZING、待出行UPCOMING、已完成COMPLETED、已取消CANCELLED、已退款REFUNDED。后端不是用一堆散落的if判斷處理這些狀態(tài)的而是抽出了一個狀態(tài)機工具類統(tǒng)一維護狀態(tài)流轉(zhuǎn)的合法性/** * 訂單狀態(tài)流轉(zhuǎn)校驗 * key: 當前狀態(tài), value: 允許流轉(zhuǎn)到的目標狀態(tài)集合 */ private static final MapOrderStatus, SetOrderStatus TRANSITIONS new EnumMap(OrderStatus.class); static { TRANSITIONS.put(OrderStatus.PENDING, EnumSet.of(OrderStatus.PAID, OrderStatus.CANCELLED)); TRANSITIONS.put(OrderStatus.PAID, EnumSet.of(OrderStatus.CUSTOMIZING, OrderStatus.REFUNDED)); TRANSITIONS.put(OrderStatus.CUSTOMIZING, EnumSet.of(OrderStatus.UPCOMING, OrderStatus.REFUNDED)); TRANSITIONS.put(OrderStatus.UPCOMING, EnumSet.of(OrderStatus.COMPLETED, OrderStatus.REFUNDED)); TRANSITIONS.put(OrderStatus.COMPLETED, EnumSet.of()); TRANSITIONS.put(OrderStatus.CANCELLED, EnumSet.of()); TRANSITIONS.put(OrderStatus.REFUNDED, EnumSet.of()); }實際金額計算里坑最多的是團體單的人均價格乘以人數(shù)這個看似簡單的邏輯。我遇到的情況是路線方案調(diào)整了比如把三星酒店升級成四星服務(wù)商重新報價后已經(jīng)繳費的成員應(yīng)該補差價還沒繳費的成員直接按新價格繳。這個在表結(jié)構(gòu)上不能只改訂單總價需要一個報價變更記錄表quote_change_log把每次改價前后的價格、影響成員、變更原因都記下來否則結(jié)算對賬時根本說不清。4. 數(shù)據(jù)庫設(shè)計畢業(yè)季場景下的核心表結(jié)構(gòu)與索引選擇數(shù)據(jù)庫設(shè)計這個環(huán)節(jié)可以說直接決定了項目后期是越寫越順還是越寫越崩。我的經(jīng)驗是先想清楚業(yè)務(wù)中最核心的實體是哪些再把實體之間的關(guān)聯(lián)關(guān)系理順最后才去畫表、定字段。4.1 九張核心表的組織邏輯整個平臺我最終沉淀出九張核心業(yè)務(wù)表它們之間的關(guān)系如下用戶表user包含用戶ID、昵稱、手機號、密碼、頭像、角色標識角色區(qū)分為普通用戶、服務(wù)商、管理員用整數(shù)字段存枚舉值。密碼存儲用BCrypt加密絕對不允許明文。路線表route路線名稱、封面圖片、主題標簽、出發(fā)城市、目的地、天數(shù)、人均價格、庫存名額、路線描述、狀態(tài)上架/下架。需求表travel_demand用戶ID、出行天數(shù)、預算、主題偏好、出發(fā)城市、成團人數(shù)、特殊要求、狀態(tài)。訂單表order訂單號唯一業(yè)務(wù)編號、用戶ID、團體ID可空、路線ID、訂單金額、支付狀態(tài)、訂單狀態(tài)、創(chuàng)建時間、支付時間。旅行團表travel_group團長ID、團名稱、成團人數(shù)上限、當前人數(shù)、出發(fā)日期、狀態(tài)、備注。成員表group_member團體ID、用戶ID、成員角色、參與狀態(tài)、繳費金額、繳費時間、退出時間。評價表review訂單ID、用戶ID、評分、內(nèi)容、出行后審核標記。報價變更表quote_change_log訂單ID、舊金額、新金額、變更原因、操作人ID、變更時間。公告表notice標題、內(nèi)容、類型、發(fā)布狀態(tài)、發(fā)布時間。這套設(shè)計的核心思想是路線與訂單分離、訂單與團體松耦合關(guān)聯(lián)。路線是靜態(tài)資源可以被多個訂單引用訂單記錄的是交易行為團體是人與組織的關(guān)系載體與訂單通過團體ID關(guān)聯(lián)但不在訂單表里冗余太多團體細節(jié)。4.2 容易被忽視的索引優(yōu)化數(shù)據(jù)量小的時候索引可有可無但答辯老師一定會問這個表字段建索引了嗎你的查詢會不會全表掃描。我在實踐中加了這些索引邏輯也說得通訂單表的user_id, status聯(lián)合索引支撐我的訂單頁面按用戶和狀態(tài)篩選。訂單表的group_id, status支撐某團體的訂單查詢。路線表的status, theme支撐首頁按主題篩選上架路線。需求表的status支撐后臺需求池的待匹配查詢。另外要注意電話號碼字段如果用varchar存建議單獨建普通索引。手機上號頻繁用于登錄查詢每次全表掃描在演示環(huán)境不覺得慢但如果老師現(xiàn)場導入了上萬條模擬數(shù)據(jù)查詢響應(yīng)延遲會非常明顯。4.3 一對多關(guān)系下的批量查詢優(yōu)化一個常見的性能問題是查詢訂單列表時需要同時顯示每個訂單關(guān)聯(lián)的路線名稱和用戶昵稱。很多人的寫法是查完訂單列表后循環(huán)再查路線和用戶這樣一次頁面請求輕松發(fā)出幾十條SQL這在答辯現(xiàn)場如果數(shù)據(jù)量一大就會卡頓。我用MyBatis的collection標簽做了一次性關(guān)聯(lián)查詢resultMap idOrderWithDetailMap typeOrderVo id propertyid columnorder_id/ result propertyorderNo columnorder_no/ collection propertyitems ofTypeOrderItemVo id propertyitemId columnitem_id/ result propertyrouteName columnroute_name/ result propertyprice columnprice/ /collection /resultMap select idselectOrderWithDetail resultMapOrderWithDetailMap SELECT o.id AS order_id, o.order_no, oi.id AS item_id, r.route_name, oi.price FROM order o LEFT JOIN order_item oi ON o.id oi.order_id LEFT JOIN route r ON oi.route_id r.id WHERE o.user_id #{userId} ORDER BY o.create_time DESC /select這條SQL執(zhí)行完內(nèi)存里就能拼出完整的訂單-明細樹結(jié)構(gòu)避免N1查詢問題。注意order是MySQL保留關(guān)鍵字建表時要加上反引號否則SQL直接報錯這是我實際踩過的坑。5. 權(quán)限控制與安全細節(jié)答辯時的高頻問點5.1 登錄態(tài)管理與自定義攔截器畢設(shè)級別的系統(tǒng)不需要做到Spring Security級別但基本的登錄態(tài)校驗、角色權(quán)限隔離、接口防繞過是必須的。我用了Session加自定義攔截器的方式思路是登錄成功后把user對象放進Session。定義一個權(quán)限攔截器注入Spring容器注冊到WebMvcConfigurer的addInterceptors方法中。配置URL規(guī)則攔截什么路徑放過什么路徑。例如/admin/**必須管理員角色/user/**必須登錄用戶/route/list和首頁瀏覽可以放行。這里有個重要的坑靜態(tài)資源要放行否則頁面樣式全掛。我見過不止一個人把攔截器配成/*全攔結(jié)果JS和CSS都被攔截了頁面打開是裸HTML排查半天才發(fā)現(xiàn)是攔截器把靜態(tài)資源攔了。建議放行規(guī)則寫成registry.addInterceptor(authInterceptor) .addPathPatterns(/**) .excludePathPatterns( /, /route/list, /route/detail/**, /user/login, /user/register, /css/**, /js/**, /images/**, /error );自定義注解加攔截器校驗權(quán)限的做法——定義一個RequireRole注解標注在Controller方法上攔截器里讀取注解并校驗當前用戶角色。思路不復雜但答辯時你可以說我通過注解實現(xiàn)了聲明式權(quán)限控制這比我每個方法開頭都判斷一下角色要有說服力得多。5.2 密碼處理與敏感信息防護密碼存儲用了BCrypt加密Spring Security的crypto包里有現(xiàn)成的BCryptPasswordEncoder不需要把整個Spring Security引進來。注冊時加密存儲登錄時用matches方法校驗。這里要提醒Session里只放用戶ID、昵稱、頭像這些必要信息絕對不要把密碼放進Session即使加密過也別放。另一個安全細節(jié)是手機號脫敏。展示用戶信息列表時對手機號中間四位打碼138****1234需要在VO層做處理而不是改數(shù)據(jù)庫數(shù)據(jù)。我專門寫了一個工具方法處理這個既滿足了頁面展示需求又不破壞原始數(shù)據(jù)。文件上傳這個環(huán)節(jié)也要注意安全。頭像上傳和路線圖片上傳都涉及文件類型校驗千萬不要只信任前端傳來的后綴名。我用Content-Type加文件頭校驗雙重檢查并限制文件大小上傳目錄放到了項目外部路徑而不是項目classpath里。這樣一個是防止上傳路徑被覆蓋另一個是把上傳文件與運行代碼物理隔離部署時也更靈活。6. 訂單支付與退款邏輯沒有真支付環(huán)境怎么辦畢業(yè)設(shè)計要展示一個完整電商閉環(huán)支付環(huán)節(jié)繞不開但你又不能在答辯現(xiàn)場真的用商用支付。我的處理方案是模擬支付通道。6.1 模擬支付的三種可行方案第一個方案是直接用數(shù)據(jù)庫事務(wù)模擬——點擊立即支付時前端彈窗展示模擬收銀臺確認后前端請求后端接口后端開啟事務(wù)執(zhí)行余額扣減訂單狀態(tài)變更資金流水記錄然后提交事務(wù)。這個方案代碼量少事務(wù)一致性校驗也能展示出來。第二個方案是接入PayPal沙箱或者支付寶沙箱環(huán)境。沙箱環(huán)境是真實支付網(wǎng)關(guān)的測試環(huán)境可以展示完整的跳轉(zhuǎn)、回調(diào)和驗簽流程。如果導師更看重商業(yè)完整性這個方案更硬核。但缺點是配置流程復雜而且沙箱環(huán)境有時不穩(wěn)定答辯現(xiàn)場如果網(wǎng)絡(luò)出問題就尷尬了。第三個方案是混合方案——系統(tǒng)里做一個支付渠道標識字段正常走模擬支付預留真實支付接口擴展位。我最終選了這個方案既展示了接口設(shè)計的可擴展性又不會在演示時翻車。6.2 退款邏輯與資金流水退款邏輯里最容易漏掉的是部分退款。團體訂單里如果兩個人退出服務(wù)商同意退款那么退款金額不是訂單總額而是這兩個人各自已繳納的金額。因此資金流水表fund_flow必須記錄退款是針對order的哪個明細、退給了哪個用戶、金額多少、剩余待退金額是多少。我的實現(xiàn)要點訂單資金操作全部通過一個資金服務(wù)類處理每個操作同步生成一條fund_flow流水。這樣可以保證無論什么時候?qū)~流水表里的資金進出總和等于訂單金額差不會出現(xiàn)錢對不上賬的問題。這筆賬如果不對齊答辯現(xiàn)場老師問一句退款后總金額怎么驗證你就容易卡殼。7. 從Demo到可演示系統(tǒng)部署與效果優(yōu)化的實戰(zhàn)經(jīng)驗代碼寫完只是第一步能讓它在別人電腦上穩(wěn)定跑起來才是關(guān)鍵。7.1 打包部署的踩坑記錄Spring Boot的打包默認是jar包內(nèi)置Tomcat。但我在打包時遇到過三個問題第一個是打包后靜態(tài)資源404。原因是Thymeleaf模板打包進了BOOT-INF/classes目錄而靜態(tài)資源的路徑匹配規(guī)則打在classpath下沒問題但如果用了src/main/webapp目錄下的資源打jar包時不會自動帶進去。正確做法是把靜態(tài)資源放在src/main/resources/static下。第二個是application.properties里的連接串問題。本地跑的時候數(shù)據(jù)庫地址是localhost到了演示環(huán)境可能數(shù)據(jù)庫在另一臺機器密碼也不同。我的做法是使用profile配置分別維護application-dev.properties和application-prod.properties啟動時用--spring.profiles.activeprod指定環(huán)境避免現(xiàn)場改配置文件。第三個是端口占用。我遇到過打包后啟動直接報端口被占用最后發(fā)現(xiàn)是本地跑了兩個實例。用server.port8081換端口解決同時順手配置了server.servlet.context-path/travel給項目加了統(tǒng)一前綴這樣后續(xù)部署到nginx做反向代理也更規(guī)范。7.2 演示數(shù)據(jù)的準備策略答辯時系統(tǒng)里空蕩蕩的大列表非常減分。我提前準備了兩套演示數(shù)據(jù)一套是路線數(shù)據(jù)每個主題類別放3到5條路線配上合適的圖片圖片記得用本地文件不要依賴外部圖床現(xiàn)場沒網(wǎng)就全掛了另一套是訂單數(shù)據(jù)構(gòu)造出已付款、定制中、已完成、已取消狀態(tài)齊全的列表方便現(xiàn)場演示不同狀態(tài)下的操作流程。這里有個經(jīng)驗演示數(shù)據(jù)的時間區(qū)間要合理比如當前日期是6月訂單的出行日期安排在6月底到7月初才算自然如果出行日期顯示在去年老師一眼就看出是假數(shù)據(jù)。而且最好準備一個模擬團體團長賬號登錄后能看到團里的成員名單有人已繳費有人未繳費這樣展示催繳功能時有真實感比現(xiàn)場新創(chuàng)建一個空白團體更有說服力。7.3 列表分頁與查詢性能管理端的訂單列表、路線列表都做了分頁用的MyBatis-Plus的分頁插件。分頁這件事看似簡單但有個隱藏細節(jié)前端傳頁碼時頁碼從1開始而后臺分頁插件內(nèi)部計算offset是page-1如果前后端約定的邊界不一致會出現(xiàn)第一頁數(shù)據(jù)空白的情況。我在自定義分頁對象里統(tǒng)一處理了這個偏移邏輯并且前端頁面確認從1開始傳參。首頁的路線篩選用了多條件動態(tài)SQLMyBatis的if標簽在where條件后拼接注意每個條件都要帶上and前綴且第一個條件不能有多余and。這里有個規(guī)范寫法where標簽配合trim prefixWHERE prefixOverridesAND|OR可以自動處理多余and問題避免SQL語法錯誤。8. 關(guān)于這個項目我想最后補充的幾個認知做完整個項目我最想分享的其實不是某個技術(shù)點而是幾個底層認知。第一個認知是畢業(yè)設(shè)計項目的評分下限由功能完整性決定上限由你對業(yè)務(wù)邏輯的解釋能力決定。功能齊全、頁面能跑、流程順暢這是能過的基礎(chǔ)但如果你想拿優(yōu)秀必須能在答辯時把一個業(yè)務(wù)決策的前因后果講清楚——為什么訂單狀態(tài)要設(shè)計成這幾個而不是兩個為什么團體和個人要分表為什么要設(shè)計報價變更記錄表這些問題的答案就是你這篇論文或項目的靈魂。第二個認知是做項目時想清楚再動手比邊做邊想省的時間多得多。我前期在表結(jié)構(gòu)設(shè)計上花了大約一半的開發(fā)時間后面真正寫代碼反而很快因為字段關(guān)系理清了ServiceImpl里的每個方法基本就是按業(yè)務(wù)流程一條龍寫下來。如果你發(fā)現(xiàn)自己寫著寫著頻繁回去改表加字段停下來想一想很可能是前面業(yè)務(wù)建模就沒立住。第三個認知是從一次演示中得來的。當時我給老師演示團體費用分攤功能恰好把一個繳費狀態(tài)演示得很清楚老師順著問了句如果一個人支付成功但系統(tǒng)突然斷電你怎么保證不會重復扣款。這個問題提醒我支付相關(guān)的核心操作必須用事務(wù)加冪等鍵保護。我在模擬支付的接收接口里做了一個請求冪等鍵的表單字段后端根據(jù)訂單號加操作類型生成唯一冪等記錄重復提交直接拒絕。這種細節(jié)平時不起眼但它是整個系統(tǒng)可信度的支撐。最后一個建議是不要只把項目當成作業(yè)把每個模塊的設(shè)計決策記錄下來哪怕只是簡單的文檔。這會幫你在答辯時快速回憶起當初為什么這么做也會讓整個項目顯得有思考、有沉淀。我當初把開發(fā)日志寫在項目根目錄的README里包括每個階段的改動原因最后寫設(shè)計文檔的時候發(fā)現(xiàn)最值錢的素材早就在那里等著了。