機設(shè)計)
1. 選題需求拆解三個標(biāo)題其實指向同一套系統(tǒng)1.1 從標(biāo)題關(guān)鍵詞反推系統(tǒng)邊界先說你拿到的這類題目。仔細看云上航空、云端飛航、航旅隨心這三個詞剝掉包裝的外殼底子完全一樣一個基于 SpringBoot 的航旅APP及配套管理后臺。這類畢設(shè)標(biāo)題在學(xué)校里非常常見或者說很多老師出題就是給一個業(yè)務(wù)場景再配上基于SpringBoot的XX平臺剩下的系統(tǒng)邊界要靠你自己去定義。我輔導(dǎo)過不少做這類題目的同學(xué)發(fā)現(xiàn)最常犯的錯不是代碼寫不出來而是在動手前沒有把系統(tǒng)到底要做什么想清楚結(jié)果做到一半發(fā)現(xiàn)功能太多畢業(yè)設(shè)計根本收不了尾。拿到這個題第一件事是拆出核心業(yè)務(wù)實體。航旅平臺繞不開四樣?xùn)|西用戶、航班、艙位庫存、訂單。圍繞這四個實體展開的就是用戶注冊登錄、航班查詢、航班預(yù)訂、訂單支付、行程管理這幾條線。APP端負責(zé)給乘客用管理端負責(zé)維護航班和艙位數(shù)據(jù)二者通過接口聯(lián)動。有個容易忽略的點APP在畢設(shè)里的形態(tài)。你可以做 Android 原生Java/Kotlin、微信小程序也可以做 H5 套殼或者干脆用 Vue 寫移動端網(wǎng)頁然后通過 WebView 打包成 APP。學(xué)校驗收時候只看是不是一個能跑的移動端應(yīng)用所以完全沒必要在客戶端上追求復(fù)雜技術(shù)把精力留給后端業(yè)務(wù)邏輯才是穩(wěn)妥路線。1.2 用戶端與管理端的功能清單與優(yōu)先級劃分把功能范圍控制住是畢設(shè)活下去的前提。我建議按下面這個表格來規(guī)劃模塊端模塊具體功能優(yōu)先級復(fù)雜度用戶端APP賬號體系注冊、登錄、修改密碼、個人信息P0中用戶端APP航班查詢按起降城市、日期查詢航班列表支持艙位篩選P0中用戶端APP在線預(yù)訂選擇航班艙位、填寫乘機人、提交訂單P0高用戶端APP訂單管理待支付訂單、查看訂單詳情、模擬支付、取消訂單P0高用戶端APP行程管理按時間線展示出行計劃、倒計時、歷史行程P1中用戶端APP消息中心航班變動通知、訂單狀態(tài)通知P1低管理后臺航班管理航班錄入、班期規(guī)則、起降時間維護P0中管理后臺艙位管理各艙位等級定價、余票庫存調(diào)整P0中管理后臺訂單管理訂單查詢、改簽/退票審核、狀態(tài)干預(yù)P1中管理后臺數(shù)據(jù)統(tǒng)計訂單量、銷售額簡單圖表P2中P0 是核心鏈路缺一個系統(tǒng)就跑不通P1 是亮點模塊用來豐富功能描述P2 如果時間不夠可以砍掉或者只做最簡單的統(tǒng)計接口前端用柱狀圖糊一下。說實話評委老師更在意核心鏈路能不能講清楚而不是你做了多少個花哨功能。1.3 為什么 SpringBoot 是這個題目的最優(yōu)解很多同學(xué)會糾結(jié)要不要用 Spring Cloud、要不要上微服務(wù)。我的回答很直接畢設(shè)里面用 SpringBoot 單應(yīng)用就夠了微服務(wù)屬于給自己挖坑。SpringBoot 的核心價值在于兩點起步依賴和自動裝配。起步依賴讓你不用再為 Spring 和第三方庫的版本兼容頭疼引入spring-boot-starter-web就自帶 Tomcat 和 Spring MVC自動裝配則通過EnableAutoConfiguration幫你在引入依賴后自動配置好相應(yīng)的 Bean比如引入 Redis 依賴后RedisTemplate就能直接注入使用。這種約定優(yōu)于配置的思路完美契合畢設(shè)場景——你的核心目標(biāo)是快速實現(xiàn)業(yè)務(wù)功能而不是花三周調(diào)各種 XML 配置。相比十幾年前 SSHStrutsSpringHibernate時代那種面對一坨配置文件無從下手的狀態(tài)SpringBoot 幾乎把搭建項目的門檻降到了零。而且答辯的時候我使用 SpringBoot 簡化了項目初始化流程通過自動裝配減少了大量樣板配置這句話本身就是個加分點老師愛聽。2. 技術(shù)棧選型與數(shù)據(jù)庫設(shè)計先把地基打穩(wěn)2.1 一套不容易翻車的技術(shù)組合直接給結(jié)論這套組合我用過很多次穩(wěn)定性很高層級選型版本建議備注后端框架SpringBoot2.7.18不要用 3.x見下方說明ORMMyBatis-Plus3.5.x內(nèi)置分頁插件、條件構(gòu)造器數(shù)據(jù)庫MySQL8.05.7 也行但 8.0 更省心緩存Redis6.x/7.x做航班查詢緩存、Token 存儲鑒權(quán)JWTjjwt 0.9.x無狀態(tài)適合 APP 場景接口文檔knife4j4.3.0方便自測和答辯演示前端APPAndroid 原生 或 Vue3H5—按個人熟悉程度選管理后臺Vue3 Element Plus—或者直接用 Thymeleaf這里特別想提一句 SpringBoot 版本的問題。很多同學(xué)一上手就去創(chuàng)建最新版項目結(jié)果 SpringBoot 3.x 要求 JDK 17而部分同學(xué)的筆記本上裝的是 JDK 8還有 MyBatis-Plus、druid 這些常用庫對 SpringBoot 3 的兼容也各有各的坑。熱搜詞里springboot版本太高這個搜索詞我猜就有一堆人踩了。穩(wěn)一點的做法是直接用SpringBoot 2.7.18它是 2.x 系列的終點版本穩(wěn)定、教程多、幾乎不報錯配合 JDK 8 完美運行答辯時完全夠用。2.2 四張核心表的設(shè)計思路與建表語句數(shù)據(jù)庫設(shè)計是整個項目的地基我的建議是不要照搬網(wǎng)上那種幾十張表的完整設(shè)計按業(yè)務(wù)需要來。核心四張表用戶表、航班表、艙位表、訂單表。用戶表不多說就是id, username, password, phone, real_name, id_card, create_time。密碼記得用 MD5 加鹽或者 BCrypt 加密別明文存儲這個細節(jié)答辯時經(jīng)常被問到。航班表是重點它和真實的航班排期還不一樣。真實系統(tǒng)里航班有班期概念比如每天一班、每周一三五飛但畢設(shè)可以簡化成每個日期一條航班記錄也就是flight表里存的是具體某一天某一班飛機CREATE TABLE flight ( id BIGINT AUTO_INCREMENT PRIMARY KEY, flight_no VARCHAR(10) NOT NULL COMMENT 航班號如 CA1234, airline VARCHAR(20) NOT NULL COMMENT 航空公司, depart_city VARCHAR(20) NOT NULL COMMENT 出發(fā)城市, arrive_city VARCHAR(20) NOT NULL COMMENT 到達城市, depart_airport VARCHAR(50) NOT NULL COMMENT 出發(fā)機場, arrive_airport VARCHAR(50) NOT NULL COMMENT 到達機場, depart_time DATETIME NOT NULL COMMENT 起飛時間, arrive_time DATETIME NOT NULL COMMENT 到達時間, flight_date DATE NOT NULL COMMENT 飛行日期, status TINYINT DEFAULT 1 COMMENT 1正常 0取消, UNIQUE KEY uk_flight_no_date (flight_no, flight_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;唯一索引(flight_no, flight_date)保證同一天同一航班號只有一條記錄這是后面防重復(fù)預(yù)訂的基礎(chǔ)。艙位表用來存每種艙位的價格和庫存CREATE TABLE cabin ( id BIGINT AUTO_INCREMENT PRIMARY KEY, flight_id BIGINT NOT NULL COMMENT 關(guān)聯(lián)航班, cabin_class VARCHAR(10) NOT NULL COMMENT Y經(jīng)濟艙 F頭等艙 C公務(wù)艙, price DECIMAL(10,2) NOT NULL COMMENT 票價, discount_rate DECIMAL(3,2) DEFAULT 1.00 COMMENT 折扣率, stock INT NOT NULL COMMENT 余票數(shù), total_stock INT NOT NULL COMMENT 總座位數(shù), UNIQUE KEY uk_flight_cabin (flight_id, cabin_class) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;訂單表在基本字段之外我強烈建議加一個快照設(shè)計。什么叫快照就是你下單時把航班號、起降時間、艙位等級、乘機人姓名身份證這些信息原樣冗余一份存到訂單里。這樣之后航班改時間了、價格變動了都不影響已經(jīng)出的訂單也不影響你打印行程單。這個設(shè)計思路在答辯時說出去老師會覺得你考慮到了真實業(yè)務(wù)場景瞬間拉開和普通同學(xué)的距離。2.3 接口統(tǒng)一規(guī)范與 Token 鑒權(quán)接口設(shè)計直接決定前后端聯(lián)調(diào)效率。我見過最痛苦的項目是每個接口返回格式都不一樣前端解析字段快瘋了。所以一開始就統(tǒng)一返回結(jié)構(gòu)Data public class ResultT { private Integer code; // 200成功500失敗401未認證 private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.setCode(200); r.setMsg(success); r.setData(data); return r; } public static T ResultT fail(String msg) { ResultT r new Result(); r.setCode(500); r.setMsg(msg); return r; } }APP 端的鑒權(quán)我推薦直接上 JWT。它的邏輯并不復(fù)雜用戶登錄成功后后端用密鑰生成一個帶過期時間的 Token 返回APP 把 Token 存在本地之后每次請求在請求頭里帶上Authorization: Bearer token后端用一個攔截器HandlerInterceptor校驗 Token校驗通過就把用戶信息放進 ThreadLocal方便 Controller 直接取。為什么不用 Session因為 APP 不是瀏覽器Session 依賴 Cookie跨端體驗不好而且分布式部署時 Session 還要額外做共享。JWT 天然無狀態(tài)擴展性更強這個選型邏輯在答辯時能講出一套完整的理由。再說一個熱搜詞里出現(xiàn)過的點全局過濾器處理上傳 PDF 時的 XSS 攻擊。這類安全問題放在畢設(shè)里其實是加分項。你可以在application.yml里配置一個過濾器對所有請求參數(shù)做script、onerror等關(guān)鍵字符的轉(zhuǎn)義XSS 過濾器不需要很復(fù)雜用 OncePerRequestFilter 包裝一層 HttpServletRequestWrapper 重寫getParameter即可。雖然航旅系統(tǒng)里大多數(shù)情況不會有人惡意攻擊你但答辯時能主動說出我做了 XSS 過濾說明你網(wǎng)絡(luò)安全意識到位。3. 航班查詢與預(yù)訂訂單業(yè)務(wù)里最考驗細節(jié)的一環(huán)3.1 多條件航班查詢與 Redis 緩存加速航班查詢是用戶打開 APP 后做的第一件事也是接口設(shè)計里最需要關(guān)注的性能點。查詢條件一般是出發(fā)城市、到達城市、日期可選艙位等級和航空公司。對應(yīng)的 SQL 不難但要注意索引SELECT f.id, f.flight_no, f.airline, f.depart_time, f.arrive_time, f.depart_airport, f.arrive_airport, c.cabin_class, c.price, c.discount_rate, c.stock FROM flight f LEFT JOIN cabin c ON c.flight_id f.id WHERE f.depart_city #{fromCity} AND f.arrive_city #{toCity} AND f.flight_date #{date} AND f.status 1 AND (c.cabin_class #{cabinClass} OR #{cabinClass} IS NULL) ORDER BY f.depart_time;注意depart_city、arrive_city、flight_date這三列是高頻查詢條件要建聯(lián)合索引。另外時間字段的存儲我強烈建議存DATETIME而不是字符串這樣前端拿到手直接格式化還能避免時區(qū)換算的一堆麻煩比如起始日期當(dāng)天 0 點 5 分的航班被字符串比較搞錯位。既然用到了 Redis查詢接口就可以加一層緩存。緩存 key 設(shè)計成flight:query:{from}:{to}:{date}:{cabinClass}value 存查詢結(jié)果的 JSONTTL 設(shè)置 5 分鐘即可。為什么要 5 分鐘因為航班數(shù)據(jù)不是頻繁變動的數(shù)據(jù)5 分鐘的延遲用戶根本感知不到而 Redis 緩存能扛住瞬時高并發(fā)也讓你的項目在技術(shù)方案上有緩存這個層次。代碼邏輯就三步先查緩存命中直接返回未命中查數(shù)據(jù)庫再寫緩存返回結(jié)果。有一點必須提醒管理后臺修改了航班價格或取消航班后對應(yīng)緩存要主動刪除否則用戶永遠查到的是舊數(shù)據(jù)。用 Spring 的CacheEvict注解或者手動redisTemplate.delete(key)都行這個問題就是典型的緩存一致性問題答辯常問。3.2 預(yù)訂事務(wù)與庫存扣減優(yōu)雅地防止超賣預(yù)訂是整個項目里業(yè)務(wù)邏輯最重的接口流程是這樣的用戶提交訂單 - 校驗航班和艙位 - 扣減庫存 - 創(chuàng)建訂單記錄狀態(tài)待支付- 返回訂單號。這里有兩個問題必須解決一是扣庫存和建訂單要放在同一個事務(wù)里任何一步失敗都要回滾二是并發(fā)場景下不能超賣也就是同一個艙位剩余 3 張票5 個人同時下單最后只能有 3 個人成功。用代碼實現(xiàn)的思路有兩條路悲觀鎖和樂觀鎖。悲觀鎖就是查庫存的時候直接SELECT ... FOR UPDATE鎖住這行其他人只能等鎖釋放。實現(xiàn)最簡單但性能差點適合畢設(shè)場景。推薦代碼示例Transactional public Order createOrder(Long userId, Long flightId, Long cabinId, ListPassenger passengers) { // 1. 悲觀鎖查詢艙位 Cabin cabin cabinMapper.selectByIdForUpdate(cabinId); if (cabin.getStock() 1) { throw new BizException(該艙位已售罄); } // 2. 扣庫存 cabin.setStock(cabin.getStock() - 1); cabinMapper.updateById(cabin); // 3. 生成訂單... }selectByIdForUpdate是 MyBatis-Plus 里自定義 SQL配合Transactional確保整個操作原子性。樂觀鎖則是在cabin表加一個version字段更新時帶上version條件UPDATE cabin SET stock stock - 1, version version 1 WHERE id #{cabinId} AND stock 0 AND version #{oldVersion}如果更新返回 0 行說明被別人搶了重新嘗試即可。在答辯時把這兩種方案都講出來再說一句考慮到畢設(shè)并發(fā)量不大我最終選了悲觀鎖方案代碼更直觀如果做高并發(fā)優(yōu)化會采用樂觀鎖既展示了知識面又體現(xiàn)了務(wù)實性。還要留個心眼處理接口冪等的問題。用戶手快了點擊兩次提交訂單結(jié)果下了兩單這就是重復(fù)提交。最穩(wěn)的辦法是在訂單表加一個order_no唯一索引用UUID去掉橫線生成訂單號插入時報主鍵沖突就說明重復(fù)下單另外前端也要做按鈕置灰。這兩層一疊加基本就防住了。3.3 艙位價格計算與訂單快照設(shè)計價格這塊別想復(fù)雜真實航空的動態(tài)定價是個大數(shù)據(jù)系統(tǒng)但畢設(shè)只要做好多艙位多價格就夠了。簡單策略就是每個航班下掛 N 個艙位經(jīng)濟艙/公務(wù)艙/頭等艙各自有基礎(chǔ)價和折扣率展示價格 基礎(chǔ)價 x 折扣率。折扣率可以隨時間變化比如提前 15 天訂是 0.6 折臨近起飛是 0.9 折邏輯寫死在一個PriceService里方便答辯時講解。下單時把價格算好存入訂單表這里就引出快照表的價值了。訂單表里冗余一份flight_snapshot字段JSON 類型存的是航班號、起降時間、艙位等級、單價、乘機人等信息的拼接串。用戶查訂單詳情時直接讀快照不需要實時去 join 航班表。這個設(shè)計的好處是訂單的歷史數(shù)據(jù)是凍結(jié)的哪怕航班改了時間用戶看到的訂單信息依然是下單那一刻的事實。用大白話講就像你租房時簽的合同房東之后漲價跟你沒關(guān)系。乘機人信息錄入時身份證號碼要校驗格式18 位正則手機號校驗 11 位這是成本極低但體驗提升明顯的小細節(jié)。建議在 APP 端做一個常用乘機人管理用戶把家人信息先錄入下單時勾選即可既省事又顯得功能完整。4. 行程管理從訂單狀態(tài)機到定時任務(wù)4.1 訂單狀態(tài)的定義與流轉(zhuǎn)規(guī)則行程管理模塊本質(zhì)上是訂單狀態(tài)時間的展示。所以第一步把訂單狀態(tài)定義清楚。我用一個狀態(tài)枚舉來管理public enum OrderStatus { PENDING_PAYMENT(0, 待支付), PAID(1, 已支付), ISSUED(2, 已出票), CHECKED_IN(3, 已值機), COMPLETED(4, 已完成), CANCELLED(5, 已取消), REFUNDED(6, 已退票); private final int code; private final String desc; // getter... }狀態(tài)流轉(zhuǎn)規(guī)則要十分明確不能出現(xiàn)從已完成突然變成已取消這種詭異情況。合法的流轉(zhuǎn)路徑是待支付 -用戶主動取消- 已取消待支付 -超時未支付定時任務(wù)取消- 已取消待支付 -模擬支付成功- 已支付/已出票已出票 -模擬值機- 已值機已值機 -飛行日期已過- 已完成已出票/已值機 -申請退票- 已退票在代碼層面不要在 Service 里寫一堆散落的 if else 判斷狀態(tài)而是封裝一個orderStateMachine或者直接在枚舉里加一個canTransitTo(target)方法統(tǒng)一校驗。例如枚舉里維護一個 Mapprivate static final MapOrderStatus, ListOrderStatus TRANSITIONS new HashMap(); static { TRANSITIONS.put(PENDING_PAYMENT, Arrays.asList(PAID, CANCELLED)); TRANSITIONS.put(PAID, Arrays.asList(ISSUED, REFUNDED)); // ... }每次狀態(tài)變更走統(tǒng)一入口非法流轉(zhuǎn)直接拋異常。答辯時你可以說這是借鑒了狀態(tài)機設(shè)計模式保證了訂單狀態(tài)的安全性這一句話就能撐起一個加分點。4.2 行程按時間軸展示與倒計時計算行程管理在 APP 端呈現(xiàn)出來的樣子應(yīng)該是最近要飛的行程排在最前面每個行程卡片上有倒計時。后端接口設(shè)計上我建議提供兩個接口查詢待出發(fā)行程訂單狀態(tài)為已出票或已值機且起飛時間大于當(dāng)前時間查詢歷史行程已完成、已取消、已退票待出發(fā)行程按起飛時間升序排列返回給前端時帶上departTime時間戳。前端拿到后用本地時間計算倒計時格式可以做成距起飛還有 2 天 5 小時 30 分。這里有個坑千萬不要在后端算了倒計時的字符串返回給前端因為前端頁面停留期間時間一直在走如果后端只返回靜態(tài)字符串倒計時不動用戶會覺得系統(tǒng)壞掉了。正確的做法是后端只返回時間戳前端通過 JS 定時器每秒刷新一次倒計時。行程詳情的頁面可以展示一個時間軸從預(yù)訂成功到支付完成到出票成功再到值機成功按時間順序縱向排列。后端只需在訂單狀態(tài)變更時記錄一條order_timeline表order_id, status, operate_time, note查詢時按時間升序返回即可實現(xiàn)非常輕量。有了這個表訂單詳情的操作履歷也順便解決了一舉兩得。4.3 定時任務(wù)清理超時訂單與航班變動提醒系統(tǒng)里有兩類場景天然適合定時任務(wù)處理一是用戶下單后一直不支付訂單一直占著庫存二是航班狀態(tài)發(fā)生變化需要通知相關(guān)用戶。第一類場景的處理邏輯不復(fù)雜。Spring 自帶的Scheduled注解就能搞定假設(shè)設(shè)定 15 分鐘未支付自動取消那么寫一個定時任務(wù)每 5 分鐘掃描一次訂單表UPDATE orders SET status 5, cancel_time NOW() WHERE status 0 AND create_time DATE_SUB(NOW(), INTERVAL 15 MINUTE);這個 SQL 執(zhí)行完還要把對應(yīng)的艙位庫存加回去因為之前的扣減操作要還回來。邏輯就是查出所有被取消的超時訂單循環(huán)釋放每個訂單對應(yīng)的艙位庫存。注意這里也涉及事務(wù)一個訂單釋放失敗不能影響其他訂單。在闡述方案的時候你可以補充一句如果要做到更精準(zhǔn)的延遲觸發(fā)應(yīng)該用 RocketMQ 的延遲消息或者 Redis 過期鍵監(jiān)聽但畢設(shè)用定時掃描已經(jīng)足夠這能體現(xiàn)你對方案邊界有認知。第二類場景航班取消或者延誤后要給已購票用戶發(fā)通知。畢設(shè)實現(xiàn)最簡單的方案不是上消息隊列而是在消息中心表里插一條記錄同時往用戶的消息列表里寫數(shù)據(jù)??梢宰龀晒芾砗笈_修改航班狀態(tài)時調(diào)用notifyService.flightChanged(flightId)這個 Service 查出所有關(guān)聯(lián)該航班的未出行訂單給每個用戶生成一條站內(nèi)消息。APP 端的消息中心拉取未讀數(shù)量有紅點提示即可。整體工作量很小但功能完成度高演示的時候效果很好。5. 從開發(fā)到答辯項目演示與高頻追問整理5.1 開發(fā)環(huán)境的一致性問題做畢設(shè)期間我見過太多本地能跑一換電腦就報廢的案例。環(huán)境統(tǒng)一是減少痛苦的唯一辦法。我這里給一個完整的參考組合JDK 8 Maven 3.6.3用 IDE 內(nèi)置也行 SpringBoot 2.7.18 MySQL 8.0 Redis 6.x Node 16前端。每個組件版本都要在項目文檔里寫清楚。關(guān)于項目構(gòu)建工具很多同學(xué)糾結(jié) Maven 還是 Gradle。我的建議是無腦選 Maven原因有兩個一是 Maven 的依賴傳遞更直觀出問題看報錯更容易定位二是網(wǎng)上 SpringBoot 相關(guān)教程和 pom 配置幾乎全是 Maven 的整合 MyBatis-Plus 時搜答案方便。熱搜詞里有人搜2020年 gradle 構(gòu)建的 springboot 項目配置文件大概率是接手了早期模板項目然后踩了一堆 Gradle 和 SpringBoot 插件版本不兼容的坑這類問題完全可以從選型上規(guī)避。打包部署的問題也要提前想好。開發(fā)期你 IDE 里直接 Run 就行但演示前的打包必須跑通。在pom.xml里確保配置了 SpringBoot 的maven-plugin執(zhí)行mvn clean package會打出一個可執(zhí)行 jar。注意一個小坑如果項目里有前端靜態(tài)資源比如管理后臺的 dist打包順序要先用npm run build構(gòu)建前端再執(zhí)行 Maven 打包把 dist 拷入src/main/resources/static這一步順序亂了打包產(chǎn)物就會缺前端頁面。5.2 演示環(huán)境與部署建議畢設(shè)演示最尷尬的場面是什么u 盤里的代碼在講臺上打不開或者演示到一半 Redis 沒啟動直接白屏。我的建議是準(zhǔn)備兩套方案首選是本機演示。提前把所有服務(wù)啟動好MySQL 服務(wù)、Redis 服務(wù)、后端 jar、APP 前端模擬器或真機。這里有個細節(jié)如果你用的電腦是公司或機房電腦MySQL 和 Redis 可能沒裝可以在答辯前一天把綠色版 MySQL 和 Redis 解壓包準(zhǔn)備在 u 盤里免安裝啟動的命令要自己先演練兩遍比如 Redis 的redis-server.exe雙擊啟動MySQL 的mysqld --initialize-insecure初始化流程。備選方案是 Docker 部署。如果你對 Docker 熟悉可以寫一個簡單的 docker-compose把 MySQL、Redis、后端三個容器編排起來。這里給一個參考 DockerfileFROM openjdk:8-jre WORKDIR /app COPY target/cloud-air-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar, --spring.profiles.activeprod]Docker 方案的好處是換任何一臺有 Docker 的電腦都能秒級復(fù)現(xiàn)環(huán)境答辯時非常加分。但要注意不要為了演示 Docker 而把簡單事搞復(fù)雜如果自己對 Docker 不熟老老實實用本機方案即可畢竟演示翻車比沒有 Docker 更減分。5.3 答辯現(xiàn)場的高頻問題與回答思路答辯環(huán)節(jié)老師大概率圍繞技術(shù)選型和項目難點提問。我把出現(xiàn)頻率最高的六個問題整理成回答思路你可以提前準(zhǔn)備問題一SpringBoot 的自動裝配原理是什么回答思路SpringBootApplication是組合注解核心是EnableAutoConfiguration。它在啟動時掃描META-INF/spring.factories2.7 之前或者META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports2.7文件拿到所有自動配置類的全限定名然后通過Conditional系注解判斷當(dāng)前環(huán)境是否滿足條件比如 Classpath 里有沒有對應(yīng)類滿足才加載配置。這就是引入依賴即自動配置的原理。問題二為什么用 JWT 而不用 Session回答思路APP 不是瀏覽器Session 依賴 Cookie 不適用JWT 無狀態(tài)、可跨端、易于擴展配合 Redis 做 Token 黑名單可以實現(xiàn)主動失效。但要主動提到JWT 無法在服務(wù)端主動銷毀這是它的局限顯得你的思考是辯證的。問題三你是怎么防止庫存超賣的按照前面講的悲觀鎖/樂觀鎖邏輯回答然后加一句我在訂單號上加了唯一約束來保證冪等防止重復(fù)下單。老師基本就會點頭。問題四Redis 緩存和數(shù)據(jù)庫數(shù)據(jù)不一致怎么辦回答思路修改數(shù)據(jù)庫后主動刪除/更新緩存緩存設(shè)置 TTL 過期兜底對一致性要求高的數(shù)據(jù)不緩存如支付結(jié)果。整個思路是最終一致性而非強一致性。問題五數(shù)據(jù)庫表是怎么設(shè)計的為什么訂單表要冗余航班信息把快照思路講一遍強調(diào)保護歷史訂單不受后續(xù)數(shù)據(jù)變更影響。這是最容易展現(xiàn)設(shè)計水平的回答。問題六項目里最大的難點是什么不要回答沒有難點也不要只回答登錄。最好的答案是航班預(yù)訂場景下的并發(fā)庫存控制和訂單狀態(tài)一致性。把事務(wù)邊界、庫存扣減、狀態(tài)機流轉(zhuǎn)完整講一遍展示你確實動手深入過。最后再分享一個我自己反復(fù)強調(diào)的技巧答辯演示前把測試環(huán)境的航班庫存故意改成只有 1 張然后現(xiàn)場演示兩個用戶同時搶票展示只有一個能成功。這個操作視覺沖擊力極強比你說一百句我處理了并發(fā)都管用。平時把這個場景錄個視頻放手機里萬一現(xiàn)場網(wǎng)絡(luò)出問題你還能兜底展示不至于冷場。做畢設(shè)這件事從頭到尾最重要的能力不是堆新技術(shù)而是把一條核心業(yè)務(wù)鏈路想明白、做扎實、講清楚這才是老師真正想看到的東西。