:架構(gòu)設(shè)計(jì)與實(shí)戰(zhàn)解析)
做了幾個(gè)Spring Boot電商項(xiàng)目之后我對這類系統(tǒng)的套路已經(jīng)熟得不能再熟了但冷鏈運(yùn)輸生鮮銷售系統(tǒng)這個(gè)組合一開始還是讓我多看了兩眼。常規(guī)的電商系統(tǒng)無非是商品、訂單、購物車、支付最多加個(gè)優(yōu)惠券和積分??梢坏┌牙滏溸\(yùn)輸和生鮮這兩個(gè)詞放進(jìn)來整個(gè)系統(tǒng)的復(fù)雜度馬上就不一樣了——你得處理溫控?cái)?shù)據(jù)的實(shí)時(shí)采集、運(yùn)輸軌跡的追蹤、保質(zhì)期和庫存批次的管理還得應(yīng)對生鮮商品在途損耗帶來的退款和賠付問題。這篇文章我就圍繞Spring Boot冷鏈運(yùn)輸生鮮銷售系統(tǒng)這個(gè)主題把整個(gè)項(xiàng)目的核心設(shè)計(jì)、關(guān)鍵模塊拆解、落地實(shí)操和避坑經(jīng)驗(yàn)一次性講透給你一份可以直接參考復(fù)現(xiàn)的完整思路。這個(gè)系統(tǒng)適合誰看如果你是正在準(zhǔn)備畢設(shè)或者找工作的Java方向?qū)W生這個(gè)選題踩中了電商、物流、物聯(lián)網(wǎng)數(shù)據(jù)接入三個(gè)熱點(diǎn)寫在簡歷上很能打如果你已經(jīng)是初級后端開發(fā)或者想轉(zhuǎn)崗做業(yè)務(wù)系統(tǒng)的工程師這篇文章能幫你把單體應(yīng)用如何承載復(fù)雜業(yè)務(wù)這條路走通。我會(huì)從整體架構(gòu)講起然后逐模塊拆解數(shù)據(jù)建模和核心流程再給出實(shí)操階段的代碼關(guān)鍵片段和參數(shù)設(shè)計(jì)邏輯最后把那些常規(guī)文檔里不會(huì)寫的問題排查經(jīng)驗(yàn)一并分享出來。1. 項(xiàng)目需求解析與整體設(shè)計(jì)思路拿到這個(gè)題目第一件事不是急著建工程而是把需求拆清楚。冷鏈運(yùn)輸生鮮銷售系統(tǒng)本質(zhì)上是一個(gè)電商交易 物流履約 環(huán)境監(jiān)控三合一的業(yè)務(wù)系統(tǒng)。如果只把它當(dāng)普通電商做冷鏈條路就會(huì)變成一堆孤立的記錄表如果只關(guān)注冷鏈監(jiān)控又做成了一個(gè)物聯(lián)網(wǎng)后臺跟銷售完全脫節(jié)。真正的核心在于生鮮商品的銷售和履約是一體的用戶在商城下單系統(tǒng)必須能感知這批貨在什么溫度環(huán)境下、由哪輛車、經(jīng)過哪些節(jié)點(diǎn)送到了消費(fèi)者手里。我當(dāng)時(shí)的做法是先畫出四條主鏈路再按鏈路去劃模塊。第一條鏈路是用戶側(cè)注冊登錄、瀏覽商品、加購物車、下單支付、確認(rèn)收貨、售后申請。這條鏈路和普通電商沒有本質(zhì)區(qū)別但生鮮商品本身有特殊性——按份賣、按斤賣、按箱賣而且?guī)齑媸歉谓壎ǘ皇呛唵我粋€(gè)總數(shù)。第二條鏈路是訂單履約側(cè)用戶下單后訂單進(jìn)入待發(fā)貨狀態(tài)倉庫按訂單揀貨打包與冷鏈批次關(guān)聯(lián)創(chuàng)建運(yùn)輸任務(wù)然后司機(jī)接單、裝車、運(yùn)輸、到達(dá)、簽收。每一步狀態(tài)變化都要推送給用戶。第三條鏈路是冷鏈監(jiān)控側(cè)車載溫控設(shè)備和冷柜傳感器定時(shí)上報(bào)溫度、濕度、位置、設(shè)備狀態(tài)系統(tǒng)要做實(shí)時(shí)展示、閾值判斷、異常報(bào)警并且異常信息要能和具體訂單、具體批次關(guān)聯(lián)起來。第四條鏈路是運(yùn)營分析側(cè)銷售數(shù)據(jù)、損耗數(shù)據(jù)、溫度達(dá)標(biāo)率、運(yùn)輸時(shí)效、爆品排行。這些數(shù)據(jù)回流到管理后臺輔助采購和補(bǔ)貨決策。四條鏈路捋完之后整個(gè)系統(tǒng)的模塊邊界就清晰了用戶模塊、商品模塊、庫存批次模塊、購物車模塊、訂單模塊、支付模塊如果做全的話、運(yùn)輸模塊、冷鏈監(jiān)控模塊、售后模塊以及一個(gè)匯總分析模塊。共用的基礎(chǔ)能力包括用戶認(rèn)證、統(tǒng)一異常處理、定時(shí)任務(wù)、消息通知和文件存儲(chǔ)。技術(shù)選型上我用的是Spring Boot 2.7作為基礎(chǔ)框架持久層選MyBatis Plus而不是純MyBatis理由很直接——生鮮系統(tǒng)里大量的單表CRUD操作用MyBatis Plus能省掉一大半重復(fù)的XML編寫像分頁查詢、邏輯刪除、自動(dòng)填充這些高頻功能都是開箱即用。數(shù)據(jù)庫選MySQL 8.0緩存用Redis這個(gè)組合在絕大多數(shù)業(yè)務(wù)系統(tǒng)里足夠穩(wěn)定可靠。權(quán)限認(rèn)證用JWT前后端分離部署前端用的是Vue 2和Element UI的經(jīng)典組合管理端、用戶端、司機(jī)端分別做三個(gè)頁面工程。搜索功能如果需要做強(qiáng)可以接入Elasticsearch但實(shí)際做下來生鮮商品的SKU數(shù)量不大MySQL的LIKE查詢配合索引已經(jīng)完全夠用ES反而徒增運(yùn)維負(fù)擔(dān)。這里插一句關(guān)于架構(gòu)風(fēng)格的思考這個(gè)項(xiàng)目完全沒上微服務(wù)也沒有引入Spring Cloud這是個(gè)刻意的取舍。冷鏈生鮮銷售系統(tǒng)在一個(gè)中等規(guī)模團(tuán)隊(duì)里核心痛點(diǎn)壓根不是并發(fā)和容災(zāi)而是業(yè)務(wù)鏈路的連貫性和數(shù)據(jù)一致性。單體應(yīng)用天然擁有本地事務(wù)一條用戶下單涉及庫存扣減、訂單生成、支付記錄、批次關(guān)聯(lián)一個(gè)Transactional就能把一致性兜住。如果拆成訂單服務(wù)、庫存服務(wù)、物流服務(wù)三個(gè)微服務(wù)反而要處理分布式事務(wù)對項(xiàng)目初期的開發(fā)效率和數(shù)據(jù)準(zhǔn)確性都是巨大的挑戰(zhàn)。當(dāng)然如果后續(xù)業(yè)務(wù)量真的大到需要拆分業(yè)務(wù)邊界已經(jīng)在前期按模塊劃好了拆分成本是可控的。2. 核心業(yè)務(wù)模塊拆解與數(shù)據(jù)建模模塊劃分清楚之后下一步就是數(shù)據(jù)建模。這是整個(gè)項(xiàng)目最考驗(yàn)功底的部分因?yàn)楸斫Y(jié)構(gòu)設(shè)計(jì)得合理后面寫業(yè)務(wù)代碼會(huì)非常順暢設(shè)計(jì)得草率到了運(yùn)輸模塊和溫控模塊就會(huì)發(fā)現(xiàn)到處缺字段、到處都是模糊關(guān)聯(lián)。2.1 商品與庫存批次的綁定關(guān)系生鮮商品的數(shù)據(jù)模型跟普通日用品有一個(gè)本質(zhì)區(qū)別普通商品是一張商品表加一個(gè)庫存數(shù)就能說清楚生鮮必須引入批次概念。比如一件澳洲谷飼西冷牛排用戶看到的是一個(gè)商品條目但倉庫里可能同時(shí)存在兩個(gè)批次A批次是三天前入庫的剩余保質(zhì)期還有4天B批次是今天早上入庫的保質(zhì)期還有7天。系統(tǒng)在做庫存扣減時(shí)必須遵循先進(jìn)先出的效期管理原則優(yōu)先分配臨期批次避免過期浪費(fèi)。所以在數(shù)據(jù)庫層面我的設(shè)計(jì)是用product表保存商品的基礎(chǔ)信息名稱、圖片、規(guī)格、銷售價(jià)、分類、上下架狀態(tài)再用batch_stock表保存批次維度的庫存批次號、商品ID、入庫日期、過期日期、剩余數(shù)量、溫度要求、當(dāng)前所在倉庫/車輛。下單扣減的不是商品總庫存而是某個(gè)批次的庫存。訂單里關(guān)聯(lián)的是商品快照加批次快照甚至可以冗余一份商品名稱和價(jià)格防止后續(xù)商品信息變更導(dǎo)致歷史訂單不可追溯。批次表里還有個(gè)容易被忽略的字段——最小銷售單位是否支持拆分。有些商品按整箱銷售有些則允許散稱這個(gè)字段直接決定了扣減庫存的時(shí)候庫存單位是啥。我見過有同學(xué)把整箱和散稱混在一個(gè)字段里最后銷售單位完全亂套這里務(wù)必在建表的時(shí)候就定清楚。2.2 訂單狀態(tài)機(jī)設(shè)計(jì)與生鮮售后特殊性訂單表是整個(gè)系統(tǒng)的核心樞紐它的狀態(tài)流轉(zhuǎn)幾乎串聯(lián)了用戶、倉庫、司機(jī)、售后所有角色。冷鏈生鮮系統(tǒng)的訂單狀態(tài)比普通電商多了一個(gè)運(yùn)輸中的分支并且由于生鮮產(chǎn)品的特殊性自提場景下還有一個(gè)待自提的狀態(tài)。我最終用的狀態(tài)機(jī)是待支付 →已支付→ 待發(fā)貨或待自提 →揀貨裝車→ 運(yùn)輸中 →到達(dá)/簽收→ 已完成以及從待支付運(yùn)輸中已完成各自發(fā)散到已取消退款處理中退款完成的售后分支。落地到代碼里我強(qiáng)烈推薦用一個(gè)OrderStatusEnum統(tǒng)一管理而不是在各處散落魔法值。后續(xù)接消息推送的時(shí)候每條狀態(tài)變更記錄都要能觸發(fā)對應(yīng)的事件通知。生鮮售后的邏輯也必須提前設(shè)計(jì)。非生鮮商品一般是七天無理由退貨但生鮮商品的退貨理由往往集中在腐爛變質(zhì)送達(dá)超時(shí)溫控不達(dá)標(biāo)每種理由要提交的憑證不一樣審核入口也不一樣。比如溫控不達(dá)標(biāo)用戶提交訂單號之后系統(tǒng)應(yīng)該能拉出這條訂單關(guān)聯(lián)的運(yùn)輸記錄和途中溫控采集數(shù)據(jù)質(zhì)檢人員根據(jù)曲線直接判斷該不該賠。我這邊專門加了一張after_sale表字段包括訂單號、用戶ID、售后類型、申請?jiān)?、憑證圖片列表、關(guān)聯(lián)批次號、審核狀態(tài)、退款金額這條鏈路做完之后整個(gè)售后體驗(yàn)會(huì)專業(yè)很多。2.3 冷鏈監(jiān)控與運(yùn)輸模塊的數(shù)據(jù)關(guān)聯(lián)冷鏈監(jiān)控這塊后臺數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)得好不好直接決定了前端看板能不能畫出來。車載設(shè)備、冷柜傳感器上報(bào)的數(shù)據(jù)格式各個(gè)廠商不太一樣但核心無外乎溫度、濕度、地理位置、設(shè)備編號、上報(bào)時(shí)間這五樣。我的方案是建一張transport_task運(yùn)輸任務(wù)表每筆訂單歸集到一個(gè)運(yùn)輸任務(wù)下然后建一張cold_chain_record溫控記錄表記錄跟任務(wù)ID關(guān)聯(lián)。這里有一個(gè)實(shí)際開發(fā)中非常容易踩坑的點(diǎn)溫控上報(bào)頻率和數(shù)據(jù)量的問題。如果每輛車每分鐘上報(bào)一次數(shù)據(jù)看起來不密但如果有20輛車同時(shí)在線那一天光冷鏈條就會(huì)產(chǎn)生接近三萬條記錄一個(gè)月就是上百萬條。如果上報(bào)頻率提高到每10秒一次數(shù)據(jù)量直接爆炸到千萬級。我建議在業(yè)務(wù)初期把上報(bào)頻率控制在1到5分鐘一次查詢的時(shí)候按天分表或者按月份做歸檔。真到了數(shù)據(jù)量大的階段可以再引入時(shí)序數(shù)據(jù)庫或者把歷史數(shù)據(jù)做冷熱分離上面說的按天分表其實(shí)就是最優(yōu)解不需要引入額外的技術(shù)棧。運(yùn)輸模塊還需要維護(hù)一張transport_node節(jié)點(diǎn)表記錄發(fā)貨倉、中轉(zhuǎn)點(diǎn)、目的地、預(yù)計(jì)到達(dá)時(shí)間、實(shí)際到達(dá)時(shí)間、交接人、溫度達(dá)標(biāo)情況。這些節(jié)點(diǎn)的流轉(zhuǎn)在用戶端小程序里展示出來就是一條物流時(shí)間軸。我見很多初做這個(gè)項(xiàng)目的人忽略了節(jié)點(diǎn)表只用最后一個(gè)地理位置推給用戶結(jié)果用戶完全不知道貨到哪一步了、還剩多久到體驗(yàn)直線下降。3. 核心功能落地實(shí)操從搭建到關(guān)鍵業(yè)務(wù)實(shí)現(xiàn)模塊和數(shù)據(jù)模型都定了接下來就是實(shí)際動(dòng)手寫代碼。這一節(jié)我挑幾個(gè)最核心、也最容易被忽略細(xì)節(jié)的環(huán)節(jié)完整走一遍實(shí)操過程包括關(guān)鍵代碼和參數(shù)設(shè)計(jì)邏輯。3.1 項(xiàng)目初始化和分層規(guī)范工程上我用的是標(biāo)準(zhǔn)的分層結(jié)構(gòu)controller層接收參數(shù)和返回結(jié)果service層塞業(yè)務(wù)邏輯mapper層做數(shù)據(jù)交互entity和dto分離。entity直接對應(yīng)數(shù)據(jù)庫字段dto承接頁面?zhèn)魅氲臄?shù)據(jù)二者嚴(yán)禁混用。這個(gè)規(guī)范聽起來是最基本的素養(yǎng)但我見過太多項(xiàng)目就是因?yàn)閑ntity直接暴露給前端導(dǎo)致前端傳一個(gè)isAdmin字段直接把權(quán)限改了。這里我用的方案是強(qiáng)制要求所有接口出入?yún)⒍急仨氉遜toentity只在service內(nèi)部流轉(zhuǎn)。Spring Boot的啟動(dòng)類方面沒有太多可講的依賴引入要重點(diǎn)提一句MyBatis Plus的代碼生成器在項(xiàng)目初期一定要配置好它可以一次性把entity、mapper、service、controller的骨架全部生成出來后面再手寫補(bǔ)充業(yè)務(wù)邏輯。我第一次做這類項(xiàng)目時(shí)手工寫了一堆getter/setter和基本CRUD代碼浪費(fèi)了大量時(shí)間現(xiàn)在回看這是完全沒必要的體力勞動(dòng)。3.2 用戶登錄與權(quán)限控制實(shí)踐用戶端和管理端用同一套用戶表還是拆兩張表我的答案是拆。生鮮銷售系統(tǒng)的用戶是C端消費(fèi)者管理后臺是B端運(yùn)營和倉儲(chǔ)人員兩者字段差異極大強(qiáng)行并表只會(huì)讓權(quán)限校驗(yàn)代碼變得混亂。用戶端我用一個(gè)member表管理端用staff表兩張表通過不同的登錄入口和Token體系隔離。Token這塊我用的就是經(jīng)典的JWT。JWT的核心邏輯不用多講——服務(wù)器簽發(fā)一個(gè)包含用戶ID、角色、過期時(shí)間的加密字符串客戶端每次請求帶上這個(gè)字符串服務(wù)端校驗(yàn)簽名后解析出身份信息。生鮮系統(tǒng)里JWT的過期時(shí)間建議不要設(shè)太長用戶端設(shè)置2小時(shí)管理后臺設(shè)置30分鐘過期后前端根據(jù)狀態(tài)碼自動(dòng)跳轉(zhuǎn)登錄頁重新獲取。這里有個(gè)細(xì)節(jié)管理后臺的密碼不能用MD5直存必須加鹽后做哈希處理用Spring Security的BCryptPasswordEncoder是現(xiàn)成的理想選擇。權(quán)限控制的管理端通過攔截器實(shí)現(xiàn)按角色管理員、倉庫操作員、司機(jī)、客服做接口級別的訪問控制。Spring Boot里寫一個(gè)HandlerInterceptor配合WebMvcConfigurer注冊攔截路徑是最標(biāo)準(zhǔn)的做法清晰、不用引入額外依賴比在Controller方法上堆一個(gè)又一個(gè)自定義注解要直觀得多。3.3 購物車合并與下單扣庫存的并發(fā)安全方案購物車這塊的邏輯不復(fù)雜但有個(gè)細(xì)節(jié)做不好的話用戶體驗(yàn)會(huì)非常差登錄前加購登錄后要能合并。處理方案是本地先存一份臨時(shí)購物車數(shù)據(jù)登錄成功后前端把臨時(shí)列表傳給后端后端按同商品同規(guī)格則累加不同則新增的規(guī)則合并進(jìn)數(shù)據(jù)庫的購物車表。下單環(huán)節(jié)是整個(gè)系統(tǒng)最考驗(yàn)并發(fā)安全的地方。生鮮商品一旦熱賣用戶同時(shí)下單的時(shí)候庫存扣減如果做不好就會(huì)超賣。最簡單也相當(dāng)可靠的做法是SQL層面的樂觀鎖UPDATE batch_stock SET stock stock - #{count} WHERE id #{batchId} AND stock #{count}這條SQL天然具備原子性數(shù)據(jù)庫行鎖保證不會(huì)出現(xiàn)兩個(gè)事務(wù)同時(shí)扣成負(fù)數(shù)。代碼層面再加上一個(gè)庫存充足判斷雙保險(xiǎn)就齊了。訂單創(chuàng)建的整個(gè)鏈路我放在一個(gè)Transactional里生成訂單號這里用Redis的自增序列拼接日期能保證分布式下的唯一性、扣減批次庫存、寫入訂單明細(xì)、寫入支付記錄單、清理購物車。記住一個(gè)原則事務(wù)要短不要在事務(wù)里做遠(yuǎn)程調(diào)用或者大批量循環(huán)寫入。熱銷品搶購場景還可以用Redis預(yù)扣庫存的方式提升并發(fā)能力但這屬于進(jìn)階優(yōu)化基礎(chǔ)功能階段不建議過度設(shè)計(jì)。3.4 溫度數(shù)據(jù)接入與冷鏈異常處理溫度采集是冷鏈系統(tǒng)區(qū)別于普通電商系統(tǒng)的標(biāo)志性功能。設(shè)備端的上報(bào)方式很多有的是走M(jìn)QTT有的是走HTTP POST簡單對接SDK沒有成型的情況下我建議先在Spring Boot里暴露一個(gè)HTTP接口讓設(shè)備端定時(shí)POST一條JSON數(shù)據(jù)過來字段無非是設(shè)備編號、溫度、濕度、經(jīng)緯度、上報(bào)時(shí)間。設(shè)備數(shù)量少比如公司自建自營時(shí)這個(gè)方案最簡單可靠設(shè)備廠商SDK成熟后再替換成MQTT網(wǎng)關(guān)接入也不遲。溫度數(shù)據(jù)的落庫我之前說了關(guān)聯(lián)到運(yùn)輸任務(wù)。核心算法在于異常判定——不能簡單地說溫度超過閾值就報(bào)警因?yàn)殚_關(guān)車門、裝卸貨的瞬間溫度波動(dòng)是正常的。我做了兩重過濾單次溫度超出閾值但持續(xù)時(shí)間不足5分鐘的不觸發(fā)報(bào)警連續(xù)3次采樣都超閾值約15分鐘才判定為溫度異常事件。寫成代碼就是維護(hù)一個(gè)計(jì)數(shù)器連續(xù)達(dá)標(biāo)則清零用定時(shí)任務(wù)或者直接在上報(bào)接口里做滑窗判斷。報(bào)警之后給運(yùn)營人員發(fā)企業(yè)微信/短信通知同時(shí)在后臺生成一條待處理工單。4. 實(shí)際調(diào)試中那些值得記下來的問題與解法項(xiàng)目開發(fā)過程中我踩過不少坑也幫別人排查過不少類似環(huán)境下的問題。這里挑幾個(gè)高頻問題整理出來并給出排查思路希望你能少走彎路。4.1 并發(fā)扣庫存導(dǎo)致超賣問題不少人在第一次測試下單功能時(shí)都會(huì)遇到庫存超賣明明數(shù)據(jù)庫庫存只有10件賣出去11單。原因也很簡單代碼里把查庫存和扣庫存分成了兩步兩個(gè)請求并發(fā)時(shí)同時(shí)讀到庫存是10又同時(shí)執(zhí)行扣減最后變成-1。解決方案就是我前面提到的SQL原子扣減UPDATE batch_stock SET stock stock - #{count} WHERE id #{batchId} AND stock #{count}。加了這個(gè)條件后即使并發(fā)再高數(shù)據(jù)庫的鎖機(jī)制也會(huì)保證扣減操作串行執(zhí)行多扣的部分會(huì)返回更新行數(shù)為0業(yè)務(wù)側(cè)根據(jù)這個(gè)結(jié)果直接拋庫存不足異常即可。在測試階段可以用JMeter模擬500線程并發(fā)下單驗(yàn)證庫存最終不會(huì)為負(fù)數(shù)。4.2 高頻率溫度上報(bào)導(dǎo)致數(shù)據(jù)庫壓力過大車載溫控終端如果配置成每10秒上報(bào)一次50輛車同時(shí)在線時(shí)一天的記錄數(shù)就是43萬條單表存儲(chǔ)和查詢很快就到了瓶頸。這是冷鏈項(xiàng)目里非常真實(shí)的問題。我的解決方法分三層上報(bào)接口先做冪等校驗(yàn)設(shè)備號上報(bào)時(shí)間相同則丟棄避免網(wǎng)絡(luò)重試導(dǎo)致的數(shù)據(jù)重復(fù)入庫采用批量插入把1分鐘內(nèi)的數(shù)據(jù)進(jìn)行聚合后一次性INSERT減少數(shù)據(jù)庫連接的開銷查詢層面給device_no和create_time建了聯(lián)合索引日常的查某輛車某段時(shí)間的溫度曲線直接在索引上掃速度完全夠用。如果上線后發(fā)現(xiàn)數(shù)據(jù)量增長超預(yù)期還可以把按天分表的方案排上日程這是成本最低的擴(kuò)展方式。4.3 訂單狀態(tài)和物流狀態(tài)不同步用戶端訂單已經(jīng)顯示已完成了但后臺運(yùn)輸任務(wù)的節(jié)點(diǎn)還停在配送中這類問題多半是狀態(tài)更新邏輯散落在不同模塊沒有統(tǒng)一的狀態(tài)機(jī)管理。我的解法是把所有狀態(tài)變更統(tǒng)一收斂到各自模塊的Service層并且提供顯式的變更方法比如orderService.markDelivered(orderId)、transportService.completeTask(taskId)。禁止在Controller里隨手改狀態(tài)更禁止在多個(gè)地方貼相同的更新SQL。像用戶點(diǎn)擊確認(rèn)收貨和司機(jī)點(diǎn)擊完成配送這兩個(gè)動(dòng)作實(shí)際上下游銜接上要有一個(gè)順序——司機(jī)先完成配送系統(tǒng)再把訂單置為已完成并推送通知順序反了就會(huì)出問題??梢杂孟㈥?duì)列或者簡單的事件監(jiān)聽把后面的通知、數(shù)據(jù)統(tǒng)計(jì)等非核心步驟異步化但狀態(tài)主鏈路的先后順序必須通過代碼邏輯鎖死。4.4 數(shù)據(jù)庫分頁查詢變慢問題訂單表、溫控記錄表到了幾萬條數(shù)據(jù)時(shí)深度分頁比如翻到第100頁就會(huì)明顯變慢原因很簡單LIMIT 9900, 20需要先掃描9990條記錄再丟棄代價(jià)非常高。生鮮系統(tǒng)內(nèi)的常規(guī)翻頁需求其實(shí)很淺用戶最多也就翻到前十幾頁所以用MyBatis Plus默認(rèn)的分頁插件就能應(yīng)付大多數(shù)場景。真遇到后臺運(yùn)營需要從幾十萬數(shù)據(jù)里篩選的場景我推薦的辦法是改造成游標(biāo)分頁——把翻頁條件改為查詢ID小于上一頁最大ID的記錄配合主鍵索引速度能提升幾個(gè)量級。另外給order_no、batch_no、create_time這些高頻查詢字段建好索引幾乎能規(guī)避掉80%的分頁性能問題。5. 一些額外的思考與經(jīng)驗(yàn)補(bǔ)充項(xiàng)目做到這個(gè)程度核心功能基本都能跑了但要想讓它在面試中講出亮點(diǎn)、或者真的在業(yè)務(wù)里抗住壓力下面幾個(gè)點(diǎn)可以再深入打磨。溫控?cái)?shù)據(jù)這塊可以進(jìn)一步做曲線可視化和達(dá)標(biāo)率統(tǒng)計(jì)。后端提供按時(shí)間范圍和任務(wù)ID聚合的接口把溫控記錄拆成時(shí)間/溫度兩個(gè)數(shù)組返回前端用ECharts畫折線圖。用戶端展示的是該訂單冷鏈達(dá)標(biāo)率98%管理端展示的是某條線路的平均溫度曲線。這個(gè)功能做出來給人的直觀感受完全不同。運(yùn)輸調(diào)度可以引入一個(gè)簡單的路徑規(guī)劃邏輯。不需要接高德的API做實(shí)時(shí)路徑規(guī)劃只需要在運(yùn)輸任務(wù)創(chuàng)建時(shí)按片區(qū)聚合訂單、分配車輛、預(yù)估到達(dá)時(shí)間窗口比如同一個(gè)小區(qū)的訂單自動(dòng)歸到同一輛車。這個(gè)邏輯用分組聚合就能實(shí)現(xiàn)但體現(xiàn)出來的卻是對業(yè)務(wù)的理解深度。還有一個(gè)容易被忽略的東西是數(shù)據(jù)權(quán)限。管理端不是所有角色都能看所有數(shù)據(jù)——司機(jī)只能看自己的運(yùn)輸任務(wù)倉庫操作員只能看庫存和揀貨單客服可以看訂單但不能看財(cái)務(wù)退款數(shù)據(jù)。這個(gè)在后臺做數(shù)據(jù)權(quán)限過濾時(shí)一定不能省權(quán)限邊界沒控制好人員變動(dòng)后風(fēng)險(xiǎn)極大。最后的架構(gòu)層面如果項(xiàng)目要繼續(xù)演進(jìn)我建議的重構(gòu)路徑是這樣的先把訂單和運(yùn)輸獨(dú)立成服務(wù)引入消息隊(duì)列解耦狀態(tài)變更冷鏈溫控?cái)?shù)據(jù)抽出來接入時(shí)序數(shù)據(jù)庫然后逐步把搜索模塊升級到Elasticsearch。但這個(gè)階段的演進(jìn)前提是業(yè)務(wù)量真的到了單庫扛不住的程度否則單體應(yīng)用加垂直拆表是性價(jià)比最高的形態(tài)。把單體應(yīng)用做好、做深、做規(guī)范遠(yuǎn)比一上來就上微服務(wù)更能體現(xiàn)工程能力。我在實(shí)際做這個(gè)項(xiàng)目時(shí)還有一個(gè)很深的體會(huì)這類業(yè)務(wù)系統(tǒng)的難點(diǎn)從來不是單個(gè)接口多難寫而是數(shù)據(jù)在多個(gè)模塊之間流轉(zhuǎn)時(shí)如何保持一致、如何不丟。所以在一開始就建立一套清晰的表結(jié)構(gòu)、統(tǒng)一的狀態(tài)變更入口、完備的日志記錄后期開發(fā)會(huì)輕快很多。希望這篇文章能把冷鏈生鮮銷售系統(tǒng)的關(guān)鍵脈絡(luò)講清楚你動(dòng)手做的時(shí)候能少踩幾個(gè)坑把這套方案的細(xì)節(jié)都落到自己的代碼里。