简介:基于Java的电费管理系统毕业设计项目,面向计算机相关专业学生或Java开发者。系统围绕用户管理、电费计算、缴费管理、数据统计与提醒服务等核心模块展开,采用Spring Boot+MyBatis+MySQL的主流技术栈,并涉及Spring Security权限控制、JWT身份验证、在线支付与可视化报表等技术点,适合学习MVC项目完整落地过程。压缩包共158个文件,大小仅1.78MB,以52个java源码为主,配合19个html、24个js、14个css组成前端页面与交互逻辑,10个xml及properties等负责持久层和工程配置,另含字体、图片、Maven封装脚本等静态资源,可在本地还原出Web工程。目前已有115人学习下载,说明该资源在同主题资料中有一定参考热度。通过该压缩包可获取完整的项目源码、页面模板和工程配置,对照学习用户认证、计费算法、支付对接、报表展示等模块的代码实现,也能作为毕业设计开发与演示的基础素材,节省从零搭建框架的时间,提升实际动手和排错能力。
1. 电费管理系统毕设:先想清楚它是业务系统还是技术展示
做电费管理系统(Java)这套毕设时,最容易踩的第一个坑就是把它当成普通的增删改查。实际上,电费管理系统的核心是计费规则——阶梯电价怎么分段、峰谷分时怎么算、预付费还是后付费,每一种规则都直接决定数据表怎么建、业务层怎么写。如果上来就建几张表开始写用户管理、房间管理,做到中间一定会发现计费逻辑和表结构对不上,最后只能推倒重来。这篇方案面向要做Java毕设或课程设计的同学,也适合需要快速搭一套小业务系统做内部演示的从业者。我会按照需求拆解、技术选型、架构设计、实现细节到验收答辩的完整链路来讲,让你拿到这套资源后能直接照着改。
2. 需求分析与技术选型:先理清角色和计费流程
电费管理系统的难点不在页面多不多,而在「抄表 → 计费 → 缴费 → 销账」这条业务链能不能闭环。很多同学一上来就画用例图,画完就写代码,结果漏掉了「电费退款」「欠费催缴」这类边界场景,做出来的系统演示时被导师一句话问住。所以我建议先做一件事:把角色和业务流程在白板上画一遍,再决定表怎么建、接口怎么定。
2.1 用户角色与功能边界:谁在用这套系统
系统里至少要区分三种角色,这是毕设答辩时第一个会被问的问题。
管理员是核心使用者,负责维护电价规则、导入抄表数据、查看统计报表、处理异常账单。业主用户是被服务对象,能查看自己的历史账单、在线缴费、打印缴费凭证。如果是面向宿舍场景,还应该有一个超级管理员角色,负责管理宿舍楼栋和房间信息。
功能边界要按角色切干净。管理员能做的不该出现在业主端,业主端不应该有修改电价的入口。我见过不少毕设把「管理费率表」和「查看我的账单」混在一个页面里,答辩时被问到权限控制就支支吾吾。正确的做法是在登录时把角色写进Session,后端每个Controller方法前做一次角色校验,前端再根据角色渲染菜单。这样即使有人绕过页面直接调接口,也拿不到越权数据。
2.2 技术栈选型:Spring Boot + MyBatis 还是纯 JSP
这套资源用的是 Java 技术栈,但具体怎么选版本,直接决定你后面开发顺不顺。
纯 JSP + Servlet 是老一代毕设的做法,优点是结构简单、没有框架黑匣子,适合课程设计这种短周期项目。但它的缺点很明显:代码和页面耦合严重,计费逻辑写在 JSP 里,后期改需求等于重写。如果你打算在毕设答辩里突出「工程规范」,我建议优先选 Spring Boot + MyBatis + MySQL + Thymeleaf 这套组合。Spring Boot 帮你省掉繁琐的 XML 配置,MyBatis 让你能精细控制 SQL,Thymeleaf 渲染页面比 JSP 更现代,也更好讲解。
用一个表把这几个技术点的选型理由列清楚:
| 技术组件 | 选型建议 | 核心理由 |
|---|---|---|
| 核心框架 | Spring Boot 2.x | 自动配置、内置 Tomcat,演示时不用单独部署 |
| ORM 层 | MyBatis | SQL 可控性好,方便展示自写的连表查询 |
| 数据库 | MySQL 5.7+ | 免费、资料多、支持事务 |
| 前端渲染 | Thymeleaf | 与 Spring Boot 集成简单,模板语法接近 HTML |
| 权限控制 | 拦截器 + Session | 毕设场景够用,不用引入厚重的安全框架 |
还有一个小建议:建立工程时直接选 Java 8 以上版本,别用老旧的 1.6。很多电费系统需要处理金额和日期,Java 8 的 LocalDate 和 BigDecimal 会让代码干净得多,后面讲避坑时你更能体会这一点。
2.3 数据库设计:六张核心表与字段定义
电费管理系统的表结构比普通管理系统多一层「抄表记录」,这是最容易漏掉的设计。很多学生只建了用户表和账单表,结果无法回答「这个月用电量怎么算出来的」这个问题,因为抄表源数据丢了。
我一般会这样设计核心表,你参考后按需调整:
用户表(sys_user)保存登录账号和角色,字段要有 user_id 主键、username、password(存 MD5 或 BCrypt 加密值)、role(区分管理员和业主)、phone、house_id(关联房间表)。
房间表(sys_house)保存楼栋、单元、房间号、面积,字段是 house_id、building_no、unit_no、house_no、user_id(当前入住用户)。这张表的作用是解决「一房一表」的对应关系。
电表表(meter)单独建,原因是同一个房间可能换过电表。字段包括 meter_id、house_id、meter_no(电表编号)、install_date、status(正常/停用)。抄表和账单都通过 meter_id 关联,而不是直接关联房间,这样历史数据不会断。
抄表记录表(reading_record)是整个系统最核心的一张表,字段有 record_id、meter_id、read_date、last_reading(上次读数)、current_reading(本次读数)、consumption(本次用电量)、operator_id(抄表人)、status(已计费/未计费)。这张表用来回答「电费怎么来的」——所有账单金额必须能追溯到这里。
费率表(tariff)保存不同电价规则,字段是 tariff_id、rule_name(如「居民阶梯」)、tier_min、tier_max、price、create_time。阶梯电价就是靠这个表动态配置的。
账单表(bill)保存最终计算结果,字段有 bill_id、house_id、record_id、period(账期,格式如 2024-06)、total_amount、status(待支付/已支付/已退款)、create_time、pay_time。注意保留 record_id 外键,这样账单和抄表数据能对应上,答辩时可以展示这条追溯链路。
数据库设计的核心原则是:业务数据(抄表记录)和计算结果(账单)分离。如果只存账单不存抄表记录,计费规则一改,历史账就无法重算;存了源数据,即使逻辑有 bug,也能重新跑一遍算出正确结果。这招在答辩时很加分,导师问「你的数据可追溯吗」,直接把这条链路讲给他。
3. 总体架构设计与关键实现:从抄表到缴费的完整链路
这一章进入实战。我会按分层结构讲代码怎么组织,再把计费和缴费两个核心模块的落地代码贴出来做说明。你看完可以直接把这个结构搬到你的工程里。
3.1 分层架构与包结构:为什么按 controller-service-mapper 切
工程好不好讲,第一眼就看包结构。我给电费管理系统推荐这个分包方式:
com.example.electricity ├── controller // 接收请求、参数校验、返回页面或 JSON ├── service // 业务逻辑:计费、缴费、统计 ├── mapper // MyBatis 数据访问接口 ├── entity // 数据实体,对应数据库表 ├── interceptor // 登录拦截器、角色校验拦截器 ├── vo // 视图对象,用于页面展示的聚合数据 └── config // 配置类:拦截器注册、全局异常处理这样切的目的有三个。第一,答辩时你说「我按三层架构组织代码」有实据支撑,不用空口说。第二,计费逻辑这种容易变的代码只在 service 层写,页面和数据库都不会被影响,改规则时不用动其他地方。第三,mapper 层只写 SQL 相关逻辑,方便你单独用数据库工具调试 SQL,不用启动整个系统。
controller 层只做三件事:接收参数、调用 service、返回视图。不要在 controller 里写任何计算逻辑。我见过有人把计费公式直接写在 Controller 里,美其名曰「方便调试」,结果改需求时页面上换个参数,Controller 代码就乱成一团。
3.2 抄表与计费模块:阶梯电价的落地代码
计费模块是电费管理系统的灵魂,也是答辩时最容易出彩的地方。这里我用一个简化版阶梯电价实现来说明设计思路。
@Service public class BillingService { @Autowired private TariffMapper tariffMapper; @Autowired private BillMapper billMapper; /** * 按阶梯电价计算账单金额 * @param consumption 本月用电量(度) * @return 应缴金额,保留两位小数 */ public BigDecimal calculateBillAmount(Integer consumption) { // 从费率表查出所有阶梯配置,按 tier_max 升序排列 List<Tariff> tiers = tariffMapper.findSortedTiers(); BigDecimal total = BigDecimal.ZERO; int remaining = consumption; int previousMax = 0; for (Tariff tier : tiers) { if (remaining <= 0) { break; } // 当前阶梯可用电量:min(剩余电量, 当前阶梯上限 - 上一阶梯上限) int tierUnits = Math.min(remaining, tier.getTierMax() - previousMax); total = total.add(BigDecimal.valueOf(tierUnits) .multiply(tier.getPrice())); remaining -= tierUnits; previousMax = tier.getTierMax(); } // 超过最大阶梯的部分按最高单价计算 if (remaining > 0) { Tariff lastTier = tiers.get(tiers.size() - 1); total = total.add(BigDecimal.valueOf(remaining) .multiply(lastTier.getPrice())); } return total.setScale(2, RoundingMode.HALF_UP); } }这段代码的关键设计点有三个。第一,用 BigDecimal 而不是 double 计算金额,避免浮点误差导致对不上账,这是后面避坑章节会重点讲的。第二,阶梯的上限从数据库读,而不是写死在一堆 if 判断里。这样电价调整时只改数据库费率表,不用重新编译代码,答辩时可以现场演示「修改电价 → 重新生成账单」的完整过程。第三,最后那个兜底逻辑很重要,实际使用中用电量可能超过费率表配置的最大档,如果不处理,超出部分会被静默丢弃,账单金额少算。
调用这个服务的时候,还需要从抄表记录里算出用电量。通常在 service 里组合两个动作:先查这条识别记录的上期读数和本期读数,算出差值得到 consumption,再调用 calculateBillAmount 得出金额,最后生成账单记录。
3.3 缴费模块与账单状态机:防止重复支付的设计
缴费模块最容易出的问题是重复支付。用户连续点击两次「确认缴费」,如果代码没有做幂等控制,系统可能生成两条缴费记录,账单状态也混乱。我在设计时用了一个简单的状态机来解决。
账单的状态流转是:待支付 → 已支付 → 已退款。任何一次更新都基于当前状态做条件更新,而不是先查再改。
@Transactional public boolean payBill(Integer billId, Integer userId) { // 条件更新:只有状态为“待支付”时,才允许变为“已支付” int updated = billMapper.updateStatusWithLock( billId, BillStatus.PAID.getCode(), BillStatus.UNPAID.getCode(), LocalDateTime.now() ); if (updated == 0) { // 更新行数为0,说明该账单已经不是待支付状态,直接返回失败 return false; } // 插入缴费记录 Payment payment = new Payment(); payment.setBillId(billId); payment.setUserId(userId); payment.setPayAmount(billMapper.selectAmount(billId)); payment.setPayMethod("在线支付"); payment.setPayTime(LocalDateTime.now()); paymentMapper.insert(payment); return true; }对应的 SQL 是这条 update 语句,注意 where 条件里的账态判断:
UPDATE bill SET status = #{targetStatus}, pay_time = #{payTime} WHERE bill_id = #{billId} AND status = #{expectedStatus}这种写法的妙处在于,数据库行锁会把并发请求串行化。两个请求同时进来,只有一个能更新成功,另一个 updated 为 0 直接返回失败。不需要引入分布式锁,也不需要复杂的 Redis 处理,用一个条件更新就解决了幂等问题。答辩时这段讲清楚,比你堆十个设计模式有用得多。
统计报表模块可以顺带说一句:月度用电统计和费用汇总,用 SQL 的 GROUP BY 和聚合函数实现,数据量大时不要在前端做统计,让 SQL 去算。
4. 避坑清单:五个最容易翻车的细节
这套系统开发中有几个坑属于「不踩不知道,踩了耽误一周」的类型,我按现象 → 原因 → 解决的方式写在这里,你做到对应功能时提前看一眼。
4.1 电费金额精度丢失:用 BigDecimal 而不是 double
现象:账单算出来的金额偶尔差一两分钱,对账怎么都对不上。
原因:Java 的 double 和 float 是二进制浮点数,0.1 在二进制里是无限循环小数,计算精度会丢失。电费计算涉及乘法和累加,误差会被放大。
解决:所有金额字段和计算全部用 BigDecimal,构造时用字符串入参,不能用new BigDecimal(0.1)这种写法。数据库里金额字段用 DECIMAL(10,2),实体类属性用 BigDecimal 类型,不要转成 double 再算。
// 错误的写法 double result = 0.1 * 3; // 正确的写法 BigDecimal result = new BigDecimal("0.1").multiply(new BigDecimal("3"));4.2 抄表数异常:本期读数小于上期的校验时机
现象:录入抄表数据时,不小心把本期读数填得比上期小,用电量变负数,账单金额变成负数。
原因:校验逻辑没放对位置。有些同学只在页面做了前端校验,后端 service 没有做兜底校验,凑巧绕过了页面直接调用接口数据就是错的。
解决:在 service 层入参加一道校验,把异常情况拦截在业务入口:
if (currentReading.compareTo(lastReading) < 0) { throw new BusinessException("本期读数不能小于上次读数,请核对电表"); } // 如果读数完全相等且非零,提示用户确认是否为真实用电 if (currentReading.compareTo(lastReading) == 0) { throw new BusinessException("本期读数与上期一致,请确认是否抄录正确"); }4.3 重复缴费:并发请求导致账单重复扣款
现象:用户在缴费页面连点了两次「确认」,或者发起支付后又刷新页面重提,生成两条缴费记录。
原因:先查询账单状态再更新的做法在高并发下存在时间差,两个请求都查到了「待支付」,然后各自执行更新,造成重复入账。
解决:用第 3 章讲的条件更新方式,UPDATE bill SET status = ? WHERE bill_id = ? AND status = '待支付',利用数据库行锁保证同一时刻只有一个请求能改状态。更新的行数是 0 就说明已经被处理过,业务直接返回「支付成功」且不重复入账,或返回「该账单已支付」。
4.4 中文乱码:过滤器和连接串参数缺失
现象:页面录入的中文保存到数据库变成问号,或者数据库的中文显示到页面成了乱码。
原因:没配置统一的字符编码过滤器,MySQL 连接串也没有指定编码,数据库表本身的字符集也不是 utf8。
解决:Spring Boot 里配置一个编码过滤器,同时确认连接串带上了编码参数:
spring.datasource.url=jdbc:mysql://localhost:3306/electricity_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai建表时显式指定字符集,CREATE TABLE bill (...) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4。这招能避免 90% 的乱码问题,剩下 10% 是 IDE 控制台显示问题,重启控制台就好。
4.5 时间字段的时区问题:账单日期差了 8 小时
现象:缴费记录的时间比实际时间慢了 8 小时,跨天统计对不上。
原因:MySQL 连接串没指定 serverTimezone,驱动和数据库的时区不一致,导致 LocalDateTime 存入后被当成 UTC 时间处理。
解决:在连接串里加上serverTimezone=Asia/Shanghai,同时实体类的日期字段统一用 LocalDateTime,不要混用 java.util.Date。这样避免了大部分时区转换的隐性坑。如果是已上线的系统,这种问题改起来很麻烦,所以开始建库时就把时区配好属于标准的后悔药动作。
5. 验收与答辩:怎么把系统讲出工程感
很多人的代码写得不错,但一进答辩现场就讲乱了。问题出在验收阶段只测了「能跑」,没有按业务链路去测。电费管理系统验收时,我会按下面这套用例走一遍,你照着试,基本能覆盖导师会问的场景。
5.1 验收用例清单
| 场景 | 操作步骤 | 预期结果 |
|---|---|---|
| 阶梯电价计算 | 配置两档费率,录入用电量 350 度 | 按分段累计金额正确,保留两位小数 |
| 账单状态流转 | 对一个待支付账单完成缴费 | 账单变为已支付,生成支付流水 |
| 并发重复缴费 | 两个请求同时支付同一账单 | 只有一条支付记录生效 |
| 异常抄表数据 | 录入本期读数小于上次读数 | 系统拦截并给出提示 |
| 越权访问 | 业主账号直接打开管理员账单列表 URL | 被拦截器拦截并跳转登录页 |
| 数据追溯 | 从账单点击关联的抄表记录 | 能看到对应电表和读数记录 |
| 删除保护 | 删除已被账单引用的费率 | 系统提示存在关联数据,禁止删除 |
| 统计报表 | 查看某房间一年月度用电量 | 图表和明细数据一致 |
5.2 答辩展示技巧
答辩演示时别从头开始点菜单,要有侧重。我会先打开费率表,展示阶梯电价的可配置性,然后录一笔抄表数据,让导师看到「抄表 → 自动计费 → 生成账单 → 缴费销账」这条完整链路。中间每一步都停下来解释一两个关键代码点,特别是 BigDecimal 精度处理和条件更新防重复支付那段。
如果导师问「这套系统还有什么可以改进」,不要直接说「没有」,而是给出一个具体的演进方向,比如「后续可以用定时任务实现每月自动抄表,再引入 Redis 缓存优化报表查询」。这点要提前准备,能让答辩收尾加分。实际开发中我给自己定的标准是「演示时不做假数据、不手动改数据库、所有功能真实走一遍」。从那以后每次接手这类管理信息系统,我都强制自己先跑全套验收用例再谈交付。希望这篇方案能帮到你。
本文还有配套的精品资源,点击获取