☰
学生公寓电费管理系统实战:微信小程序+Java+MySQL全解析
2026/10/12 2:42:05 网站建设 项目流程

每年新生入学那阵儿,宿舍楼下的充值柜台都是重灾区。排队半小时,就为了插一下电卡看看还剩几度电;宿管阿姨一边给学生操作,一边还得解释为什么这个月电费比上个月高了二十块。后来我帮朋友折腾过一套宿舍管理系统,顺手把电费这一块完全翻新了,做成了基于微信小程序的学生公寓电费管理系统——学生手机点点就能查余额、充电费、报修,管理员在后台录入读数、看欠费名单、导出账单。这套项目前后花了一个多月,包含了完整的前端小程序、后端Java接口、MySQL数据库、设计文档和答辩PPT,代码都能跑通。今天我把整个项目的拆解思路、技术选型、核心代码逻辑和调试过程中踩过的坑一次性讲清楚,特别适合正在做课程设计、毕业设计,或者想给学校后勤做一套轻量级缴费工具的同学参考。

整个系统说到底就是“一屏看全楼,一键管全楼”。学生端解决“不知道自己宿舍还剩多少电、欠费了被断电才知道”的信息差;管理端解决“抄表全靠腿、对账全靠笔、催费全靠吼”的粗放式运营。我见过太多类似的项目,要么只做了前端界面没有后端逻辑,要么把业务规则写得过于理想化,一上线就对不上账。这套系统在设计时,我始终把握一个原则:所有的钱和度数的变动都必须有记录、可追溯、能平账,这是公寓电费系统和普通demo之间最本质的区别。

1. 项目定位与需求拆解:这套系统到底在解决什么

1.1 三种角色的真实痛点和系统边界

开发这类系统之前,最先要搞清楚的不是用什么框架,而是“谁在用、他们现在怎么干活、哪里最痛”。我梳理了学生公寓电费管理链路里最典型的三个角色,并且把痛点和功能点逐一对应了起来。

  • 学生:最关心“还剩多少钱、能用几天、这个月为什么用了这么多”。痛点是得到答案的成本极高——要么跑去楼下充电卡、要么等月底公告。系统对应的功能是余额查询、充值缴费、用电明细、在线报修。
  • 宿管/后勤管理员:最关心“哪些宿舍快欠费了、电表读数怎么录、月底账能不能对上”。痛点是逐间抄表、手工记账、催费全靠贴条。系统对应的功能是宿舍电表管理、读数录入、欠费预警、账单导出。
  • 系统运维/超级管理员:最关心“单价谁定的、补助怎么发、出问题找谁”。痛点是权限边界模糊,谁都改数据,最后对不上账。系统对应的功能是角色权限控制、基础参数配置、日志审计。

这三类角色对应的界面差异很大,所以我在设计时把小程序端拆成了“普通用户端”和“管理员端”两套视角,同一套后端接口通过角色字段控制权限。很多课程设计做到最后变成“人人能登录、人人能改单价”,这在真实场景里是灾难,答辩的时候也容易被问住。所以你设计项目时,第一优先级就是把角色边界定清楚。

1.2 核心业务流程和状态流转

理解系统最好的方式是把一次完整的“电费生命周期”讲清楚:管理员在系统里录入本月初每个房间的电表读数,系统根据“本次读数 - 上次读数”算出用电量,再乘上单价,从房间余额里扣钱并生成一条用电明细;学生打开小程序看到余额被扣了,于是发起充值,充值的金额进入房间余额,同时产生一条充值订单。如果余额低于某个阈值,系统标记该房间为“欠费预警”,再低就触发“欠费断电”状态;学生充值到账后,状态自动恢复。

这个流程里有几个容易忽略的关键点。比如“预付费模式”和“后付费模式”在很多学校是混用的:有的学校是先充值后用电,余额不足直接断电;有的学校是先用后扣,月底出账单再催缴。我做的这套系统默认支持预付费模式,同时在后端留下一个模式开关,方便不同学校切换。另外,电费是跟随“房间”而不是“学生个人”的,学生搬宿舍,剩余电费要跟着房间走,这套系统的余额字段就放在宿舍房间表里,而不是用户表里。这一点你答辩的时候主动讲出来,会显得你对业务流程理解得很透。

1.3 功能清单反推项目工作量

在动手写代码前,一定要先列功能清单并给每个功能估算开发量,不然很容易后期失控。我整理这套系统的完整功能清单如下:

  • 学生端:微信登录与学号绑定、首页电费卡片、用电明细列表、在线充值(模拟支付)、余额不足提醒、房间报修、个人缴费记录。
  • 管理端:宿舍楼栋和房间管理、学生信息导入、电表读数录入(手工/批量)、欠费宿舍列表、自助充电补助发放、电费单价配置、报修工单处理、账单明细导出。
  • 公共模块:手机号绑定校验、订单幂等校验、数据库自动备份、操作日志记录、定时任务执行(每日扣费扫描、欠费预警、月度结算)。

这套清单做下来,如果是单人开发,后端大约需要三周,小程序端大约需要两周,剩下时间全部用来联调和改bug。功能看似不多,但每个都要考虑边界情况,比如“订单已经支付成功但网络断了,前端该显示什么状态”“管理员录错读数导致电费是负数怎么办”。宁可先砍掉花哨的图表和大屏展示,也要把账务流做扎实。

2. 技术选型与数据库设计:为什么是“小程序 + Java + MySQL”

2.1 技术栈选择的底层逻辑

微信小程序是当下校园类工具最合适的载体,没有之一。原因很直接:学生不用下载App,扫码或搜索就能用;微信自带身份体系,学生点一下授权,后端就能拿到OpenID作为账号标识,省去了注册、找回密码这一大堆交互。相比纯H5页面,小程序能调用微信原生的登录、支付、订阅消息能力,体验更顺滑;相比原生App,开发和分发成本低得多,后勤老师也更容易接受。

后端我用的是Java Spring Boot,因为校园里最普及的课程设计技术栈就是Java,加上Spring Boot生态成熟,接口开发效率高,一个问题搜出来社区资料也特别多。数据库选了MySQL,对于这种千万行级别都不到的数据量来说绰绰有余。可能有人会问,为什么不直接用Python Flask或者Node.js?我的回答是:能跑,但如果你要交文档、要答辩、要讲清楚设计模式,Spring Boot那套分层结构(Controller / Service / Mapper)是最标准、最容易被老师认可的思路。

前端小程序原生开发,没有引入第三方UI框架。这样做的好处是包体小、编译快、版本兼容问题少。页面上展示电费趋势图时用了简单的Canvas手绘折线,也不依赖图表库。说实话,课程设计里引入一个大型图表库徒增体积,万一版本升级接口变了,调试成本非常高。

2.2 数据库表结构:每张表为什么这么设计

数据库是这套系统的地基。很多新手一上来就建一张“用户表”加一张“电费表”,结果做到后面发现充值记录对不上账、不知道某度电是哪个时段用的。我的表结构设计遵循一个原则:谁的钱、谁的度数、什么时间、什么原因、操作人是谁——五个维度必须全部能回答。

  • 用户表:负责存学生的微信OpenID、学号、姓名、手机号、所属房间、角色标识。重点说一下,表里不要直接做学号主键,因为学生转专业、换号码的情况很常见,自增ID主键加唯一索引学号更稳妥。
  • 房间表:就是“电费钱包”。字段包括楼栋、房间号、当前余额、房间状态(正常/欠费预警/已断电)、补助阀值。房间状态和余额都冗余存一份,虽然严格来说可以实时计算,但冗余能大幅简化查询和断电判断。
  • 电表读数表:每间房每次读数的记录,字段包括房间ID、抄表日期、当前读数、上期读数、抄表方式、抄表人。这张表保留了原始的抄表痕迹,月末对不上的时候能回溯是哪次录入出了问题。
  • 充值订单表:包括订单号、用户ID、房间ID、充值金额、支付状态、创建时间、支付时间。订单号是全局唯一的业务主键,所有对账都以这个号为准。
  • 用电消费明细表:包括扣费周期、起止读数、用电量、单价、金额、扣费时间。这张表回答“我这个月为什么花了这么多钱”。
  • 报修工单表:包括报修人、宿舍号、故障描述、图片URL、状态字段、处理结果、完成时间。

核心建表SQL片段大致长这样:

CREATE TABLE room ( id BIGINT AUTO_INCREMENT PRIMARY KEY, building_no VARCHAR(20) NOT NULL, room_number VARCHAR(20) NOT NULL, current_balance DECIMAL(10,2) DEFAULT 0.00, status TINYINT DEFAULT 0 COMMENT '0-正常 1-预警 2-已断电', threshold DECIMAL(10,2) DEFAULT 5.00, UNIQUE KEY uk_building_room (building_no, room_number) ); CREATE TABLE charge_order ( id BIGINT AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(32) NOT NULL UNIQUE, user_id BIGINT NOT NULL, room_id BIGINT NOT NULL, amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT '0-待支付 1-已支付 2-已取消', create_time DATETIME NOT NULL, pay_time DATETIME );

这里有个跟直觉相反的细节:余额放在房间表而不是用户表,这是我踩过坑之后总结出来的。一开始我把余额放在用户表,结果A同学退宿、B同学入住,搞了半天还要做余额迁移;后来改成余额跟房间走,谁住在里面谁就享受这个房间的余额,逻辑瞬间就顺了。数据库的每条分表决策,本质上都是对业务规则的提前定义。

2.3 接口统一返回和鉴权约定

给小程序用的后端接口,统一返回格式非常重要。小程序端的回调写法极其依赖返回结构的一致性,如果一会儿返回JSON、一会儿直接返回字符串,前端解析代码就会变得乱七八糟。我用的统一返回结构是:

{ "code": 200, "message": "操作成功", "data": {} }

code为200表示成功,非200表示各种业务错误,前端只要判断code是否等于200即可,不需要每个接口单独写异常解析。涉及分页的接口,data里统一放total和list两个字段,这个约定写进接口文档后,前端和后端联调时几乎不用来回问。

鉴权部分,学生端采用微信登录后返回Token,后续请求在Header里带Authorization: Bearer <token>;管理员端则使用账号密码登录后同样拿Token。后端用拦截器统一校验Token,白名单接口(比如获取OpenID的登录接口)放行,其余一律拦截。这个方案实现简单,对课程设计级别的系统完全够用。

3. 核心业务逻辑与关键代码实现

3.1 微信登录与学号绑定:前后端各自该做什么

微信小程序的登录流程看似简单,但有很多实现得不对的项目。我讲一个标准且安全的做法:前端调用wx.login()拿到临时凭证code,把code发给后端;后端用这个code加上AppID和AppSecret去微信接口换openid和session_key。关键点:openid的换取必须放在后端完成,不能在小程序端直接请求微信接口,否则AppSecret会暴露给前端。

拿到openid后,后端去用户表查询有没有这个openid。如果存在,直接生成Token返回,前端跳首页;如果不存在,说明这个微信号还没绑定学号,后端先返回一个“未绑定”标识,前端跳转到一个绑定学号的页面,让用户填学号和姓名,调bindStudent接口完成绑定。

后端绑定接口里有个细节——不能无条件相信前端传来的学号姓名,否则任何人都能绑定别人的宿舍。我在项目里加了一步校验:从学生名单表里查这个学号是否存在、姓名是否匹配,校验通过才绑定。这相当于把“身份可信”的责任从代码层转移到了初始数据层。

核心代码如下:

@PostMapping("/wx/login") public Result login(@RequestBody WxLoginDTO dto) { String openid = wxService.code2Session(dto.getCode()); User user = userMapper.selectByOpenid(openid); if (user == null) { return Result.failed(1001, "未绑定学生身份"); } String token = tokenService.createToken(user.getId()); return Result.success(token); } @PostMapping("/user/bind") public Result bind(@RequestBody BindDTO dto, @RequestHeader("Authorization") String token) { StudentInfo info = studentInfoMapper.checkByNoAndName(dto.getStudentNo(), dto.getRealName()); if (info == null) { return Result.failed(1002, "学号与姓名不匹配"); } User user = new User(); user.setOpenid(openidService.getOpenidByToken(token)); user.setStudentNo(dto.getStudentNo()); user.setRealName(dto.getRealName()); user.setRoomId(info.getRoomId()); userMapper.insert(user); return Result.success(); }

3.2 电费计算的核心逻辑:单价、阶梯电价与公摊损耗

电费计算看着简单——读数差乘单价,但真实场景里藏着两个很容易被答辩老师追问的细节:阶梯电价和公摊电费。

阶梯电价的意思是,用电量在某段区间内是一个价,超过区间后单价上涨。比如每月0到50度按0.54元/度,50到100度按0.62元/度,超过100度按0.82元/度。启用电阶梯电价功能后,计算逻辑就变成分段累加:

public BigDecimal calculateFee(BigDecimal usage) { BigDecimal fee = BigDecimal.ZERO; BigDecimal remaining = usage; for (PriceTier tier : priceTierList) { if (remaining.compareTo(BigDecimal.ZERO) <= 0) break; BigDecimal tierRange = tier.getEndValue().subtract(tier.getStartValue()); BigDecimal usedInTier = remaining.min(tierRange); fee = fee.add(usedInTier.multiply(tier.getUnitPrice())); remaining = remaining.subtract(usedInTier); } // 再加上公摊损耗费,按房间建筑面积分摊 fee = fee.add(publicAreaFee); return fee.setScale(2, RoundingMode.HALF_UP); }

公摊电费也是不少学校真实存在的逻辑:走廊灯、楼道应急灯、公共洗衣房的电费不能算在某一个房间头上,通常按宿舍人数或面积均摊。我在设计时把这部分费用单独做成一个“公共分摊任务”,每月月底自动跑一次,生成所有房间的公共电费清单并统一扣费。这样学生看到的账单里,会明确区分“宿舍用电”和“公共分摊”两项,解释起来也清楚。

扣费时机有两种策略:管理员手工按“抄表周期扣费”和系统定时自动扣费。我推荐定时自动扣费:每天凌晨两点,系统扫描所有房间,有最新读数的就用最新读数计算,没有新读数的沿用上期读数。定时任务实现很简单,Spring Boot里加一个@Scheduled(cron = "0 0 2 * * ?")注解方法就行。

3.3 充值与余额变更的并发安全:不要让一分钱悄悄消失

这块是整套系统实现成败的胜负手。电费充值和扣款都涉及余额增减,如果直接写成“读出余额 -> 修改余额 -> 写回余额”,并发下一定丢数据。比如学生同时在App上发起两笔充值,两笔请求同时读到余额100,分别增加50和30,最终写回的可能只是130,而不是180。

解决并发有两个层次。第一个层次用数据库能力兜底,SQL直接原子更新:

UPDATE room SET current_balance = current_balance + #{amount} WHERE id = #{roomId};

扣款时加一个条件,防止扣成负数:

UPDATE room SET current_balance = current_balance - #{amount} WHERE id = #{roomId} AND current_balance >= #{amount};

这样即便并发来了,数据库的行锁也会让操作串行执行;第二条SQL影响行数为0时,业务层就知道“余额不足,不能扣费”,自动触发欠费预警。第二个层次是接口层的幂等校验:同一个充值订单,无论用户手滑点了几次确定,后端只允许处理一次。做法是支付回调里先查订单状态,只有当订单还是“待支付”时才更新余额,否则直接返回成功,避免重复入账。

微信支付的小程序支付流程在真实上线时需要商户号和支付资质,学生项目里通常申请不下来。我在这套系统里做了一个“模拟支付”开关:支付页面走完整下单流程,但点击确认后走支付沙箱接口,或者直接模拟支付成功,后端回到订单支付成功逻辑。这样业务闭环完全跑通,又不卡在资质申请上。答辩时你可以明确讲:真实环境只需替换支付接口,其余逻辑完全复用。

3.4 欠费预警与远程断电的实现链路

欠费断电是整个系统最“硬核”的功能,也最容易做得华而不实。真实公寓里要远程断电,需要电表硬件支持远程阀控,也就是智能电表里有个继电器,管理平台远程下发“拉闸”或“合闸”指令。这套系统里,我做了三档状态:

  • 余额大于预警阈值(比如20元):正常状态;
  • 余额小于预警阈值但大于断电阈值(比如5元):预警状态,前端首页出现黄色提醒条,后端给对应用户发微信订阅消息;
  • 余额小于断电阈值:断电状态,后端置字段status=2,同时如果对接了智能电表平台,就调用对应接口下发电表拉闸指令。

断电前的订阅消息提醒是个很人性化的设计。微信的订阅消息需要用户主动授权一次,获得授权后后端可以在“用户同意、有实际触发场景”的情况下给他发送一条“您的宿舍余额已不足X元,请及时充值”。注意,订阅消息是一次性授权,用户点了一次授权只能接收一条消息,下次提醒需要再次请求授权,这是微信平台自身的规则限制。

后端每日扫描代码片段:

@Scheduled(cron = "0 0 2 * * ?") public void dailyCheck() { List<Room> rooms = roomMapper.selectAll(); for (Room room : rooms) { if (room.getCurrentBalance().compareTo(room.getThreshold()) < 0) { // 给关联用户发送订阅消息 messageService.sendLowBalanceNotify(room); if (room.getCurrentBalance().compareTo(BigDecimal.ZERO) <= 0) { room.setStatus(2); // 调用电表平台断电接口(模拟) ammeterClient.powerOff(room.getId()); } else { room.setStatus(1); } } else { room.setStatus(0); } roomMapper.updateById(room); } }

3.5 报修工单的状态机设计

宿舍里除了电费,最常见的诉求是报修。电费系统里集成报修功能不算复杂,但状态流转要设计清楚,不然就会出现“学生报修了,管理员也说处理了,但学生没看到结论”的问题。我用的状态机很简单:0-待受理、1-处理中、2-已完成、3-已关闭。学生提交报修创建工单,管理员在管理端看到待受理列表后点击受理,状态变处理中,同时学生端显示“师傅处理中”;管理员填写处理结果后,状态变已完成,学生端同步显示结果。这里有个容易被忽略的体验点:状态变化时要给学生发一条订阅消息,不然学生不知道工单的推进进度。报修模块虽然代码量不大,但它是整个系统“有人情味”的加分项,建议一定要保留。

4. 小程序端页面设计与交互:让数据在手机屏幕上“活”起来

4.1 首页视觉与信息密度:一眼看到最重要的三个数字

首页的UI设计,我用的是“一个核心卡片 + 三条明细列表 + 一个操作按钮”的结构。顶部卡片展示当前宿舍:余额大字显示、房间号、所属楼栋和当前用电状态(正常/预警/已断电)。卡片正下方放两个高亮度操作按钮:充值缴费、查看明细。中间部分是“本月用电”的简版柱状图,按最近7天的用电量画出趋势,方便学生快速感知自己宿舍用电是否异常。页面底部是最近三条用电扣费记录和充值记录。

小程序的首页是所有功能的价值浓缩,我不建议把菜单堆得密密麻麻。视觉上采用白底加蓝色渐变主按钮,状态用红黄绿三色区分,余额不足时,红色脉冲提醒会一起出现。这里说个原生的交互技巧:小程序onShow生命周期非常关键,学生每次从前台切回小程序,首页都要拉取最新余额,因为极端情况下学生在另外一个设备上头已经充值了,当前设备还显示旧数据,会引发误解。

关键页面结构示意:

<view class="balance-card"> <text class="room-name">{{roomInfo.buildingNo}}栋{{roomInfo.roomNumber}}</text> <text class="balance-num">{{roomInfo.currentBalance}}</text> <text class="status-tag" wx:if="{{roomInfo.status == 2}}">已断电</text> </view>

4.2 充值缴费的业务闭环:订单状态必须先立起来

充值缴费的交互流程看上去简单,但代码里隐藏着各种状态问题。我在订单表里设计了三状态:待支付、已支付、已取消。前端点击“充值”后,先请求后端接口创建订单,拿到订单号后进入支付确认页;学生点击“确认支付”,后端在模拟支付模式下直接把订单置为已支付,给房间余额加钱,然后返回结果。

这里有一个非常值得注意的交互细节:充值成功后不要立即跳回首页刷新余额,而要等接口返回成功的回调再刷新。否则在极端情况下,余额还没来得及入账,用户看到余额没变,会下意识再点一次充值,于是重复扣费。另外,充值需要支持输入自定义金额和快速金额(10元、20元、50元)两种方式,快速金额的按钮要足够大,减少手机端输入的麻烦。支付成功页展示新增余额、新的总余额和大字“充值成功”,这个简单页面能极大降低学生的焦虑感。

4.3 管理员端业务页面:抄表录入与欠费名单的表格体验

管理端的重点不在花哨,而在高效。我用的是“顶部Tab切换 + 列表 + 弹窗表单”的交互模式。进入管理端首页,默认展示“欠费宿舍名单”,按余额从低到高排序,列表项显示楼栋、房间、余额、上次扣费时间,点击可直接给该宿舍发放补助或远程合闸。这个页面是宿管每天打开最多的页面,所以加载速度一定要快,如果宿舍数量大,后台接口要支持分页和搜索。

抄表录入是另一个核心功能。管理员可以逐间录入,也可以批量粘贴数据。为了减少手工录入出错,我在录入表单里面做了“上期读数自动带出”的功能:管理员输入本次读数后,系统自动计算用电量并给出参考电费,确认后提交。每次提交都会往meter_reading表插入一条记录,同时生成扣费流水。录入错误的场景人人都会遇到,系统需要支持“回退上一条记录”的操作,把余额和状态恢复原状,否则月底对账时非常痛苦。

4.4 前端公共方法封装:请求拦截、Token注入与统一错误提示

小程序端的公共代码,我单独封装了util/request.js,所有页面请求都走这一个入口。这样能统一处理四件事:自动拼上baseUrl、自动在请求头里带上Token、code非200时弹出统一错误提示、请求过程中自动展示加载动画。很多新手在每个页面写重复的wx.request,导致项目越写越乱,到联调时改一个baseUrl要改几十处。封装后,从开发环境切生产环境只需要改一个变量。

另外要特别注意小程序的wx.setStorageSync缓存策略:Token和用户基本信息可以存到本地存储,但余额、电费明细这些动态数据不应该长时间缓存,每次进入页面都应该拉取新数据,缓存只用于减少冷启动空白感。我在首页加了一个“骨架屏”占位——数据没回来之前,页面先显示灰色布局,避免用户以为自己打开了个空页面。这个细节很多源码里都没有,做完之后整体的用户口碑会明显不一样。

封装请求的核心代码:

const request = (url, method, data) => { return new Promise((resolve, reject) => { wx.request({ url: baseUrl + url, method: method || 'GET', data: data || {}, header: { 'Authorization': 'Bearer ' + wx.getStorageSync('token') }, success: (res) => { if (res.data.code === 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message, icon: 'none' }); reject(res.data); } }, fail: (err) => { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); };

5. 本地部署、联调排错与项目演示实录

5.1 从源码到跑起来的完整步骤

项目拿回来之后,第一步不是急着写代码,而是先把环境搭好。我把环境和启动顺序完整列一下,按顺序操作基本不会卡壳:

  • 安装JDK 1.8或更高版本,配置环境变量JAVA_HOME。
  • 安装MySQL 5.7及以上版本,执行项目里的init.sql建库建表。
  • 安装Maven,配置阿里云镜像(国内网络下载依赖会快很多)。
  • 用IDEA导入后端代码,等待Maven依赖下载完毕。
  • 修改application.yml里的数据库账号密码、小程序AppID和AppSecret。
  • 运行DormElectricApplication主类,后端默认端口8080。
  • 打开微信开发者工具,导入小程序前端目录,修改util/config.js里的baseUrl为http://localhost:8080。
  • 开发工具里勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”后,就可以正常请求本地后端了。
  • 如果使用现成的源码,注意先检查数据库有没有初始数据,例如学生名单、房间数据和一条最新的电表读数,否则登录绑定会提示“查无此人”。

有一个经常被新手忽略的坑:微信开发者工具默认模拟器的用户代理和真机不同,本地调试时一切正常,一到真机预览就请求失败。这是因为小程序真机要求后端接口必须是HTTPS且域名备案。课程设计阶段通常在局域网内演示,这时候需要让手机和电脑连同一个Wi-Fi,后端地址从localhost改成电脑的局域网IP,比如http://192.168.1.100:8080,同时在微信开发者工具里点“真机调试”,用手机扫码预览。这一步很多人卡一整天,其实就改一个IP的事。

5.2 联调过程中我踩过的几个坑

第一个坑是MySQL中文乱码。建库的时候如果没指定字符集,存进去的中文全变成问号。解决办法是建库时显式指定utf8mb4,数据库连接的URL里也必须加上characterEncoding=utf8。第二个坑是端口被占用。本机恰好有个旧服务占了8080,后端一启动就报Port already in use。我直接把后端端口改成了8080,同时把小程序端baseUrl同步改为8080,但这个改动在联调时很容易被忽略,导致前端报404。建议给所有配置项统一做一个常量管理,端口号集中写在一个地方。

第三个坑是跨域问题。小程序端请求后端接口,本质上是跨域请求,如果后端没配跨域过滤器,浏览器请求会报CORS错误。我一开始只在Controller上加了@CrossOrigin,但拦截器拦截的请求仍然会先被CORS挡住。最后解决方案是采用一个全局CORS配置拦截器,把所有接口的跨域问题统一解决。第四个坑是定时任务的冲突——本地调试时数据库时间凌晨两点已经过了,定时任务不执行,这时候我写了一个测试接口手动触发任务,调试完再删除。

5.3 高频问题速查表

联调阶段把大量高频问题整理成了一张速查表,这里分享给大家,按图索骥能省很多排查时间。

症状可能原因解决方案
小程序请求后端超时基础库版本过低或后端未启动检查后端Console是否有启动成功日志;真机调试时确认IP可达
登录后一直显示未绑定学生名单中没有该学号,或前端传的学号姓名不匹配先在后端数据库直接查学生名单表,确认数据存在
充值成功但余额没增加订单状态判断有误,或更新SQL未生效看后端日志确认订单状态是否从0置为1,再检查房间ID是否传对
中文乱码数据库字符集不对或URL缺少编码参数字符集统一用utf8mb4,连接URL加characterEncoding=utf8
定时任务不跑没有加@EnableScheduling注解在启动类上补充该注解
页面白屏或数据不显示前端data字段名与后端返回字段不一致用Console打印接口返回,逐个字段对照
并发充值金额变少没有用到原子更新SQL参照上文SQL改为current_balance = current_balance + amount形式

5.4 演示与答辩的加分细节

项目做出来了,最后一步是怎么把成果讲好。很多同学演示时喜欢面面俱到,结果五分钟演讲什么都讲了,老师什么都没记住。我的建议是演示只讲三个场景:学生从登录到看到自己宿舍余额的完整过程;管理员录入一次读数后宿舍余额变化的完整过程;余额不足触发预警和断电提醒的完整过程。这三个场景刚好覆盖了登录鉴权、核心业务计算、定时任务与消息推送四条主线,也最能体现系统设计的完整性。

答辩时如果被问到“这个系统有什么难点”,千万不要只说“做了很多功能”。要主动抛出设计取舍:比如余额为什么放在房间表而不是用户表;为什么用SQL原子更新而不是Java代码里先查后改;为什么模拟支付而不是直接跳过支付。这些是能体现真实工程思考的点,比“我用了Spring Boot和微信小程序”这种回答有分量得多。另外事前准备好一份带演示数据的Excel账单,打印出来给评委看,这个小动作通常会让答辩老师眼前一亮。

最后再分享一个经验:项目做完不是终点,而是起点。我在这套系统里留了一个抽象出来的“计量计费引擎”,换掉数据源就是一套通用的预付费账户系统,不光能管电费,以后管水费、燃气费、洗衣费都能复用。你在做类似项目时,如果时间允许,也建议把自己的代码往抽象层推一步,用合理的接口隔离开核心计费逻辑和外部依赖,这样等你回头维护或者扩展时,会发现当初多花的一天特别值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询