平臺(tái)全棧設(shè)計(jì)與實(shí)現(xiàn))
1. 項(xiàng)目背景與整體設(shè)計(jì)思路這個(gè)選題我第一眼看到就想拍大腿典型的技術(shù)棧好看業(yè)務(wù)能落地型畢設(shè)題目。Spring Boot做后端接口Android做前端移動(dòng)端中間再配上MySQL存數(shù)據(jù)一套完整的家教服務(wù)平臺(tái)就出來(lái)了。你說(shuō)它是管理系統(tǒng)其實(shí)本質(zhì)上是一個(gè)雙向匹配的在線平臺(tái)——家長(zhǎng)端找家教、家教端接單子中間夾雜訂單、評(píng)價(jià)、排課、結(jié)算這一條完整的業(yè)務(wù)鏈。先聊聊為什么這個(gè)題適合做畢業(yè)設(shè)計(jì)因?yàn)樗膹?fù)雜度剛好卡在能把你和其他人區(qū)分開(kāi)的位置。你要是只做一個(gè)普通的CRUD管理系統(tǒng)老師一眼就能看穿但加上訂單狀態(tài)機(jī)、教師等級(jí)認(rèn)證、家長(zhǎng)評(píng)價(jià)體系之后整個(gè)系統(tǒng)的業(yè)務(wù)完整性立刻不一樣了。而且Spring Boot Android這個(gè)組合本身就自帶話題性答辯的時(shí)候老師問(wèn)什么你都能接得上話。再說(shuō)說(shuō)場(chǎng)景怎么拆。一個(gè)合格的家教平臺(tái)至少要覆蓋三類角色管理員、家長(zhǎng)、家教老師。家長(zhǎng)端要能瀏覽老師列表、按科目篩選、看評(píng)分、下單預(yù)約家教老師要能上傳資質(zhì)、管理自己的課程時(shí)間、接受或拒絕訂單管理員則要負(fù)責(zé)審核老師資質(zhì)、處理投訴、統(tǒng)計(jì)平臺(tái)數(shù)據(jù)。這三條線合在一起才是完整的家教管理系統(tǒng)缺了任何一條線都只是半成品。從技術(shù)演進(jìn)的角度看其實(shí)現(xiàn)在很多人會(huì)糾結(jié)要不要用小程序替代Android端我的看法是畢設(shè)直接用Android原生就行原因后面工具選型部分詳細(xì)說(shuō)。這里先明確一個(gè)共識(shí)——做這個(gè)項(xiàng)目的核心目標(biāo)不是真的上線運(yùn)營(yíng)而是通過(guò)一個(gè)完整的全棧項(xiàng)目展示你對(duì)前端交互后端接口數(shù)據(jù)庫(kù)設(shè)計(jì)的整合能力。2. 技術(shù)選型與核心原理2.1 Spring Boot 3.x MyBatis Plus 的組合邏輯后端我推薦直接用Spring Boot 3.x配合MyBatis Plus這是目前Java系畢設(shè)里的主流搭配。Spring Boot負(fù)責(zé)把整個(gè)Web服務(wù)的骨架搭起來(lái)內(nèi)嵌Tomcat一行代碼都不用配就能跑起來(lái)這對(duì)時(shí)間緊張的畢業(yè)生來(lái)說(shuō)太友好了。MyBatis Plus相比純MyBatis的優(yōu)勢(shì)在于單表CRUD根本不用寫SQLBaseMapper里那些insert、selectById、updateById直接拿來(lái)用省下來(lái)的時(shí)間都?jí)蚰惆延唵螤顟B(tài)機(jī)多寫兩個(gè)狀態(tài)了。不過(guò)我要提醒一句MyBatis Plus不是萬(wàn)能藥多表關(guān)聯(lián)查詢老老實(shí)實(shí)自己寫XML里的SQL別硬套它那個(gè)Wrapper很多時(shí)候查出來(lái)的字段對(duì)不上會(huì)讓你排查到懷疑人生。接口設(shè)計(jì)上走RESTful風(fēng)格統(tǒng)一返回Result對(duì)象code message dataAndroid端拿到這個(gè)結(jié)構(gòu)體再解析協(xié)作效率會(huì)高很多。權(quán)限認(rèn)證用JWT登錄成功后頒發(fā)一個(gè)有效期為24小時(shí)的tokenAndroid端保存到SharedPreferences里每次請(qǐng)求帶上Authorization頭就行。為什么不用SessionAndroid端不像瀏覽器會(huì)自動(dòng)管理Cookie用JWT免去了很多會(huì)話同步的麻煩而且天然支持無(wú)狀態(tài)擴(kuò)展這點(diǎn)在答辯的時(shí)候可以拿出來(lái)說(shuō)。2.2 Android原生開(kāi)發(fā)到底選Java還是Kotlin現(xiàn)在去搜Android開(kāi)發(fā)相關(guān)內(nèi)容鋪天蓋地都是Kotlin但我不建議畢設(shè)一上來(lái)就啃Kotlin除非你之前已經(jīng)用Kotlin寫過(guò)項(xiàng)目。原因是大部分學(xué)校的Android課程還是用Java教的你需要在短時(shí)間內(nèi)同時(shí)搞明白Activity生命周期、RecyclerView適配器、網(wǎng)絡(luò)請(qǐng)求回調(diào)這幾個(gè)硬骨頭沒(méi)必要再疊加一門新語(yǔ)言的認(rèn)知負(fù)擔(dān)。當(dāng)然你完全可以最后沖刺階段把代碼重構(gòu)成Kotlin這屬于錦上添花。但核心邏輯用Java寫完并且能跑通比什么都強(qiáng)。Android端架構(gòu)上我建議MVP就夠了Activity當(dāng)View層Presenter負(fù)責(zé)業(yè)務(wù)邏輯Model管數(shù)據(jù)。MVVM也行但LiveData ViewModel這套組合對(duì)于畢設(shè)來(lái)說(shuō)有點(diǎn)over-engineering你還要處理生命周期綁定的細(xì)節(jié)時(shí)間不劃算。網(wǎng)絡(luò)框架用OkHttp Retrofit。Retrofit的注解式接口定義方式非常適合這種前后端分離的協(xié)作模式后端接口一定義好Android端照著接口文檔寫interface就行自動(dòng)把JSON解析成Java對(duì)象。JSON解析就用Gson簡(jiǎn)單直接都是Java系的東西配合格外順暢。2.3 數(shù)據(jù)庫(kù)選型和表結(jié)構(gòu)設(shè)計(jì)的第一性原理MySQL 8.0是標(biāo)配這沒(méi)什么好糾結(jié)的。關(guān)鍵在于表結(jié)構(gòu)怎么設(shè)計(jì)——這決定了你的業(yè)務(wù)邏輯是清晰還是混亂。我見(jiàn)過(guò)太多人做畢設(shè)時(shí)隨手下兩張表就開(kāi)始寫代碼結(jié)果到后面訂單狀態(tài)一復(fù)雜整個(gè)系統(tǒng)直接崩盤。一個(gè)家教平臺(tái)的核心表我建議至少包含這些用戶表user、老師詳情表teacher_info、科目表subject、老師科目關(guān)聯(lián)表teacher_subject、訂單表order、排課表course_schedule、評(píng)價(jià)表review、收藏表favorite、公告表notice。這些表之間的外鍵關(guān)系要不要物理外鍵我建議不要邏輯外鍵就夠了。畢業(yè)設(shè)計(jì)階段物理外鍵的維護(hù)成本大于收益而且你答辯時(shí)完全可以說(shuō)生產(chǎn)環(huán)境通常禁用物理外鍵以保證擴(kuò)展性這反而是加分項(xiàng)。訂單狀態(tài)這塊單獨(dú)強(qiáng)調(diào)一下用int類型存狀態(tài)碼0待支付、1待上課、2已上課、3已完成、4已取消、5退款中。不要直接存字符串狀態(tài)碼配合常量類或者枚舉類使用可讀性照樣高而且后續(xù)要統(tǒng)計(jì)所有退款訂單這種數(shù)據(jù)的時(shí)候?qū)慡QL會(huì)舒服很多。3. 后端核心模塊設(shè)計(jì)與實(shí)現(xiàn)3.1 基于JWT的登錄認(rèn)證與角色權(quán)限控制登錄這塊是整個(gè)系統(tǒng)的入口做得不好后面全崩。我的方案是登錄接口接收手機(jī)號(hào)和密碼校驗(yàn)通過(guò)后用JJWT庫(kù)生成tokentoken的payload里塞userId和role字段1管理員、2家長(zhǎng)、3老師然后返回給客戶端。后續(xù)請(qǐng)求通過(guò)攔截器解析token把用戶信息放到ThreadLocal里業(yè)務(wù)層隨時(shí)能拿到當(dāng)前操作人。角色權(quán)限控制用一個(gè)自定義注解RequireRole配合Spring攔截器在handler執(zhí)行前做校驗(yàn)。比如發(fā)布公告這個(gè)接口標(biāo)注RequireRole(1)家教接單接口標(biāo)注RequireRole(3)家長(zhǎng)下單接口標(biāo)注RequireRole(2)。代碼看起來(lái)是這樣PostMapping(/order/create) RequireRole(2) public ResultString createOrder(RequestBody OrderCreateRequest request) { // 業(yè)務(wù)邏輯... }攔截器里先解析token再檢查當(dāng)前用戶角色是否匹配注解要求不匹配直接返回403。另外密碼存儲(chǔ)必須用BCrypt加密spring-security-crypto這個(gè)依賴單獨(dú)拎出來(lái)用就行不需要引入整套Spring Security。BCrypt有個(gè)特點(diǎn)每次加密同一個(gè)密碼得到的密文都不一樣因?yàn)閮?nèi)部帶了隨機(jī)鹽這比MD5那種固定哈希值安全一個(gè)數(shù)量級(jí)。3.2 家教檢索的核心算法與實(shí)現(xiàn)家長(zhǎng)端最核心的功能就是搜索家教這決定了平臺(tái)的使用體驗(yàn)。我的實(shí)現(xiàn)邏輯是按科目id 區(qū)域 價(jià)格區(qū)間 綜合評(píng)分做組合篩選分頁(yè)返回教師列表。綜合評(píng)分的計(jì)算是(科目匹配分?jǐn)?shù)×0.4 教齡分?jǐn)?shù)×0.2 評(píng)價(jià)分?jǐn)?shù)×0.4)每個(gè)維度都?xì)w一化到0-5分用這個(gè)算出來(lái)的分?jǐn)?shù)做排序。這個(gè)算法的關(guān)鍵在于打分的規(guī)則要講得清楚答辯的時(shí)候這是重點(diǎn)展示環(huán)節(jié)。你在代碼里封裝一個(gè)ScoreCalculator類從teacher_subject表里取科目匹配度從teacher_info里取教齡從review表里AVG出評(píng)價(jià)分然后按權(quán)重加總。排序整合在SQL里做也行但如果在Java層計(jì)算你就必須注意N1查詢的問(wèn)題——每個(gè)老師都要查一次評(píng)價(jià)表的話10個(gè)老師就是11次查詢。我當(dāng)時(shí)是先把教師列表一次性查出再按id批量查相關(guān)數(shù)據(jù)內(nèi)存里做匹配性能至少快3倍。3.3 訂單狀態(tài)機(jī)與排課沖突校驗(yàn)訂單是整個(gè)平臺(tái)的主線我用一個(gè)簡(jiǎn)單的狀態(tài)機(jī)來(lái)管理待支付0→ 待上課1→ 已完成2待支付超過(guò)30分鐘自動(dòng)取消家長(zhǎng)可以主動(dòng)取消待支付和待上課狀態(tài)的訂單上課完成后雙方確認(rèn)訂單進(jìn)入已完成狀態(tài)此時(shí)家長(zhǎng)才能評(píng)價(jià)。每次創(chuàng)建訂單前必須要做排課沖突校驗(yàn)這是很多粗制濫造系統(tǒng)會(huì)漏掉的地方。校驗(yàn)邏輯是查詢老師在該時(shí)間段開(kāi)始時(shí)間到結(jié)束時(shí)間是否存在時(shí)間重疊的已接訂單或已排課記錄存在一律拒絕創(chuàng)建訂單。SQL這樣寫SELECT COUNT(*) FROM order WHERE teacher_id #{teacherId} AND status IN (1, 2) AND #{endTime} start_time AND #{startTime} end_time這個(gè)重疊判斷條件很經(jīng)典兩個(gè)區(qū)間[a,b]和[c,d]存在交集只要滿足c b且a d即可。我在第一次實(shí)現(xiàn)時(shí)寫成了start_time BETWEEN #{startTime} AND #{endTime}結(jié)果漏掉了訂單完全包含已有排課的情況后來(lái)才發(fā)現(xiàn)這種寫法只覆蓋了一部分重疊場(chǎng)景。改成交集條件后各種邊界情況全部覆蓋了。3.4 評(píng)價(jià)系統(tǒng)與教師評(píng)分動(dòng)態(tài)更新評(píng)價(jià)表的設(shè)計(jì)要精細(xì)一點(diǎn)id、order_id一個(gè)訂單只能評(píng)價(jià)一次所以這個(gè)字段加唯一索引、rating1-5分、content、create_time。家長(zhǎng)提交評(píng)價(jià)后transaction里要同時(shí)做兩件事插入評(píng)價(jià)記錄、更新老師的綜合評(píng)分。評(píng)分字段放在teacher_info表里冗余存儲(chǔ)這樣列表頁(yè)展示老師評(píng)分就只需要查教師表不需要每次都聚合review表。但這里有個(gè)體驗(yàn)細(xì)節(jié)容易忽略評(píng)價(jià)必須要在訂單完成后7天內(nèi)提交超過(guò)時(shí)間就鎖定不能再評(píng)。這個(gè)限制一方面防止惡意刷評(píng)價(jià)另一方面也督促家長(zhǎng)及時(shí)反饋。在代碼上就用一個(gè)update語(yǔ)句配合時(shí)間條件來(lái)控制寫起來(lái)很輕量。4. Android端核心模塊與實(shí)現(xiàn)4.1 項(xiàng)目架構(gòu)與Navigation底部導(dǎo)航設(shè)計(jì)Android端我遵循單Activity多Fragment的設(shè)計(jì)主界面一個(gè)MainActivity底部三個(gè)Tab首頁(yè)找家教、訂單我的訂單、我的個(gè)人中心。Fragment之間的切換用FragmentManager FragmentTransaction不需要引入Navigation組件——那個(gè)對(duì)畢設(shè)來(lái)說(shuō)配置太繁瑣而且你現(xiàn)在不熟的話踩坑成本太高。底部導(dǎo)航欄用BottomNavigationView菜單資源里定義三個(gè)item每個(gè)item對(duì)應(yīng)一個(gè)Fragment的tag切換時(shí)show/hide而不是replace。為什么這么做因?yàn)閞eplace每次都會(huì)重新走一遍Fragment的生命周期導(dǎo)致網(wǎng)絡(luò)請(qǐng)求重新執(zhí)行用戶翻個(gè)Tab回來(lái)數(shù)據(jù)全部刷新一遍體驗(yàn)非常差。show/hide的方式能保留Fragment的狀態(tài)實(shí)測(cè)滑動(dòng)列表位置不會(huì)被重置。4.2 Retrofit網(wǎng)絡(luò)層封裝與響應(yīng)統(tǒng)一解析Android端網(wǎng)絡(luò)層是整個(gè)項(xiàng)目最容易寫亂的地方。我的做法是定義ApiService接口用注解聲明后端所有接口然后用Retrofit.Builder創(chuàng)建實(shí)例配合GsonConverterFactory做JSON轉(zhuǎn)換。統(tǒng)一的響應(yīng)體Result 在Android端也用對(duì)應(yīng)結(jié)構(gòu)體映射解析完判斷code是否為200不是就直接toast提示后端返回的消息。網(wǎng)絡(luò)請(qǐng)求的異步處理用Retrofit的Callback機(jī)制就好不要因?yàn)閳D新鮮去引入RxJava。那會(huì)讓數(shù)據(jù)流變得復(fù)雜而且你如果之前沒(méi)接觸過(guò)響應(yīng)式編程光理解Observable和Observer的關(guān)系都要花不少時(shí)間。Callback雖然寫起來(lái)顯得啰嗦但邏輯清晰——onResponse處理成功、onFailure處理失敗很適合這個(gè)項(xiàng)目。兩三年前我?guī)腿伺挪檫^(guò)一個(gè)bug用戶上傳頭像后圖片一直沒(méi)顯示查了半天發(fā)現(xiàn)是Android 10對(duì)文件訪問(wèn)權(quán)限收緊直接用file://協(xié)議打開(kāi)相冊(cè)選中的URI會(huì)崩潰。解決方案是用ContentResolver把URI拷貝到應(yīng)用私有目錄再交給Glide加載。這個(gè)坑新手必踩畢設(shè)里做頭像上傳功能時(shí)一定要提前處理。4.3 教師列表展示與篩選交互設(shè)計(jì)教師列表頁(yè)用RecyclerView CardView的組合每個(gè)item展示老師的頭像、昵稱、主教科目、教齡、綜合評(píng)分和價(jià)格。篩選條件用BottomSheetDialog彈出面板里面放科目、區(qū)域、價(jià)格范圍等選項(xiàng)點(diǎn)擊確定后重新請(qǐng)求列表接口。滑動(dòng)加載更多用RecyclerView的OnScrollListener實(shí)現(xiàn)監(jiān)聽(tīng)最后一個(gè)可見(jiàn)item的位置是否接近總數(shù)是就加載下一頁(yè)。這里有個(gè)細(xì)節(jié)需要加一個(gè)isLoading標(biāo)志位防止重復(fù)請(qǐng)求否則用戶快速滑動(dòng)時(shí)可能同時(shí)觸發(fā)多次分頁(yè)請(qǐng)求數(shù)據(jù)順序就亂套了。每頁(yè)我設(shè)20條一屏剛好放5個(gè)卡片滑動(dòng)兩三屏才觸發(fā)一次加載交互節(jié)奏剛剛好。5. 數(shù)據(jù)庫(kù)設(shè)計(jì)實(shí)戰(zhàn)與核心建表語(yǔ)句5.1 核心表結(jié)構(gòu)全解析把完整的建表SQL都貼出來(lái)不現(xiàn)實(shí)挑幾張核心表說(shuō)。用戶表是最基礎(chǔ)的字段包括id、phone、password、role、nickname、avatar、status、create_time。這里phone要加唯一索引platform登錄直接綁定手機(jī)號(hào)不搞郵箱注冊(cè)那套復(fù)雜流程。訂單表的字段設(shè)計(jì)直接決定業(yè)務(wù)邏輯好不好寫CREATE TABLE order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 訂單編號(hào), parent_id bigint(20) NOT NULL COMMENT 家長(zhǎng)用戶ID, teacher_id bigint(20) NOT NULL COMMENT 老師用戶ID, subject_id bigint(20) NOT NULL COMMENT 科目ID, start_time datetime NOT NULL COMMENT 上課開(kāi)始時(shí)間, end_time datetime NOT NULL COMMENT 上課結(jié)束時(shí)間, price decimal(10,2) NOT NULL COMMENT 訂單金額, status int(11) NOT NULL DEFAULT 0 COMMENT 訂單狀態(tài), create_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id), KEY idx_teacher_time (teacher_id,start_time,end_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;order_no用yyyyMMddHHmmss 3位隨機(jī)數(shù)生成這是最簡(jiǎn)單不會(huì)重復(fù)的方式。索引設(shè)計(jì)上聯(lián)合索引teacher_id, start_time, end_time能極大加快排課沖突校驗(yàn)的查詢速度這個(gè)索引在真實(shí)場(chǎng)景中就是量級(jí)上的性能差異。5.2 一對(duì)多與多對(duì)多關(guān)系的處理策略教師和科目之間是多對(duì)多關(guān)系需要一張關(guān)聯(lián)表teacher_subject來(lái)維系字段就三個(gè)id、teacher_id、subject_id??颇勘肀旧碓谙到y(tǒng)里基本就是一個(gè)靜態(tài)字典數(shù)學(xué)、英語(yǔ)、物理、化學(xué)、語(yǔ)文、生物、歷史、地理、政治、鋼琴、繪畫、編程。畢設(shè)階段直接在SQL初始化腳本里INSERT進(jìn)去就行不用專門做科目管理功能。老師詳情表teacher_info和用戶表是一對(duì)一關(guān)系存放老師專屬的信息教齡、學(xué)歷、學(xué)校/機(jī)構(gòu)、教學(xué)風(fēng)格、每小時(shí)價(jià)格、可教區(qū)域等。為什么不分一張表全塞進(jìn)user里因?yàn)榧议L(zhǎng)端的用戶根本不需要這些屬性分表后邏輯更清晰而且這也能向老師展示你掌握了垂直分表的設(shè)計(jì)思路。6. 核心業(yè)務(wù)流程與代碼實(shí)現(xiàn)6.1 下單流程的完整時(shí)序下單是整個(gè)系統(tǒng)里橫跨前后端最多的業(yè)務(wù)建議先從時(shí)序?qū)用胬砬宄议L(zhǎng)在教師詳情頁(yè)點(diǎn)擊預(yù)約試聽(tīng)→ 選擇上課時(shí)間和時(shí)長(zhǎng) → 后端校驗(yàn)老師是否空閑 → 創(chuàng)建訂單狀態(tài)為待支付→ 家長(zhǎng)確認(rèn)支付 → 模擬支付成功回調(diào) → 訂單狀態(tài)變?yōu)榇险n??紤]到畢設(shè)沒(méi)辦法接真實(shí)支付渠道我用的方案是做一個(gè)模擬支付頁(yè)面點(diǎn)擊確認(rèn)支付后直接跳轉(zhuǎn)一個(gè)模擬支付成功的回調(diào)接口接口把訂單狀態(tài)更新為待上課。這個(gè)方案雖然是模擬的但業(yè)務(wù)閉環(huán)是完整的答辯時(shí)說(shuō)明實(shí)際生產(chǎn)可替換為微信/支付寶官方SDK就夠了。6.2 接口定義與Android端調(diào)用對(duì)齊前后端接口對(duì)齊的關(guān)鍵在字段命名。后端返回的JSON字段統(tǒng)一用駝峰命名法和Java屬性保持一致這樣Gson解析時(shí)不需要額外寫SerializedName注解。日期格式統(tǒng)一為yyyy-MM-dd HH:mm:ss直接作為字符串傳輸Android端用SimpleDateFormat解析出Date對(duì)象再格式化展示。我列幾個(gè)核心接口路徑POST /api/auth/login登錄、GET /api/teacher/list教師列表、GET /api/teacher/detail/{id}教師詳情、POST /api/order/create創(chuàng)建訂單、GET /api/order/my我的訂單、POST /api/review/submit提交評(píng)價(jià)。這些接口路徑在設(shè)計(jì)階段就要定好寫進(jìn)接口文檔里前后端各自照著文檔開(kāi)發(fā)能少吵很多架。6.3 教師端接單與日程管理老師端的核心場(chǎng)景是接單和排課。在我的日程頁(yè)面老師可以看到自己所有時(shí)間段的排課情況支持手動(dòng)添加和刪除排課。排課數(shù)據(jù)存在course_schedule表里字段包含teacher_id、start_time、end_time、type1空閑、2已預(yù)約、3不可約。對(duì)老師來(lái)說(shuō)這條時(shí)間軸就是他的可售賣資源。比較有意思的是推薦算法在這里的應(yīng)用當(dāng)家長(zhǎng)瀏覽教師列表時(shí)我調(diào)了一個(gè)活躍推薦排序因子近期有排課活動(dòng)的老師權(quán)重升高。這個(gè)功能看著不起眼但能體現(xiàn)你對(duì)業(yè)務(wù)的理解深度——平臺(tái)需要鼓勵(lì)老師保持活躍而不是注冊(cè)完就消失。7. 前端關(guān)鍵頁(yè)面與交互實(shí)現(xiàn)7.1 教師詳情頁(yè)的信息層級(jí)設(shè)計(jì)教師詳情頁(yè)直接決定家長(zhǎng)會(huì)不會(huì)下單。我按這樣的信息層級(jí)排布最頂部是大圖頭像和名字下面一排展示教齡、學(xué)歷、評(píng)分三個(gè)標(biāo)簽再往下是ta的可教科目和價(jià)格區(qū)間繼續(xù)往下是個(gè)人介紹和教學(xué)風(fēng)格最后是評(píng)價(jià)列表。整個(gè)頁(yè)面用NestedScrollView包裹評(píng)價(jià)列表嵌套R(shí)ecyclerView時(shí)要注意設(shè)置setNestedScrollingEnabled(false)否則會(huì)出現(xiàn)滑動(dòng)沖突。頁(yè)面底部固定一個(gè)預(yù)約試聽(tīng)按鈕顏色用平臺(tái)主色點(diǎn)擊后彈BottomSheetDialog選擇上課時(shí)間。這個(gè)按鈕要一直懸浮在頁(yè)面底部用戶瀏覽完所有信息后最自然的動(dòng)作就是點(diǎn)擊它。7.2 下拉刷新和加載狀態(tài)的細(xì)節(jié)處理頁(yè)面加載狀態(tài)我用三種視圖管理加載中顯示居中ProgressBar加載失敗顯示帶重試按鈕的提示視圖加載成功顯示內(nèi)容。用ViewStub按需inflate這三種狀態(tài)視圖比動(dòng)態(tài)addView效率更高也避免了視圖層級(jí)過(guò)深的問(wèn)題。下拉刷新用SwipeRefreshLayout在onRefresh回調(diào)里重新請(qǐng)求第一頁(yè)數(shù)據(jù)請(qǐng)求完成后調(diào)用setRefreshing(false)結(jié)束動(dòng)畫。這里有個(gè)很多人會(huì)忽略的坑如果刷新請(qǐng)求失敗一定要結(jié)束刷新動(dòng)畫否則用戶會(huì)看到轉(zhuǎn)圈圈停不下來(lái)的詭異狀態(tài)。正確做法是在finally代碼塊里調(diào)用setRefreshing(false)。8. 系統(tǒng)優(yōu)化點(diǎn)與進(jìn)階提升方向8.1 服務(wù)端性能優(yōu)化三板斧畢設(shè)如果能展示出性能優(yōu)化意識(shí)答辯老師通常會(huì)高看一眼。我做了三件事第一教師列表接口開(kāi)啟了Spring Cache緩存key為teacherList:page:{page}:size:{size}緩存過(guò)期時(shí)間60秒有效降低數(shù)據(jù)庫(kù)壓力第二熱門科目和評(píng)價(jià)數(shù)據(jù)用Redis緩存你就算只寫幾行RedisTemplate的get/set代碼也足夠展示你對(duì)緩存技術(shù)的理解第三SQL層面盡量覆蓋索引避免在查詢條件中使用函數(shù)導(dǎo)致索引失效。這里說(shuō)個(gè)實(shí)際的緩存一致性經(jīng)驗(yàn)我在寫評(píng)價(jià)和更新教師評(píng)分的事務(wù)里手動(dòng)刪除對(duì)應(yīng)教師的緩存key這樣下次請(qǐng)求時(shí)就能拿到最新的評(píng)分?jǐn)?shù)據(jù)。這種寫操作刪緩存的玩法雖然是Cache Aside Pattern的基礎(chǔ)操作但在畢設(shè)里能自己悟出來(lái)并寫出來(lái)說(shuō)明你確實(shí)理解了緩存的核心邏輯。8.2 Android端體驗(yàn)優(yōu)化細(xì)節(jié)Android端可以做很多小而美的優(yōu)化圖片加載用Glide配置占位圖和錯(cuò)誤圖列表頁(yè)的item布局用ConstraintLayout減少布局嵌套層級(jí)提升渲染速度大列表加setHasFixedSize(true)告訴RecyclerView尺寸不變跳過(guò)重新測(cè)量布局的過(guò)程。另外建議在項(xiàng)目里加一個(gè)BaseActivity和BaseFragment把網(wǎng)絡(luò)請(qǐng)求的Loading對(duì)話框和錯(cuò)誤Toast統(tǒng)一封裝。這樣每個(gè)頁(yè)面的代碼會(huì)清爽很多而且這種框架思維是導(dǎo)師喜歡的風(fēng)格。8.3 功能擴(kuò)展的想象空間做完全部功能后可以想想還有哪些地方能擴(kuò)展增加消息推送功能的話可以用WebSocket實(shí)現(xiàn)站內(nèi)聊天增加后臺(tái)數(shù)據(jù)分析的話可以在管理員端加訂單統(tǒng)計(jì)報(bào)表按天/周/月維度展示GMV增長(zhǎng)曲線增加推薦系統(tǒng)的話可以基于用戶行為記錄做猜你喜歡的教師推薦。這些擴(kuò)展方向在論文的未來(lái)展望章節(jié)里寫出來(lái)既能體現(xiàn)你思考的深度又不會(huì)給自己增加實(shí)際工作量。我當(dāng)年就是這么干的老師看完后特意在答辯時(shí)問(wèn)了一圈擴(kuò)展方案的技術(shù)實(shí)現(xiàn)思路。9. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄9.1 跨域問(wèn)題與Android請(qǐng)求失敗的區(qū)分前后端聯(lián)調(diào)時(shí)最容易碰到的就是請(qǐng)求失敗。如果你用瀏覽器調(diào)試接口發(fā)現(xiàn)一切正常但Android端請(qǐng)求總是走onFailure大概率是網(wǎng)絡(luò)請(qǐng)求被拒絕。排查方法先在AndroidManifest.xml確認(rèn)加了INTERNET權(quán)限——這個(gè)問(wèn)題出現(xiàn)頻率高到令人發(fā)指再看是不是用了http明文請(qǐng)求Android 9.0之后默認(rèn)禁止明文流量需要在manifest里配置usesCleartextTraffictrue或者在networkSecurityConfig里配置域名白名單。如果你是后端接口在瀏覽器里直接訪問(wèn)出現(xiàn)跨域報(bào)錯(cuò)記得加一個(gè)CorsFilter或者用CrossOrigin注解解決。這在前后端分離項(xiàng)目里是必踩的坑提前處理能省一晚上時(shí)間。9.2 時(shí)間參數(shù)時(shí)區(qū)和格式不一致問(wèn)題Java后端默認(rèn)的日期解析格式是ISO標(biāo)準(zhǔn)的yyyy-MM-ddTHH:mm:ss.SSSZ但前端傳來(lái)的可能是yyyy-MM-dd HH:mm:ss直接用RequestBody接收會(huì)導(dǎo)致解析失敗。解決方案是用JsonFormat注解標(biāo)注時(shí)間字段的格式同時(shí)指定timezone為GMT8避免時(shí)區(qū)偏移導(dǎo)致時(shí)間差8小時(shí)的問(wèn)題。這個(gè)坑我第一次做項(xiàng)目時(shí)踩過(guò)排查了整整一個(gè)晚上最后發(fā)現(xiàn)是Jackson反序列化默認(rèn)不帶解析自定義格式導(dǎo)致的。后來(lái)我在所有LocalDateTime字段上都加了JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)從此再?zèng)]為時(shí)間格式頭疼過(guò)。9.3 Android內(nèi)存泄漏的幾個(gè)典型場(chǎng)景Android端的內(nèi)存泄漏問(wèn)題可以從源頭避免Activity里持有靜態(tài)Context引用、Handler匿名內(nèi)部類持有Activity引用、網(wǎng)絡(luò)請(qǐng)求回調(diào)在Activity銷毀后仍然執(zhí)行、Bitmap沒(méi)有及時(shí)回收。我的建議是網(wǎng)絡(luò)請(qǐng)求用ApplicationContext起步回調(diào)回來(lái)后判斷Activity是否已經(jīng)isFinishing是就直接return不再更新UI。實(shí)際項(xiàng)目里我用IntentService處理頭像上傳的任務(wù)這樣Activity銷毀了任務(wù)也能繼續(xù)執(zhí)行上傳完成后再通過(guò)EventBus通知刷新。雖然EventBus現(xiàn)在有點(diǎn)被嫌棄但畢設(shè)里用起來(lái)非常順手而且這套模式在真實(shí)項(xiàng)目中也很常見(jiàn)。10. 部署發(fā)布與環(huán)境配置經(jīng)驗(yàn)10.1 后端打包部署的完整流程后端部署我用的是阿里云的一臺(tái)2核4G的輕量應(yīng)用服務(wù)器操作系統(tǒng)Ubuntu 22.04安裝JDK 17和MySQL 8.0。打包時(shí)用mvn clean package -DskipTests生成jar包通過(guò)scp命令傳到服務(wù)器 /home/app 目錄下用nohup java -jar xxx.jar app.log 21 啟動(dòng)。生產(chǎn)環(huán)境和本地環(huán)境用不同的application-xxx.yml配置文件通過(guò)啟動(dòng)參數(shù)--spring.profiles.activeprod指定。數(shù)據(jù)庫(kù)連接、Redis地址這些敏感配置不要寫死在代碼里通過(guò)環(huán)境變量注入。這樣代碼倉(cāng)庫(kù)里的配置都是脫敏的安全習(xí)慣從畢設(shè)就開(kāi)始養(yǎng)成。10.2 Android簽名打包與真機(jī)調(diào)試Android端調(diào)試時(shí)強(qiáng)烈建議直接連真機(jī)調(diào)試不要用模擬器。模擬器雖然看起來(lái)方便但冷啟動(dòng)慢、GPS模擬麻煩、相機(jī)權(quán)限需要額外配置這些限制都會(huì)影響家教App這種真實(shí)業(yè)務(wù)App的調(diào)試體驗(yàn)。我用的是小米手機(jī)開(kāi)USB調(diào)試Android Studio直接識(shí)別設(shè)備一鍵run到手機(jī)上看到效果的速度比模擬器快三倍。正式簽名打包時(shí)在build.gradle里配置好簽名證書生成release APK放到服務(wù)器上提供下載。這里有個(gè)加分項(xiàng)可以做一張二維碼用Android的Scan QR Code功能掃碼下載安裝包演示的時(shí)候非常炫酷而且讓老師感覺(jué)到這個(gè)系統(tǒng)真的是可交付的。11. 項(xiàng)目答辯指南與亮點(diǎn)包裝11.1 演示流程圖與核心賣點(diǎn)提煉答辯演示的時(shí)候按這條主線講故事從家長(zhǎng)搜索家教開(kāi)始 → 查看老師詳情和評(píng)價(jià) → 選擇時(shí)段預(yù)約試聽(tīng) → 支付創(chuàng)建訂單 → 老師端確認(rèn)接單 → 排課日程更新 → 上課完成后評(píng)價(jià) → 評(píng)分動(dòng)態(tài)更新到列表頁(yè)。這條鏈路一氣呵成能向老師展示整個(gè)系統(tǒng)的完整度和業(yè)務(wù)閉環(huán)。核心賣點(diǎn)提煉三個(gè)詞全棧前端Android后端Spring Boot數(shù)據(jù)庫(kù)MySQL、閉環(huán)訂單狀態(tài)從創(chuàng)建到完成全流程管理、體驗(yàn)教師篩選、評(píng)分算法、排課沖突校驗(yàn)這些功能細(xì)節(jié)。這三個(gè)詞在匯報(bào)開(kāi)場(chǎng)就拋出來(lái)定好基調(diào)后面演示的時(shí)候不斷呼應(yīng)。11.2 常見(jiàn)答辯問(wèn)題應(yīng)對(duì)策略老師最愛(ài)問(wèn)的問(wèn)題基本集中在幾個(gè)方向?yàn)槭裁催x這個(gè)技術(shù)棧訂單狀態(tài)是怎么管理的并發(fā)場(chǎng)景下有沒(méi)有考慮超賣問(wèn)題你作為畢設(shè)項(xiàng)目怎么防止一個(gè)老師的同一時(shí)間段被多個(gè)家長(zhǎng)同時(shí)預(yù)約超賣問(wèn)題這個(gè)值得提前做準(zhǔn)備。我在下單接口里用數(shù)據(jù)庫(kù)層面的條件更新來(lái)保證原子性UPDATE teacher_schedule SET status 2 WHERE id #{id} AND status 1如果返回影響行數(shù)為0說(shuō)明已經(jīng)被搶了這一點(diǎn)在并發(fā)場(chǎng)景下能保證不會(huì)出現(xiàn)同一位老師同一時(shí)段被兩個(gè)人約走的情況。把這條SQL的邏輯在答辯時(shí)講出來(lái)老師就能看出來(lái)你確實(shí)思考過(guò)并發(fā)問(wèn)題。還有老師會(huì)問(wèn)JWT和傳統(tǒng)Session有什么區(qū)別為什么不用OAuth2.0你的回答思路是JWT無(wú)狀態(tài)適合移動(dòng)端和分布式場(chǎng)景OAuth2.0更適合開(kāi)放平臺(tái)給第三方授權(quán)登錄的場(chǎng)景我們這個(gè)系統(tǒng)自己管理用戶體系JWT方案更簡(jiǎn)潔高效。最后再說(shuō)說(shuō)做這個(gè)項(xiàng)目我個(gè)人的心態(tài)變化。剛開(kāi)始寫訂單模塊的時(shí)候我以為最難的是Android界面怎么畫得好看但真正做下來(lái)才發(fā)現(xiàn)數(shù)據(jù)庫(kù)表怎么設(shè)計(jì)、接口怎么定義、狀態(tài)怎么流轉(zhuǎn)才是核心難點(diǎn)。前端界面反而是在后端邏輯理清楚之后水到渠成的事情。所以如果你也在準(zhǔn)備做一個(gè)類似的系統(tǒng)我真心建議你先花三天時(shí)間把表結(jié)構(gòu)和接口設(shè)計(jì)文檔寫透再動(dòng)手寫代碼。你會(huì)發(fā)現(xiàn)后面的開(kāi)發(fā)速度快到超乎想象而且這種先設(shè)計(jì)再編碼的做事習(xí)慣到實(shí)際工作中比掌握某個(gè)具體框架值錢得多。