商鋪管理系統(tǒng):Spring Boot+Vue+MySQL全棧實(shí)戰(zhàn)解析)
社區(qū)商鋪管理系統(tǒng)這類畢設(shè)題目每年都能見(jiàn)到不少但真正做扎實(shí)的其實(shí)不多。我前后參與評(píng)審和帶過(guò)幾個(gè)類似的畢業(yè)設(shè)計(jì)項(xiàng)目這套“社區(qū)商鋪管理系統(tǒng)”算是比較典型的一套完整方案后端是Spring Boot前端是Vue加Element UI數(shù)據(jù)庫(kù)用的MySQL覆蓋商鋪檔案、租戶管理、合同臺(tái)賬、費(fèi)用收繳、報(bào)修跟蹤等核心模塊。不管你是正在做畢設(shè)需要參考還是想快速搭一套輕量級(jí)的商鋪管理后臺(tái)這篇文章會(huì)把設(shè)計(jì)思路、數(shù)據(jù)庫(kù)結(jié)構(gòu)、關(guān)鍵實(shí)現(xiàn)、部署步驟和答辯常見(jiàn)追問(wèn)一次講清楚。這系統(tǒng)解決的核心問(wèn)題很具體社區(qū)里幾十上百間商鋪如果只靠Excel表格管理租期、租金、水電費(fèi)動(dòng)不動(dòng)就對(duì)不上賬。把商鋪信息、租戶關(guān)系、合同狀態(tài)、費(fèi)用記錄全部落到一個(gè)系統(tǒng)里誰(shuí)在租、租到什么時(shí)候、欠了多少錢(qián)、哪間鋪?zhàn)涌罩蜷_(kāi)后臺(tái)一眼就能看清。對(duì)做畢設(shè)的同學(xué)來(lái)說(shuō)這類系統(tǒng)的業(yè)務(wù)邏輯不復(fù)雜但鏈條完整特別適合用來(lái)展示從需求分析到數(shù)據(jù)庫(kù)設(shè)計(jì)再到前后端編碼的完整能力。下面我按實(shí)際動(dòng)手做這個(gè)項(xiàng)目的思路來(lái)講不繞彎子。1. 項(xiàng)目定位與需求拆解1.1 這類系統(tǒng)到底在管什么先說(shuō)場(chǎng)景。社區(qū)商鋪的形態(tài)很多有小區(qū)底商、社區(qū)商業(yè)街、產(chǎn)業(yè)園區(qū)配套商業(yè)區(qū)少則十幾間多則上百間。管理方往往是物業(yè)公司或者社區(qū)運(yùn)營(yíng)方他們?nèi)粘R幚淼牟皇恰百u(mài)東西”而是“管鋪?zhàn)印蹦拈g鋪?zhàn)幼獬鋈チ?、租給了誰(shuí)、合同什么時(shí)候到期、這個(gè)月該收多少租金和水電費(fèi)、租戶提交的報(bào)修單處理到什么程度。這些信息如果散落在Excel和微信聊天記錄里時(shí)間一長(zhǎng)必定對(duì)不上賬尤其是同時(shí)存在“已退租未結(jié)清”“合同已到期但租戶還在用”這類特殊情況時(shí)純粹靠人腦記根本沒(méi)有可行性。所以這套系統(tǒng)的核心價(jià)值就是把商鋪全生命周期管起來(lái)從錄入一間鋪?zhàn)拥幕拘畔㈤_(kāi)始到找到租戶、簽合同收押金、每月自動(dòng)生成應(yīng)收賬單、收款登記、續(xù)租或退租結(jié)算整個(gè)鏈條每個(gè)環(huán)節(jié)都有對(duì)應(yīng)的數(shù)據(jù)記錄和狀態(tài)變更。對(duì)做畢設(shè)而言這個(gè)業(yè)務(wù)域特別合適因?yàn)樗恍枰獜?fù)雜算法但涉及多個(gè)實(shí)體的關(guān)聯(lián)、狀態(tài)的扭轉(zhuǎn)、統(tǒng)計(jì)報(bào)表的輸出能很好地體現(xiàn)一個(gè)開(kāi)發(fā)者對(duì)業(yè)務(wù)建模和工程化的理解。1.2 核心功能模塊與業(yè)務(wù)流程我拆解這個(gè)系統(tǒng)時(shí)習(xí)慣先畫(huà)業(yè)務(wù)流程圖再動(dòng)手寫(xiě)代碼。整體上分為六大模塊商鋪管理維護(hù)商鋪檔案包括鋪號(hào)、樓層、面積、租金單價(jià)、物業(yè)費(fèi)單價(jià)、當(dāng)前狀態(tài)空閑/已租/裝修中/停用。租戶管理管理租戶基本信息包括姓名、手機(jī)號(hào)、證件號(hào)、緊急聯(lián)系人支持按名稱或手機(jī)號(hào)快速檢索。合同管理商鋪與租戶之間簽約、續(xù)租、退租、到期提醒。合同里記錄租期、月租金、押金、遞增規(guī)則。收費(fèi)管理按合同生成月度賬單收費(fèi)類型涵蓋租金、物業(yè)費(fèi)、水費(fèi)、電費(fèi)、滯納金支持線下收款后登記核銷(xiāo)、逾期標(biāo)記。報(bào)修管理租戶發(fā)起報(bào)修管理員派單、回訪、完結(jié)記錄處理人、費(fèi)用。系統(tǒng)管理登錄認(rèn)證、用戶管理、角色權(quán)限、操作日志。業(yè)務(wù)流程的主線是“建檔→出租→收費(fèi)→結(jié)算”。一間新鋪?zhàn)酉扔晒芾韱T建檔狀態(tài)設(shè)為“空閑”找到租戶后簽訂合同合同一旦生效系統(tǒng)自動(dòng)把商鋪狀態(tài)改為“已租”同時(shí)根據(jù)合同起止日期生成首月的應(yīng)收賬單此后每月由定時(shí)任務(wù)或管理員手動(dòng)觸發(fā)生成當(dāng)月的租金加物業(yè)費(fèi)賬單租戶繳費(fèi)后做收款登記賬單狀態(tài)變?yōu)椤耙牙U”合同到期時(shí)如果續(xù)簽就延長(zhǎng)合同并調(diào)整租金如果不續(xù)簽辦理退租結(jié)算把押金抵扣未繳費(fèi)用后退還商鋪狀態(tài)改回“空閑”。這條鏈路把核心業(yè)務(wù)全部串起來(lái)了也是后面設(shè)計(jì)表結(jié)構(gòu)和接口的主線。1.3 選擇這個(gè)題目的優(yōu)勢(shì)與難點(diǎn)從畢設(shè)選題角度看“社區(qū)商鋪管理系統(tǒng)”的優(yōu)勢(shì)很明顯業(yè)務(wù)不冷門(mén)任何人一看題目就知道要做什么技術(shù)??梢赃x主流水準(zhǔn)前端Vue、后端Spring Boot、數(shù)據(jù)庫(kù)MySQL工作量大但完全可控演示效果好后臺(tái)界面上有表格、表單、圖表甚至可以做ECharts出租率趨勢(shì)圖答辯時(shí)能展示出很完整的項(xiàng)目成果。難度則集中在幾處一是合同與賬單的關(guān)聯(lián)邏輯尤其是跨月、跨年的賬單生成和費(fèi)用統(tǒng)計(jì)容易算錯(cuò)二是權(quán)限控制要做到不同角色看不到不同菜單而不是只在頁(yè)面里隱藏按鈕三是時(shí)間字段的處理容易踩時(shí)區(qū)坑后面我會(huì)單獨(dú)講。明確這三個(gè)難點(diǎn)之后開(kāi)發(fā)時(shí)就能有意識(shí)地避開(kāi)。2. 技術(shù)選型與架構(gòu)設(shè)計(jì)2.1 為什么選Spring Boot加Vue前后端分離畢業(yè)設(shè)計(jì)的主流技術(shù)方案我對(duì)比過(guò)幾類純Servlet加JSP的傳統(tǒng)模式、SSM配合JSP、Spring Boot加Thymeleaf服務(wù)端渲染、Spring Boot加Vue前后端分離。我的建議是直接用Spring Boot加Vue這套理由很實(shí)際。第一前后端分離讓代碼組織更清晰。后端只暴露REST接口前端只做頁(yè)面交互兩邊都能單獨(dú)調(diào)試、單獨(dú)部署。答辯時(shí)你能夠把前后端分開(kāi)展示講接口設(shè)計(jì)、講頁(yè)面組件每個(gè)部分都有自己的工作量不會(huì)被質(zhì)疑“是不是只有CRUD”。第二Spring Boot把配置簡(jiǎn)化了很多。內(nèi)嵌Tomcat不用單獨(dú)裝容器一個(gè)jar包就能跑起來(lái)數(shù)據(jù)庫(kù)連接池、MyBatis、校驗(yàn)框架通過(guò)starter引入即可省去大量XML配置。對(duì)一個(gè)半年周期內(nèi)還要寫(xiě)論文、畫(huà)圖、調(diào)格式的畢設(shè)來(lái)說(shuō)時(shí)間要花在業(yè)務(wù)上而不是環(huán)境折騰上。第三Vue的生態(tài)成熟Element UI的組件覆蓋了后臺(tái)管理系統(tǒng)需要的表單、表格、彈窗、分頁(yè)、菜單、面包屑套上就能出一套像樣的界面。對(duì)非設(shè)計(jì)出身的開(kāi)發(fā)者來(lái)說(shuō)這是性價(jià)比最高的選擇。當(dāng)然如果你對(duì)Java不熟備選方案是Python的Django/Flask搭配Vue或者Node.js的Express前后端同構(gòu)但考慮到答辯時(shí)老師普遍更認(rèn)可Java系列而且社區(qū)里能找到的參考源碼和文檔最多我還是推薦Spring Boot。2.2 整體架構(gòu)與請(qǐng)求鏈路系統(tǒng)整體采用標(biāo)準(zhǔn)的前后端分離架構(gòu)。前端是Vue加Vue Router加Vuex加Axios加Element UI使用Vite構(gòu)建后端是Spring Boot加MyBatis-Plus加MySQL接口遵循RESTful風(fēng)格開(kāi)發(fā)階段前端通過(guò)Vite代理轉(zhuǎn)發(fā)請(qǐng)求到后端的8080端口生產(chǎn)環(huán)境則把前后端分別部署。一條典型的請(qǐng)求鏈路是這樣用戶打開(kāi)登錄頁(yè)輸入賬號(hào)密碼前端調(diào)用/api/login接口后端認(rèn)證成功后返回一個(gè)Token前端把Token存到localStorageAxios攔截器里每次請(qǐng)求都帶上Authorization頭后端通過(guò)攔截器校驗(yàn)Token解析出當(dāng)前用戶和角色再到Service層做業(yè)務(wù)判斷Mapper層執(zhí)行SQL最終把結(jié)果以統(tǒng)一返回結(jié)構(gòu){ code, message, data }返回給前端。這個(gè)統(tǒng)一返回結(jié)構(gòu)很重要前端可以直接根據(jù)code判斷成功與失敗不需要在每一個(gè)請(qǐng)求里單獨(dú)處理異常。架構(gòu)上分層做得很清楚Controller層只做參數(shù)接收和結(jié)果封裝不寫(xiě)業(yè)務(wù)Service層寫(xiě)核心邏輯比如賬單生成、合同狀態(tài)流轉(zhuǎn)Mapper層只負(fù)責(zé)SQL與實(shí)體映射。這樣做的好處是答辯時(shí)老師問(wèn)“你的事務(wù)怎么控制的”你就能直接說(shuō)賬單生成是多表操作在Service上加Transactional保證合同狀態(tài)和賬單數(shù)據(jù)不會(huì)出現(xiàn)一半成功一半失敗。2.3 數(shù)據(jù)庫(kù)設(shè)計(jì)思路與核心表結(jié)構(gòu)數(shù)據(jù)庫(kù)設(shè)計(jì)是整個(gè)項(xiàng)目的地基我把核心表結(jié)構(gòu)列出來(lái)并把設(shè)計(jì)原因講清楚。商鋪表shop字段類型說(shuō)明idbigint主鍵自增shop_novarchar(20)鋪號(hào)唯一索引shop_namevarchar(100)商鋪名稱floorvarchar(10)所在樓層areadecimal(10,2)建筑面積平方米rent_unit_pricedecimal(10,2)月租金單價(jià)元/平米property_fee_unit_pricedecimal(10,2)物業(yè)費(fèi)單價(jià)元/平米/月statustinyint狀態(tài)0空閑 1已租 2裝修中 3停用remarkvarchar(255)備注租戶表tenantid、tenant_name、phone、id_card、emergency_contact、emergency_phone、create_time。合同表contract設(shè)計(jì)時(shí)要注意冗余字段id、contract_no、shop_id、tenant_id、start_date、end_date、monthly_rent、monthly_property_fee、deposit、rent_increase_rate、status1生效 2已到期 3已解約、sign_date、remark。收費(fèi)記錄表charge_recordid、contract_id、charge_type1租金 2物業(yè)費(fèi) 3水費(fèi) 4電費(fèi) 5滯納金、amount、period賬期如2025-03、status0未繳 1已繳 2逾期、pay_date、pay_method、operator_id。報(bào)修表repair_record、公告表notice、用戶表sys_user、角色表sys_role、用戶角色關(guān)聯(lián)表sys_user_role、操作日志表sys_log。設(shè)計(jì)上的幾個(gè)關(guān)鍵點(diǎn)要單獨(dú)講外鍵關(guān)系和查詢效率。業(yè)務(wù)表之間有關(guān)聯(lián)比如合同表關(guān)聯(lián)shop_id和tenant_id但我在數(shù)據(jù)庫(kù)層面不建物理外鍵而是在代碼里保證引用關(guān)系。物理外鍵在刪數(shù)據(jù)時(shí)會(huì)帶來(lái)一堆約束麻煩線上系統(tǒng)也普遍不推薦用關(guān)鍵是索引要建對(duì)。對(duì)shop_id、tenant_id、contract_id這些關(guān)聯(lián)字段都要建普通索引否則數(shù)據(jù)量上來(lái)后聯(lián)表查詢會(huì)變慢。金額字段一律用decimal而不是float/double。Java后端用BigDecimal接收數(shù)據(jù)庫(kù)用decimal(10,2)。浮點(diǎn)數(shù)算金額0.1加0.2不等于0.3的問(wèn)題在答辯現(xiàn)場(chǎng)非常尷尬這一點(diǎn)一定要提前規(guī)避。時(shí)間字段用datetime所有表都帶上create_time和update_time。MyBatis-Plus的自動(dòng)填充功能可以讓插入和更新時(shí)自動(dòng)填這兩個(gè)字段省去手動(dòng)維護(hù)。賬期period字段為什么不直接用日期因?yàn)橘~單是按自然月生成的2025-03這個(gè)字段在統(tǒng)計(jì)月度收入時(shí)直接group by period就可以不需要再轉(zhuǎn)換日期格式。這個(gè)設(shè)計(jì)是業(yè)務(wù)驅(qū)動(dòng)出來(lái)的不是拍腦袋決定的。3. 核心模塊實(shí)現(xiàn)深度解析3.1 商鋪管理模塊狀態(tài)流轉(zhuǎn)是關(guān)鍵商鋪管理表面上是增刪改查核心是狀態(tài)流轉(zhuǎn)。一間商鋪可以從空閑變成已租、從已租變成裝修中、再重新變成空閑也可以被停用。我在后端定義一個(gè)枚舉ShopStatus在Service層做流轉(zhuǎn)校驗(yàn)只有“空閑”狀態(tài)的商鋪才能簽新合同變成“已租”“已租”狀態(tài)的商鋪不允許直接刪除只能走退租流程“停用”狀態(tài)的商鋪不參與出租率統(tǒng)計(jì)。這里有一個(gè)好用的技巧凡是需要和業(yè)務(wù)狀態(tài)打交道的修改操作都寫(xiě)專門(mén)的Service方法而不是讓前端直接調(diào)通用update接口傳一個(gè)status字段。比如簽約操作是createContract()里面做了三件事校驗(yàn)商鋪狀態(tài)、插入合同記錄、更新商鋪狀態(tài)為已租。這樣狀態(tài)流轉(zhuǎn)的邏輯只在服務(wù)端前端只能通過(guò)業(yè)務(wù)接口觸發(fā)避免了有人繞過(guò)校驗(yàn)直接改狀態(tài)的情況。代碼里用Transactional包住整個(gè)方法任何一個(gè)環(huán)節(jié)出錯(cuò)都回滾。核心方法大概長(zhǎng)這樣Transactional(rollbackFor Exception.class) public void signContract(SignContractRequest req) { Shop shop shopMapper.selectById(req.getShopId()); if (shop null || !ShopStatus.FREE.equals(shop.getStatus())) { throw new BusinessException(該商鋪當(dāng)前不可出租); } Contract contract new Contract(); contract.setShopId(req.getShopId()); contract.setTenantId(req.getTenantId()); contract.setStartDate(req.getStartDate()); contract.setEndDate(req.getEndDate()); contract.setMonthlyRent(req.getMonthlyRent()); contract.setDeposit(req.getDeposit()); contract.setStatus(ContractStatus.ACTIVE); contractMapper.insert(contract); shop.setStatus(ShopStatus.RENTED); shopMapper.updateById(shop); generateFirstMonthBill(contract.getId()); }3.2 合同管理模塊日期與關(guān)聯(lián)狀態(tài)的處理合同管理是這個(gè)項(xiàng)目里業(yè)務(wù)邏輯最重的部分我踩過(guò)不少坑。先說(shuō)簽約時(shí)租金怎么算。商鋪表里存的是“租金單價(jià)元/平米/月”但合同里存的是“月租金元”。簽約時(shí)要根據(jù)面積乘以單價(jià)算出來(lái)比如一間50平米的鋪?zhàn)訂蝺r(jià)80元/平月租金就是4000元。為什么合同里要冗余一個(gè)月的租金總價(jià)而不是每次現(xiàn)算因?yàn)閷?lái)可能單獨(dú)對(duì)某個(gè)合同調(diào)價(jià)比如商圈整體漲價(jià)后給老租戶打折如果只存單價(jià)歷史合同的金額會(huì)跟著變化財(cái)務(wù)核算就亂了。所以合同一旦生成月租金就以合同為準(zhǔn)商鋪的單價(jià)只作為新建合同的默認(rèn)值互不影響。日期處理也是重點(diǎn)。合同的start_date和end_date用Java的LocalDate來(lái)接收向前端返回時(shí)格式化成yyyy-MM-dd。計(jì)算租期內(nèi)到底要生成幾個(gè)月的賬單有一種常見(jiàn)做法是用ChronoUnit.MONTHS.between(start, end)再加1比如3月1日到5月31日是3個(gè)月。這里有個(gè)坑ChronoUnit計(jì)算的是兩個(gè)日期之間的完整月數(shù)如果合同是3月15日到5月14日算出來(lái)是2個(gè)月但實(shí)際按自然月算賬單可能是跨了3、4、5三個(gè)月的部分月份。所以我在生成賬單時(shí)不用這個(gè)API而是按月從合同開(kāi)始月份循環(huán)到結(jié)束月份每個(gè)月生成一條賬期記錄金額按整月收。雖然部分月份實(shí)際不是整月但社區(qū)商鋪的結(jié)算習(xí)慣通常都是按月整收這種約定俗成的規(guī)則在計(jì)費(fèi)邏輯里要先和需求方確認(rèn)清楚。合同到期提醒功能也建議做??梢悦刻炫芤粋€(gè)定時(shí)任務(wù)掃一遍狀態(tài)為生效、結(jié)束日期在30天內(nèi)的合同把記錄寫(xiě)入提醒列表讓管理員在首頁(yè)看到。這個(gè)功能不復(fù)雜但對(duì)“系統(tǒng)有沒(méi)有用”的感知提升很明顯。3.3 收費(fèi)管理模塊賬單生成與核銷(xiāo)收費(fèi)模塊是核心中的核心。我設(shè)計(jì)了一個(gè)計(jì)費(fèi)方法generateMonthlyBill(contractId, yearMonth)邏輯是查詢合同是否在指定月份處于生效狀態(tài)檢查該月份是否已經(jīng)生成過(guò)賬單有則跳過(guò)避免重復(fù)生成根據(jù)合同中的月租金和月物業(yè)費(fèi)生成兩條charge_record記錄一條charge_type等于1租金一條等于2物業(yè)費(fèi)如果有水電費(fèi)抄表記錄再生成對(duì)應(yīng)的水費(fèi)、電費(fèi)記錄。賬單生成時(shí)機(jī)有兩種做法一種是在合同生效時(shí)一次性生成整個(gè)租期的所有賬單另一種是每個(gè)月生成當(dāng)月的。我推薦后者因?yàn)楹贤赡苤型咀兏⑼俗饣蛘哒{(diào)價(jià)一次性生成一堆賬單后面要維護(hù)很麻煩。每個(gè)月由管理員點(diǎn)一下“生成本月賬單”或者配置一個(gè)定時(shí)任務(wù)每天跑一次檢查本月是否已生成就能覆蓋絕大多數(shù)場(chǎng)景。SQL里用唯一索引unique key uk_contract_period_type(contract_id, period, charge_type)來(lái)保證一筆費(fèi)用在一個(gè)賬期內(nèi)只能有一條記錄這是數(shù)據(jù)庫(kù)層面的兜底。收款核銷(xiāo)的流程是管理員看到某條賬單狀態(tài)未繳點(diǎn)擊“收款”彈出收款窗口記錄收款方式、實(shí)收金額允許部分減免并把減免原因?qū)懭雮渥?、收款時(shí)間然后更新賬單狀態(tài)為已繳。這里業(yè)務(wù)上有個(gè)處理要點(diǎn)拖欠繳費(fèi)時(shí)要不要自動(dòng)產(chǎn)生滯納金。社區(qū)商鋪通常按合同約定滯納金比例比如每日千分之三但計(jì)算起來(lái)比較復(fù)雜我的做法是在退租結(jié)算時(shí)統(tǒng)一計(jì)算未繳賬單的滯納金日常不自動(dòng)累加這樣既滿足業(yè)務(wù)預(yù)期又減少了定時(shí)計(jì)算的負(fù)擔(dān)。3.4 統(tǒng)計(jì)報(bào)表與可視化統(tǒng)計(jì)報(bào)表是答辯時(shí)的加分項(xiàng)我建議至少做三個(gè)視圖。出租率趨勢(shì)圖按月統(tǒng)計(jì)每個(gè)月的出租商鋪數(shù)除以總商鋪數(shù)前端用ECharts畫(huà)折線圖。SQL上可以從合同表里查出每個(gè)月份有效合同的數(shù)量也可以用商鋪表中status等于已租的數(shù)量除以總數(shù)。前一種能看歷史趨勢(shì)后一種只能看當(dāng)前快照答辯展示建議用前一種更有“數(shù)據(jù)感”。月度收入趨勢(shì)圖按實(shí)收月份統(tǒng)計(jì)charge_record里status等于已繳的賬單金額合計(jì)能看出收租高峰和低谷。這里注意統(tǒng)計(jì)口徑是“實(shí)收月份”而不是“賬期月份”因?yàn)榭赡艽嬖谘a(bǔ)繳以前月份款項(xiàng)的情況兩個(gè)口徑的差別在答辯時(shí)講清楚了老師會(huì)認(rèn)可你對(duì)統(tǒng)計(jì)口徑的敏感度。費(fèi)用類型占比餅圖統(tǒng)計(jì)一個(gè)時(shí)間段內(nèi)租金、物業(yè)費(fèi)、水電費(fèi)各自的總額和占比。這個(gè)圖最直觀非技術(shù)背景的人也能一眼看懂。做報(bào)表時(shí)SQL層建議直接寫(xiě)分組統(tǒng)計(jì)不要先把明細(xì)查出來(lái)然后在Java里for循環(huán)累加。雖然對(duì)數(shù)據(jù)量小的畢設(shè)來(lái)說(shuō)性能差別不大但寫(xiě)一手“用SQL完成聚合”的代碼在答辯時(shí)更顯得有工程素養(yǎng)。例如當(dāng)月收入統(tǒng)計(jì)select DATE_FORMAT(pay_date, %Y-%m) as month, sum(amount) as total_income from charge_record where status 1 and pay_date is not null group by DATE_FORMAT(pay_date, %Y-%m) order by month desc;4. 實(shí)操過(guò)程與部署實(shí)施4.1 從零初始化項(xiàng)目的環(huán)境準(zhǔn)備在開(kāi)始寫(xiě)代碼前先把環(huán)境準(zhǔn)備好我列一個(gè)清單JDK 1.8或11我用1.8兼容性最好Maven 3.6以上MySQL 5.7或8.0我用5.7如果8.0要注意驅(qū)動(dòng)和時(shí)區(qū)配置Node.js 14以上IDEA、Navicat或DBeaver數(shù)據(jù)庫(kù)初始化時(shí)建一個(gè)名為community_shop的庫(kù)字符集選utf8mb4。然后執(zhí)行建表腳本我習(xí)慣把所有建表語(yǔ)句放進(jìn)一個(gè)schema.sql里統(tǒng)一管理再插一批測(cè)試數(shù)據(jù)。測(cè)試數(shù)據(jù)很重要不只是為了演示效果更是為了讓自己在開(kāi)發(fā)階段就面對(duì)真實(shí)數(shù)據(jù)形態(tài)比如有的商鋪已經(jīng)出租多年、有的上個(gè)月剛退租、有些賬單逾期未繳這樣頁(yè)面上的圖表才不會(huì)顯得空洞。后端項(xiàng)目用Spring Initializr生成引入spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、lombok、spring-boot-starter-validation、jjwt這些依賴。application.yml里配置數(shù)據(jù)源、MyBatis-Plus的mapper掃描、JWT密鑰和過(guò)期時(shí)間。注意數(shù)據(jù)庫(kù)連接URL里一定要帶serverTimezoneAsia/Shanghai否則連上庫(kù)以后時(shí)間字段會(huì)差8個(gè)小時(shí)這是個(gè)特別經(jīng)典的坑。配置示例spring: datasource: url: jdbc:mysql://localhost:3306/community_shop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl jwt: secret: your-secret-key expire-hours: 244.2 核心接口與前后端聯(lián)調(diào)后端接口統(tǒng)一以/api開(kāi)頭通過(guò)JWT攔截器做認(rèn)證。登錄接口、查詢商鋪列表接口、生成賬單接口分別對(duì)應(yīng)不同的權(quán)限點(diǎn)。我把主要接口列出來(lái)商鋪相關(guān)GET /api/shop/page、POST /api/shop、PUT /api/shop、DELETE /api/shop/{id}、PUT /api/shop/{id}/status租戶相關(guān)GET /api/tenant/page、POST /api/tenant、PUT /api/tenant合同相關(guān)POST /api/contract/sign、GET /api/contract/page、PUT /api/contract/renew、PUT /api/contract/terminate收費(fèi)相關(guān)POST /api/charge/generateMonthly、GET /api/charge/page、POST /api/charge/pay/{id}統(tǒng)計(jì)相關(guān)GET /api/stats/rentRate、GET /api/stats/monthlyIncome、GET /api/stats/incomeType前后端聯(lián)調(diào)時(shí)的重點(diǎn)在Token傳遞。前端Axios的request攔截器里加Authorization頭response攔截器里對(duì)code等于401的統(tǒng)一跳轉(zhuǎn)登錄頁(yè)。一開(kāi)始最容易出的問(wèn)題是跨域開(kāi)發(fā)環(huán)境我是在Vite的配置里加了proxy把/api前綴的請(qǐng)求代理到http://localhost:8080這樣瀏覽器的請(qǐng)求是同源的就不需要后端額外開(kāi)放CORS。這個(gè)方法推薦優(yōu)先用比在Spring里配CrossOrigin更干凈也不會(huì)影響生產(chǎn)部署。另外“導(dǎo)出Excel”功能我強(qiáng)烈建議做進(jìn)去。社區(qū)商鋪管理方經(jīng)常要把賬單表發(fā)出去對(duì)賬一個(gè)導(dǎo)出按鈕能生成Excel文件在畢設(shè)里既是一個(gè)實(shí)用亮點(diǎn)又能在答辯時(shí)現(xiàn)場(chǎng)演示成本卻很低。做法是后端引入easypoi依賴把查出來(lái)的收費(fèi)記錄列表直接轉(zhuǎn)成Excel響應(yīng)給前端前端用window.open下載或者axios接收blob。這個(gè)功能屬于“投入小、產(chǎn)出高”的典型代表。4.3 本地運(yùn)行與打包上線的完整流程整個(gè)項(xiàng)目在本地跑起來(lái)只需要幾步啟動(dòng)MySQL執(zhí)行初始化腳本導(dǎo)入表結(jié)構(gòu)和測(cè)試數(shù)據(jù)修改application.yml里的數(shù)據(jù)庫(kù)賬號(hào)密碼在IDEA里啟動(dòng)Spring Boot后端看到Started日志說(shuō)明成功默認(rèn)端口8080在前端目錄執(zhí)行npm install安裝依賴再npm run devVite默認(rèn)5173端口會(huì)打開(kāi)登錄頁(yè)面用管理員賬號(hào)登錄進(jìn)入后逐個(gè)模塊點(diǎn)一遍確認(rèn)接口和數(shù)據(jù)都正常。如果要把項(xiàng)目打包部署到服務(wù)器后端執(zhí)行mvn package會(huì)生成一個(gè)可執(zhí)行的jar包把jar、MySQL、前端構(gòu)建產(chǎn)物放到同一臺(tái)服務(wù)器上前端執(zhí)行npm run build產(chǎn)物是一個(gè)dist目錄用Nginx做靜態(tài)托管并把/api路徑反向代理到Spring Boot的8080端口。Nginx配置里server_name、root、location /api的proxy_pass是關(guān)鍵。前端路由如果是history模式需要在Nginx里配置try_files否則刷新頁(yè)面就會(huì)404這個(gè)坑幾乎每個(gè)第一次部署前后端分離項(xiàng)目的人都會(huì)遇到一次。Nginx核心配置server { listen 80; server_name your-domain.com; root /opt/community-shop/dist; index 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; } location / { try_files $uri $uri/ /index.html; } }5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 高頻問(wèn)題速查表我在開(kāi)發(fā)和帶學(xué)生做類似項(xiàng)目時(shí)遇到最多的問(wèn)題基本集中在下面這些整理成一張速查表現(xiàn)象可能原因解決方案登錄頁(yè)驗(yàn)證碼不顯示或接口404后端沒(méi)啟動(dòng)或路徑不對(duì)檢查控制臺(tái)日志確認(rèn)端口和上下文路徑前端請(qǐng)求接口報(bào)CORS錯(cuò)誤未配置代理或未開(kāi)CORS用Vite proxy代理或后端配置跨域過(guò)濾器數(shù)據(jù)庫(kù)時(shí)間差8小時(shí)連接URL缺serverTimezone加上serverTimezoneAsia/Shanghai中文亂碼連接字符集不是utf8mb4連接URL加characterEncodingutf8庫(kù)表字符集統(tǒng)一npm install卡住依賴源太慢設(shè)置鏡像源加速賬單生成重復(fù)唯一索引沒(méi)建對(duì)contract_id、period、charge_type加唯一索引刷新頁(yè)面404前端history路由部署未配置Nginx加try_files規(guī)則導(dǎo)出Excel中文亂碼瀏覽器下載文件名編碼文件名URLEncode后放入Content-Disposition修改數(shù)據(jù)后列表不刷新前端緩存或查詢條件問(wèn)題檢查分頁(yè)參數(shù)和響應(yīng)數(shù)據(jù)清緩存調(diào)試5.2 答辯時(shí)如何把項(xiàng)目講清楚畢業(yè)設(shè)計(jì)的最后一道坎是答辯。很多同學(xué)代碼寫(xiě)完了但講的時(shí)候只能念PPT很吃虧。我給幾個(gè)建議一定要準(zhǔn)備一張“業(yè)務(wù)流程圖”或者“系統(tǒng)功能結(jié)構(gòu)圖”答辯時(shí)第一件事就是講清楚這個(gè)系統(tǒng)是給誰(shuí)用的、解決什么問(wèn)題然后順著“商鋪建檔→簽約→月度收費(fèi)→退租結(jié)算”這條主線講把每個(gè)環(huán)節(jié)對(duì)應(yīng)的頁(yè)面和接口指出來(lái)。時(shí)間控制在5分鐘內(nèi)重點(diǎn)是體現(xiàn)出你理解業(yè)務(wù)而不是只會(huì)敲代碼。準(zhǔn)備幾個(gè)“為什么”答案。老師必問(wèn)的問(wèn)題包括為什么選MySQL不選Oracle為什么金額用decimal為什么不用物理外鍵合同到期提醒是怎么做的權(quán)限控制怎么實(shí)現(xiàn)的這些問(wèn)題我在前面的內(nèi)容里都已經(jīng)把答案給了關(guān)鍵是要自己理解、用自己的話表達(dá)出來(lái)。還有一個(gè)小技巧答辯演示時(shí)準(zhǔn)備好一組完整的演示數(shù)據(jù)包括一間已經(jīng)出租一年多的商鋪、一間空鋪、幾條不同狀態(tài)的電費(fèi)賬單以及兩個(gè)不同權(quán)限的賬號(hào)。演示時(shí)先講普通操作再登錄管理員賬號(hào)演示權(quán)限差異最后打開(kāi)統(tǒng)計(jì)圖表頁(yè)面連續(xù)操作下來(lái)整套系統(tǒng)給人的印象就是完整、有深度的而不是只做了個(gè)登錄加兩個(gè)列表的“半成品”。5.3 實(shí)際開(kāi)發(fā)過(guò)程中我踩過(guò)的三個(gè)坑第一個(gè)坑是分頁(yè)插件導(dǎo)致的SQL異常。MyBatis-Plus的分頁(yè)查詢需要單獨(dú)配置一個(gè)分頁(yè)攔截器如果沒(méi)配置Page對(duì)象返回的total始終為0你以為是自己代碼寫(xiě)錯(cuò)了其實(shí)只是少裝了一個(gè)插件。第二個(gè)坑是Element UI的日期選擇器返回的格式問(wèn)題如果你把DatePicker的value-format不設(shè)成yyyy-MM-dd前端拿到的就是標(biāo)準(zhǔn)時(shí)間對(duì)象直接傳給后端會(huì)導(dǎo)致格式解析報(bào)錯(cuò)。第三個(gè)坑是權(quán)限框架的引入順序一旦引入攔截器后發(fā)現(xiàn)靜態(tài)資源和登錄接口都被攔截排查的思路是先看攔截器排除列表是否包含了登錄接口和靜態(tài)路徑這個(gè)順序問(wèn)題我在做項(xiàng)目時(shí)調(diào)試了整整一個(gè)下午。排查思路分享遇到問(wèn)題不要慌先看后端日志再看瀏覽器Network面板。后端日志定位是參數(shù)問(wèn)題、SQL問(wèn)題還是業(yè)務(wù)異常Network面板確認(rèn)請(qǐng)求有沒(méi)有發(fā)出去、響應(yīng)碼是多少、返回體是什么。90%的聯(lián)調(diào)問(wèn)題靠這兩個(gè)工具就能解決。6. 經(jīng)驗(yàn)總結(jié)與擴(kuò)展建議整套系統(tǒng)做下來(lái)我個(gè)人最大的體會(huì)是畢設(shè)項(xiàng)目的難不在“代碼量”而在“業(yè)務(wù)想清楚”。社區(qū)商鋪管理系統(tǒng)的每個(gè)模塊單看都不難但把它們連成一條完整的業(yè)務(wù)鏈讓數(shù)據(jù)在商鋪、租戶、合同、賬單之間保持一致這個(gè)過(guò)程才是真正的鍛煉。如果這個(gè)項(xiàng)目你想繼續(xù)擴(kuò)展方向其實(shí)很多??梢约右粋€(gè)租戶端小程序讓租戶在手機(jī)上查看賬單并在線繳費(fèi)、提交報(bào)修申請(qǐng)可以在收費(fèi)模塊里加入對(duì)接微信支付或支付寶的沙箱環(huán)境做成在線支付閉環(huán)也可以加入更細(xì)的權(quán)限粒度比如每個(gè)管理員只能管理自己負(fù)責(zé)的樓棟。想走數(shù)據(jù)分析方向的話還可以把出租率、回款率、空置率做成大屏看板展示效果會(huì)更有沖擊力。最后分享一個(gè)做畢設(shè)時(shí)很實(shí)用的小習(xí)慣每寫(xiě)完一個(gè)模塊就寫(xiě)一小段測(cè)試代碼或者用Postman把接口驗(yàn)證一遍并把驗(yàn)證記錄貼在項(xiàng)目的README里。這樣到寫(xiě)論文的時(shí)候“系統(tǒng)測(cè)試”這一章的內(nèi)容自然就有了不用最后幾天熬夜補(bǔ)材料。希望這篇文章能幫你少走一些彎路。