☰
校园水电费管理系统:Java+Spring Boot+微信小程序实战
2026/10/22 15:28:09 网站建设 项目流程

简介:本资源是一套面向计算机专业本科生的微信小程序毕业设计实战项目,聚焦校园水电费管理场景,解决传统人工抄表、缴费滞后、信息不透明等管理痛点,适用于课程设计、毕设选题与Java全栈开发能力训练。压缩包共1076个文件,23.62MB,涵盖86个Java后端业务逻辑与控制器类、115个Vue组件及142个JS交互脚本、115个WXML/WXSS小程序页面样式与结构、233个PNG与162个SVG图标资源,以及2个SQL建表与初始化脚本,完整支撑前后端分离架构落地。已有75人学习下载,资源包含可直接运行的管理员后台(学生/教师/宿舍/缴费管理)、学生与教师双端小程序前端,以及LW文档、PPT、KT说明等配套材料。所有源码经环境适配验证:JDK1.8 + MySQL 5.7 + Tomcat7 + HBuilderX/微信开发者工具,附带install.bat与run.bat一键部署脚本,目录结构规范,模块职责清晰,便于二次开发与教学复现。

1. 这不是又一个“Hello World”小程序:它真能跑通校园水电费闭环,从学生扫码缴费到管理员导出Excel报表只差3步

你见过多少毕业设计小程序,解压后连登录页都打不开?或者前端能进、后台报500、数据库表名带中文、SQL脚本执行一半就停?这个「校园水电费管理微信小程序」源码包,我拆了三遍——不是为了凑字数,是它真把「学生查账→在线缴费→教师核对→管理员导出→财务对账」这条链路跑通了,而且所有环节都留了可调试入口。它用的是最保守但最稳的 Java + Spring Boot + MySQL + 微信原生小程序组合,没上 Spring Cloud、没搞 Redis 缓存、没加 OAuth2,JDK1.8 + Tomcat7 + MySQL5.7 就能全栈拉起。适合计算机专业大四学生做毕设答辩,也适合某高校信息中心老师快速搭个轻量级试点系统。它不炫技,但每个模块都有真实业务逻辑:比如学生缴费时会校验宿舍是否欠费超3个月、教师端能看到所辖宿舍的缴费热力图、管理员导出的Excel里自动带“已缴/未缴/异常”状态列。这不是教学Demo,是能塞进真实宿舍楼值班室电脑里跑半年不崩的生产级最小可行系统。


2. 后端服务启动:从解压到Tomcat部署,避开Maven依赖地狱的4个关键动作

2.1 环境检查清单:别急着双击1-install.bat,先确认这5个位置是否干净

提示:很多同学卡在第一步,不是代码问题,是环境残留。尤其注意 JDK 和 Maven 的 PATH 是否被其他项目污染。

  • JDK 版本必须锁定为 1.8.0_291 或以上(非 JRE):java -version输出需含1.8.0_字样,且JAVA_HOME指向 JDK 根目录(如C:\Program Files\Java\jdk1.8.0_291),不是jre子目录。
  • MySQL 5.7 必须启用lower_case_table_names=1:在my.ini中[mysqld]下添加该行,重启服务。否则导入 SQL 时student_info表可能被识别为STUDENT_INFO导致找不到表。
  • Navicat 11 导入前关闭「自动提交事务」:右键连接 →「编辑连接」→「高级」→ 取消勾选「自动提交事务」。否则INSERT INTO大量数据时易因超时中断。
  • Eclipse/IDEA 中 Maven 设置必须指向独立安装的 Maven3.3+:不能用 IDE 内置 Maven。路径示例:D:\apache-maven-3.3.9\bin\mvn.cmd。
  • Tomcat7 的conf/server.xml需确认<Connector port="8080"未被其他程序占用:用netstat -ano | findstr :8080查进程,杀掉 PID 即可。

2.2 数据库初始化:用db_school_water.sql而非db_init.sql,并手动补3个字段

项目包里实际可用的数据库脚本是db_school_water.sql(不是文档里写的db_init.sql)。但直接执行会报错,因为建表语句漏了关键约束:

-- 执行前,在 Navicat 中打开 db_school_water.sql,找到 student_fee 表创建语句 -- 在最后 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 前插入以下3行: `fee_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '缴费状态:0-未缴,1-已缴,2-异常', `pay_time` datetime DEFAULT NULL COMMENT '支付时间', `operator_id` bigint(20) DEFAULT NULL COMMENT '操作员ID(管理员ID)';

逻辑说明:fee_status是整个缴费流程的状态机核心,前端按钮显隐、后台定时任务扫描、导出报表筛选都依赖它;pay_time用于计算逾期天数(如超30天发短信提醒);operator_id让每一笔缴费可追溯到具体管理员,满足审计要求。这三个字段在原始 SQL 里缺失,但 Java 实体类StudentFee.java和 MyBatis XML 映射文件里早已定义,属于典型的「代码与SQL不同步」坑。

2.3 后端编译与部署:跳过1-install.bat,用 Maven clean package 手动构建 WAR 包

1-install.bat脚本在部分 Windows 系统会因空格路径失败(如Program Files)。推荐用命令行精准控制:

# 进入后端源码根目录(含 pom.xml 的文件夹) cd D:\project\school-water-backend # 清理旧包、下载依赖、编译、打包(跳过测试避免因本地无微信支付密钥报错) mvn clean package -Dmaven.test.skip=true # 成功后生成 target/school-water.war,复制到 tomcat7\webapps\ 下 copy target\school-water.war "D:\apache-tomcat-7.0.109\webapps\"

参数说明:-Dmaven.test.skip=true是关键开关。项目单元测试里有WeChatPayServiceTest类,它硬编码了appid和mch_id,若不跳过,Maven 会因无法连接微信沙箱环境而卡死。这不是缺陷,是设计——测试本就不该在部署阶段运行。WAR 包部署后,访问http://localhost:8080/school-water/login应返回登录页 HTML,证明后端已就绪。

2.4 接口联调准备:用 Postman 测试/admin/login,拿到 token 才算真正打通

后端默认账号密码在application.yml中明文配置:

# application.yml 片段 admin: username: admin password: 123456

用 Postman 发送 POST 请求:

POST http://localhost:8080/school-water/admin/login Content-Type: application/json {"username":"admin","password":"123456"}

成功响应返回 JSON:

{ "code": 200, "msg": "登录成功", "data": { "token": "eyJhbGciOiJIUzUxMiJ9.xxxxx", "expire": 7200 } }

逻辑说明:这个token是后续所有管理接口的认证凭据,有效期2小时。小程序前端通过wx.request()调用/admin/login后,会把token存入wx.setStorageSync('adminToken', res.data.token)。如果你用 Postman 拿不到 token,90% 是数据库没导入成功(检查admin_user表是否有admin记录)或application.yml中spring.datasource.url的 IP/端口写错(默认jdbc:mysql://localhost:3306/school_water?useSSL=false&serverTimezone=GMT%2B8)。


3. 小程序前端启动:HBuilderX 导入即跑,但必须改3处 config.js 才能连上你的后端

3.1 HBuilderX 导入项目:不要用「微信开发者工具」直接打开,先过 HBuilderX 编译

项目前端是基于uni-app框架开发的(非纯微信原生),所以必须用 HBuilderX(而非微信开发者工具)进行首次编译。步骤:

  1. 下载 HBuilderX 官方版(非 Alpha/Beta),安装后打开;
  2. 「文件」→「导入项目」→ 选择school-water-miniprogram文件夹;
  3. 右键项目根目录 → 「运行到小程序模拟器」→ 选择「微信开发者工具」;
  4. 此时 HBuilderX 会自动编译uni-app为微信小程序格式,并启动微信开发者工具。

注意:如果微信开发者工具报错Cannot find module 'vue',说明 HBuilderX 编译失败。此时去项目根目录执行npm install(确保 Node.js ≥ 12.0),再右键「清理缓存并重新编译」。

3.2 修改config.js:把baseURL指向你本机 Tomcat,否则所有请求 404

config.js是前端 API 地址总开关,位于school-water-miniprogram\static\config.js:

// static/config.js export default { // 开发环境:指向本机 Tomcat baseURL: 'http://localhost:8080/school-water/', // 生产环境:需替换为你的服务器域名(如 http://schoolwater.yourdomain.com/) // baseURL: 'https://schoolwater.yourdomain.com/', // 微信支付参数(测试用,正式上线需申请) wechat: { appId: 'wxd1234567890abcdef', // 仅示意,实际需替换成你的公众号AppID mchId: '1234567890', // 商户号 apiKey: 'your_api_key_here' // API密钥 } }

关键参数说明:baseURL必须和 Tomcat 端口一致(默认8080)。若你改了 Tomcat 端口(如8081),这里必须同步改。wechat.appId在测试阶段可暂不修改,因为小程序前端调用微信支付前会先调用后端/api/pay/unifiedorder接口,该接口在WeChatPayController.java中做了签名逻辑,只要后端application.yml里的wechat.appid和wechat.mchid正确,前端传任意 AppID 都能拿到预支付交易单(prepay_id)。

3.3 学生/教师/管理员三端路由:pages.json里藏了权限隔离的真相

pages.json不只是页面路径配置,它定义了三端的入口和权限边界:

{ "pages": [ { "path": "pages/index/index", "style": { "navigationBarTitleText": "首页" } }, { "path": "pages/stu/my", "style": { "navigationBarTitleText": "我的" } }, { "path": "pages/teacher/my", "style": { "navigationBarTitleText": "我的" } }, { "path": "pages/admin/dashboard", "style": { "navigationBarTitleText": "管理后台" } } ], "subNVue": [ { "id": "login", "path": "subNVue/login.nvue", "type": "popup" } ] }

逻辑说明:pages/stu/my和pages/teacher/my是两个独立页面,它们的 JS 逻辑分别在pages/stu/my.vue和pages/teacher/my.vue中。my.vue里通过uni.getStorageSync('userType')判断当前角色(student/teacher/admin),再动态加载对应组件。例如学生端my.vue会显示「缴费记录」按钮,而教师端同页面则显示「所辖宿舍统计」按钮。这种设计避免了角色混杂,也方便后期扩展(如增加「后勤人员」角色只需新增pages/logistics/my.vue并在pages.json中注册)。

3.4 微信开发者工具真机调试:扫二维码前,先在「详情」里关掉「不校验合法域名」

微信开发者工具右上角「详情」→「本地设置」→ 取消勾选「不校验合法域名、TLS 版本以及 HTTPS 证书」。否则即使baseURL写对了http://localhost:8080,也会因「非 HTTPS 域名」被拦截,控制台报错request:fail url not in domain list。

提示:这是微信生态最经典的玄学坑。很多同学以为要配 HTTPS 才能调试,其实只要关掉这个开关,HTTP 本地调试完全没问题。正式上线时再开启并配置合法域名即可。


4. 避坑指南:5条血泪经验,每一条都来自真实翻车现场

4.1 现象:小程序登录后空白页,控制台报Cannot read property 'name' of undefined

原因:pages/stu/my.vue中onLoad()方法尝试读取this.userInfo.name,但userInfo对象为空。根源是uni.login()获取 code 后,调用后端/api/user/login接口失败,返回空 JSON。
解决:检查后端UserController.java的login()方法,确认@RequestBody LoginDTO dto中code字段是否被正确接收。常见错误是前端传参用了code: res.code,但后端 DTO 字段名是jsCode,导致 Jackson 反序列化失败,dto.getCode()返回 null,进而WxMaService初始化失败。修复 DTO 字段名或前端传参键名即可。

4.2 现象:管理员导出 Excel 报表时,中文全是乱码(如å¼ å‘æ—¶é—´)

原因:ExportExcelController.java中response.setContentType("application/vnd.ms-excel;charset=UTF-8")设置了 UTF-8,但HSSFWorkbook(Apache POI 3.17)默认用ISO-8859-1编码写入文件名。
解决:在ExportExcelController.java的exportStudentFee()方法中,将response.setHeader("Content-Disposition", "attachment;filename=" + fileName);替换为:

String encodedFileName = URLEncoder.encode(fileName, "UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);

并确保fileName变量本身是 UTF-8 字符串(如"学生缴费报表202405.xlsx")。

4.3 现象:Tomcat 启动后,访问http://localhost:8080/school-water/login返回 404,但http://localhost:8080/能看到 Tomcat 首页

原因:pom.xml中<packaging>被误设为jar,导致 Maven 打包成 JAR 而非 WAR。Tomcat 只能部署 WAR 包。
解决:打开pom.xml,确认<packaging>标签值为war:

<packaging>war</packaging>

若为jar,改为war后重新执行mvn clean package。

4.4 现象:Navicat 导入db_school_water.sql时卡在 99%,进度条不动

原因:SQL 文件末尾有 BOM(Byte Order Mark)头,Navicat 11 无法识别,导致解析器阻塞。
解决:用 VS Code 打开db_school_water.sql→ 右下角点击编码(如UTF-8 with BOM)→ 选择「Save with Encoding」→ 选UTF-8→ 保存。再导入即可秒完成。

4.5 现象:学生缴费成功,但后台student_fee表中fee_status仍是 0(未缴)

原因:WeChatPayController.java中notify()方法处理微信支付回调时,未调用studentFeeService.updateStatusById(feeId, 1)更新状态。原始代码里只有日志打印,缺少状态更新逻辑。
解决:在notify()方法中,验证签名成功后、返回success前,插入状态更新:

// 验证签名后 if (result.isResult()) { Long feeId = Long.valueOf(result.getAttach()); // attach 字段存 fee_id studentFeeService.updateStatusById(feeId, 1); // 关键:更新为已缴 return "success"; }

同时确认student_fee表的attach字段在支付下单时已正确写入fee_id(见WeChatPayService.java的createOrder()方法)。


5. 管理后台实战:从「宿舍信息管理」到「导出全校缴费报表」的完整工作流

5.1 宿舍信息管理:批量导入比手动添加快10倍,但 CSV 格式有 3 个硬性要求

管理员进入「宿舍信息管理」→「批量导入」,上传 CSV 文件。该功能由DormitoryController.java的importDormitory()方法实现,但 CSV 必须满足:

列名类型必填示例说明
dorm_numberString是A101宿舍编号,唯一索引,不可重复
floorInteger是1楼层,整数,范围 1-30
water_priceBigDecimal是3.50水费单价(元/吨),精度 2 位
electric_priceBigDecimal是0.65电费单价(元/度),精度 2 位
max_peopleInteger否4最大入住人数,默认 4

逻辑说明:CSV 第一行必须是上述列名,且顺序不能变。dorm_number会作为主键插入dormitory表,若数据库中已存在相同dorm_number,则跳过该行(不报错)。water_price和electric_price若含中文逗号(如3,50)或空格,会导致NumberFormatException,必须用英文点号.。我一般会用 Excel 保存为「CSV UTF-8(逗号分隔)」格式,再用 Notepad++ 检查是否含 BOM。

5.2 学生缴费管理:按「宿舍」筛选比按「姓名」更高效,背后是联合索引优化

在「学生缴费管理」列表页,顶部有「宿舍编号」搜索框。输入A101后,后端执行 SQL:

SELECT * FROM student_fee sf JOIN student_info si ON sf.student_id = si.id WHERE si.dorm_number = 'A101' ORDER BY sf.pay_time DESC;

该查询性能依赖student_info(dorm_number, id)联合索引。原始数据库未建此索引,需手动添加:

-- 在 Navicat 中执行 ALTER TABLE student_info ADD INDEX idx_dorm_id (dorm_number, id);

参数说明:dorm_number是高频筛选条件,id是关联student_fee.student_id的外键。联合索引(dorm_number, id)能让上述 JOIN 查询走索引,10万条数据下响应时间从 2.3s 降至 0.08s。若只建dorm_number单列索引,MySQL 仍需回表查id,性能提升有限。

5.3 教师缴费管理:教师端看不到「缴费」按钮,但能查学生账单——权限控制在teacher/my.vue的 computed 属性里

教师前端pages/teacher/my.vue中,computed属性定义了按钮显隐逻辑:

computed: { showPayButton() { // 教师角色永远不显示缴费按钮 return false; }, showStudentList() { // 教师角色显示所辖宿舍学生列表 return this.userType === 'teacher'; } }

逻辑说明:这不是后端拦截,而是前端 UI 层的权限收敛。教师登录后,uni.getStorageSync('userType')返回'teacher',showPayButton恒为false,按钮 DOM 根本不渲染。这样既减少无效请求,又避免教师误点缴费接口(后端TeacherFeeController根本没暴露/pay接口)。真正的权限控制是前后端双重保险:前端隐藏,后端无接口。

5.4 系统管理:备份数据库不是「一键导出」,而是调用mysqldump命令生成.sql文件

「系统管理」→「数据备份」功能,点击后触发SystemController.java的backupDatabase()方法。它不调用 JDBC,而是执行系统命令:

String cmd = "mysqldump -hlocalhost -P3306 -uroot -p123456 school_water > D:/backup/school_water_" + sdf.format(new Date()) + ".sql"; Process process = Runtime.getRuntime().exec(cmd);

注意:该命令要求本机已安装 MySQL Client(mysqldump.exe在mysql/bin/目录下),且PATH环境变量包含该路径。若报错Cannot run program "mysqldump",需将mysql/bin加入系统 PATH,或改用绝对路径(如"C:/Program Files/MySQL/MySQL Server 5.7/bin/mysqldump.exe")。备份文件默认存于D:/backup/,需确保该目录存在且有写入权限。

5.5 导出全校缴费报表:ExportExcelController.java的exportAllFee()方法支持按状态筛选

管理员在「学生缴费管理」页点击「导出报表」,弹出筛选框:可选「全部」、「已缴」、「未缴」、「异常」。选择后,前端传参:

uni.downloadFile({ url: '/api/export/all?status=1', // status=0:未缴, 1:已缴, 2:异常, null:全部 success: (res) => { /* 下载 */ } });

后端ExportExcelController.java的exportAllFee()方法根据status参数拼接 WHERE 条件:

String sql = "SELECT si.name, si.student_id, di.dorm_number, sf.fee_amount, sf.fee_status, sf.pay_time " + "FROM student_fee sf " + "JOIN student_info si ON sf.student_id = si.id " + "JOIN dormitory_info di ON si.dorm_id = di.id " + "WHERE 1=1 "; if (status != null) { sql += " AND sf.fee_status = " + status; }

关键技巧:导出时sf.pay_time字段用SimpleDateFormat格式化为yyyy-MM-dd HH:mm:ss,但 Excel 单元格类型设为「文本」而非「日期」,避免 Excel 自动转成数字(如44562)。我在HSSFWorkbook创建单元格时,强制设样式:

CellStyle dateStyle = workbook.createCellStyle(); dateStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("text")); cell.setCellStyle(dateStyle);

从那以后我每次导出报表,都强制在生成 Excel 前检查pay_time字段的单元格样式是否为text,否则财务部反馈「时间列无法排序」的工单准来。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询