管理微信小程序畢設(shè)源碼:從數(shù)據(jù)庫到支付回調(diào)的完整閉環(huán))
簡介校園水電費(fèi)管理微信小程序畢業(yè)設(shè)計源碼基于Java技術(shù)棧開發(fā)適合計算機(jī)、電子信息工程、數(shù)學(xué)等專業(yè)正在準(zhǔn)備畢業(yè)設(shè)計或課程作業(yè)的學(xué)生。資源包共包含1072個文件既有Java后端服務(wù)、Vue管理端頁面、微信小程序wxml/wxss頁面、JavaScript交互邏輯也有JSON/XML配置文件、SQL數(shù)據(jù)庫腳本以及png、svg、jpg等前端圖標(biāo)與素材壓縮包大小16.56MB整體結(jié)構(gòu)緊湊便于分類學(xué)習(xí)。項目代碼經(jīng)導(dǎo)師指導(dǎo)并調(diào)試通過自帶安裝運(yùn)行批處理與工程配置可快速啟動項目便于理解水電費(fèi)賬單管理、用戶繳費(fèi)、用能統(tǒng)計等核心模塊的實現(xiàn)思路也能覆蓋課程設(shè)計和期末大作業(yè)的實戰(zhàn)場景。目前已有230人學(xué)習(xí)下載對于需要參考高分畢設(shè)項目、二次開發(fā)或項目實戰(zhàn)練手的學(xué)生都是一份實用價值較高的完整源碼包可輔助快速完成畢業(yè)設(shè)計。1. 校園水電費(fèi)管理微信小程序源碼為什么我建議你把它當(dāng)作第一個「完整閉環(huán)」畢設(shè)項目每年畢設(shè)季總有人拿著「校園水電費(fèi)管理微信小程序源碼」這個標(biāo)題來找我問的第一句通常是「這東西到底能不能跑、老師會不會一眼看出是網(wǎng)上扒的」。我的回答一般是這個題目比大多數(shù)圖書管理系統(tǒng)、商城系統(tǒng)都更能體現(xiàn)一個完整業(yè)務(wù)閉環(huán)——它同時涉及微信登錄、狀態(tài)機(jī)賬單從待繳到已繳、費(fèi)用計算規(guī)則、角色權(quán)限學(xué)生和管理員、甚至支付回調(diào)。我做過的模擬項目X功能就是抄表、計費(fèi)、催繳、繳費(fèi)、臺賬查詢五件事前后端加起來三千多行代碼卻把畢設(shè)答辯里「你講講這個復(fù)雜業(yè)務(wù)怎么設(shè)計的」這類問題全堵住了。如果你正在找能落地、邏輯不單薄、工作量看得見的項目這確實是個值得投入的方向。2. 把這個源碼跑起來之前技術(shù)選型和項目骨架2.1 為什么小程序端用原生、后端用 Spring Boot 是校園場景最不容易翻車的組合常見做法是前端用微信小程序原生框架后端用 Spring Boot 加 MySQL。有人會覺得原生小程序?qū)懫饋聿蝗?uni-app 順手但校園水電費(fèi)這種場景頁面主要是列表、表單、賬單詳情和圖表原生小程序完全夠用而且少了 H5 轉(zhuǎn)小程序那一層兼容問題。后端選 Spring Boot 的原因也很直接你隨便找一個室友給他看application.yml里的數(shù)據(jù)庫賬號和端口配置他都能幫你把環(huán)境搭起來而如果選了 Node.js 或 Go很多同學(xué)對依賴管理不熟春節(jié)期間環(huán)境裝不上就卡住了。我一般會建議后端用 MVC 分層controller 只做參數(shù)接收和返回service 寫計費(fèi)邏輯mapper 操作數(shù)據(jù)庫。這樣做的好處是如果你從網(wǎng)上找到的這份校園水電費(fèi)管理微信小程序源碼里 service 層寫得特別薄、所有 SQL 都堆在 controller 里你一眼就能看出來這項目質(zhì)量不高不值得當(dāng)?shù)鬃痈摹?.2 拿到一份源碼后先看哪幾個目錄和文件我拿到一份校園水電費(fèi)管理微信小程序源碼第一件事不是急著放進(jìn)微信開發(fā)者工具而是先掃目錄結(jié)構(gòu)。正常情況下應(yīng)該是這樣wechat-mini-program/ ├── pages/ │ ├── login/ # 登錄頁 │ ├── index/ # 首頁我的房間、本月用量 │ ├── bill/ # 賬單列表與詳情 │ ├── payment/ # 收銀臺 │ └── admin/ # 管理員抄表錄入、價格設(shè)置 ├── utils/ │ ├── request.js # 封裝 wx.request統(tǒng)一帶 token │ └── format.js # 金額、日期格式化 ├── app.js ├── app.json └── project.config.json同時后端源碼應(yīng)該是這種姿勢backend/ ├── src/main/java/com/xxx/ │ ├── controller/ │ ├── service/ │ ├── mapper/ │ ├── entity/ │ └── config/ ├── src/main/resources/ │ ├── mapper/*.xml # SQL │ └── application.yml └── pom.xml先看application.yml里的數(shù)據(jù)庫連接是不是寫死的、有沒有用jdbc:mysql://localhost:3306/dormitory這種可改的寫法——去平臺化的穩(wěn)定配置是你能不能本地跑起來的前提。再看app.js里的globalData.apiBaseUrl小程序端所有請求都走這個公共地址本地調(diào)試時改成http://localhost:8080。如果一份源碼這兩個文件都寫死了某個公網(wǎng)地址我勸你立刻關(guān)掉因為你不知道它連的是誰的數(shù)據(jù)庫。2.3 最小啟動步驟兩個小時內(nèi)在本地把它跑通我按自己跑通某水電費(fèi)項目源碼的順序給你一套可復(fù)現(xiàn)的步驟# 1. 創(chuàng)建數(shù)據(jù)庫并導(dǎo)入 mysql -u root -p -e CREATE DATABASE dormitory DEFAULT CHARSET utf8mb4; mysql -u root -p dormitory sql/init.sql # 2. 修改后端配置 cd backend vim src/main/resources/application.yml # 改 spring.datasource.username / password / url # 3. 啟動后端 mvn spring-boot:run # 4. 確認(rèn)后端起來 curl http://localhost:8080/api/health關(guān)鍵參數(shù)就三個數(shù)據(jù)庫名、端口默認(rèn) 8080微信開發(fā)者工具不校驗這個、以及小程序project.config.json里的appid測試號可以填touristappid。跑起來后用微信開發(fā)者工具打開小程序根目錄在utils/request.js里把 baseURL 指到http://localhost:8080然后在模擬器里點登錄——能拿到 openid 且首頁不白屏說明這套源碼的基礎(chǔ)鏈路是通的。# 如果登錄報錯先看后端日志有沒有這句話 # code2session err → 說明 appid 或 secret 不對注意真機(jī)預(yù)覽時localhost指向的是你手機(jī)自己不是電腦。要做真機(jī)聯(lián)調(diào)把 baseURL 改成電腦的局域網(wǎng) IP并且微信公眾平臺里要把該 IP 加入 request 合法域名調(diào)試階段可勾選「不校驗合法域名」。3. 別急著寫界面計費(fèi)模型和數(shù)據(jù)庫表設(shè)計決定你答辯時有沒有底氣3.1 三張核心表房間表、抄表記錄表、賬單表我在做某跨平臺系統(tǒng)的水電費(fèi)模塊時第一版表結(jié)構(gòu)設(shè)計很簡單——一張user表、一張record表結(jié)果項目走到一半就卡住了損耗分?jǐn)倹]法算、階梯計費(fèi)對不上、同一個房間一個月產(chǎn)生了兩條賬單。后來重構(gòu)成三張表加一張日志表才順過來。CREATE TABLE room ( id int NOT NULL AUTO_INCREMENT, building varchar(32) NOT NULL COMMENT 樓棟號, room_number varchar(32) NOT NULL COMMENT 房間號, capacity int DEFAULT 4 COMMENT 標(biāo)準(zhǔn)入住人數(shù), area decimal(6,2) DEFAULT 0 COMMENT 面積用于分?jǐn)? PRIMARY KEY (id), UNIQUE KEY uk_building_room (building,room_number) ) ENGINEInnoDB COMMENT房間表; CREATE TABLE meter_record ( id bigint NOT NULL AUTO_INCREMENT, room_id int NOT NULL, period char(7) NOT NULL COMMENT 賬期如 2025-06, type tinyint NOT NULL COMMENT 1電 2水, last_value decimal(10,2) NOT NULL COMMENT 上次表底, current_value decimal(10,2) NOT NULL COMMENT 本次表底, read_time datetime NOT NULL, operator_id bigint DEFAULT NULL COMMENT 抄表管理員, PRIMARY KEY (id), UNIQUE KEY uk_room_period_type (room_id,period,type) ) ENGINEInnoDB COMMENT抄表記錄表; CREATE TABLE bill ( id bigint NOT NULL AUTO_INCREMENT, room_id int NOT NULL, period char(7) NOT NULL, bill_no varchar(40) NOT NULL, total_amount int NOT NULL COMMENT 總金額單位分, status tinyint NOT NULL DEFAULT 0 COMMENT 0待繳 1已繳 2作廢, created_at datetime DEFAULT CURRENT_TIMESTAMP, paid_at datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_bill_no (bill_no), KEY idx_room_period (room_id,period) ) ENGINEInnoDB COMMENT賬單表;這里最值得你注意的就是UNIQUE KEY uk_room_period_type。沒有這個唯一約束兩個管理員同一天對同一房間重復(fù)錄入抄表數(shù)系統(tǒng)就會生成兩張賬單學(xué)生對賬時必然翻車。我在模擬項目X里就被這個坑絆過一次后來加了唯一索引重復(fù)錄入直接報錯才把數(shù)據(jù)可靠性救回來。3.2 階梯計費(fèi)和損耗分?jǐn)們蓚€必調(diào)的參數(shù)水電費(fèi)看著簡單真去算時發(fā)現(xiàn)全是細(xì)節(jié)。以電費(fèi)為例校園宿舍常見的規(guī)則是每月每房間基礎(chǔ)額度 20 度超過部分按階梯計價。我把計費(fèi)邏輯寫成一個獨立 service 方法public int calcElectricFee(BigDecimal usedAmount, FeeRate rate) { // 單位分 int total 0; BigDecimal remain usedAmount; for (LadderItem item : rate.getLadderList()) { if (remain.compareTo(item.getThreshold()) 0) { total item.getThreshold().multiply(item.getPrice()) .setScale(0, RoundingMode.HALF_UP).intValue(); remain remain.subtract(item.getThreshold()); } else { total remain.multiply(item.getPrice()) .setScale(0, RoundingMode.HALF_UP).intValue(); break; } } return total; }計費(fèi)參數(shù)我建議做成數(shù)據(jù)庫表而不是寫死在代碼里字段包括ladder_type電/水、threshold檔位閾值、price單價、effective_date生效日期。理由很實際你答辯時老師大概率會問「如果明年電費(fèi)調(diào)整了怎么辦」你直接演示改數(shù)據(jù)庫不改代碼比解釋「我硬編碼了」有說服力得多。損耗分?jǐn)偸橇硪粋€容易做錯的地方。我的方案是公攤電 總表數(shù) - 各分表數(shù)之和然后按每個房間用電量占比分?jǐn)?。注意這里要用「用電量占比」而不是「按人頭均攤」因為用電多的房間多攤、用電少的少攤學(xué)生才不會有意見。公式是room_share public_loss * room_usage / total_usage這個除法會產(chǎn)生除不盡的小數(shù)所以最后要保證sum(room_share) public_loss做法是最后一個房間的份額用總損耗減去前面所有房間的份額而不是自己再算一遍。3.3 金額字段用「分」還是「元」這個選擇影響整個支付模塊新手拿到源碼后最喜歡把total_amount設(shè)計成decimal(10,2)存元因為 SQL 里看起來直觀。但只要你接支付哪怕只是模擬支付我強(qiáng)烈建議你存「分」理由有兩個一是金額計算過程全是乘法除法用元會出現(xiàn)0.1 * 3 0.30000000000000004這種浮點誤差用整型分永遠(yuǎn)不會二是支付回調(diào)里平臺返回的金額單位就是分你后端直接比對少做一次單位轉(zhuǎn)換就少一個出錯點。上面的bill表里total_amount int NOT NULL COMMENT 總金額單位分就是這個用意。展示給用戶看時再做格式化(total_amount / 100).toFixed(2)。這條習(xí)慣我不只在畢設(shè)項目里用后來在公司做計費(fèi)系統(tǒng)也是這個規(guī)矩。4. 跑通一條完整鏈路登錄、綁定房間、抄表出賬、模擬支付4.1 從 wx.login 到 openid這條身份識別鏈路只有一種正確寫法校園水電費(fèi)小程序的登錄本質(zhì)上不是「用戶名密碼登錄」而是通過微信的wx.login拿到臨時 code再發(fā)給后端換取 openid。前端關(guān)鍵代碼// utils/request.js 中的登錄封裝 login() { return new Promise((resolve, reject) { wx.login({ success: (res) { wx.request({ url: ${this.baseUrl}/api/login, method: POST, data: { code: res.code }, success: (res2) { const { token } res2.data.data wx.setStorageSync(token, token) resolve(token) }, fail: reject }) } }) }) }后端對應(yīng)處理邏輯PostMapping(/login) public Result login(RequestBody LoginReq req) { // 1. 調(diào)用微信 jscode2session 接口用 code 換 openid String openid wechatClient.code2Session(req.getCode()); // 2. 查 user 表不存在則創(chuàng)建新用戶 User user userMapper.selectByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); userMapper.insert(user); } // 3. 生成自己的 tokenUUID 即可并返回 String token UUID.randomUUID().toString().replace(-, ); userMapper.saveToken(user.getId(), token); return Result.ok(token); }這里有一個代碼邏輯要注意jscode2session是后端去調(diào)微信接口不是你前端直接調(diào)。原因是這個接口需要appSecret而appSecret放在小程序前端就等于公開了任何人都能冒充你的后端。很多畢設(shè)源碼圖省事讓前端直接拿 code 換 openid這種代碼答辯時被老師追問「密鑰安全怎么保證」就會卡殼。你自己改的時候務(wù)必把a(bǔ)ppSecret留在后端配置文件里。4.2 綁定房間與身份校驗一個「入住碼」就能解決的取舍登錄只解決了「你是誰微信號」還沒解決「你住哪個房間」。校園場景里學(xué)生和房間的綁定不能亂來——如果隨便填個房間號就綁成功那任何人都能看別人的賬單。常見做法有兩種預(yù)置名單校驗或者入住碼綁定。我更喜歡入住碼方案因為邏輯簡單且演示效果好管理員在后臺給某房間生成一個 6 位入住碼學(xué)生在小程序里輸入房間號和入住碼完成綁定。后端代碼PostMapping(/bindRoom) public Result bindRoom(RequestParam String roomNo, RequestParam String joinCode, RequestParam Long userId) { Room room roomMapper.selectByRoomNo(roomNo); if (room null || !room.getJoinCode().equals(joinCode)) { return Result.fail(房間號或入住碼錯誤); } // 解綁舊綁定再綁新房 userMapper.updateRoom(userId, room.getId()); return Result.ok(); }這個方案比「上傳名單核對學(xué)號姓名」少一步導(dǎo)入操作也比「不校驗直接綁」顯得有設(shè)計感。你在答辯時可以說入住碼由管理員生成離校后解綁即作廢能防止學(xué)生串房。4.3 沒有商戶號也能演示支付模擬支付回調(diào)的正確姿勢校園開發(fā)者最現(xiàn)實的問題個人小程序沒有微信支付權(quán)限拿不到商戶號。很多人的處理辦法是把支付按鈕做成「假裝支付成功」賬單狀態(tài)直接改成已繳。這個做法在答辯時只要老師細(xì)看就會覺得項目「黑匣子」——你解釋不清錢去哪了。我的建議是把支付流程做成兩層模擬支付一層真實微信支付留好接口。后端收銀臺代碼這樣寫// 發(fā)起支付創(chuàng)建支付單狀態(tài)為待支付 public PayOrder createPayOrder(Long billId) { PayOrder order new PayOrder(); order.setBillId(billId); order.setOrderNo(generateOrderNo()); order.setStatus(WAIT_PAY); payOrderMapper.insert(order); return order; } // 模擬支付回調(diào)不調(diào)微信直接標(biāo)記支付成功 PostMapping(/mockPayNotify) public Result mockPayNotify(RequestParam String orderNo) { PayOrder order payOrderMapper.selectByOrderNo(orderNo); if (order null || !WAIT_PAY.equals(order.getStatus())) { return Result.fail(訂單不存在或已處理); } // 冪等重復(fù)回調(diào)不重復(fù)修改 order.setStatus(PAID); order.setPaidAt(new Date()); payOrderMapper.update(order); // 賬單同步更新 billMapper.markPaid(order.getBillId()); return Result.ok(); }真實微信支付時你只需要把mockPayNotify替換成微信的支付回調(diào)地址并把payOrderMapper里的狀態(tài)更新邏輯換成微信支付結(jié)果驗簽其他部分完全不用動。這個「接口留白」的做法比把代碼寫死強(qiáng)得多。5. 校園水電費(fèi)系統(tǒng)最常見的 5 個坑現(xiàn)象、原因、解決5.1 換設(shè)備登錄后看到的是別人的賬單現(xiàn)象學(xué)生換手機(jī)或者重新授權(quán)登錄后首頁顯示的不是自己房間的賬單而是另一個房間的數(shù)據(jù)。原因user表里沒有把openid和room_id的對應(yīng)關(guān)系存好。很多源碼是「登錄時創(chuàng)建一個新用戶綁定房間時再創(chuàng)建一個新用戶」導(dǎo)致兩次登錄生成兩條記錄綁定房間只寫到了第二條記錄上。解決user表設(shè)計為openid唯一綁定房間只做UPDATE user SET room_idxx WHERE openidyy不要走「新用戶插入」邏輯。5.2 階梯電費(fèi)算出一個無限循環(huán)小數(shù)現(xiàn)象賬單金額莫名其妙多出一分錢或者階梯計算后金額對不上。原因用double或float存儲并計算單價二進(jìn)制無法精確表示 0.1。解決金額全部用分int存儲Java 里用BigDecimal計算最后setScale(0, HALF_UP)。水電表底數(shù)可以保留兩位小數(shù)但只作為表底存儲單價乘完的結(jié)果立刻轉(zhuǎn)成整數(shù)分。5.3 賬期和費(fèi)率生效日期錯位現(xiàn)象6 月的賬單用了 7 月 1 日才生效的新電價。原因生成賬單時代碼里直接查SELECT * FROM fee_rate取第一條而這條記錄恰好是后來修改過的。解決fee_rate表增加effective_date字段生成賬單時按WHERE effective_date 賬期最后一天 ORDER BY effective_date DESC LIMIT 1取費(fèi)率。賬期是 6 月就用 6 月底之前最后生效的費(fèi)率而不是當(dāng)前最新費(fèi)率。5.4 學(xué)生雙擊支付按鈕生成了兩張重復(fù)賬單現(xiàn)象學(xué)生點了一次繳費(fèi)沒反應(yīng)又點了一次結(jié)果支付列表里出現(xiàn)兩條一模一樣的賬單。原因支付創(chuàng)建接口沒有做冪等控制兩次請求各生成一個訂單。解決兩個層面兜底。數(shù)據(jù)庫層面pay_order表的bill_id加唯一索引同一個賬單只能有一條未支付訂單。代碼層面創(chuàng)建訂單前先查SELECT * FROM pay_order WHERE bill_id ? AND status WAIT_PAY存在就直接返回舊訂單號。5.5 本地能跑真機(jī)一調(diào)用就連接失敗現(xiàn)象在微信開發(fā)者工具里一切正常換成手機(jī)預(yù)覽就請求失敗。原因開發(fā)者工具里localhost能通是因為工具和電腦在同一臺機(jī)器上真機(jī)上localhost指向手機(jī)自身請求自然到達(dá)不了電腦。解決調(diào)試階段在request.js里把 baseURL 換成電腦的局域網(wǎng) IP同時微信開發(fā)者工具勾選「不校驗合法域名」。如果換了 IP 仍不行檢查 Windows 防火墻有沒有放行 8080 端口。這個坑我?guī)缀趺孔鲆粋€項目都要栽一次所以現(xiàn)在已經(jīng)養(yǎng)成習(xí)慣項目根目錄放一個config.js第一行就是環(huán)境注釋。6. 讓這個項目從「能跑」變成「能打」批量抄表和欠費(fèi)提醒6.1 管理端抄表脫離手工錄入Excel 批量導(dǎo)入怎么做畢設(shè)里演示抄表逐條手輸 6 個房間還能忍輸 60 個房間時演示就有點尷尬。給管理端加一個批量導(dǎo)入導(dǎo)出的功能屬于性價比很高的進(jìn)階點。常見做法是后端用 Apache POI 讀取 Excel核心邏輯如下public ImportResult importExcel(MultipartFile file) { Workbook wb WorkbookFactory.create(file.getInputStream()); Sheet sheet wb.getSheetAt(0); ListMeterRecord list new ArrayList(); for (Row row : sheet) { if (row.getRowNum() 0) continue; // 跳過表頭 String building row.getCell(0).getStringCellValue(); String roomNo row.getCell(1).getStringCellValue(); BigDecimal currentValue BigDecimal.valueOf(row.getCell(2).getNumericCellValue()); // 查找房間 id沒有則報錯 Room room roomMapper.selectByBuildingAndNo(building, roomNo); if (room null) { importResult.addError(row.getRowNum(), 房間不存在); continue; } list.add(buildRecord(room.getId(), currentValue)); } // 批量插入依賴唯一索引跳過重復(fù)賬期 meterRecordMapper.batchInsertIgnore(list); return importResult; }參數(shù)說明batchInsertIgnore對應(yīng) SQL 里的INSERT IGNORE INTO配合表結(jié)構(gòu)里uk_room_period_type唯一索引重復(fù)賬期會靜默跳過而不是報錯中斷。在 Excel 表里增加「上次表底」列并在導(dǎo)入時和庫里比對如果本次表底小于上次表底就判定為錄入異?!@是實際使用中很容易被忽略的校驗。6.2 欠費(fèi)主動推送把消息訂閱加進(jìn)來賬單生成后沒人看是常態(tài)要做成一套能用的欠費(fèi)提醒系統(tǒng)可以在用戶端加一個「訂閱消息」入口。微信小程序的訂閱消息需要用戶主動點擊授權(quán)所以常規(guī)做法是用戶繳費(fèi)成功后彈出「允許發(fā)送賬單提醒」然后后端在每月出賬后調(diào)用subscribeMessage.send推送模板消息。這里有個反直覺點微信的訂閱消息是「一次性」的用戶授權(quán)一次只能收到一條推送不是永久的。所以你的授權(quán)入口要設(shè)計在「繳費(fèi)成功」這個用戶粘性最高的環(huán)節(jié)讓他每次繳費(fèi)都順便訂閱下個月的賬單提醒而不是在登錄時誘導(dǎo)授權(quán)。6.3 月度賬單統(tǒng)計一條 SQL 看出整個系統(tǒng)有沒有做對你寫完計費(fèi)邏輯后可以用一條 SQL 自檢SELECT COUNT(*) AS total_bills, SUM(CASE WHEN status 1 THEN 1 ELSE 0 END) AS paid_bills, ROUND(SUM(CASE WHEN status 1 THEN 1 ELSE 0 END) / COUNT(*), 4) AS paid_rate, SUM(total_amount) AS should_receive, SUM(CASE WHEN status 1 THEN total_amount ELSE 0 END) AS real_received FROM bill WHERE period 2025-06;這條 SQL 能查出當(dāng)月應(yīng)收、實收、繳費(fèi)率三個指標(biāo)是你在答辯時展示「我做的是完整系統(tǒng)」最直接的證據(jù)。如果實收金額對不上那就是前面 5.2 的精度問題沒處理干凈。我大四那年做某圖像處理 Demo完全跳過了計費(fèi)規(guī)則直接寫界面結(jié)果導(dǎo)師一句「你這費(fèi)用用元算出現(xiàn)浮點誤差怎么辦」就把我問住了。后來重做了這套水電費(fèi)系統(tǒng)把費(fèi)率表、抄表記錄、賬單關(guān)系想清楚才真正弄明白業(yè)務(wù)系統(tǒng)應(yīng)該怎么搭。這個方向值得你投入不是因為能省事而是做完一遍你對「登錄—業(yè)務(wù)—支付—對賬」整條鏈路的理解會徹底不一樣希望幫到你。本文還有配套的精品資源點擊獲取