家政預(yù)約平臺(tái):從狀態(tài)機(jī)設(shè)計(jì)到上線交付全解析)
前陣子陸續(xù)有幾位讀者來問同一個(gè)需求想做一個(gè)社區(qū)家政服務(wù)預(yù)約平臺(tái)技術(shù)棧限定springbootvue還要帶源碼、文檔最好能幫忙調(diào)試、改改基礎(chǔ)功能、答疑。我一開始以為只是隨便問問后來發(fā)現(xiàn)這個(gè)問題背后其實(shí)是完整的一類項(xiàng)目訴求——既要能通過課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)的驗(yàn)收又要真正跑起來給小區(qū)物業(yè)、中小家政公司內(nèi)部試用。順手把我的經(jīng)驗(yàn)整理成一篇從需求設(shè)計(jì)、數(shù)據(jù)庫、后端、前端到聯(lián)調(diào)交付盡量說全說透希望能幫你省下幾周摸坑的時(shí)間。1. 為什么社區(qū)家政預(yù)約平臺(tái)選擇了springbootvue這條技術(shù)路線社區(qū)家政服務(wù)的典型場(chǎng)景大家都不陌生保潔、家電清洗、保姆月嫂、管道維修這類服務(wù)過去靠電話預(yù)約或者微信群接龍漏單、撞單、爽約經(jīng)常發(fā)生想要查某個(gè)訂單的歷史記錄比翻聊天記錄還難。一個(gè)預(yù)約平臺(tái)要做的就是三件事業(yè)主能在線看服務(wù)、選時(shí)間下單家政人員能接單和確認(rèn)完成平臺(tái)方能審核上架和做基本的數(shù)據(jù)統(tǒng)計(jì)。技術(shù)選型上springbootvue幾乎是最穩(wěn)妥的組合甚至可以說是這類管理型Web項(xiàng)目的默認(rèn)答案。先說后端。SpringBoot把配置簡(jiǎn)化到了極致一個(gè)啟動(dòng)類加一個(gè)application.yml就能跑起來內(nèi)嵌Tomcat連部署都不用額外裝。配合MyBatis-Plus這類國(guó)產(chǎn)增強(qiáng)框架單表增刪改查基本不用寫SQL代碼量可以壓到很低。對(duì)大多數(shù)學(xué)生項(xiàng)目來說不需要上微服務(wù)、消息隊(duì)列這些重型中間件SpringBoot單體能扛住幾百人的小區(qū)使用量也足夠答辯演示。再說前端。Vue的數(shù)據(jù)綁定和組件化思想對(duì)新手來說上手門檻遠(yuǎn)低于React。配合Element Plus組件庫表單、表格、彈窗、時(shí)間選擇器這些后臺(tái)管理頁面的常規(guī)組件全都現(xiàn)成一個(gè)上午就能把頁面框架搭出來。加上Vue生態(tài)的成長(zhǎng)路線很平滑后面想升級(jí)做小程序端、或改成移動(dòng)端H5核心邏輯都能復(fù)用。還有一個(gè)很現(xiàn)實(shí)的原因遇到問題時(shí)能搜到的資料率很高。SpringBoot的接口如何寫、JWT怎么集成、Element Plus怎么用、Vue路由守衛(wèi)怎么寫幾乎每個(gè)坑都有人踩過、有人寫博客講清楚。對(duì)一個(gè)以完成項(xiàng)目為主要目標(biāo)的開發(fā)者來說這比任何技術(shù)先進(jìn)性都重要。實(shí)際上這幾年我接觸到的家政類、預(yù)約類、社區(qū)服務(wù)類項(xiàng)目超過一半都采用了這套組合跑通率也最高。選它不一定最驚艷但一定最不容易翻車。2. 業(yè)務(wù)閉環(huán)先于代碼訂單狀態(tài)機(jī)的設(shè)計(jì)決定了系統(tǒng)上限很多開發(fā)者拿到項(xiàng)目需求第一件事就是打開數(shù)據(jù)庫建表恨不得一個(gè)中午把表建完。但如果家政預(yù)約平臺(tái)也這么干后面大概率要做大量返工。原因很簡(jiǎn)單這類系統(tǒng)的核心不是服務(wù)分類的增刪改查而是訂單從創(chuàng)建到完成的整個(gè)生命周期。生命周期里的狀態(tài)怎么流轉(zhuǎn)、每個(gè)狀態(tài)下誰能做什么操作、不允許跳轉(zhuǎn)哪些狀態(tài)這些不做清楚接口寫出來一定前后矛盾。理清業(yè)務(wù)閉環(huán)我建議先守住三張角色圖和一條主流程線。三個(gè)角色分別是客戶下單的人、家政人員接單干活的人、管理員平臺(tái)運(yùn)營(yíng)的人??蛻舻牟僮髀窂绞亲?cè)登錄、瀏覽服務(wù)分類、查看服務(wù)詳情、選擇預(yù)約日期時(shí)間、填地址與聯(lián)系人、提交訂單、等待確認(rèn)、服務(wù)完成后評(píng)價(jià)。家政人員的路徑是登錄后查看被分配或者被點(diǎn)單的預(yù)約、確認(rèn)接單、按時(shí)上門、服務(wù)結(jié)束后確認(rèn)完成。管理員路徑是審核服務(wù)項(xiàng)目是否上架、審核家政人員入駐資質(zhì)、查看訂單列表處理異常單、查看基礎(chǔ)數(shù)據(jù)看板。主流程線就是一條訂單狀態(tài)機(jī)。我實(shí)際項(xiàng)目里的狀態(tài)取值是這樣的狀態(tài)值名稱觸發(fā)動(dòng)作0待確認(rèn)客戶提交預(yù)約訂單1已確認(rèn)家政人員接單或管理員后臺(tái)確認(rèn)2服務(wù)中家政人員點(diǎn)擊開始服務(wù)3已完成家政人員點(diǎn)擊完成服務(wù)4已取消客戶取消或管理員取消5售后中客戶發(fā)起投訴或退款申請(qǐng)狀態(tài)機(jī)的核心原則是每次狀態(tài)變更只能從當(dāng)前狀態(tài)跳轉(zhuǎn)到合法狀態(tài)不允許跳級(jí)不允許逆向特殊情況除外比如管理員強(qiáng)制取消。例如待確認(rèn)狀態(tài)可以由客戶自己取消變成已取消但一旦家政人員接單變成已確認(rèn)就不能靜默取消必須走退款或售后流程。這個(gè)規(guī)則在代碼里必須顯式定義而不是靠每個(gè)接口各自做一個(gè)if判斷。為方便講解訂單狀態(tài)機(jī)的設(shè)計(jì)值得前置到建表之前。先畫狀態(tài)轉(zhuǎn)換表再對(duì)著表寫接口邏輯會(huì)清晰很多。通常我建一個(gè)常量類專門管理狀態(tài)值不讓魔法數(shù)字散落在業(yè)務(wù)代碼里。3. 后端落地細(xì)節(jié)分層結(jié)構(gòu)、登錄鑒權(quán)與統(tǒng)一返回格式3.1 三層結(jié)構(gòu)怎么拆才清晰后端代碼結(jié)構(gòu)不必花哨但一定要分層明確。我習(xí)慣拆成這樣controller只接收參數(shù)、調(diào)用服務(wù)、返回統(tǒng)一結(jié)果不在里面寫業(yè)務(wù)邏輯service負(fù)責(zé)業(yè)務(wù)邏輯比如下單校驗(yàn)訂單狀態(tài)、校驗(yàn)服務(wù)時(shí)間沖突mapper繼承BaseMapper負(fù)責(zé)數(shù)據(jù)庫交互entity與表字段一一對(duì)應(yīng)的實(shí)體類dto / vo入?yún)?duì)象和出參對(duì)象避免把實(shí)體類直接暴露給前端有人圖省事直接在controller里寫一堆業(yè)務(wù)代碼項(xiàng)目小的時(shí)候還能跑但只要加上預(yù)約時(shí)間是否可選工人當(dāng)天是否已經(jīng)被約滿這些判斷controller會(huì)迅速膨脹到幾百行。分層的好處不是代碼好看是后續(xù)排查問題和改需求時(shí)能快速定位。比如面試官或老師問訂單狀態(tài)這個(gè)邏輯在哪里控制你能直接說出service.order.status里的checkAndUpdate方法這就是說服力。3.2 JWT登錄和角色權(quán)限用一套賬號(hào)表頂三代家政平臺(tái)涉及三種角色的登錄。很多新手會(huì)建三張表user表、worker表、admin表然后分別寫三套登錄接口。這個(gè)設(shè)計(jì)維護(hù)成本很高而且三種角色其實(shí)共享賬號(hào)密碼、手機(jī)號(hào)、頭像這些基礎(chǔ)字段。我更推薦一張account賬號(hào)表加一個(gè)role字段區(qū)分角色值分別為USER、WORKER、ADMIN。家政人員或管理員本質(zhì)上都是account擴(kuò)展一個(gè)worker_info表存他們的專屬資料即可。登錄接口返回JWT令牌前端把令牌存localStorage每次請(qǐng)求帶上Authorization頭。后端寫一個(gè)登錄攔截器對(duì)需要登錄的接口校驗(yàn)令牌并從令牌中解析出用戶id和角色。這里提個(gè)建議如果項(xiàng)目沒有強(qiáng)制要求不必上Spring Security。它雖然功能全但概念多、配置重對(duì)這類系統(tǒng)屬于殺雞用牛刀。用攔截器配合角色判斷代碼簡(jiǎn)單、運(yùn)行效率高講起原理也更容易自圓其說。3.3 統(tǒng)一返回與全局異常別讓報(bào)錯(cuò)暴露給用戶前后端聯(lián)調(diào)時(shí)最怕的就是接口返回結(jié)構(gòu)不統(tǒng)一。有的接口返回true有的返回操作成功前端取數(shù)據(jù)時(shí)要針對(duì)每個(gè)接口單獨(dú)處理極容易出bug。建議定義統(tǒng)一返回類Resultpublic class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.code 200; result.message success; result.data data; return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.code code; result.message message; return result; } }同時(shí)寫一個(gè)全局異常處理器把參數(shù)校驗(yàn)異常、業(yè)務(wù)異常、兜底異常統(tǒng)一轉(zhuǎn)成Result返回這樣即使代碼某個(gè)位置漏了處理前端也只會(huì)看到規(guī)范化的JSON而不是一長(zhǎng)串堆棧信息。配合自定義異常類BizException在service里隨時(shí)拋出業(yè)務(wù)錯(cuò)誤比如該時(shí)間段已被預(yù)約前端直接彈message提示即可。后端接口里訂單相關(guān)的操作我建議刻意取語義清晰的動(dòng)作詞createOrder對(duì)應(yīng)下單confirmOrder對(duì)應(yīng)接單cancelOrder對(duì)應(yīng)取消completeOrder對(duì)應(yīng)完成。動(dòng)詞式接口配合狀態(tài)機(jī)整個(gè)調(diào)用鏈會(huì)非常直觀。4. 數(shù)據(jù)庫表這樣設(shè)計(jì)后續(xù)改需求才不會(huì)推倒重來4.1 七張核心表的職責(zé)劃分家政預(yù)約平臺(tái)不算復(fù)雜但至少需要七張核心業(yè)務(wù)表。一張表對(duì)應(yīng)一個(gè)清晰領(lǐng)域概念這能讓后期改造的維護(hù)成本大幅降低。account表賬號(hào)公共信息username、password、phone、role、nickname、avatar、statusaddress表客戶的收貨/服務(wù)地址receiverName、receiverPhone、省市區(qū)、詳細(xì)地址、是否默認(rèn)service_category表服務(wù)分類如保潔、維修、月嫂service_item表服務(wù)項(xiàng)目掛接分類包含名稱、價(jià)格、計(jì)價(jià)單位、預(yù)計(jì)耗時(shí)、封面圖worker_info表家政人員資料關(guān)聯(lián)account包含真名、電話、可服務(wù)項(xiàng)目、接單狀態(tài)、審核狀態(tài)order_info表預(yù)約訂單主表comment表服務(wù)評(píng)價(jià)關(guān)聯(lián)訂單、用戶和家政人員分開來看只是普通的“增刪改查表”但合理拆分的價(jià)值在于新增一個(gè)服務(wù)維度字段只要改service_item表不影響訂單家政人員入駐審核功能只需擴(kuò)展worker_info字段未來想給不同服務(wù)加不同計(jì)價(jià)規(guī)則也只要在service_item里加字段。表職責(zé)清晰意味著需求變更的沖擊被隔離在局部而不是一路波及所有表。4.2 訂單表字段設(shè)計(jì)的幾個(gè)關(guān)鍵決策order_info是整個(gè)系統(tǒng)的心臟它的設(shè)計(jì)質(zhì)量直接決定開發(fā)體驗(yàn)。核心字段我建議這樣安排id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 自增ID, order_no VARCHAR(32) UNIQUE COMMENT 訂單號(hào)給用戶看的編號(hào), user_id BIGINT COMMENT 下單用戶ID, worker_id BIGINT COMMENT 接單家政人員ID, service_item_id BIGINT COMMENT 服務(wù)項(xiàng)目ID, address_id BIGINT COMMENT 服務(wù)地址ID, service_time_start DATETIME COMMENT 預(yù)約開始時(shí)間, service_time_end DATETIME COMMENT 預(yù)約結(jié)束時(shí)間, status TINYINT COMMENT 訂單狀態(tài) 0待確認(rèn) 1已確認(rèn) 2服務(wù)中 3已完成 4已取消 5售后中, order_amount DECIMAL(10,2) COMMENT 訂單金額, pay_type TINYINT COMMENT 支付方式, remark VARCHAR(255) COMMENT 客戶備注, create_time DATETIME COMMENT 創(chuàng)建時(shí)間, pay_time DATETIME COMMENT 支付時(shí)間這里有幾個(gè)決策值得展開。第一為什么訂單號(hào)order_no要單獨(dú)存在而不是直接用自增id因?yàn)橛脩艉秃笈_(tái)溝通時(shí)會(huì)說我那個(gè)訂單號(hào)是多少自增id位數(shù)少且連續(xù)容易被試探和猜測(cè)也不利于分段檢索。訂單號(hào)通常用時(shí)間戳加隨機(jī)數(shù)生成長(zhǎng)度16到20位之間即可。第二為什么預(yù)留service_time_end而不是只存service_time_start因?yàn)榧艺?wù)普遍按時(shí)長(zhǎng)計(jì)費(fèi)。保潔按小時(shí)、維修按上門時(shí)長(zhǎng)預(yù)留結(jié)束時(shí)間后訂單列表頁可以展示預(yù)計(jì)服務(wù)時(shí)長(zhǎng)統(tǒng)計(jì)家政人員每天的總工作量也要用到這個(gè)值。哪怕MVP階段用不到也應(yīng)該先占住這個(gè)位置后面補(bǔ)上比改表結(jié)構(gòu)容易得多。第三物理外鍵要不要加我的建議是不加。數(shù)據(jù)庫級(jí)的外鍵約束刪除分類或服務(wù)時(shí)會(huì)受到各種限制維護(hù)成本高。業(yè)務(wù)層面通過service_item_id關(guān)聯(lián)在service代碼里做存在性校驗(yàn)即可。這個(gè)做法在真實(shí)企業(yè)項(xiàng)目里很常見面試時(shí)也能講出理由。第四關(guān)于時(shí)間的坑。這類系統(tǒng)任何時(shí)間字段都要統(tǒng)一用datetime或者timestamp且后端接收前端傳來的時(shí)間參數(shù)時(shí)一定要用JsonFormat標(biāo)注格式或者全局配置Jackson序列化規(guī)則否則前端傳2025-06-01 09:00:00后端解析失敗直接報(bào)400錯(cuò)誤。我甚至遇到過前后端顯示時(shí)區(qū)不一致數(shù)據(jù)庫存的時(shí)間比實(shí)際早了8小時(shí)的情況排查到最后發(fā)現(xiàn)是MySQL時(shí)區(qū)配置問題。這些內(nèi)容在后端配置階段就應(yīng)該處理掉而不是等到聯(lián)調(diào)再救火。5. 前端從0到能點(diǎn)通全流程路由、組件與預(yù)約表單的體驗(yàn)5.1 前后臺(tái)同工程路由和布局家政平臺(tái)的前端我建議把用戶前臺(tái)和管理員后臺(tái)放在同一個(gè)Vue工程里。很多教程喜歡拆成兩個(gè)前端項(xiàng)目說是職責(zé)分離但實(shí)際上兩人份的登錄態(tài)維護(hù)、兩套打包配置、兩處部署對(duì)單人開發(fā)是負(fù)擔(dān)。用路由前綴區(qū)分即可普通頁面放在 / 下面后臺(tái)管理頁面統(tǒng)一掛在 /admin 下。路由層面簡(jiǎn)單的劃分公共頁面登錄頁、注冊(cè)頁、首頁、服務(wù)列表、服務(wù)詳情用戶頁面預(yù)約下單、訂單列表、訂單詳情、服務(wù)地址管理、個(gè)人中心管理頁面數(shù)據(jù)看板、訂單管理、服務(wù)分類管理、服務(wù)項(xiàng)目管理、家政人員管理、評(píng)價(jià)管理這樣劃分后權(quán)限控制也方便普通用戶登錄后能訪問用戶頁面管理員進(jìn)入后臺(tái)路由守衛(wèi)按角色過濾。5.2 登錄態(tài)與權(quán)限路由守衛(wèi)必須做前端路由守衛(wèi)是這個(gè)系統(tǒng)不能省的部分。用Vue Router的beforeEach鉤子做統(tǒng)一攔截邏輯也不復(fù)雜判斷目標(biāo)路由是否需要登錄需要?jiǎng)t檢查本地有token沒有有token還要判斷訪問的是不是管理員頁面是則校驗(yàn)角色是否ADMIN不滿足條件就跳回登錄頁。有人覺得后臺(tái)的某些頁面反正接口也會(huì)校驗(yàn)權(quán)限前端路由守衛(wèi)是多余的。實(shí)際上不是這樣。前端守衛(wèi)的價(jià)值在于體驗(yàn)未登錄用戶點(diǎn)擊我的訂單時(shí)能及時(shí)被帶到登錄頁而不是等接口報(bào)401再彈錯(cuò)誤。權(quán)限錯(cuò)誤和未登錄錯(cuò)誤的提示文案也應(yīng)該區(qū)分否則用戶完全不知道為什么點(diǎn)按鈕沒反應(yīng)。5.3 預(yù)約表單的體驗(yàn)細(xì)節(jié)時(shí)間選擇、防重復(fù)提交預(yù)約下單頁是整個(gè)前端交互的難點(diǎn)問題集中在三個(gè)地方。第一個(gè)是時(shí)間選擇。Element Plus的date-picker和time-picker支持禁用過去時(shí)間設(shè)置disabled-date為今天之前的日期不可選即可。如果業(yè)務(wù)上要求同一天內(nèi)上午的時(shí)間也禁掉可以用time-picker的disabled-hours把當(dāng)前時(shí)分之前的時(shí)間禁用。這塊很容易被忽略等測(cè)試人員發(fā)現(xiàn)為什么能預(yù)約昨天的時(shí)間時(shí)再改又要多一輪聯(lián)調(diào)。第二個(gè)是地址聯(lián)動(dòng)。選擇省市區(qū)用級(jí)聯(lián)選擇器最后一行填詳細(xì)地址。地址保存后要支持設(shè)為默認(rèn)地址這個(gè)字段在account里用is_default標(biāo)記下單頁默認(rèn)選中默認(rèn)地址即可。注意切換地址時(shí)表單校驗(yàn)要更新防止用戶改了地址但提交時(shí)仍是舊數(shù)據(jù)。第三個(gè)是防重復(fù)提交。用戶點(diǎn)擊提交預(yù)約后如果接口響應(yīng)慢他會(huì)習(xí)慣性再點(diǎn)一次導(dǎo)致同一個(gè)訂單創(chuàng)建兩份。前端解決方案是提交后按鈕立刻loading并禁用等待接口返回后再恢復(fù)。但這只是體驗(yàn)層面的兜底真正的安全防線在后端的防重復(fù)創(chuàng)建邏輯這一點(diǎn)下一章詳細(xì)講。用戶端預(yù)約成功后的引導(dǎo)也很重要。下單成功后不要直接清空頁面而是跳轉(zhuǎn)到一個(gè)支付成功或下單成功提示頁并顯示查看訂單按鈕讓用戶看到下一步的等待確認(rèn)狀態(tài)。這能大幅減少我的訂單到底提交了沒有這類反饋。6. 預(yù)約沖突與訂單狀態(tài)流轉(zhuǎn)聯(lián)調(diào)時(shí)最容易翻車的地方6.1 并發(fā)預(yù)約沖突數(shù)據(jù)庫唯一索引 vs 事務(wù)查詢下面聊兩個(gè)真正會(huì)在聯(lián)調(diào)階段炸掉的點(diǎn)。第一個(gè)是預(yù)約沖突。場(chǎng)景是這樣用戶A預(yù)約了某保潔阿姨周六上午9點(diǎn)到11點(diǎn)用戶B在同一時(shí)間也要約同一個(gè)阿姨。如果后端只做普通的查詢發(fā)現(xiàn)9點(diǎn)到11點(diǎn)沒記錄就允許下單在高并發(fā)或用戶雙擊的場(chǎng)景下可能兩個(gè)請(qǐng)求同時(shí)查到空閑然后同時(shí)插入成功阿姨就被約重了。網(wǎng)上常見的防御手段有兩種。第一種是前端把時(shí)間按鈕置灰看起來能解決問題但同一個(gè)用戶開兩個(gè)瀏覽器標(biāo)簽頁、或者手速夠快連點(diǎn)兩次照樣繞過去。第二種是數(shù)據(jù)庫加唯一索引比如對(duì)(worker_id, service_time_start)加唯一約束能擋住同一開始時(shí)間的重復(fù)預(yù)約但如果用戶A約9點(diǎn)到11點(diǎn)用戶B約10點(diǎn)到12點(diǎn)兩個(gè)記錄的開始時(shí)間不同唯一索引也攔不住。真正可靠的做法是用事務(wù)加鎖。在創(chuàng)建訂單時(shí)開一個(gè)事務(wù)先查該家政人員在目標(biāo)時(shí)間段是否存在重疊訂單Transactional public Long createOrder(OrderCreateDTO dto) { // 加行鎖或區(qū)間鎖防止并發(fā) Long conflictId orderMapper.selectConflictOrder(workerId, startTime, endTime); if (conflictId ! null) { throw new BizException(該家政人員在此時(shí)段已被預(yù)約請(qǐng)更換時(shí)間); } // 插入訂單 ... }對(duì)應(yīng)SQL查詢重疊訂單的邏輯是SELECT id FROM order_info WHERE worker_id #{workerId} AND status IN (0, 1, 2) AND service_time_start #{endTime} AND service_time_end #{startTime} LIMIT 1這個(gè)條件判斷的是時(shí)間區(qū)間是否有交集覆蓋率比單純查startTime準(zhǔn)確得多。配合數(shù)據(jù)庫里的worker_id和service_time_start唯一索引雙保險(xiǎn)下來基本可以杜絕撞單。對(duì)課程設(shè)計(jì)而言事務(wù)加重疊查詢已經(jīng)十分夠用不需要上分布式鎖。6.2 狀態(tài)轉(zhuǎn)換不寫保護(hù)后臺(tái)一定會(huì)把已取消訂單接單第二個(gè)坑是狀態(tài)轉(zhuǎn)換的原子性。我見過不少代碼這么寫接單接口先查訂單拿到訂單后判斷status是不是0如果是0就update成1。問題在于網(wǎng)絡(luò)延遲、用戶取消請(qǐng)求先到、管理員并發(fā)操作時(shí)兩步操作之間狀態(tài)可能已經(jīng)被改了但第二步拿著舊數(shù)據(jù)照樣更新成功。要避免這個(gè)問題最好的辦法是把校驗(yàn)和更新合成一條帶狀態(tài)條件的SQLint rows orderMapper.updateStatusIfMatch(orderId, targetStatus, expectStatus); if (rows 0) { throw new BizException(訂單狀態(tài)已變化請(qǐng)刷新后重試); }對(duì)應(yīng)的更新SQLUPDATE order_info SET status #{targetStatus}, update_time NOW() WHERE id #{orderId} AND status #{expectStatus}這里影響行數(shù)rows為0說明狀態(tài)不是預(yù)期狀態(tài)直接拋異常讓前端刷新。這一招在電商、庫存類系統(tǒng)里是??徒凶鳂酚^鎖。好處是簡(jiǎn)單、無額外依賴、天然防并發(fā)。像取消訂單這些操作也一樣統(tǒng)一走帶狀態(tài)校驗(yàn)的更新方法訂單狀態(tài)就不會(huì)被用戶反復(fù)橫跳搞亂。如果服務(wù)層能保證所有狀態(tài)變更都收斂到同一個(gè)方法里比如changeOrderStatus(orderId, expectedStatus, targetStatus)后面加一個(gè)待支付狀態(tài)或者退款中狀態(tài)都只要在這個(gè)方法里補(bǔ)充規(guī)則而不是滿項(xiàng)目找散落的update語句這也是前面說狀態(tài)機(jī)設(shè)計(jì)重要的落地體現(xiàn)。7. 從本機(jī)跑通到交付驗(yàn)收源碼交付階段的調(diào)試、修改與答疑7.1 本機(jī)把項(xiàng)目跑起來的順序無論你是學(xué)這套項(xiàng)目還是準(zhǔn)備交付給他人先保證自己能按干凈步驟跑起來。順序一般是這樣新建數(shù)據(jù)庫導(dǎo)入項(xiàng)目根目錄下sql文件確認(rèn)初始化數(shù)據(jù)存在修改后端application.yml里的數(shù)據(jù)源配置數(shù)據(jù)庫名、用戶名、密碼按本地環(huán)境調(diào)整啟動(dòng)后端確認(rèn)控制臺(tái)無報(bào)錯(cuò)接口文檔頁或測(cè)試接口能通前端目錄執(zhí)行npm install然后npm run dev等待頁面自動(dòng)打開用初始化給的默認(rèn)賬號(hào)登錄跑一遍下單、接單、完成的完整流程強(qiáng)調(diào)一個(gè)容易被卡住的細(xì)節(jié)很多項(xiàng)目的sql文件是有人用MySQL舊版本導(dǎo)出的字符集可能是utf8mb4也可能不是。導(dǎo)入之前先確認(rèn)排序規(guī)則如果是utf8mb4_general_ci統(tǒng)一在數(shù)據(jù)庫配置里也設(shè)置成utf8mb4否則中文數(shù)據(jù)可能顯示亂碼。另外前后端數(shù)字端口要分開后端常見8080前端Vite開發(fā)端口常是5173或8081不要讓兩個(gè)端口撞在一起。7.2 跨域問題的三種處理方式前后端分離必然遇到跨域。報(bào)錯(cuò)信息通常是Access-Control-Allow-Origin之類新手一看見就慌其實(shí)就三類解法。第一種在后端寫CORS配置類允許指定來源跨域。這種方式代碼最少但如果前端端口改了要回頭改后端配置稍微麻煩。第二種在前端開發(fā)服務(wù)器配代理比如Vue CLI的vue.config.js里配置devServer.proxy把/api轉(zhuǎn)發(fā)到后端地址。這種方式最推薦在開發(fā)階段使用前端代碼里所有請(qǐng)求都走相對(duì)路徑/api/xxx跟部署后的nginx代理行為一致遷移到生產(chǎn)環(huán)境時(shí)前端代碼一行不用改。第三種在上線后用nginx反向代理把 / 指向前端dist靜態(tài)文件/api/ 轉(zhuǎn)發(fā)到后端jar的端口徹底進(jìn)站同一域名從根上消滅跨域。實(shí)際開發(fā)時(shí)我一般開發(fā)用第二種、上線用第三種。后端不寫跨域代碼框架層面干干凈凈。7.3 文檔、基礎(chǔ)修改與答疑的邊界交付階段的文檔和答疑比很多人想的重要。落地說就是三份東西數(shù)據(jù)庫腳本、環(huán)境啟動(dòng)說明、功能清單表。環(huán)境說明里至少寫清JDK版本、Maven或Gradle版本、Node版本、MySQL版本這些不一致是跑不起來的頭號(hào)原因。功能清單表按角色列清楚每個(gè)頁面和接口的對(duì)應(yīng)關(guān)系驗(yàn)收時(shí)照著表格走一遍雙方都省心。所謂基礎(chǔ)修改在裝修服務(wù)預(yù)約平臺(tái)里通常指換logo和站點(diǎn)名稱、改首頁輪播圖、調(diào)整服務(wù)項(xiàng)目?jī)r(jià)格、修改聯(lián)系方式。這些改動(dòng)的共同特點(diǎn)是不涉及表結(jié)構(gòu)變化。做這類改動(dòng)時(shí)建議把靜態(tài)資源統(tǒng)一放在assets目錄文案盡量集中在配置項(xiàng)里而不是散落在幾十個(gè)vue文件內(nèi)部。我甚至?xí)颜军c(diǎn)名稱做成一個(gè)公共常量文件改一行全局生效答疑時(shí)只需要告訴對(duì)方這個(gè)文件第二行就是站點(diǎn)名。答疑階段最常被問的技術(shù)問題其實(shí)大半都不是業(yè)務(wù)代碼的問題而是環(huán)境問題端口占用、npm install失敗、MySQL密碼加密導(dǎo)致連接失敗、Node版本過高導(dǎo)致依賴不兼容。準(zhǔn)備一份常見環(huán)境問題排查清單比反復(fù)遠(yuǎn)程看屏幕要高效得多。Node版本過高導(dǎo)致安裝依賴失敗這個(gè)尤其常見遇到時(shí)優(yōu)先建議降級(jí)到16或18版本再試能解決七成相關(guān)報(bào)錯(cuò)。8. 接手一套源碼時(shí)最快讀懂項(xiàng)目的方式最后分享一個(gè)我自己屢試不爽的經(jīng)驗(yàn)?zāi)玫揭惶啄吧创a不要從頭一行一行讀代碼。那是圖書館式閱讀法效率極低。正確順序是先把項(xiàng)目跑起來注冊(cè)賬號(hào)走一遍完整業(yè)務(wù)流程對(duì)系統(tǒng)能做什么建立體感然后打開數(shù)據(jù)庫看表對(duì)著訂單狀態(tài)流轉(zhuǎn)圖理解數(shù)據(jù)怎么變化最后再回到代碼里重點(diǎn)看service層的事務(wù)和狀態(tài)更新邏輯。三步下來你基本就知道改哪個(gè)文件能改什么功能了。這套方式對(duì)家政預(yù)約平臺(tái)的源碼同樣適用。如果要改預(yù)約時(shí)間粒度從小時(shí)變成半小時(shí)先改前端時(shí)間選擇器的format和step再改后端的service_time字段校驗(yàn)最后檢查重疊查詢邏輯里的時(shí)間比較方式三處改完就對(duì)齊了。很多人覺得基礎(chǔ)修改很難其實(shí)難在不敢動(dòng)、沒有定位問題的思路而不是改動(dòng)本身。我實(shí)際操作中還有個(gè)小習(xí)慣每拿到一個(gè)系統(tǒng)先在本地開一個(gè)分支或者備份一份sql任何改動(dòng)前都能一鍵還原。對(duì)待交付項(xiàng)目這能避免改壞了又不好意思問原作者的尷尬。希望這篇能把家政預(yù)約平臺(tái)的骨架和容易踩的坑講清楚接下來你可以動(dòng)手打開代碼了跑通第一個(gè)訂單的時(shí)候會(huì)特別有成就感。