開(kāi)發(fā)實(shí)戰(zhàn))
校園里的閑置交易做過(guò)的人都知道有多麻煩。去年我在某高校做畢設(shè)調(diào)研時(shí)發(fā)現(xiàn)幾乎所有學(xué)生群都有人在賣舊書、臺(tái)燈、自行車但信息幾分鐘就被聊天刷沒(méi)了想找同款基本靠緣分。當(dāng)時(shí)就決定做一個(gè)專門的校園二手交易平臺(tái)技術(shù)棧選了 Spring Boot 加微信小程序也就是標(biāo)題里那個(gè)“springboot校園二手交易平臺(tái)小程序”。這篇內(nèi)容不打算只貼源碼和截圖而是把整個(gè)項(xiàng)目的需求拆解、數(shù)據(jù)庫(kù)設(shè)計(jì)、核心接口邏輯、小程序端實(shí)現(xiàn)以及我最想分享的踩坑記錄都寫出來(lái)。哪怕你不想關(guān)注拿源碼單按這篇文章里的架構(gòu)思路和數(shù)據(jù)表去復(fù)刻也能把一個(gè)可上線運(yùn)行的二手交易系統(tǒng)搭起來(lái)。這個(gè)項(xiàng)目適合誰(shuí)正在做畢業(yè)設(shè)計(jì)的學(xué)生、想系統(tǒng)走一遍前后端分離開(kāi)發(fā)的初學(xué)者、或者單純想了解校園交易類小程序業(yè)務(wù)閉環(huán)的產(chǎn)品和技術(shù)新人。閱讀過(guò)程中你會(huì)發(fā)現(xiàn)它沒(méi)有懸乎的高并發(fā)和分布式核心價(jià)值在于把“登錄、發(fā)帖、交易、狀態(tài)流轉(zhuǎn)”這幾件小事做扎實(shí)而這恰恰是很多二手平臺(tái)項(xiàng)目翻車的地方。1. 項(xiàng)目從需求到架構(gòu)先搞清楚二手交易到底在交易什么1.1 校園閑置場(chǎng)景的真實(shí)痛點(diǎn)校園二手交易和閑魚同城交易不太一樣。閑魚的買家賣家是完全陌生關(guān)系靠的是芝麻信用、平臺(tái)擔(dān)保和事后評(píng)價(jià)體系來(lái)建立信任而校園場(chǎng)景里買賣雙方大概率都在同一個(gè)校區(qū)、同一棟宿舍樓甚至可能是同一個(gè)學(xué)院的信任基礎(chǔ)天然存在但信息和撮合機(jī)制卻很原始。我在前期調(diào)研里梳理出幾個(gè)典型問(wèn)題第一信息承載分散社團(tuán)群、表白墻、宿舍群各有各的帖子沒(méi)有統(tǒng)一的瀏覽和搜索入口第二商品的成色、價(jià)格、交易方式完全靠賣家自己描述買家需要反復(fù)私聊確認(rèn)溝通成本高第三缺少訂單狀態(tài)概念說(shuō)好面交卻放鴿子的情況非常多第四沒(méi)有“已售出”的標(biāo)記過(guò)期很久的商品還在被點(diǎn)贊和詢問(wèn)體驗(yàn)很差。因此這個(gè)項(xiàng)目的核心需求不是做一個(gè)“能登錄、能發(fā)帖”的玩具而是要完成一條相對(duì)完整的交易閉環(huán)用戶登錄 - 發(fā)布商品 - 買家瀏覽/搜索/聯(lián)系 - 表達(dá)購(gòu)買意向 - 雙方確認(rèn) - 訂單狀態(tài)流轉(zhuǎn) - 線下交易完成。再加上收藏、留言、個(gè)人中心等輔助功能就是一個(gè)能自洽的業(yè)務(wù)模型。1.2 為什么是 Spring Boot 加微信小程序技術(shù)選型上后端我最終定了 Spring Boot前端定了微信原生小程序沒(méi)有引入太重的微服務(wù)或跨端框架。理由很直接。Spring Boot 的優(yōu)勢(shì)在于快速搭建、生態(tài)成熟。這個(gè)系統(tǒng)需要的能力無(wú)非是 REST API、文件存儲(chǔ)、鑒權(quán)、數(shù)據(jù)庫(kù)訪問(wèn)Spring Boot 都能用很簡(jiǎn)潔的方式集成而且網(wǎng)上資料多遇到問(wèn)題能找到大量參考。相比 Go、Node 之類的方案對(duì)剛接觸全棧開(kāi)發(fā)的人來(lái)說(shuō)Spring Boot 的思維方式更規(guī)整分層也更清晰。微信小程序作為前端載體則是因?yàn)樗烊毁N合校園場(chǎng)景。學(xué)生不需要額外下載 App微信掃一下就進(jìn)去分享到群和朋友圈也方便。小程序的原生框架對(duì)后端的請(qǐng)求封裝、登錄態(tài)管理、圖片上傳都有現(xiàn)成接口開(kāi)發(fā)成本比 React Native 或 Flutter 低不少。而且小程序的發(fā)布審核并不會(huì)卡常規(guī)分類的二手交易類目只要避免出現(xiàn)明顯的平臺(tái)化交易嫌疑比如不要內(nèi)置支付通道審核就不會(huì)有太大問(wèn)題。整套系統(tǒng)采用前后端分離小程序端只負(fù)責(zé)界面交互和請(qǐng)求發(fā)送后端只負(fù)責(zé)業(yè)務(wù)邏輯、數(shù)據(jù)校驗(yàn)和接口響應(yīng)。這樣做的直接好處是后續(xù)如果要加一個(gè)管理后臺(tái)或者把小程序端換成 H5完全不用動(dòng)后端核心邏輯。1.3 模塊拆分與數(shù)據(jù)流方向在畫 ER 圖之前我先按業(yè)務(wù)角色把系統(tǒng)拆成了幾個(gè)模塊用戶模塊、商品模塊、訂單模塊、收藏與留言模塊、圖片存儲(chǔ)模塊。每個(gè)模塊的職責(zé)盡量單一模塊之間只通過(guò)接口或者數(shù)據(jù)表外鍵產(chǎn)生關(guān)聯(lián)。數(shù)據(jù)流的方向是這樣的小程序端向 Spring Boot 后端發(fā)起 HTTP 請(qǐng)求后端通過(guò)攔截器統(tǒng)一處理 Token 鑒權(quán)Controller 層接收參數(shù)并做基礎(chǔ)校驗(yàn)Service 層處理業(yè)務(wù)邏輯Mapper 層通過(guò) MyBatis 與 MySQL 交互。圖片上傳走獨(dú)立的接口先落盤到服務(wù)器存儲(chǔ)目錄再返回 URL。所有接口統(tǒng)一返回 JSON格式是{ code, message, data }小程序端對(duì)這個(gè)結(jié)構(gòu)做統(tǒng)一解析和處理。這個(gè)數(shù)據(jù)流聽(tīng)起來(lái)簡(jiǎn)單但實(shí)際操作中如果不提前把統(tǒng)一返回結(jié)構(gòu)定死前后端聯(lián)調(diào)時(shí)會(huì)有無(wú)窮無(wú)盡的體力消耗。我強(qiáng)烈建議在項(xiàng)目一開(kāi)始就定好Result包裝類和錯(cuò)誤碼規(guī)范避免一個(gè)接口返回true、另一個(gè)接口返回success聯(lián)調(diào)時(shí)全靠猜。2. 后端核心實(shí)現(xiàn)表結(jié)構(gòu)、登錄態(tài)與交易狀態(tài)機(jī)2.1 數(shù)據(jù)庫(kù)設(shè)計(jì)要把業(yè)務(wù)演變想清楚數(shù)據(jù)庫(kù)是這種業(yè)務(wù)系統(tǒng)的地基表結(jié)構(gòu)設(shè)計(jì)得不好后面寫接口會(huì)到處別扭。我最終設(shè)計(jì)的核心表如下user用戶表字段包括id、openid、nickname、avatar_url、phone、campus、create_time。product商品表id、user_id、title、description、price、original_price、condition_level、category_id、status、view_count、create_time、update_time。product_image商品圖片表id、product_id、image_url、sort_order。category分類表id、name、icon。order訂單表id、order_no、product_id、buyer_id、seller_id、amount、status、create_time、update_time。favorite收藏表id、user_id、product_id、create_time。message留言表id、product_id、from_user_id、to_user_id、content、create_time。這里有一個(gè)關(guān)鍵點(diǎn)product表里的status字段我用了0-在售、1-已售出、2-下架而不是直接刪除商品。二手平臺(tái)最忌諱真正刪除商品記錄因?yàn)橛唵魏土粞远纪怄湹缴唐芬坏﹦h了歷史記錄就全斷了管理后臺(tái)也無(wú)法追溯。用戶“刪除”自己的商品時(shí)實(shí)際執(zhí)行的是軟刪除也就是只改狀態(tài)不刪行。價(jià)格字段要特別注意。單價(jià)用DECIMAL(10, 2)而不是FLOAT因?yàn)楦↑c(diǎn)數(shù)在計(jì)算時(shí)會(huì)有精度誤差訂單金額、廣告位價(jià)格這類數(shù)據(jù)一旦出錯(cuò)就是事故。我見(jiàn)過(guò)有人用浮點(diǎn)存價(jià)格導(dǎo)致對(duì)賬少了幾塊錢的例子校園項(xiàng)目雖然量小但該嚴(yán)謹(jǐn)?shù)牡胤讲荒芩?。索引方面product表的status和create_time要建聯(lián)合索引因?yàn)槭醉?yè)列表、分類商品列表基本都按“在售并且時(shí)間倒序”來(lái)查。order表的buyer_id和seller_id分別建索引用于個(gè)人中心的“我買到的/我賣出的”列表查詢。如果數(shù)據(jù)量到幾十萬(wàn)級(jí)別這些索引會(huì)極大緩解慢查詢不過(guò)現(xiàn)階段規(guī)模下沒(méi)必要做分庫(kù)分表。2.2 微信登錄與 Token 鑒權(quán)的完整鏈路微信小程序的登錄是典型的 code2session 換 openid 流程很多新手在這里被繞暈。我整理一下實(shí)際代碼里的順序小程序端調(diào)用wx.login()拿到臨時(shí)憑證code。小程序把code發(fā)給后端/api/auth/login。后端拿著code調(diào)用微信接口code2session換取openid和session_key。后端根據(jù)openid查詢用戶如果沒(méi)有則自動(dòng)注冊(cè)一個(gè)新用戶并生成默認(rèn)昵稱和頭像。后端為用戶簽發(fā)一個(gè)自定義的 Token返回給小程序小程序存入wx.setStorageSync后續(xù)每個(gè)請(qǐng)求的頭里帶上Authorization。Token 方案我選擇的是自定義 Token 存 Redis而不是 JWT。原因很樸素JWT 是無(wú)狀態(tài)的用戶被封禁或改頭像之后已經(jīng)簽發(fā)的 Token 仍然有效想踢人下線只能等過(guò)期。校園平臺(tái)雖然不需要太強(qiáng)管控但如果以后要做管理員封號(hào)功能存 Redis 的 Token 能實(shí)現(xiàn)即時(shí)失效靈活得多。后端用攔截器統(tǒng)一處理 Token 解析。攔截器里從請(qǐng)求頭拿出Authorization從 Redis 查用戶信息查到了就往ThreadLocal里塞當(dāng)前用戶 IDController 層就可以直接用注解拿到當(dāng)前登錄用戶。查不到就返回401。注意code2session的接口有每天調(diào)用上限雖然日常項(xiàng)目不會(huì)觸發(fā)但一定不要把code寫在日志里。這個(gè)臨時(shí)憑證和session_key一旦泄露存在被偽造請(qǐng)求的風(fēng)險(xiǎn)。2.3 商品發(fā)布、圖片上傳與列表查詢商品發(fā)布接口是核心中的核心邏輯上分兩部分先處理圖片再寫入商品信息。圖片上傳我用的方式是小程序端wx.chooseMedia選擇圖片通過(guò)wx.uploadFile以multipart/form-data形式傳到后端/api/upload。后端接收文件后先校驗(yàn)文件大小、擴(kuò)展名只允許 jpg/png/webp單張不超過(guò) 5MB然后按日期存儲(chǔ)到本地目錄生成帶時(shí)間戳的隨機(jī)文件名避免重名覆蓋。文件名我習(xí)慣用yyyyMMddHHmmss 隨機(jī)數(shù)的格式比如20250605153021_8291a.jpg這樣即使用戶用一個(gè)叫“1.jpg”的文件反復(fù)上傳也不會(huì)撞車。上傳成功后后端返回 URL 列表小程序端再把 URL 和商品表單內(nèi)容一起提交到發(fā)布接口。這里有一個(gè)常見(jiàn)的坑有些開(kāi)發(fā)者會(huì)在上傳接口里順便創(chuàng)建商品記錄結(jié)果用戶傳了圖但沒(méi)填標(biāo)題就離開(kāi)了數(shù)據(jù)庫(kù)里就會(huì)多出一堆臟數(shù)據(jù)。正確做法是上傳僅返回地址發(fā)布接口才真正落庫(kù)商品表里維護(hù)一個(gè)product_image子表來(lái)關(guān)聯(lián)多個(gè)圖片 URL。商品列表查詢要考慮過(guò)濾條件按分類、按價(jià)格區(qū)間、按關(guān)鍵詞搜索。關(guān)鍵詞搜索我用的是LIKE %keyword%匹配標(biāo)題和描述數(shù)據(jù)量小時(shí)完全夠用。如果以后商品量暴增再升級(jí)到全文檢索也不遲。2.4 訂單狀態(tài)機(jī)讓買賣雙方都有明確預(yù)期二手交易系統(tǒng)的訂單狀態(tài)不能像電商那樣復(fù)雜但也不能完全沒(méi)有。我設(shè)計(jì)了五個(gè)狀態(tài)待付款、待發(fā)貨、待收貨、已完成、已取消。當(dāng)然校園二手很大程度是線下當(dāng)面交易所以“待發(fā)貨”和“待收貨”在實(shí)際場(chǎng)景里可以解釋成“約定時(shí)間地點(diǎn)”和“線下交易完成”。訂單的生成要防重復(fù)。同一商品同一時(shí)間只能有一個(gè)買家發(fā)起交易后端在生成訂單前必須查一次商品狀態(tài)并且用數(shù)據(jù)庫(kù)的樂(lè)觀鎖或UPDATE ... WHERE status 在售的原子更新來(lái)?yè)屨忌唐?。我最終選了UPDATE product SET status 1 WHERE id ? AND status 0這種方式如果更新影響行數(shù)為 0說(shuō)明商品已經(jīng)被別人搶占了直接返回“商品已售出”。這個(gè)設(shè)計(jì)在并發(fā)量不高時(shí)完全夠用而且避免了先查再更新的時(shí)間差問(wèn)題。我實(shí)測(cè)過(guò)用 Postman 并發(fā)打 20 個(gè)同樣的購(gòu)買請(qǐng)求只有 1 個(gè)成功其余全部正確攔截。如果將來(lái)并發(fā)量變大可以換成 Redis 分布式鎖但目前沒(méi)必要過(guò)度設(shè)計(jì)。確認(rèn)收貨之后買賣雙方都可以看到訂單的完成狀態(tài)。如果有一方超過(guò)約定時(shí)間沒(méi)有操作可以在列表里提供“提醒對(duì)方”的功能通過(guò)站內(nèi)信或模板消息觸發(fā)。注意這里我沒(méi)有接入真實(shí)支付和自動(dòng)發(fā)貨邏輯因?yàn)樾@二手以面交為主接入支付反而會(huì)把項(xiàng)目變得復(fù)雜且審核風(fēng)險(xiǎn)升高。2.5 留言與收藏增強(qiáng)購(gòu)買意向的表達(dá)留言功能本質(zhì)是提供一個(gè)輕量的商品溝通渠道。它的字段只有product_id、from_user_id、to_user_id、content、create_time。查詢時(shí)按商品ID倒序取最新 20 條不需要做網(wǎng)絡(luò)通信因?yàn)樾?nèi)當(dāng)面溝通更高效。這里要注意防止 XSS用戶輸入的留言內(nèi)容在存儲(chǔ)和展示前都要做、、、、等字符的轉(zhuǎn)義處理。收藏功能則是給買家一個(gè)“先存下來(lái)再慢慢考慮”的入口。收藏表用user_id product_id做聯(lián)合唯一索引確保同一個(gè)人不能重復(fù)收藏同一商品。收藏?cái)?shù)量不用實(shí)時(shí)統(tǒng)計(jì)到商品表可以在需要時(shí)COUNT(*)查出來(lái)展示。這也是性能和簡(jiǎn)潔之間的平衡。3. 小程序端頁(yè)面設(shè)計(jì)、請(qǐng)求封裝與表單校驗(yàn)3.1 整體頁(yè)面結(jié)構(gòu)與導(dǎo)航設(shè)計(jì)小程序端我采用了五個(gè) Tab 的結(jié)構(gòu)首頁(yè)、分類、發(fā)布、消息、我的。這個(gè)結(jié)構(gòu)對(duì)二手交易類應(yīng)用來(lái)說(shuō)比較成熟用戶進(jìn)入后第一眼看到的是商品流發(fā)布按鈕固定在顯眼位置消息中心承載留言和訂單通知我的頁(yè)面展示個(gè)人數(shù)據(jù)和功能入口。首頁(yè)商品流用雙列瀑布流展示在售商品每張卡片包含主圖、標(biāo)題、價(jià)格、成色標(biāo)簽。瀑布流要注意圖片比例問(wèn)題不同賣家上傳的圖片比例可能不同我在后端返回圖片URL時(shí)額外給每個(gè)URL附加了寬高比字段小程序端用modeaspectFill統(tǒng)一裁切視覺(jué)上整齊很多。很多新手忽略這個(gè)細(xì)節(jié)結(jié)果首頁(yè)有的卡片高有的卡片矮顯得很凌亂。分類頁(yè)則按預(yù)設(shè)的分類展示商品左側(cè)分類欄、右側(cè)商品列表點(diǎn)擊分類后請(qǐng)求對(duì)應(yīng)接口并置頂刷新。分類數(shù)據(jù)來(lái)自數(shù)據(jù)庫(kù)不寫死在前端這樣以后加分類不用發(fā)新版小程序。3.2 發(fā)布功能的交互與校驗(yàn)發(fā)布頁(yè)是體現(xiàn)項(xiàng)目完成度的試金石。我參考閑魚的發(fā)布交互頂部選擇商品分類中間是標(biāo)題輸入、描述輸入、價(jià)格輸入、成色選擇下面是九宮格圖片上傳。圖片上傳區(qū)域在用戶選擇圖片后立即上傳到服務(wù)器但商品本身要等用戶點(diǎn)擊“發(fā)布”才創(chuàng)建。表單校驗(yàn)要前置到前端。標(biāo)題必填且限制 20 字以內(nèi)價(jià)格必須為正數(shù)且最多兩位小數(shù)成色必須選擇一種。前端校驗(yàn)過(guò)了后端接口還會(huì)再校驗(yàn)一遍雙重校驗(yàn)可以避免繞過(guò)客戶端直接調(diào)接口產(chǎn)生的臟數(shù)據(jù)。價(jià)格輸入的實(shí)現(xiàn)上我用typedigit讓鍵盤直接彈出數(shù)字鍵盤再配合maxlength6限制輸入長(zhǎng)度。發(fā)布成功的提示我用了一個(gè)輕量 toast然后跳轉(zhuǎn)到“我的發(fā)布”列表用戶能立刻看到自己剛發(fā)布的商品。如果跳回首頁(yè)因?yàn)榱斜眄?yè)沒(méi)有刷新的概念用戶會(huì)以為發(fā)布失敗了這是產(chǎn)品交互上的細(xì)節(jié)影響體驗(yàn)非常大。3.3 請(qǐng)求統(tǒng)一封裝與登錄態(tài)管理小程序端最需要規(guī)劃的就是網(wǎng)絡(luò)請(qǐng)求。我在utils/request.js里統(tǒng)一封裝了wx.request每次請(qǐng)求前自動(dòng)從本地存儲(chǔ)讀取 Token放進(jìn)Authorization請(qǐng)求頭每次響應(yīng)先判斷 HTTP 狀態(tài)碼再判斷業(yè)務(wù)碼code如果業(yè)務(wù)碼是401就自動(dòng)跳轉(zhuǎn)登錄頁(yè)。這個(gè)封裝讓業(yè)務(wù)代碼里完全不需要寫wx.request的細(xì)節(jié)頁(yè)面里只要寫request({ url: /api/product/list, method: GET })就能拿到數(shù)據(jù)?;卣{(diào)地獄的問(wèn)題也通過(guò) Promise 解決了異步邏輯用async/await寫起來(lái)非常順。注意小程序真機(jī)調(diào)試時(shí)一定要在微信公眾平臺(tái)配置 request 合法域名并且域名必須備案且支持 HTTPS。開(kāi)發(fā)階段可以在開(kāi)發(fā)者工具里勾選“不校驗(yàn)合法域名”但上線前必須改回來(lái)否則正式版會(huì)白屏。這個(gè)坑我踩過(guò)一次排查了很久才意識(shí)到是域名沒(méi)有加進(jìn)白名單。3.4 商品詳情、訂單流程與消息提醒商品詳情頁(yè)除了展示圖片和價(jià)格還要展示賣家的信息比如昵稱、頭像、校區(qū)以及“聯(lián)系買家”和“立即購(gòu)買”兩個(gè)操作按鈕。聯(lián)系買家走的是留言或引導(dǎo)復(fù)制微信號(hào)考慮到項(xiàng)目審核我沒(méi)有做內(nèi)置聊天而是用留言板加復(fù)制聯(lián)系方式的組合。訂單流程在小程序端的表現(xiàn)是買家點(diǎn)擊“立即購(gòu)買”后彈窗確認(rèn)價(jià)格確認(rèn)后調(diào)用后端下單接口賣家在“我賣出的”列表里看到新訂單可以點(diǎn)擊“約定的線下交易已完成”訂單進(jìn)入待收貨狀態(tài)買家收到貨后確認(rèn)訂單完成。每一步操作后端都會(huì)更新?tīng)顟B(tài)并校驗(yàn)當(dāng)前用戶是否是訂單的買方或賣方防止越權(quán)操作。消息中心展示兩類內(nèi)容最新留言和訂單狀態(tài)變更。數(shù)據(jù)來(lái)自后端聚合查詢將留言記錄和訂單狀態(tài)變更記錄合并按時(shí)間排序。這里要注意分頁(yè)我用了簡(jiǎn)單的“下拉加載下一頁(yè)”模式每頁(yè) 10 條避免一次性拉取太多數(shù)據(jù)導(dǎo)致頁(yè)面卡頓。4. 部署上線與問(wèn)題排查把項(xiàng)目真正跑起來(lái)4.1 從本地到服務(wù)器的部署流程這個(gè)項(xiàng)目是標(biāo)準(zhǔn)的單體 Spring Boot 應(yīng)用部署不復(fù)雜但步驟要踩對(duì)。我用的方案是Maven 打包成可執(zhí)行的 jar通過(guò) scp 上傳到服務(wù)器再用nohup java -jar啟動(dòng)配合 Nginx 做反向代理和 HTTPS 終結(jié)。打包時(shí)要注意 profile 的切換。開(kāi)發(fā)環(huán)境連本地 MySQL生產(chǎn)環(huán)境要連服務(wù)器上的 MySQL數(shù)據(jù)庫(kù)地址和密碼不能寫死。我的做法是 Spring Boot 的多環(huán)境配置application-dev.yml和application-prod.yml打包時(shí)通過(guò)--spring.profiles.activeprod指定環(huán)境。不然每次部署都要改配置文件改漏一次就等著聯(lián)調(diào)翻車。Nginx 部分要配置兩個(gè)轉(zhuǎn)發(fā)一是把/api/開(kāi)頭的請(qǐng)求轉(zhuǎn)發(fā)到 Spring Boot 的8080端口二是把靜態(tài)圖片目錄映射到一個(gè)對(duì)外可訪問(wèn)的 URL比如http://你的域名/upload/指向服務(wù)器上的上傳目錄。HTTPS 證書用免費(fèi)的證書服務(wù)配置好 SSL 后微信小程序端才能正常發(fā)起請(qǐng)求。4.2 常見(jiàn)問(wèn)題與排查速查表這一部分是我實(shí)際開(kāi)發(fā)中遇到的典型問(wèn)題整理成表格方便快速定位現(xiàn)象可能原因解決方案小程序請(qǐng)求全部失敗真機(jī)上白屏request 合法域名未配置或域名未備案在微信公眾平臺(tái)配置合法域名使用 HTTPS登錄后用戶信息不顯示Token 解析失敗或 Redis 中 Key 過(guò)期檢查攔截器邏輯確認(rèn) Redis 是否有數(shù)據(jù)Token 有效期設(shè)為 7 天圖片上傳成功但商品頁(yè)圖片裂開(kāi)圖片 URL 用了相對(duì)路徑小程序無(wú)法訪問(wèn)返回絕對(duì) URL或在前端拼接完整域名同一個(gè)商品被兩個(gè)人同時(shí)下單成功缺少商品狀態(tài)原子更新使用UPDATE product SET status 1 WHERE id ? AND status 0發(fā)布商品列表偶發(fā)重復(fù)數(shù)據(jù)頁(yè)面下拉刷新與上拉加載事件沖突用 loading 鎖防止并發(fā)請(qǐng)求價(jià)格出現(xiàn) 0.30000000000000004數(shù)據(jù)庫(kù)或 JSON 用了浮點(diǎn)數(shù)數(shù)據(jù)庫(kù)用 DECIMALJava 用 BigDecimal4.3 安全和性能的幾個(gè)硬性原則安全方面有幾條我始終堅(jiān)持的底線。第一是越權(quán)防護(hù)查詢訂單和操作商品前必須校驗(yàn)當(dāng)前登錄用戶與資源所有者是否為同一人不能只依賴前端隱藏按鈕接口層面必須做攔截。第二是參數(shù)校驗(yàn)不僅依賴前端后端 Controller 層要用Valid或手動(dòng)校驗(yàn)所有關(guān)鍵參數(shù)包括分頁(yè)的page、size不能為負(fù)排序字段用白名單。第三是文件上傳的安全圖片類型要校驗(yàn)真實(shí)文件頭而不是只看擴(kuò)展名惡意腳本文件不能進(jìn)服務(wù)器。性能方面現(xiàn)階段業(yè)務(wù)量不大最值得關(guān)注的是 SQL 查詢效率和圖片體積。列表查詢不要SELECT *只需要返回卡片需要的字段圖片上傳前在小程序端先壓縮一遍可以極大減少存儲(chǔ)空間和加載帶寬。我在項(xiàng)目里用wx.compressImage把圖片統(tǒng)一壓到最大寬度 800 像素保證清晰度的同時(shí)體積控制在 200KB 以內(nèi)首頁(yè)加載速度明顯更快。5. 做完整個(gè)項(xiàng)目的體會(huì)與后續(xù)擴(kuò)展思路5.1 幾個(gè)親測(cè)有效的開(kāi)發(fā)心得這個(gè)項(xiàng)目完整走下來(lái)我最大的體會(huì)是業(yè)務(wù)流程的完整度比技術(shù)花活重要得多。很多二手交易平臺(tái)的項(xiàng)目案例只做了商品 CRUD根本沒(méi)有訂單狀態(tài)、沒(méi)有交易沖突處理、沒(méi)有越權(quán)校驗(yàn)看起來(lái)功能挺多但本質(zhì)是管理系統(tǒng)而不是交易系統(tǒng)。做這類項(xiàng)目先畫狀態(tài)圖先定義好訂單的生命周期再動(dòng)代碼能省掉后期大量返工。第二個(gè)體會(huì)是日志和聯(lián)調(diào)工具要早用早熟。我在開(kāi)發(fā)階段就養(yǎng)成了每次接口調(diào)用都打印請(qǐng)求參數(shù)和響應(yīng)結(jié)果的習(xí)慣而且用 Postman 或者 Apifox 管理接口文檔聯(lián)調(diào)時(shí)雙方都對(duì)著同一份文檔爭(zhēng)論少很多。如果你自己同時(shí)負(fù)責(zé)前后端也要注意別因?yàn)椤白约盒睦镉袛?shù)”就不寫接口文檔兩周后再看自己都容易遺忘字段含義。第三小程序端的錯(cuò)誤提示要友好。500 錯(cuò)誤時(shí)不能直接彈“服務(wù)器內(nèi)部錯(cuò)誤”這種摸不著頭腦的話。我封裝請(qǐng)求時(shí)對(duì)code做了映射例如“商品已售出”對(duì)應(yīng)提示“手慢了一步商品已經(jīng)被買走了”“401”對(duì)應(yīng)“登錄已過(guò)期請(qǐng)重新進(jìn)入”。好的錯(cuò)誤提示能把用戶從困惑里拉回來(lái)不怎么增加開(kāi)發(fā)成本但體驗(yàn)提升非常明顯。5.2 這個(gè)項(xiàng)目還能往哪些方向擴(kuò)展如果這個(gè)項(xiàng)目要繼續(xù)迭代我最建議加一個(gè)管理后臺(tái)因?yàn)檫\(yùn)營(yíng)校園二手平臺(tái)總得有人處理違規(guī)商品和用戶糾紛。管理后臺(tái)可以復(fù)用后端的接口單獨(dú)做一套 Web 前端用 Vue 或 React 都行核心功能包括商品審核、用戶管理、訂單數(shù)據(jù)看板。第二個(gè)優(yōu)先級(jí)是對(duì)消息通知的完善?,F(xiàn)在消息中心的實(shí)現(xiàn)是被動(dòng)拉取用戶離開(kāi)小程序后就不知道自己的商品有沒(méi)有被留言。接入微信的訂閱消息后可以在被留言、訂單狀態(tài)變更時(shí)推送模板消息用戶留存率會(huì)高很多。這里注意訂閱消息的模板要提前申請(qǐng)而且推送是一次性的需要在用戶觸發(fā)操作時(shí)向后端傳遞授權(quán)結(jié)果。再往后可以考慮做價(jià)格參考和信用體系。價(jià)格參考可以從歷史成交記錄里算同品類均價(jià)幫買賣雙方定一個(gè)公平價(jià)格信用體系可以來(lái)自用戶完成訂單數(shù)量和實(shí)名認(rèn)證不用太復(fù)雜一個(gè)信譽(yù)分字段加上最近 30 天成交記錄就夠了。這些擴(kuò)展不會(huì)改變核心交易鏈路但能把系統(tǒng)的可用性往上拉一個(gè)臺(tái)階。5.3 最后分享一點(diǎn)我踩過(guò)的坑最后說(shuō)一個(gè)我印象最深的坑開(kāi)發(fā)初期我直接用了懶加載的圖片組件wx.lazy-load結(jié)果首頁(yè)瀑布流的圖片在快速滑動(dòng)時(shí)經(jīng)常出現(xiàn)空白占位用戶以為頁(yè)面出 bug 了。調(diào)試了很久發(fā)現(xiàn)是圖片 URL 不帶寬高信息組件無(wú)法預(yù)知尺寸。我最終的處理是后端返回圖片時(shí)順帶返回寬高比前端拿到比例后立即布局再配合懶加載這個(gè)問(wèn)題就消失了。類似這樣的細(xì)節(jié)還有很多比如訂單號(hào)不能只靠時(shí)間戳、發(fā)布按鈕在 Tab 中間不能是普通頁(yè)面跳轉(zhuǎn)、退出登錄必須清掉本地存儲(chǔ)和緩存。這些都是文檔里不會(huì)寫、但真要做產(chǎn)品時(shí)躲不過(guò)去的細(xì)節(jié)。希望這篇內(nèi)容能幫你少走幾步彎路把精力花在真正有意義的功能和體驗(yàn)上。