農(nóng)產(chǎn)品交易小程序開(kāi)發(fā)實(shí)踐)
簡(jiǎn)介本資源是一套完整的云浮市特色農(nóng)產(chǎn)品交易微信小程序畢業(yè)設(shè)計(jì)項(xiàng)目面向計(jì)算機(jī)專業(yè)本科生、自學(xué)開(kāi)發(fā)者及工程實(shí)訓(xùn)學(xué)習(xí)者解決農(nóng)產(chǎn)品線上交易系統(tǒng)從需求分析到部署落地的全流程實(shí)踐問(wèn)題。壓縮包共含可運(yùn)行源碼、SQL建表腳本與配套文檔三類核心文件總大小38.34MB其中Spring Boot后端JDK8Tomcat7MySQL5.7提供RESTful接口UniAppVue前端實(shí)現(xiàn)跨平臺(tái)小程序界面與交互邏輯代碼經(jīng)調(diào)試可直接運(yùn)行。已有80人學(xué)習(xí)下載適合作為課程設(shè)計(jì)、大作業(yè)或畢設(shè)基礎(chǔ)框架支持二次開(kāi)發(fā)與功能擴(kuò)展。資源結(jié)構(gòu)清晰包含完整前后端分離架構(gòu)、數(shù)據(jù)庫(kù)初始化腳本及部署說(shuō)明便于理解電商類系統(tǒng)的技術(shù)選型與模塊劃分特別適合掌握J(rèn)ava全棧開(kāi)發(fā)與小程序跨端實(shí)踐的學(xué)習(xí)者快速上手。 說(shuō)實(shí)話看到“基于微信小程序的云浮市特色農(nóng)產(chǎn)品交易”這個(gè)題目時(shí)我第一反應(yīng)是這不就是一個(gè)農(nóng)產(chǎn)品版的電商小程序嗎SpringBoot做后端、Vue做管理后臺(tái)、uniapp做小程序端三件套一搭頁(yè)面一比劃功能一填這事就成了。但真動(dòng)手做下來(lái)才發(fā)現(xiàn)這類項(xiàng)目表面上是把幾個(gè)框架串起來(lái)實(shí)際上破事比想象中多得多。我是把它當(dāng)成一個(gè)真正要上線的云浮特產(chǎn)電商平臺(tái)來(lái)做的。云浮的特產(chǎn)——羅定稻米、新興涼果、郁南無(wú)核黃皮、砂糖橘——都是有地域標(biāo)簽的農(nóng)產(chǎn)品不是工業(yè)流水線上的標(biāo)準(zhǔn)品這就決定了它在商品管理、庫(kù)存單位、訂單履約上都和普通的電商模板有差別。這篇文章把整個(gè)項(xiàng)目的設(shè)計(jì)思路和實(shí)現(xiàn)過(guò)程完整理一遍包括為什么選uniapp而不是原生小程序、三端之間的數(shù)據(jù)流怎么設(shè)計(jì)、數(shù)據(jù)庫(kù)里的訂單狀態(tài)機(jī)怎么流轉(zhuǎn)、以及真機(jī)聯(lián)調(diào)時(shí)那些讓人抓狂的坑希望能給正在做同類項(xiàng)目的朋友一些實(shí)際參考。1. 從“三端分離”看技術(shù)選型uniapp、SpringBoot、Vue各自解決什么問(wèn)題1.1 為什么不是原生微信小程序而是uniapp這個(gè)項(xiàng)目雖然標(biāo)題是“基于微信小程序”但我最終選擇用uniapp來(lái)做小程序端。原因很簡(jiǎn)單uniapp寫(xiě)一套代碼能同時(shí)編譯成微信小程序、H5和Android/iOS的App。云浮特產(chǎn)這種項(xiàng)目通常做完小程序之后下一步就是想要一個(gè)給農(nóng)戶用的App端或者想在公眾號(hào)里嵌一個(gè)H5商城。如果一開(kāi)始用原生微信小程序后面這些擴(kuò)展都得重新寫(xiě)一遍用uniapp大部分代碼可以復(fù)用。但這不代表uniapp沒(méi)有代價(jià)。最明顯的就是uniapp編譯到微信小程序時(shí)它是在運(yùn)行時(shí)套了一層框架部分原生組件、基礎(chǔ)庫(kù)API的行為會(huì)有差異。比如微信原生的wx.getMenuButtonBoundingClientRect在uniapp里得用條件編譯#ifdef MP-WEIXIN包一層才能拿到膠囊按鈕的位置。這類跨端適配問(wèn)題后面章節(jié)會(huì)詳細(xì)說(shuō)。1.2 SpringBoot后端不是“寫(xiě)接口”那么簡(jiǎn)單SpringBoot在這個(gè)項(xiàng)目里是純后端API服務(wù)但它的職責(zé)比“提供幾個(gè)增刪改查接口”要重得多。農(nóng)產(chǎn)品交易平臺(tái)涉及小程序端用戶、農(nóng)戶/商家端、平臺(tái)管理員三類角色登錄態(tài)怎么維持、訂單狀態(tài)怎么流轉(zhuǎn)、支付回調(diào)怎么處理、文件上傳怎么管理全都要在后端做統(tǒng)一設(shè)計(jì)。技術(shù)棧上我選了SpringBoot 2.7.x MyBatis-Plus MySQL。這里有個(gè)很實(shí)際的版本陷阱SpringBoot 3.x要求JDK17很多教程和舊版依賴比如某些版本的MyBatis-Plus、Druid連接池不兼容啟動(dòng)直接報(bào)錯(cuò)。我做項(xiàng)目時(shí)吃過(guò)這個(gè)虧后來(lái)老老實(shí)實(shí)回到2.7.x資料多網(wǎng)上的坑基本都有答案對(duì)于交付周期短的項(xiàng)目來(lái)說(shuō)穩(wěn)定比新版本重要。1.3 Vue管理后臺(tái)工作量不比小程序少很多人覺(jué)得管理后臺(tái)就是幾個(gè)表格頁(yè)面隨便寫(xiě)寫(xiě)就行。實(shí)際上這個(gè)項(xiàng)目里管理后臺(tái)是運(yùn)營(yíng)的核心工具商品審核、上下架、訂單發(fā)貨、退款處理、數(shù)據(jù)統(tǒng)計(jì)全在后臺(tái)完成。Vue端遇到的高頻問(wèn)題反而不是框架本身而是和業(yè)務(wù)相關(guān)的狀態(tài)管理、權(quán)限控制、跨域聯(lián)調(diào)。管理后臺(tái)采用Vue3 Vite Element Plus。如果參考的課程設(shè)計(jì)資料都是Vue2 Element UI的老教程也可以選Vue2.7不建議兩頭搖擺。管理端頁(yè)面功能相對(duì)固定能否跑通業(yè)務(wù)閉環(huán)比技術(shù)棧新舊重要得多。2. 農(nóng)產(chǎn)品交易系統(tǒng)的業(yè)務(wù)模型設(shè)計(jì)不是普通電商的縮小版2.1 用戶角色與核心業(yè)務(wù)流云浮特色農(nóng)產(chǎn)品交易平臺(tái)的核心角色分三類普通消費(fèi)者、農(nóng)戶/商家、平臺(tái)管理員。普通消費(fèi)者在小程序端逛商品、下單、支付、評(píng)價(jià)農(nóng)戶在小程序端或后臺(tái)維護(hù)商品、處理訂單、發(fā)貨管理員在Vue后臺(tái)審核商品、管理用戶、看數(shù)據(jù)。這三類角色的功能差異直接在菜單和接口權(quán)限上體現(xiàn)出來(lái)。小程序端給消費(fèi)者用不做商家入駐功能商家管理功能放在管理后臺(tái)的獨(dú)立模塊里管理員擁有全部權(quán)限。角色權(quán)限用SpringBoot攔截器 JWT實(shí)現(xiàn)Vue端再用路由守衛(wèi)控制頁(yè)面訪問(wèn)兩層都要做不能只靠前端隱藏菜單。核心業(yè)務(wù)流是瀏覽商品 → 加入購(gòu)物車 → 提交訂單 → 支付 → 商家發(fā)貨 → 確認(rèn)收貨 → 評(píng)價(jià)。農(nóng)產(chǎn)品交易比普通電商多一個(gè)“發(fā)貨時(shí)效”問(wèn)題因?yàn)樯r類商品對(duì)物流要求高所以訂單在“待發(fā)貨”狀態(tài)要顯示發(fā)貨倒計(jì)時(shí)超時(shí)后用戶可申請(qǐng)平臺(tái)介入這是農(nóng)產(chǎn)品平臺(tái)的常見(jiàn)需求。2.2 數(shù)據(jù)庫(kù)表設(shè)計(jì)的幾個(gè)關(guān)鍵決策數(shù)據(jù)庫(kù)設(shè)計(jì)是整個(gè)項(xiàng)目的地基。核心表包括用戶表、商品表、商品分類表、購(gòu)物車表、訂單表、訂單明細(xì)表、收貨地址表、輪播圖表、公告表、評(píng)價(jià)表。有幾個(gè)關(guān)鍵點(diǎn)值得單獨(dú)說(shuō)。第一個(gè)是庫(kù)存單位。農(nóng)產(chǎn)品的庫(kù)存單位不是統(tǒng)一的“件”而是斤、箱、份、袋。比如羅定稻米按袋賣郁南無(wú)核黃皮按斤賣新興涼果按盒賣。如果商品表只存一個(gè)stock字段下單時(shí)很難判斷“5斤黃皮”和“1箱涼果”哪個(gè)庫(kù)存充足。我的做法是在商品表加一個(gè)unit字段下單數(shù)量統(tǒng)一在業(yè)務(wù)層做換算前端商品詳情頁(yè)根據(jù)單位展示價(jià)格和購(gòu)買數(shù)量。第二個(gè)是訂單狀態(tài)機(jī)。訂單狀態(tài)不能只用幾個(gè)字符串字段隨意改要在后端定義明確的狀態(tài)流轉(zhuǎn)規(guī)則。我用的狀態(tài)枚舉是待支付(0) → 已支付/待發(fā)貨(1) → 已發(fā)貨(2) → 已完成(3)加上已取消(4)和退款中(5)。每個(gè)狀態(tài)的可達(dá)狀態(tài)在代碼里寫(xiě)死比如“待支付”只能轉(zhuǎn)“已支付”或“已取消”“已發(fā)貨”不能直接改成“已完成”而不走“確認(rèn)收貨”。這樣能避免很多臟數(shù)據(jù)。當(dāng)前狀態(tài)允許跳轉(zhuǎn)狀態(tài)觸發(fā)動(dòng)作待支付已支付、已取消支付成功回調(diào) / 用戶取消或超時(shí)已支付已發(fā)貨商家發(fā)貨已發(fā)貨已完成用戶確認(rèn)收貨已完成無(wú)不可逆已取消無(wú)不可逆第三個(gè)是收貨地址冗余。小程序端可以用微信的收貨地址能力獲取用戶地址但正式下單時(shí)要把省市區(qū)和詳細(xì)地址冗余存儲(chǔ)到訂單表里。為什么因?yàn)橛脩艉罄m(xù)修改默認(rèn)地址時(shí)歷史訂單的收貨信息不應(yīng)該跟著變。如果訂單表只存address_id一旦用戶改地址歷史訂單的發(fā)貨地址全亂了。這是做電商項(xiàng)目非常容易忽略的坑。第四個(gè)是商品圖片。農(nóng)產(chǎn)品需要多圖展示比如果實(shí)特寫(xiě)、產(chǎn)地實(shí)拍、包裝圖。我的方案是商品表存一個(gè)主圖URL字段另外建一張product_image表存多圖列表。也可以用JSON字段直接存URL數(shù)組但后期如果要做圖片懶加載、縮略圖裁剪還是獨(dú)立表更靈活。3. 小程序端實(shí)現(xiàn)細(xì)節(jié)頁(yè)面骨架、視頻播放、地圖選型和分享邏輯3.1 tabBar頁(yè)面結(jié)構(gòu)與頂部導(dǎo)航欄適配小程序端頁(yè)面結(jié)構(gòu)是這樣規(guī)劃的底部tabBar四個(gè)頁(yè)面——“首頁(yè)”“分類”“購(gòu)物車”“我的”其余頁(yè)面如商品詳情、搜索、結(jié)算、訂單列表全部作為普通頁(yè)面跳轉(zhuǎn)。頁(yè)面結(jié)構(gòu)定了先做的是導(dǎo)航欄適配。微信小程序的導(dǎo)航欄分為默認(rèn)導(dǎo)航和自定義導(dǎo)航兩種。如果想讓首頁(yè)更美觀把搜索框和輪播圖做得更有設(shè)計(jì)感就需要自定義導(dǎo)航欄這時(shí)候必須處理一個(gè)經(jīng)典問(wèn)題頂部導(dǎo)航欄高度。自定義導(dǎo)航欄時(shí)navigationStyle設(shè)為custom之后要在頁(yè)面里手動(dòng)留出狀態(tài)欄和導(dǎo)航欄高度。計(jì)算方式如下// #ifdef MP-WEIXIN const info uni.getSystemInfoSync() const menu wx.getMenuButtonBoundingClientRect() // 狀態(tài)欄高度 const statusBarHeight info.statusBarHeight // 導(dǎo)航欄高度 (膠囊頂部 - 狀態(tài)欄高度) * 2 膠囊高度 const navHeight (menu.top - statusBarHeight) * 2 menu.height // #endif這個(gè)公式很多人不理解簡(jiǎn)單解釋微信膠囊按鈕的垂直位置和導(dǎo)航欄高度不是簡(jiǎn)單的“上下留白”。狀態(tài)欄到膠囊按鈕頂部的距離等于膠囊按鈕底部到導(dǎo)航欄底部的距離所以導(dǎo)航欄高度 膠囊按鈕高度 上下兩個(gè)等距留白。我在iPhone 14和某些安卓機(jī)上實(shí)測(cè)過(guò)這個(gè)公式計(jì)算出來(lái)的高度基本是準(zhǔn)的。真機(jī)上還有底部安全區(qū)問(wèn)題尤其iPhone的Home Indicator。頁(yè)面底部留白要加padding-bottom: env(safe-area-inset-bottom)否則“提交訂單”按鈕會(huì)被手勢(shì)條擋住。3.2 商品詳情頁(yè)的圖片、視頻與PDF預(yù)覽農(nóng)產(chǎn)品詳情頁(yè)的信息密度比普通商品高除了主圖、價(jià)格、規(guī)格、庫(kù)存還要展示產(chǎn)地實(shí)拍、果樹(shù)生長(zhǎng)過(guò)程、檢測(cè)報(bào)告。這里會(huì)涉及到三類文件的預(yù)覽。圖片預(yù)覽最簡(jiǎn)單uniapp里用uni.previewImage傳一個(gè)圖片URL數(shù)組支持左右滑動(dòng)查看大圖不用自己寫(xiě)縮放邏輯。視頻這塊要特別注意。微信小程序的video組件支持HLSm3u8播放所以產(chǎn)地果園的實(shí)時(shí)監(jiān)控或宣傳視頻如果服務(wù)端能輸出m3u8流小程序端可以直接播放。但rtsp流不行微信小程序沒(méi)有原生rtsp播放能力。我做項(xiàng)目時(shí)試過(guò)直接用video組件播rtsp的地址結(jié)果在開(kāi)發(fā)者工具里就有兼容性提示真機(jī)上完全黑屏。解決方案是后端用ffmpeg把rtsp流轉(zhuǎn)成hls切片ffmpeg -i rtsp://your_camera_stream -c:v copy -c:a copy -f hls -hls_time 4 -hls_list_size 10 stream.m3u8如果只是做演示不用實(shí)時(shí)監(jiān)控那就更簡(jiǎn)單直接把.mp4文件傳到服務(wù)器video組件就能播。PDF預(yù)覽是另一個(gè)高頻需求農(nóng)產(chǎn)品的農(nóng)藥殘留檢測(cè)報(bào)告、綠色食品認(rèn)證一般都導(dǎo)出成PDF。小程序端不能用iframe直接嵌網(wǎng)頁(yè)我用的方案是uni.downloadFile下載PDF到臨時(shí)目錄再用uni.openDocument打開(kāi)。注意uni.openDocument只支持打開(kāi)本地文件路徑不能直接傳遠(yuǎn)程URL。3.3 地圖組件內(nèi)置map、騰訊地圖SDK還是天地圖這個(gè)項(xiàng)目的商品詳情和自提點(diǎn)頁(yè)面需要地圖展示比如展示產(chǎn)地位置、配送范圍。我一開(kāi)始直接用微信小程序內(nèi)置的map組件在開(kāi)發(fā)者工具里一切正常但真機(jī)上問(wèn)題來(lái)了map是原生組件層級(jí)高于普通組件頁(yè)面里的彈窗、下拉框、懸浮按鈕都會(huì)被地圖蓋住。安卓機(jī)上尤其明顯地圖直接遮擋住商品詳情頁(yè)的價(jià)格欄。解決方案有三個(gè)方向用cover-view和cover-image覆蓋在map上層。但cover-view的樣式支持有限復(fù)雜布局很難實(shí)現(xiàn)。在新版本基礎(chǔ)庫(kù)中map組件的同層渲染能力有所改善但不是所有機(jī)型都穩(wěn)定。放棄內(nèi)置map改用地圖服務(wù)商的小程序SDK。比如騰訊位置服務(wù)的小程序SDK它提供的是普通組件不存在原生組件層級(jí)遮擋問(wèn)題能直接渲染到頁(yè)面上樣式控制也更自由。關(guān)于天地圖確實(shí)有用戶問(wèn)“微信小程序可以使用天地圖畫(huà)地圖組件嗎”。天地圖主要提供Web端API微信小程序沒(méi)有內(nèi)置天地圖組件。想用的話一般是通過(guò)web-view嵌入天地圖的Web頁(yè)面但這樣和原生頁(yè)面交互不方便而且web-view里的地圖圖層是網(wǎng)頁(yè)渲染性能和體驗(yàn)都打折。實(shí)際做項(xiàng)目展示產(chǎn)地位置、配送范圍這類輕量需求我更推薦騰訊地圖或高德地圖的小程序SDK。華為鴻蒙端的兼容性也要提前看部分地圖SDK的鴻蒙適配晚于安卓開(kāi)發(fā)前要確認(rèn)版本。3.4 分包加載、自定義分享與社交裂變場(chǎng)景小程序主包體積限制是2MB超過(guò)就必須分包。這個(gè)項(xiàng)目把商品詳情、商品搜索、結(jié)算頁(yè)面、訂單列表放到了分包里主包只保留tabBar頁(yè)面和公共組件。如果用到分包異步化——也就是在某個(gè)分包中的自定義組件被另一個(gè)分包引用時(shí)異步加載——要注意基礎(chǔ)庫(kù)版本要求是2.20.2以上。開(kāi)發(fā)者工具里看著沒(méi)問(wèn)題真機(jī)上如果基礎(chǔ)庫(kù)版本過(guò)低分包異步化的組件會(huì)加載失敗頁(yè)面直接白屏。分享功能對(duì)農(nóng)產(chǎn)品電商特別重要。云浮特產(chǎn)有很強(qiáng)的社交屬性用戶買到好吃的黃皮大概率會(huì)分享給朋友。自定義分享可以用onShareAppMessage實(shí)現(xiàn)返回標(biāo)題、圖片路徑、跳轉(zhuǎn)路徑。我在跳轉(zhuǎn)路徑里帶了一個(gè)inviterId參數(shù)用戶從分享鏈接進(jìn)入小程序后后端能識(shí)別推薦人做“好友助力”“拼團(tuán)優(yōu)惠”這類裂變玩法。這個(gè)設(shè)計(jì)在答辯時(shí)是很加分的亮點(diǎn)。4. SpringBoot后端接口設(shè)計(jì)、登錄態(tài)、文件上傳與部署問(wèn)題4.1 統(tǒng)一響應(yīng)體和全局異常搭一個(gè)能長(zhǎng)期維護(hù)的接口框架三端聯(lián)調(diào)時(shí)接口返回格式不統(tǒng)一是大坑。小程序端、Vue后臺(tái)、還有可能對(duì)接的第三方物流接口如果每個(gè)接口返回結(jié)構(gòu)都不一樣前端解析邏輯會(huì)寫(xiě)得非常痛苦。我的做法是定義統(tǒng)一響應(yīng)體public class ResultT { private Integer code; private String msg; private T data; // getter/setter 省略 }成功返回code200業(yè)務(wù)失敗返回code400未登錄返回code401服務(wù)器異常返回code500。controller層不直接返回Map或裸數(shù)據(jù)全部包裝成Result。配合全局異常處理器把參數(shù)校驗(yàn)異常、業(yè)務(wù)異常、未知異常統(tǒng)一攔截這樣前端只需要看code字段就能知道請(qǐng)求狀態(tài)不需要每個(gè)接口單獨(dú)判斷。接口文檔我用的是Knife4jSwagger的增強(qiáng)版。生成接口文檔后小程序端和Vue端可以并行開(kāi)發(fā)不用等后端寫(xiě)完再聯(lián)調(diào)。這里有個(gè)經(jīng)驗(yàn)接口文檔不僅給團(tuán)隊(duì)看等答辯或?qū)懳臋n時(shí)把接口截圖放進(jìn)說(shuō)明書(shū)里能省很多事。4.2 登錄態(tài)與接口安全JWT到底怎么用小程序端登錄不能直接用賬號(hào)密碼微信生態(tài)的標(biāo)準(zhǔn)流程是小程序端wx.login拿到code后端用appid和secret去微信的code2session接口換取openid再用openid生成本系統(tǒng)的token返回給前端。后續(xù)請(qǐng)求在請(qǐng)求頭里帶上token后端攔截器校驗(yàn)。token我這里用的是JWT而不是傳統(tǒng)session原因是管理后臺(tái)和小程序端都可能要用JWT無(wú)狀態(tài)適合接口分離架構(gòu)。JWT里只放userId和角色信息不存敏感信息過(guò)期時(shí)間設(shè)置7天。攔截器做一個(gè)白名單配置登錄接口、商品瀏覽接口、輪播圖接口放行其他接口全部校驗(yàn)token。管理后臺(tái)的登錄是獨(dú)立的一套賬號(hào)密碼體系登錄成功后同樣簽發(fā)JWT。Vue端用路由守衛(wèi)判斷本地有沒(méi)有token沒(méi)有就跳登錄頁(yè)。這里提醒一句前端路由守衛(wèi)只是用戶體驗(yàn)層面的控制真正的權(quán)限校驗(yàn)必須在后端做否則別人直接調(diào)接口繞過(guò)頁(yè)面限制數(shù)據(jù)就裸奔了。接口安全還有一個(gè)場(chǎng)景如果平臺(tái)要和第三方物流系統(tǒng)或支付系統(tǒng)對(duì)接可以用API Key 時(shí)間戳 簽名的機(jī)制。每次請(qǐng)求帶上appKey、timestamp和signsign HMAC-MD5(appKey timestamp secret)。服務(wù)端用同樣的算法算一次簽名比對(duì)一致才放行。簽名里必須帶時(shí)間戳并校驗(yàn)過(guò)期時(shí)間防重放攻擊。4.3 農(nóng)產(chǎn)品圖片上傳、PDF的XSS風(fēng)險(xiǎn)與搜索分詞商品圖片上傳是基礎(chǔ)功能。上傳到本地服務(wù)器時(shí)要注意三點(diǎn)一是在application.yml里配置靜態(tài)資源映射否則上傳后訪問(wèn)不到文件二是限制上傳大小和文件類型圖片只允許jpg、png、webp三是對(duì)文件名做重命名避免用戶上傳的文件名包含特殊字符。PDF預(yù)覽功能涉及XSS風(fēng)險(xiǎn)。如果后臺(tái)允許上傳PDF并且前端用web-view直接預(yù)覽惡意PDF里可以嵌入JavaScript腳本。在這個(gè)項(xiàng)目里我的做法是上傳PDF時(shí)校驗(yàn)文件類型和魔數(shù)存儲(chǔ)時(shí)給PDF文件設(shè)置獨(dú)立的訪問(wèn)目錄并配置Content-Disposition: attachment讓瀏覽器優(yōu)先下載而不是在頁(yè)面內(nèi)渲染。預(yù)覽時(shí)用uni.openDocument獨(dú)立打開(kāi)不嵌入業(yè)務(wù)頁(yè)面。搜索功能如果只用SQL的LIKE %黃皮%用戶搜索“無(wú)核黃皮”時(shí)能匹配但搜索“黃皮果”可能就匹配不到。要提升搜索體驗(yàn)可以集成HanLP分詞庫(kù)。在SpringBoot里引入hanlp依賴后對(duì)商品名稱做分詞把分詞結(jié)果存到一個(gè)搜索索引字段用戶搜索時(shí)先分詞再匹配。當(dāng)然這個(gè)項(xiàng)目如果數(shù)據(jù)量不大LIKE模糊查詢基本夠用分詞功能屬于進(jìn)階優(yōu)化。4.4 Linux部署從jar包到Nginx反向代理開(kāi)發(fā)環(huán)境和部署環(huán)境是兩碼事。開(kāi)發(fā)時(shí)用的application-dev.yml可以直連本地?cái)?shù)據(jù)庫(kù)但部署到Linux服務(wù)器時(shí)要用application-prod.yml區(qū)分環(huán)境。數(shù)據(jù)庫(kù)密碼不能明文寫(xiě)死在配置文件里用環(huán)境變量注入spring: datasource: url: jdbc:mysql://${DB_HOST}:3306/yunfu_shop?useUnicodetruecharacterEncodingutf8mb4useSSLfalse username: ${DB_USERNAME} password: ${DB_PASSWORD}啟動(dòng)命令是經(jīng)典的nohup java -jar yunfu-shop-0.0.1.jar --spring.profiles.activeprod app.log 21 前端這邊Vue后臺(tái)構(gòu)建后生成dist目錄放到Nginx的html目錄下。Nginx配置兩個(gè)關(guān)鍵點(diǎn)一個(gè)是try_files處理Vue路由的history模式否則刷新頁(yè)面會(huì)404另一個(gè)是接口反向代理將/api前綴的請(qǐng)求轉(zhuǎn)發(fā)到SpringBoot端口server { listen 443 ssl; server_name yourdomain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }日志排查方面SpringBoot用logback按天滾動(dòng)日志每次接口報(bào)錯(cuò)先看app.log里有沒(méi)有堆棧。做一次完整部署比看十篇部署教程都管用。5. Vue管理后臺(tái)商品、訂單和農(nóng)戶數(shù)據(jù)的可視化管理5.1 Vue3和Vue2怎么選管理后臺(tái)的技術(shù)選型我的建議很簡(jiǎn)單如果你熟悉組合式API和現(xiàn)代前端生態(tài)用Vue3 Vite Element Plus如果你參考的老教程、老項(xiàng)目都是Vue2 Element UI直接用Vue2.7不要為了追新而兩頭踩坑。為什么管理后臺(tái)的核心價(jià)值是業(yè)務(wù)功能完整不是技術(shù)棧新。你花兩天時(shí)間在Vue3的生態(tài)適配問(wèn)題上不如把這兩天用來(lái)把商品管理、訂單流程做扎實(shí)。我在項(xiàng)目里用Vue3主要原因是Element Plus的組件質(zhì)量和后續(xù)維護(hù)性更好而且和Vite的構(gòu)建速度搭配很舒服。5.2 管理端核心頁(yè)面拆解管理后臺(tái)的主要頁(yè)面按重要程度排序商品管理。商品列表、上下架、編輯、審核。列表頁(yè)要支持按商品名稱、分類、狀態(tài)篩選表格里能直接改庫(kù)存和價(jià)格。商品上架前有審核流程審核通過(guò)才在小程序端展示。訂單管理。訂單列表、訂單詳情、發(fā)貨操作、退款處理。列表要按狀態(tài)Tab切換方便運(yùn)營(yíng)快速看到待發(fā)貨訂單。發(fā)貨操作需要填寫(xiě)物流公司、物流單號(hào)。用戶管理。區(qū)分普通用戶和農(nóng)戶。普通用戶列表展示注冊(cè)信息、訂單數(shù)量農(nóng)戶列表展示入駐狀態(tài)、在售商品數(shù)。管理員可以對(duì)違規(guī)用戶禁用賬號(hào)。數(shù)據(jù)統(tǒng)計(jì)。一個(gè)Dashboard頁(yè)面展示今日訂單數(shù)、交易金額、商品銷量Top10、訂單趨勢(shì)折線圖。這個(gè)頁(yè)面是答辯時(shí)的視覺(jué)亮點(diǎn)建議用ECharts實(shí)現(xiàn)。5.3 computed與watch的正確姿勢(shì)一個(gè)價(jià)格計(jì)算的實(shí)例Vue端一個(gè)很經(jīng)典的業(yè)務(wù)場(chǎng)景運(yùn)營(yíng)在后臺(tái)設(shè)置農(nóng)產(chǎn)品活動(dòng)價(jià)要同時(shí)填寫(xiě)原價(jià)、折扣率、實(shí)際售價(jià)三個(gè)字段。用戶改原價(jià)或折扣率后實(shí)際售價(jià)自動(dòng)計(jì)算如果實(shí)際售價(jià)低于成本價(jià)要給出提示。這個(gè)場(chǎng)景正好能用computed和watch的對(duì)比來(lái)說(shuō)明。實(shí)際售價(jià)應(yīng)該用computed計(jì)算因?yàn)樗怯稍瓋r(jià)和折扣率派生出來(lái)的狀態(tài)computed有緩存依賴的原價(jià)或折扣率變化時(shí)才重新計(jì)算。而watch適合用來(lái)處理“價(jià)格低于成本價(jià)時(shí)彈出警告”這種副作用場(chǎng)景// price為一個(gè)響應(yīng)式對(duì)象 const finalPrice computed(() { return (price.original * price.discount / 10).toFixed(2) }) watch(() price.original, (newVal) { if (newVal price.costPrice) { ElMessage.warning(原價(jià)不能低于成本價(jià)) } })這個(gè)例子在面試和答辯時(shí)很好用因?yàn)楹芏嗳苏f(shuō)不清楚computed和watch的區(qū)別。一句話總結(jié)computed根據(jù)已有的響應(yīng)式數(shù)據(jù)派生出新值watch監(jiān)聽(tīng)已有數(shù)據(jù)的變化再去做一件事。6. 跨端聯(lián)調(diào)與部署的高頻坑點(diǎn)白屏、手勢(shì)返回、地圖遮擋和抓包調(diào)試6.1 同樣一套代碼為什么開(kāi)發(fā)者工具和真機(jī)表現(xiàn)不一樣我在做這個(gè)項(xiàng)目的過(guò)程中遇到過(guò)“uniapp在手機(jī)上預(yù)覽沒(méi)問(wèn)題但在微信開(kāi)發(fā)者工具里白屏”的情況。注意這里和常見(jiàn)的“真機(jī)白屏”相反但同樣值得排查。開(kāi)發(fā)者工具白屏最常見(jiàn)的原因是這兩個(gè)第一個(gè)是基礎(chǔ)庫(kù)版本不一致。開(kāi)發(fā)者工具默認(rèn)使用較新版本的基礎(chǔ)庫(kù)但如果你給某個(gè)頁(yè)面配置了低版本兼容或者使用了高版本才支持的API工具里可能直接渲染失敗。這時(shí)候先在“詳情-本地設(shè)置”里切換基礎(chǔ)庫(kù)版本找到能正常顯示的那個(gè)版本。第二個(gè)是配置文件編譯問(wèn)題。uniapp項(xiàng)目在開(kāi)發(fā)者工具里白屏很多情況下是pages.json的頁(yè)面路徑配置有誤或者某個(gè)頁(yè)面文件里引入了不存在的組件。開(kāi)發(fā)者工具的編譯錯(cuò)誤日志不一定顯眼但打開(kāi)控制臺(tái)看運(yùn)行時(shí)錯(cuò)誤基本能找到線索。真機(jī)白屏的常見(jiàn)原因則不同90%是因?yàn)樾〕绦蚝笈_(tái)的request合法域名沒(méi)配置。開(kāi)發(fā)者工具有“不校驗(yàn)合法域名”的開(kāi)關(guān)所以工具里能請(qǐng)求接口真機(jī)上請(qǐng)求直接被攔截頁(yè)面數(shù)據(jù)拿不到看起來(lái)就是白屏。6.2 安卓手勢(shì)返回導(dǎo)致應(yīng)用退出的排查鏈路這是另一個(gè)讓我印象深刻的坑uniapp編譯成App后在部分安卓手機(jī)上從二級(jí)頁(yè)面手勢(shì)返回時(shí)應(yīng)用直接退出了。用戶重新打開(kāi)再手勢(shì)返回又退出反復(fù)幾次后頁(yè)面才恢復(fù)正常。這個(gè)問(wèn)題的根因在于uniapp在小程序端維護(hù)自己的頁(yè)面棧但編譯成App后安卓系統(tǒng)的手勢(shì)返回會(huì)和頁(yè)面棧的pop行為產(chǎn)生沖突。如果二級(jí)頁(yè)面是直接用uni.redirectTo跳轉(zhuǎn)的頁(yè)面棧里沒(méi)有留下可返回的頁(yè)面系統(tǒng)手勢(shì)返回時(shí)找不到上一頁(yè)就觸發(fā)了應(yīng)用級(jí)退出。解決辦法是統(tǒng)一頁(yè)面跳轉(zhuǎn)規(guī)范頁(yè)面跳轉(zhuǎn)盡量用uni.navigateTo不要用uni.redirectTo除非確實(shí)不需要返回。對(duì)tabBar頁(yè)面監(jiān)聽(tīng)onBackPress生命周期攔截返回操作。在manifest.json的App模塊配置里檢查Android的返回行為設(shè)置。排查這個(gè)問(wèn)題的過(guò)程也讓我理解了為什么uniapp官方文檔反復(fù)強(qiáng)調(diào)生命周期管理。頁(yè)面棧概念聽(tīng)起來(lái)抽象但實(shí)際踩坑時(shí)就是“為什么返回按鈕能退出應(yīng)用”這種直白的問(wèn)題。6.3 地圖組件遮擋從內(nèi)置map到地圖SDK的遷移前面提到過(guò)內(nèi)置map組件在真機(jī)上會(huì)遮擋頁(yè)面其他元素。這個(gè)坑的具體表現(xiàn)是商品詳情頁(yè)有地圖展示地圖下方有個(gè)“立即購(gòu)買”的懸浮按鈕安卓真機(jī)上按鈕被地圖蓋住點(diǎn)擊無(wú)響應(yīng)。排查過(guò)程是這樣的先懷疑是層級(jí)問(wèn)題把按鈕的z-index調(diào)到9999沒(méi)用把按鈕改成cover-view能點(diǎn)了但樣式和普通view有差異圓角、投影效果都受限。最終方案是換用騰訊位置服務(wù)的小程序SDK。SDK提供的是正常渲染的普通組件沒(méi)有原生組件層級(jí)問(wèn)題懸浮按鈕、彈窗都能正常顯示。這個(gè)坑提醒我在項(xiàng)目初期做技術(shù)選型時(shí)如果頁(yè)面有地圖需求就要預(yù)判到原生組件的層級(jí)限制。不管是騰訊地圖還是高德地圖優(yōu)先用SDK組件而不是內(nèi)置map。6.4 抓包調(diào)試的合規(guī)思路看清自己應(yīng)用的接口流向聯(lián)調(diào)階段最需要的就是抓包。微信小程序開(kāi)發(fā)中抓包的主要方式有兩種一是直接在微信開(kāi)發(fā)者工具的Network面板看請(qǐng)求二是把手機(jī)或工具的代理指向本地的抓包工具比如Reqable、Charles抓取HTTPS請(qǐng)求時(shí)需要安裝對(duì)應(yīng)的CA證書(shū)。這里必須強(qiáng)調(diào)合規(guī)邊界。抓包調(diào)試只應(yīng)該針對(duì)自己開(kāi)發(fā)的應(yīng)用、自己的測(cè)試環(huán)境。小程序發(fā)布后線上版本有完整的安全校驗(yàn)機(jī)制不要嘗試對(duì)他人線上應(yīng)用做越權(quán)分析也不要使用抓包工具繞過(guò)任何驗(yàn)證邏輯。做開(kāi)發(fā)時(shí)在小程序后臺(tái)開(kāi)啟“調(diào)試模式”配合代理工具排查自己接口的請(qǐng)求參數(shù)和響應(yīng)數(shù)據(jù)是完全正當(dāng)?shù)?。我在?shí)際排查接口問(wèn)題時(shí)通常先在開(kāi)發(fā)者工具里看請(qǐng)求頭、請(qǐng)求體和響應(yīng)體確認(rèn)是前端參數(shù)傳錯(cuò)還是后端邏輯錯(cuò)誤。如果開(kāi)發(fā)者工具里請(qǐng)求正常真機(jī)上不行就檢查域名白名單和TLS證書(shū)配置。這種排查鏈路比盲改代碼高效得多。7. 項(xiàng)目交付前必須處理的合規(guī)與發(fā)布事項(xiàng)7.1 軟著申請(qǐng)的材料準(zhǔn)備如果項(xiàng)目要上架應(yīng)用市場(chǎng)或參與評(píng)審軟件著作權(quán)登記是繞不開(kāi)的。軟著申請(qǐng)需要的材料主要有軟件著作權(quán)登記申請(qǐng)表源代碼文檔通常要求前后各30頁(yè)或40頁(yè)每頁(yè)50行左右PDF格式用戶操作手冊(cè)或設(shè)計(jì)說(shuō)明書(shū)包含系統(tǒng)首頁(yè)、功能截圖、操作說(shuō)明身份證明文件源代碼文檔有個(gè)容易忽略的細(xì)節(jié)代碼頁(yè)要有頁(yè)碼并且要包含完整的項(xiàng)目名稱、版本號(hào)信息。記得在提交前建一個(gè)docs目錄把答辯用的演示PPT、設(shè)計(jì)說(shuō)明書(shū)、源代碼文檔、數(shù)據(jù)庫(kù)SQL腳本統(tǒng)一歸檔。軟著審批周期通常需要1-2個(gè)月如果項(xiàng)目要急著上架提前規(guī)劃申請(qǐng)時(shí)間。7.2 小程序和安卓應(yīng)用市場(chǎng)上架的檢查清單小程序上架前有一堆細(xì)節(jié)列個(gè)自查清單小程序類目要選對(duì)。如果做農(nóng)產(chǎn)品銷售涉及食品類目一般需要營(yíng)業(yè)執(zhí)照和食品經(jīng)營(yíng)許可證。純畢設(shè)項(xiàng)目如果沒(méi)證可以先用“工具-信息查詢”類目演示或者只做開(kāi)發(fā)版預(yù)覽。所有網(wǎng)絡(luò)請(qǐng)求必須使用HTTPS并在小程序后臺(tái)配置request合法域名。設(shè)置隱私政策說(shuō)明用戶信息收集范圍和使用方式。小程序名稱和logo不能侵權(quán)。如果要把uniapp代碼編譯成安卓App上架應(yīng)用市場(chǎng)還需要軟著證書(shū)、隱私政策、應(yīng)用簽名文件。上架前要在manifest.json里配置App圖標(biāo)、啟動(dòng)圖、包名、版本號(hào)。上架華為、小米、OPPO、vivo、應(yīng)用寶等應(yīng)用市場(chǎng)時(shí)各個(gè)市場(chǎng)的審核標(biāo)準(zhǔn)略有差異最好先準(zhǔn)備一份通用的隱私政策和用戶協(xié)議。7.3 演示和答辯中如何把“云浮特色”講出亮點(diǎn)同樣是農(nóng)產(chǎn)品電商項(xiàng)目很多組做出來(lái)千篇一律。想要讓答辯老師或評(píng)審覺(jué)得有特色關(guān)鍵不在于展示了多少頁(yè)面而在于能否把“云浮特色農(nóng)產(chǎn)品”這個(gè)地域標(biāo)簽真正融入功能設(shè)計(jì)。我的經(jīng)驗(yàn)是準(zhǔn)備三條演示主線第一條完整下單鏈路。從首頁(yè)進(jìn)入時(shí)令推薦專區(qū)選擇郁南無(wú)核黃皮查看產(chǎn)地溯源信息加入購(gòu)物車提交訂單模擬支付訂單狀態(tài)流轉(zhuǎn)。這條鏈路展示了核心業(yè)務(wù)閉環(huán)。第二條管理后臺(tái)運(yùn)營(yíng)流。管理員審核農(nóng)戶提交的商品設(shè)置活動(dòng)價(jià)商家發(fā)貨填物流單號(hào)用戶確認(rèn)收貨后評(píng)價(jià)。這條鏈路展示了角色協(xié)同。第三條技術(shù)亮點(diǎn)講解。JWT登錄態(tài)、訂單狀態(tài)機(jī)、地圖配送范圍展示、自定義分享帶推薦人參數(shù)、uniapp一套代碼編譯多端。每講一個(gè)亮點(diǎn)對(duì)應(yīng)說(shuō)明解決了什么問(wèn)題。我發(fā)現(xiàn)演示時(shí)最打動(dòng)人心的不是花哨的動(dòng)畫(huà)而是“你注意到了別人沒(méi)注意的業(yè)務(wù)細(xì)節(jié)”。比如你能解釋清楚為什么歷史訂單要冗余收貨地址、為什么生鮮商品的訂單狀態(tài)機(jī)里有發(fā)貨倒計(jì)時(shí)這比一句“我用了SpringBoot Vue”強(qiáng)得多。做一個(gè)農(nóng)產(chǎn)品交易項(xiàng)目和做一個(gè)純技術(shù)Demo最大的區(qū)別就在這里。技術(shù)棧只是工具真正考驗(yàn)的是對(duì)業(yè)務(wù)場(chǎng)景的理解。把云浮特產(chǎn)的季節(jié)性問(wèn)題、庫(kù)存單位問(wèn)題、產(chǎn)地信任問(wèn)題想清楚了系統(tǒng)自然就有靈魂。希望這篇記錄能幫你少走點(diǎn)彎路。本文還有配套的精品資源點(diǎn)擊獲取