:時間沖突檢測與狀態(tài)機(jī)設(shè)計(jì)實(shí)戰(zhàn))
我去年完整做了一套汽車租賃管理系統(tǒng)技術(shù)棧就是標(biāo)題里那套SpringBoot Vue3 MyBatis MySQL前后端分離。做之前以為就是一個普通的業(yè)務(wù)管理系統(tǒng)做順了才發(fā)現(xiàn)租賃類業(yè)務(wù)和常規(guī)CRUD完全是兩碼事——它有一個繞不開的核心約束同一輛車在同一時間段只能被一個訂單占用。這個時間窗口的沖突檢測加上訂單從下單到還車的全生命周期狀態(tài)流轉(zhuǎn)才是這套系統(tǒng)真正有含金量的地方。這篇文章把我從頭到尾的設(shè)計(jì)思路、表結(jié)構(gòu)設(shè)計(jì)、核心接口邏輯、前端交互方案以及實(shí)測中踩過的坑完整復(fù)盤一遍。不管你是正在準(zhǔn)備Java全棧項(xiàng)目、想充實(shí)簡歷的應(yīng)屆生還是公司要快速落地一套租賃類業(yè)務(wù)后臺這套設(shè)計(jì)都能直接參考。1. 技術(shù)棧選型為什么是SpringBootVue3MyBatis而不是別家組合1.1 四個組件各自解決什么問題這套技術(shù)棧放到今天已經(jīng)不算新潮但它依舊是Java企業(yè)級項(xiàng)目里最穩(wěn)的組合。SpringBoot負(fù)責(zé)把后端的集成工作簡化到極致嵌入式Tomcat、自動配置、starter機(jī)制省去大量XML配置MyBatis負(fù)責(zé)SQL的可控性租賃系統(tǒng)里查詢條件特別多車型、價格區(qū)間、狀態(tài)、租期組合查詢一多動態(tài)SQL的價值就體現(xiàn)出來了Vue3配合Vite和Element Plus做后臺管理界面和用戶端頁面都很順手MySQL負(fù)責(zé)數(shù)據(jù)持久化輕量、穩(wěn)定、資料多作為中小型業(yè)務(wù)系統(tǒng)的數(shù)據(jù)庫完全夠用。有人會問現(xiàn)在MyBatis-Plus這么流行為什么不用兩個原因。第一這個項(xiàng)目體量不大原生MyBatis完全能把SQL控制住不會出現(xiàn)幾百行動態(tài)SQL的極端場景第二MyBatis的動態(tài)SQL和XML映射是Java面試??键c(diǎn)用原生方式把這塊吃透對面試幫助更大。實(shí)際開發(fā)中團(tuán)隊(duì)用MP還是原生那是團(tuán)隊(duì)習(xí)慣問題但對于個人項(xiàng)目我建議至少要能獨(dú)立寫出XML里的動態(tài)SQL。1.2 項(xiàng)目邊界這個系統(tǒng)到底做什么做一個系統(tǒng)之前先把邊界劃清楚否則容易越做越失控。我的設(shè)計(jì)是兩類角色、兩條主線。普通用戶能做的事注冊登錄、瀏覽車輛、選擇租期下單、在線支付押金和租金、查看個人訂單、還車結(jié)算。管理員能做的事車輛增刪改查、車輛上架下架、訂單審核、確認(rèn)取車、確認(rèn)還車、訂單結(jié)算。這個邊界劃好后前后端的功能范圍就定了接口數(shù)量也能數(shù)得出來。全系統(tǒng)核心接口大概二十來個后端分用戶模塊、車輛模塊、訂單模塊前端分用戶端和管理端兩個獨(dú)立視圖。邊界清晰的好處是開發(fā)過程中不會出現(xiàn)“要不要加個站內(nèi)信”、“要不要做積分系統(tǒng)”這種沒完沒了的需求蔓延。1.3 前后端分離的關(guān)鍵收益前后端分離不是趕時髦對這個項(xiàng)目來說有幾個實(shí)際收益。第一開發(fā)過程中后端不需要關(guān)心頁面長什么樣只返回JSON前端也不需要關(guān)心SQL怎么查只負(fù)責(zé)渲染和交互我可以并行推進(jìn)第二部署上后端是一個jar包前端是一堆靜態(tài)文件可以單獨(dú)發(fā)布前端改樣式不用重新打包后端第三將來如果要做小程序端或者App端后端接口可以直接復(fù)用不用重寫。代價也有就是聯(lián)調(diào)成本變高了。前端需要知道后端接口返回的數(shù)據(jù)結(jié)構(gòu)后端需要知道前端需要什么字段。我的辦法是后端統(tǒng)一返回結(jié)構(gòu)所有數(shù)據(jù)都包在Result對象里前端拿到后按約定解析。這個統(tǒng)一約定后面專門有一節(jié)說。2. 數(shù)據(jù)庫模型車輛、訂單、用戶三張核心表的設(shè)計(jì)邏輯2.1 核心表結(jié)構(gòu)與建表SQL數(shù)據(jù)庫設(shè)計(jì)是這套系統(tǒng)的地基。我用的核心表就三張用戶表、車輛表、訂單表。不要加太多冗余表更不要用物理外鍵業(yè)務(wù)關(guān)系靠代碼邏輯維護(hù)索引來保證查詢性能。用戶表id、用戶名、密碼、手機(jī)號、身份證號、角色、狀態(tài)、創(chuàng)建時間。密碼我用了BCrypt加密存儲明文存密碼在真實(shí)項(xiàng)目里是絕對紅線。數(shù)據(jù)庫字段類型金額用decimal時間用datetime狀態(tài)用tinyint。車輛表這里信息量比較大我貼一下實(shí)際的建表SQLCREATE TABLE car ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 車輛ID, brand VARCHAR(50) NOT NULL COMMENT 品牌, model VARCHAR(50) NOT NULL COMMENT 車型, plate_number VARCHAR(20) NOT NULL UNIQUE COMMENT 車牌號, category TINYINT NOT NULL DEFAULT 0 COMMENT 車型分類 0-經(jīng)濟(jì)型 1-SUV 2-商務(wù)型 3-豪華型, daily_price DECIMAL(10,2) NOT NULL COMMENT 日租金, deposit DECIMAL(10,2) NOT NULL COMMENT 押金, status TINYINT NOT NULL DEFAULT 0 COMMENT 狀態(tài) 0-可租 1-已租出 2-維修中 3-已下架, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 創(chuàng)建時間 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT車輛表; CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 訂單ID, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 訂單編號, user_id BIGINT NOT NULL COMMENT 用戶ID, car_id BIGINT NOT NULL COMMENT 車輛ID, start_time DATETIME NOT NULL COMMENT 取車時間, end_time DATETIME NOT NULL COMMENT 還車時間, daily_price DECIMAL(10,2) NOT NULL COMMENT 下單時日租金快照, total_days INT NOT NULL COMMENT 租期天數(shù), amount DECIMAL(10,2) NOT NULL COMMENT 租金金額, deposit DECIMAL(10,2) NOT NULL COMMENT 押金金額, status TINYINT NOT NULL DEFAULT 0 COMMENT 狀態(tài) 0-待支付 1-已支付待取車 2-租賃中 3-已完成 4-已取消, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 下單時間, pay_time DATETIME NULL COMMENT 支付時間, finish_time DATETIME NULL COMMENT 完成時間, INDEX idx_user_id (user_id), INDEX idx_car_id (car_id), INDEX idx_start_time (start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT訂單表;有幾個字段容易被忽略我單獨(dú)說。orders表里的daily_price字段是“下單時日租金快照”這個很重要。車輛日常價格會變比如節(jié)假日調(diào)價訂單一旦創(chuàng)建租金就得按下單那一刻的價格算不能在結(jié)算時又去讀車輛表的最新價格否則用戶會投訴。這是一個很典型的訂單設(shè)計(jì)原則金額類信息必須做快照不做實(shí)時關(guān)聯(lián)。訂單編號我用的是當(dāng)前時間加隨機(jī)數(shù)生成格式類似yyyyMMddHHmmss 4位隨機(jī)數(shù)保證同一秒內(nèi)并發(fā)也不會重復(fù)。生產(chǎn)環(huán)境可以用雪花ID或者Redis自增個人項(xiàng)目這個簡化方案完全夠。2.2 車輛和訂單的狀態(tài)機(jī)設(shè)計(jì)狀態(tài)字段我全部用tinyint從0開始遞增。為什么不用字符串三個原因省空間、查詢快、代碼里定義常量類后可讀性也不差。真正要注意的是別把魔法數(shù)字寫死在代碼里我在后端建了一個枚舉類把所有狀態(tài)做成枚舉前端再用映射字典把數(shù)字翻譯成中文標(biāo)簽。訂單狀態(tài)流轉(zhuǎn)是最核心的業(yè)務(wù)規(guī)則。從用戶角度看是這個鏈路待支付 - 已支付待取車 - 租賃中 - 已完成任意狀態(tài)下取消則進(jìn)入已取消。從管理員角度看這里面有兩次人工確認(rèn)動作確認(rèn)取車和確認(rèn)還車。為什么要人工確認(rèn)因?yàn)榫€下要驗(yàn)車車況沒問題才交接這個動作必須由管理員觸發(fā)不能全自動。狀態(tài)流轉(zhuǎn)用表格表示當(dāng)前狀態(tài)觸發(fā)動作下一狀態(tài)誰觸發(fā)0 待支付支付成功1 已支付待取車用戶0 待支付取消訂單4 已取消用戶1 已支付待取車管理員確認(rèn)取車2 租賃中管理員2 租賃中管理員確認(rèn)還車3 已完成管理員1 已支付待取車超時未取車4 已取消系統(tǒng)/管理員這套狀態(tài)機(jī)定義清晰后代碼里每個狀態(tài)下的按鈕、接口邏輯、車輛狀態(tài)配合都變得非常明確不需要在每個接口里臨時判斷一堆if。2.3 時間沖突檢測租賃系統(tǒng)最核心的業(yè)務(wù)校驗(yàn)這是整套系統(tǒng)最有含金量的地方。車輛表里有status字段標(biāo)記是否在租但只有這個還不夠——一個訂單還沒創(chuàng)建時車是“可租”的可一旦有人在某個時間段已經(jīng)預(yù)定了又沒到取車時間車已經(jīng)處于被占用的窗口期。這個時候需要靠訂單表來判斷。判斷邏輯可以歸結(jié)為一句話新訂單的租期 [newStart, newEnd] 與某個已占用訂單的租期 [oldStart, oldEnd] 是否存在重疊區(qū)間。SQL寫法是這樣的SELECT COUNT(*) FROM orders WHERE car_id #{carId} AND status IN (1, 2) AND start_time #{newEndTime} AND end_time #{newStartTime}這里status IN (1, 2)排除了已取消和已完成的訂單。這個區(qū)間重疊判斷覆蓋了四種情況新訂單完全包含舊訂單、新訂單被舊訂單包含、新訂單左端與舊訂單右端重疊、新訂單右端與舊訂單左端重疊。凡是這四種情況里任意一種count都大于0就不能下單。邊界情況要注意處理有人會問前一個訂單還車時間是9:00新訂單取車時間也是9:00算不算沖突嚴(yán)格說這取決于業(yè)務(wù)規(guī)則。我的規(guī)則是不允許邊界緊貼所以SQL里用的是和不是和。如果業(yè)務(wù)上允許9點(diǎn)整還車9點(diǎn)整租出那要用和處理。這個細(xì)節(jié)最好和業(yè)務(wù)方確認(rèn)清楚再定。3. 后端實(shí)現(xiàn)里容易翻車的三個點(diǎn)金額、狀態(tài)、動態(tài)SQL3.1 金額計(jì)算與BigDecimal的精度陷阱租金計(jì)算看起來簡單日租金 × 天數(shù) 押金但實(shí)操里有兩個坑。第一個坑是數(shù)據(jù)類型金額一律用BigDecimal計(jì)算數(shù)據(jù)庫里用decimal(10,2)絕不能用float或double。0.1 0.2 在double里的結(jié)果是0.30000000000000004這個精度錯誤在金額上會造成分賬差錯測試時還不容易發(fā)現(xiàn)。第二個坑是租期天數(shù)的計(jì)算邏輯兩個datetime之間做差得到毫秒數(shù)再除以一天的毫秒數(shù)這里牽扯時區(qū)問題取整規(guī)則也要定好。我這里的使用者是按整天租賃的所以天數(shù)計(jì)算采用向上取整當(dāng)天取當(dāng)晚還也算一天。代碼很簡單long diffMs endTime.getTime() - startTime.getTime(); int totalDays (int) Math.ceil(diffMs / (1000.0 * 3600 * 24)); BigDecimal amount dailyPrice.multiply(BigDecimal.valueOf(totalDays));這個計(jì)算邏輯后端寫好后前端也必須要寫一模一樣的預(yù)估價邏輯。我的做法是在前端展示預(yù)估費(fèi)用但最終費(fèi)用以后端訂單詳情里的金額為準(zhǔn)。如果兩邊口徑不一致用戶看到預(yù)估100元下單后變成120元體驗(yàn)非常差。3.2 防止“搶車”條件更新比先查再改可靠租賃系統(tǒng)并發(fā)場景下容易出現(xiàn)一個bug兩個用戶同時看中同一輛車同時提交訂單結(jié)果兩個訂單都創(chuàng)建成功。解決辦法是兩層防護(hù)。第一層創(chuàng)建訂單前執(zhí)行上面那個沖突檢測SQLcount為0才允許創(chuàng)建第二層創(chuàng)建訂單時會同步把車輛狀態(tài)改為已租出這個更新操作必須加條件UPDATE car SET status 1 WHERE id #{carId} AND status 0注意這里的關(guān)鍵點(diǎn)update語句里帶了AND status 0這個條件。如果車輛已經(jīng)被其他訂單改成了已租出這條update受影響的行數(shù)就是0通過Java代碼判斷affectedRows如果為0就拋出異常讓當(dāng)前事務(wù)回滾這樣即使兩個用戶同時提交也只有一個能成功。這個設(shè)計(jì)比“先SELECT再UPDATE”的常規(guī)寫法更可靠因?yàn)樵诟卟l(fā)環(huán)境下select之后、update之前的那段時間里車輛狀態(tài)可能已經(jīng)被別人改了。條件更新本質(zhì)上是一種樂觀鎖思路用狀態(tài)本身做版本校驗(yàn)。3.3 MyBatis動態(tài)SQL和事務(wù)邊界MyBatis的動態(tài)SQL是查詢條件不確定時的核心武器。比如車輛列表頁用戶可以按品牌模糊查、按車型分類查、按價格區(qū)段查、按狀態(tài)查這些條件任意組合。用XML的 標(biāo)簽加 標(biāo)簽就能輕松搞定select idlistCars resultTypecom.example.entity.Car SELECT * FROM car where if testbrand ! null and brand ! AND brand LIKE CONCAT(%, #{brand}, %) /if if testcategory ! null AND category #{category} /if if teststatus ! null AND status #{status} /if /where ORDER BY create_time DESC /select標(biāo)簽的好處是如果所有條件都不滿足它不會生成WHERE關(guān)鍵字如果第一個條件成立它會自動去掉多出來的AND。這里要注意 里的判空不僅要判null還要判空字符串尤其前端傳空字符串的時候brand ! null攔不住。事務(wù)邊界是另一個容易翻車的地方。創(chuàng)建訂單這個操作涉及三件事插入訂單記錄、更新車輛狀態(tài)為已租出、記錄支付流水。這三個操作必須在一個事務(wù)里任何一個失敗都要全部回滾否則會出現(xiàn)訂單存在但車輛狀態(tài)沒改的臟數(shù)據(jù)。實(shí)現(xiàn)方式就是在service方法上添加Transactional注解。但要注意一個Spring事務(wù)自調(diào)用失效的問題同一個類里方法A調(diào)用方法BB上面有Transactional是無效的事務(wù)不會開啟。事務(wù)方法是代理對象調(diào)用才生效所以事務(wù)邏輯要設(shè)計(jì)在獨(dú)立的service類里別在controller里寫業(yè)務(wù)事務(wù)也別在一個類內(nèi)部調(diào)來調(diào)去。4. Vue3前端用戶端和管理端分開設(shè)計(jì)4.1 前端工程結(jié)構(gòu)和路由設(shè)計(jì)前端我用Vite初始化Vue3項(xiàng)目用TypeScript還是JavaScript這個項(xiàng)目我用的是JavaScript團(tuán)隊(duì)項(xiàng)目用TS另說個人項(xiàng)目里JS改起來更快。UI庫用Element Plus狀態(tài)管理用PiniaHTTP請求用Axios。目錄結(jié)構(gòu)比較常規(guī)src/ api/ 接口請求定義 store/ Pinia狀態(tài)管理存token和用戶信息 router/ 路由配置 views/user/ 用戶端頁面 views/admin/ 管理端頁面 utils/ axios實(shí)例和工具函數(shù)路由這塊用前端路由守衛(wèi)配合后端鑒權(quán)雙層控制。前端路由分為用戶端和管理端兩組管理員登錄后才顯示管理端菜單普通用戶訪問管理端路由直接跳首頁。后端的攔截器還要再校驗(yàn)一次兩層的權(quán)限攔截不能互相替代。4.2 用戶端選車、下單、支付的交互鏈路用戶端的核心交互是四個步驟瀏覽車輛 - 查看詳情 - 選時間下單 - 支付。車輛列表頁用卡片展示每輛車顯示名稱、分類標(biāo)簽、日租金、押金、當(dāng)前狀態(tài)。狀態(tài)為“可租”的車輛才可以點(diǎn)擊查看詳情其他狀態(tài)置灰。這個細(xì)節(jié)很重要用戶看到一輛在租的車點(diǎn)進(jìn)去發(fā)現(xiàn)下不了單體驗(yàn)很差。車輛詳情頁是交互最關(guān)鍵的地方。頁面里放了兩個日期選擇器分別是取車時間和還車時間默認(rèn)取車時間是明天還車時間是后天。用戶一選完時間前端立刻計(jì)算預(yù)估費(fèi)用并展示出來。計(jì)算公式和后端完全一致const diffMs new Date(endTime).getTime() - new Date(startTime).getTime() const days Math.ceil(diffMs / (1000 * 3600 * 24)) const estimate dailyPrice * days deposit用戶點(diǎn)擊下單前端把carId、startTime、endTime提交到后端。后端返回訂單ID和應(yīng)付金額前端彈一個支付確認(rèn)框上面顯示明細(xì)租金多少、押金多少、合計(jì)多少。本項(xiàng)目用模擬支付點(diǎn)確認(rèn)就調(diào)用支付接口把訂單狀態(tài)從待支付改成已支付待取車。真實(shí)項(xiàng)目中這一步要對接微信或支付寶邏輯上只是把模擬支付的接口換成真正的支付回調(diào)。4.3 管理端用狀態(tài)驅(qū)動按鈕顯隱管理端訂單管理頁是最能體現(xiàn)狀態(tài)機(jī)設(shè)計(jì)價值的地方。頁面用表格展示所有訂單每一行根據(jù)訂單狀態(tài)顯示不同的操作按鈕訂單狀態(tài)顯示的操作按鈕0 待支付查看詳情、取消1 已支付待取車確認(rèn)取車2 租賃中確認(rèn)還車3 已完成查看詳情4 已取消查看詳情Element Plus的el-table里可以通過條件渲染動態(tài)控制按鈕是否顯示操作列根據(jù)狀態(tài)對象里的屬性來判斷。這樣做的好處是代碼邏輯非常簡單不會出現(xiàn)一堆亂七八糟的v-if每個狀態(tài)對應(yīng)什么操作是固定的數(shù)據(jù)驅(qū)動的思路。車輛管理頁相對簡單表格展示車輛列表新增和編輯用彈窗表單。這里容易被忽略的是車輛分類和日租金的表單校驗(yàn)分類用一個下拉框取值范圍要和后端枚舉對齊。還有“上架/下架”操作下架的車不允許再下單這個操作本質(zhì)就是更新車輛status字段為3。4.4 前端狀態(tài)管理和權(quán)限控制用戶登錄后的token、用戶基本信息、角色信息我都放在Pinia里。Axios的請求攔截器統(tǒng)一從store里取token加到Authorization請求頭響應(yīng)攔截器判斷后端返回code如果不是200就彈出錯誤提示如果是401表示token過期清除本地登錄狀態(tài)跳轉(zhuǎn)登錄頁。這個模式幾乎是Vue3前后端分離項(xiàng)目里固定套路但值得注意的一點(diǎn)是響應(yīng)攔截器的錯誤提示不能太生硬要區(qū)分是網(wǎng)絡(luò)錯誤還是業(yè)務(wù)錯誤業(yè)務(wù)錯誤比如庫存不足要用后端的message內(nèi)容提示網(wǎng)絡(luò)錯誤則統(tǒng)一提示“網(wǎng)絡(luò)異常請稍后重試”。5. 前后端聯(lián)調(diào)的關(guān)鍵細(xì)節(jié)統(tǒng)一返回結(jié)構(gòu)、JWT鑒權(quán)、跨域5.1 統(tǒng)一接口返回結(jié)構(gòu)前后端分離的項(xiàng)目最忌諱各寫各的后端返回原生對象字段名一個叫createTime前端需要的卻是createdAt聯(lián)調(diào)時改來改去。我從一開始就定了統(tǒng)一的返回結(jié)構(gòu){ code: 200, message: success, data: {} }code為200表示成功非200表示業(yè)務(wù)異常message里放給用戶看的提示信息data里放業(yè)務(wù)數(shù)據(jù)。后端所有接口都返回這個Result對象泛型保證data的類型安全。這么做的好處是前端的響應(yīng)攔截器可以全局統(tǒng)一處理code為200時直接返回data非200時統(tǒng)一彈message。前端每個請求都不用重復(fù)寫錯誤處理代碼。5.2 JWT登錄態(tài)設(shè)計(jì)前后端分離后服務(wù)端不再維護(hù)Session登錄態(tài)用什么方案我用的是JWT。流程是登錄接口校驗(yàn)用戶名密碼成功后后端生成一個token返回給前端前端存到localStorage里之后每次請求都在Authorization頭帶上這個token后端攔截器解析token獲取用戶信息。生成token我用的是jjwt庫token里可以放userId和role過期時間設(shè)24小時。后端寫一個攔截器在HandlerInterceptor的preHandle里解析token解析成功就放行失敗就返回401。要注意的是攔截器放行的白名單要放好登錄、注冊、車輛列表查詢這些接口是不需要登錄就能訪問的不能攔截但下單、支付、管理端所有接口必須登錄后才能訪問。管理員的接口校驗(yàn)要加角色判斷普通用戶拿自己的token調(diào)管理員接口也必須拒絕。5.3 開發(fā)環(huán)境和生產(chǎn)環(huán)境的跨域處理開發(fā)階段前后端分離前端跑在5173端口后端跑在8080端口跨域問題避免不了。我的方案是開發(fā)環(huán)境用Vite的proxy代理把前端的/api請求轉(zhuǎn)發(fā)到后端// vite.config.js server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }生產(chǎn)環(huán)境則用Nginx反向代理讓前端靜態(tài)頁面和后端接口在同一個域名下徹底避免跨域問題。Nginx配置里把/api路徑轉(zhuǎn)發(fā)到后端服務(wù)其他路徑返回前端dist目錄的靜態(tài)文件。我在部署時會加上try_files配置后面踩坑記錄里會詳細(xì)說。6. 打包部署與實(shí)測踩坑記錄6.1 前后端打包流程后端打包用Maven命令很簡單mvn clean package -DskipTests生成的jar在target目錄直接java -jar跑起來。前端打包用Vitenpm run build生成dist目錄。生產(chǎn)部署我是把后端jar和前端dist都放到同一臺服務(wù)器上后端跑SpringBoot服務(wù)監(jiān)聽8080端口前端dist目錄由Nginx托管。6.2 我在實(shí)測中遇到的真坑第一個坑是MySQL時區(qū)導(dǎo)致的日期錯亂。系統(tǒng)上線后發(fā)現(xiàn)訂單的創(chuàng)建時間比本地時間晚了8個小時原因是JDBC連接串里沒配時區(qū)。解決方法是連接串加上參數(shù)jdbc:mysql://localhost:3306/car_rental?serverTimezoneAsia/ShanghaicharacterEncodingutf8useSSLfalse這個坑在本地開發(fā)時可能不出現(xiàn)因?yàn)楸镜豈ySQL時區(qū)恰好和系統(tǒng)一致部署到云服務(wù)器或者用Docker跑MySQL時就會暴露。遇到時間相關(guān)的問題第一反應(yīng)就是查時區(qū)配置。第二個坑是Linux下MySQL表名大小寫敏感。本地Windows開發(fā)時一切正常部署到Linux服務(wù)器后程序報(bào)“Table car_rental.CAR doesnt exist”因?yàn)槲业腟QL里寫的是大寫表名Windows下MySQL默認(rèn)不區(qū)分大小寫Linux下區(qū)分。解決方法兩種要么所有SQL統(tǒng)一小寫表名要么在MySQL配置里加lower_case_table_names1。個人項(xiàng)目改SQL統(tǒng)一比較穩(wěn)妥。第三個坑是Vue打包部署后刷新頁面出現(xiàn)404。原因很簡單前端路由用的是history模式Nginx默認(rèn)配置下刷新 /orders/123 這樣的路徑時Nginx去磁盤上找這個路徑對應(yīng)的文件找不到就404。要在Nginx配置里加上location / { try_files $uri $uri/ /index.html; }這個配置的意思是如果請求的路徑不存在就回退到index.html讓前端路由自己解析。第四個坑是業(yè)務(wù)層面的訂單取消或還車后車輛狀態(tài)要記得恢復(fù)。這個看起來理所當(dāng)然但我在開發(fā)時漏過用戶下單后取消訂單訂單狀態(tài)改成已取消但車輛狀態(tài)還是已租出這輛車就永遠(yuǎn)租不出去了。后來在service方法里嚴(yán)格按狀態(tài)機(jī)流轉(zhuǎn)訂單取消的同時把車輛狀態(tài)改回可租并且放在同一個事務(wù)里才算徹底解決。6.3 這個項(xiàng)目的后續(xù)擴(kuò)展空間系統(tǒng)做完了功能上是完整的但還有幾個方向可以繼續(xù)擴(kuò)展。第一個是把前端預(yù)估費(fèi)用的邏輯抽出來獨(dú)立成一個費(fèi)用計(jì)算工具后續(xù)如果支持半天租、按時租前后端只改這個工具就行。第二個是給車輛加價格日歷節(jié)假日和旺季可以動態(tài)調(diào)整日租金這個功能在真正的租車公司里幾乎必備。第三個是增加一些統(tǒng)計(jì)報(bào)表比如車輛出租率、訂單收入趨勢、熱門車型排行管理端用ECharts畫幾個圖表項(xiàng)目看起來也更完整。第四個是把管理端的角色再細(xì)分比如操作員和超級管理員操作員只能處理訂單超級管理員才能改車輛配置。說回到這套系統(tǒng)本身我個人實(shí)測下來最大的體會是租賃類業(yè)務(wù)系統(tǒng)的難度不在增刪改查而在業(yè)務(wù)約束的完整性。一輛車的狀態(tài)從可租到被租再到還車背后牽扯的是時間段沖突檢測、事務(wù)邊界、狀態(tài)流轉(zhuǎn)的異?;謴?fù)這些邏輯捋清楚了代碼寫起來會非常順。這套設(shè)計(jì)不止適用于汽車租賃共享單車、酒店房間預(yù)約、機(jī)械設(shè)備租借核心模型都可以直接平移過去。如果你正在找項(xiàng)目練手我建議在跑通基礎(chǔ)功能后真實(shí)地給自己設(shè)計(jì)幾個并發(fā)場景去壓一壓比如同時搶同一輛車、訂單取消的瞬間又有新用戶下單把這些場景都處理干凈這個項(xiàng)目就算真正吃透了。