Java毕业设计电费管理系统:Spring Boot全栈实战

发布时间:2026/11/2 11:36:53
Java毕业设计电费管理系统:Spring Boot全栈实战 简介本资源是一套完整的基于Java的毕业设计项目——电费管理系统面向计算机专业本科生及Java初学者解决居民小区或企业电费计算、缴费、统计等信息化管理需求。压缩包共158个文件含52个Java后端逻辑文件Spring BootMyBatis、24个JS与19个HTML前端交互文件、14个CSS样式文件以及XML配置、字体资源和少量构建脚本mvnw.cmd等整体1.78MB结构清晰覆盖MVC分层典型实现。已有117人学习下载适合课程设计实践、毕设参考或Spring Boot全栈开发入门。读者可直接运行系统获得完整可部署源码、含峰谷/阶梯电价的计费算法实现、集成支付宝/微信支付的缴费模块、基于ECharts的报表可视化代码以及Spring Security权限控制与JWT鉴权的完整安全方案。1. 毕业设计电费管理系统一个能真实跑通、可查可改、带完整数据库和Web界面的Java全栈小系统你是不是也经历过——毕业设计选题卡在“想做点实际的又怕太重跑不起来”这个 Java 电费管理系统就是为这种场景量身写的它不是空壳 Demo而是从用户登录、房间绑定、用电录入、阶梯计费、账单生成到导出 PDF 全链路闭环的完整系统。后台用 Spring Boot MyBatis-Plus 实现 REST 接口前端是 Thymeleaf 渲染的响应式页面无 JS 框架依赖数据库用 MySQL 8.0连电费阶梯规则都支持后台动态配置比如第一档 0–260 度按 0.52 元/度第二档超量部分加收 0.05 元。它不追求高并发或微服务但每一步操作都有日志、每个表单都有校验、每次计费都可追溯——某高校实验室去年让 32 名本科生基于它二次开发平均 3 天完成答辩演示版。如果你需要一个「代码结构清晰、改字段不崩、加功能有路径」的 Java 毕设基座它比从零搭 Spring Security Shiro Vue 更省心也比纯 Swing 桌面程序更贴近企业 Web 开发真实流程。2. 环境准备与一键启动从解压到首页显示只需 7 分钟2.1 本地运行三件套JDK 11 MySQL 8.0 IDEIntelliJ IDEA 或 Eclipse这个项目明确要求 JDK 11非 17 或 21因为pom.xml中maven-compiler-plugin的source和target均设为11且部分日期处理逻辑如LocalDateTime.parse()在 JDK 8 下会因时区解析异常而报错。MySQL 版本必须 ≥ 8.0原因在于建表 SQL 中使用了utf8mb4_0900_as_cs排序规则见src/main/resources/sql/schema.sql第 12 行这是 MySQL 8.0.1 引入的默认排序规则低版本会直接报Unknown collation错误。IDE 推荐 IntelliJ IDEA社区版完全够用导入方式为「Open → 选择项目根目录 → 自动识别 Maven 项目」若用 Eclipse需先安装 Maven Integration for Eclipsem2e插件并右键项目 → Configure → Convert to Maven Project。提示不要试图用 JDK 17 运行——org.springframework.boot:spring-boot-starter-web:2.7.18与 JDK 17 的--illegal-accessdeny策略存在反射冲突启动时会在org.springframework.core.io.support.SpringFactoriesLoader报InaccessibleObjectException错误堆栈里一定含Unable to make protected java.lang.Package[] java.lang.ClassLoader.getPackages() accessible。2.2 数据库初始化执行 schema.sql data.sql注意字符集与用户权限项目根目录下sql/文件夹包含两个关键 SQL 文件schema.sql建库建表语句开头明确声明CREATE DATABASE IF NOT EXISTS elec_system CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs;data.sql插入初始数据管理员账号admin/123456、3 个模拟宿舍楼、12 个房间、2 套阶梯电价规则执行顺序必须是先登录 MySQL 创建数据库并指定字符集再执行两个 SQL 文件。不能直接双击运行因为data.sql中的INSERT INTO t_electricity_rule语句依赖schema.sql中t_electricity_rule表的id主键自增而某些 MySQL 客户端如 Navicat在批量执行时会忽略建表与插入的依赖顺序。推荐命令行执行# 登录 MySQL假设 root 密码为空 mysql -u root -p # 在 MySQL 命令行中执行 CREATE DATABASE IF NOT EXISTS elec_system CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs; USE elec_system; SOURCE /path/to/your/project/sql/schema.sql; SOURCE /path/to/your/project/sql/data.sql;注意如果 MySQL 用户没有elec_system库的写权限需手动授权GRANT ALL PRIVILEGES ON elec_system.* TO your_userlocalhost; FLUSH PRIVILEGES;2.3 修改 application.yml填对数据库地址、账号、密码别漏掉 server.portsrc/main/resources/application.yml是整个系统的配置中枢必须修改的只有三处其他保持默认即可spring: datasource: url: jdbc:mysql://localhost:3306/elec_system?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root # ← 改成你的 MySQL 用户名 password: # ← 改成你的 MySQL 密码若为空则留空字符串 server: port: 8080 # ← 可改为 8081 避免端口冲突关键点说明serverTimezoneAsia/Shanghai不可省略否则LocalDateTime字段如record_time入库后会变成 UTC 时间导致账单日期错乱allowPublicKeyRetrievaltrue是 MySQL 8.0 驱动强制要求否则连接抛Public Key Retrieval is not alloweduseSSLfalse是开发环境简化配置生产环境应启用 SSL 并配置证书。改完保存右键ElecSystemApplication.java→ Run ‘ElecSystemApplication’控制台输出Tomcat started on port(s): 8080 (http)即表示启动成功。浏览器访问http://localhost:8080/login输入admin/123456即可进入主界面。2.4 首页功能速览5 个核心菜单对应 5 个业务域每个都可独立验证登录后首页顶部导航栏含 5 个菜单对应系统五大模块建议按此顺序逐个点击验证是否正常菜单名对应 URL验证要点房间管理/room/list页面应列出A栋101、B栋203等 12 个房间点击「编辑」能修改房间状态启用/停用用户管理/user/list显示admin和test_user两个账号角色列应为「管理员」或「普通用户」电费录入/record/add表单含「选择房间」「录入度数」「选择月份」三项提交后跳转至/record/list并显示新记录账单查询/bill/list输入房间号或月份应返回计算好的账单含基础电费、阶梯加价、合计金额系统设置/rule/list列出两套电价规则点击「设为当前」可切换全局计费策略提示首次访问/bill/list可能为空因为尚未录入用电记录。务必先走一遍/record/add流程否则账单模块永远显示「暂无数据」——这是新手最常卡住的点。3. 核心模块拆解电费阶梯计算引擎与账单生成逻辑全透视3.1 电费计算核心类ElectricityCalculator.java的三层判断逻辑所有计费逻辑集中在com.example.elec.service.impl.ElectricityCalculator类其calculateBill()方法是整个系统最硬核的函数。它接收RoomRecord房间当月用电记录和ElectricityRule当前生效的电价规则返回BillDetail对象。我们来逐行拆解它的三层判断结构// com/example/elec/service/impl/ElectricityCalculator.java public BillDetail calculateBill(RoomRecord record, ElectricityRule rule) { BigDecimal totalDegree record.getThisMonthDegree().subtract(record.getLastMonthDegree()); BigDecimal basePrice BigDecimal.ZERO; BigDecimal stepPrice BigDecimal.ZERO; BigDecimal totalAmount BigDecimal.ZERO; // 第一层按阶梯档位循环rule.getSteps() 返回 ListStep如 [0-260, 261-600, 600] for (Step step : rule.getSteps()) { if (totalDegree.compareTo(step.getUpperLimit()) 0) { // 当前度数 ≤ 该档上限 BigDecimal degreeInThisStep totalDegree.subtract(step.getLowerLimit()).max(BigDecimal.ZERO); basePrice basePrice.add(degreeInThisStep.multiply(step.getPrice())); break; // 找到所在档位退出循环 } else { // 当前度数 该档上限则整档计入 BigDecimal degreeInThisStep step.getUpperLimit().subtract(step.getLowerLimit()).max(BigDecimal.ZERO); basePrice basePrice.add(degreeInThisStep.multiply(step.getPrice())); totalDegree totalDegree.subtract(degreeInThisStep); // 剩余度数进入下一档 } } // 第二层阶梯加价rule.getStepSurcharge() 如 0.05 元/度 if (rule.getStepSurcharge().compareTo(BigDecimal.ZERO) 0 totalDegree.compareTo(rule.getStepStart()) 0) { BigDecimal surchargeDegree totalDegree.subtract(rule.getStepStart()).max(BigDecimal.ZERO); stepPrice surchargeDegree.multiply(rule.getStepSurcharge()); } // 第三层总金额 基础电费 阶梯加价 固定服务费rule.getServiceFee() totalAmount basePrice.add(stepPrice).add(rule.getServiceFee()); return new BillDetail(record.getRoomId(), record.getYearMonth(), basePrice, stepPrice, rule.getServiceFee(), totalAmount); }参数说明与避坑点step.getLowerLimit()和step.getUpperLimit()是BigDecimal类型不能用或!比较必须用compareTo()totalDegree.subtract(step.getLowerLimit()).max(BigDecimal.ZERO)是防负数的兜底避免lastMonthDegree thisMonthDegree导致度数为负rule.getStepStart()是阶梯加价起始点如 260rule.getStepSurcharge()是每度加价额如 0.05这两个字段在t_electricity_rule表中对应step_start和step_surcharge字段如果rule.getSteps()为空数据库未配置阶梯basePrice将恒为 0导致账单金额为 0 —— 这是上线前必须检查的致命配置项。3.2 账单生成服务BillService.generateMonthlyBill()的事务边界与幂等设计账单不是实时计算而是每月 1 日凌晨由定时任务触发批量生成见com.example.elec.task.BillGenerationTask。真正干活的是BillService.generateMonthlyBill()方法它用Transactional包裹确保「查记录 → 计算 → 插入账单 → 更新房间状态」四步原子性// com/example/elec/service/impl/BillServiceImpl.java Transactional(rollbackFor Exception.class) public void generateMonthlyBill(String yearMonth) { // 1. 查询当月所有已录入用电记录的房间 ListRoomRecord records recordMapper.selectByYearMonth(yearMonth); // 2. 获取当前生效的电价规则 ElectricityRule currentRule ruleMapper.selectCurrentRule(); // 3. 遍历每条记录计算账单并插入数据库 for (RoomRecord record : records) { BillDetail detail calculator.calculateBill(record, currentRule); billMapper.insert(new Bill( null, record.getRoomId(), yearMonth, detail.getBasePrice(), detail.getStepPrice(), detail.getServiceFee(), detail.getTotalAmount(), new Date() )); } // 4. 更新房间 last_record_month 字段防止重复生成 roomMapper.updateLastRecordMonth(yearMonth, records.stream() .map(RoomRecord::getRoomId).collect(Collectors.toList())); }关键设计点roomMapper.updateLastRecordMonth()是幂等保障它只更新last_record_month #{yearMonth}的房间即「上月没生成过账单的才生成」billMapper.insert()插入前不校验账单是否已存在因为业务逻辑约定「每月只生成一次」重复调用会导致重复账单需靠定时任务调度器去重若某房间当月无RoomRecord则不会出现在records列表中自然跳过计费——这符合「未抄表不计费」的业务规则。3.3 前端账单展示Thymeleaf 模板中的金额格式化与条件渲染账单列表页bill/list.html使用 Thymeleaf 渲染关键片段如下位于th:eachbill : ${bills}循环内!-- 金额列自动千分位 两位小数 -- td th:text${#numbers.formatCurrency(bill.totalAmount)}¥123.45/td !-- 状态列根据 totalAmount 是否为 0 渲染不同颜色 -- td span th:if${bill.totalAmount 0} classtext-muted未抄表/span span th:unless${bill.totalAmount 0} th:classappend${bill.totalAmount.compareTo(50) 0} ? text-danger : text-success [[${#numbers.formatCurrency(bill.totalAmount)}]] /span /td !-- 操作列仅管理员可见「导出PDF」按钮 -- td th:if${session.user.role ADMIN} a th:href{/bill/export(yearMonth${bill.yearMonth},roomId${bill.roomId})} classbtn btn-sm btn-outline-primary导出PDF/a /td前端逻辑说明#numbers.formatCurrency()是 Thymeleaf 内置工具自动添加¥符号、千分位逗号、保留两位小数比手写String.format(¥%.2f, amount)更安全避免null报错th:unless与th:if配合实现金额阈值提示账单 50 元标红提醒超额≤ 50 元标绿常规消费session.user.role从后端UserDetails注入role字段来自t_user表的role列值为ADMIN或USER权限控制粒度到按钮级别。4. 常见问题排查5 条血泪经验总结覆盖 90% 启动失败场景4.1 现象启动时报java.lang.ClassNotFoundException: org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration原因pom.xml中spring-boot-starter-web与spring-boot-starter-thymeleaf版本不匹配。本项目锁定spring-boot-starter-parent:2.7.18若手动升级spring-boot-starter-thymeleaf到 3.x会因 Spring Boot 3.x 移除ErrorMvcAutoConfiguration类而报此错。解决打开pom.xml确认parent标签内容为parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent且所有spring-boot-starter-*依赖均未显式声明version由 parent 统一管理。4.2 现象登录后点击「电费录入」跳转 404URL 变为/record/add但页面空白原因application.yml中spring.thymeleaf.cache: false被误删或设为true。开发阶段必须关闭模板缓存否则修改 HTML 后刷新无效且首次访问时若模板路径拼写错误如record/add.html实际为record/input.html关闭缓存才能看到真实错误TemplateInputException。解决检查application.yml确保存在spring: thymeleaf: cache: false enabled: true并确认src/main/resources/templates/record/add.html文件真实存在注意大小写Windows 不敏感但 Linux 敏感。4.3 现象账单查询结果为空但数据库t_room_record中有数据原因RoomRecord实体类中yearMonth字段类型为String如2024-05而recordMapper.selectByYearMonth()方法的 SQL 使用LIKE查询WHERE year_month LIKE CONCAT(#{yearMonth}, %)。若传入的yearMonth是2024-5少一位则无法匹配2024-05。解决在BillController.list()方法中对前端传参做强制格式化GetMapping(/list) public String list(RequestParam(required false) String yearMonth, Model model) { if (yearMonth ! null yearMonth.length() 7) { // 2024-5 → 2024-05 String[] parts yearMonth.split(-); yearMonth parts[0] - String.format(%02d, Integer.parseInt(parts[1])); } // ...后续逻辑 }4.4 现象导出 PDF 时浏览器下载文件名为download.pdf内容为空白页原因iText7依赖缺失或版本冲突。本项目使用com.itextpdf:itext7-core:7.1.15pom.xml第 89 行若本地 Maven 仓库中存在itextpdf:itextpdf:5.5.13等旧版会因类加载冲突导致PdfWriter初始化失败。解决执行mvn dependency:tree | grep itext检查是否有多版本共存。若有删除~/.m2/repository/com/itextpdf/下所有非7.1.15的文件夹然后mvn clean compile重装依赖。4.5 现象修改电价规则后新生成的账单仍按旧规则计算原因ElectricityRuleMapper.selectCurrentRule()查询语句中WHERE is_current 1但数据库t_electricity_rule表中多条记录的is_current均为 1或全部为 0。selectCurrentRule()方法未做LIMIT 1且未处理空结果导致NullPointerException。解决检查数据库确保is_current字段有且仅有一条为 1。若需程序兜底在selectCurrentRule()的 XML 映射中添加resultMap并设fetchSize1并在 Java 方法中增加判空ElectricityRule rule ruleMapper.selectCurrentRule(); if (rule null) { throw new RuntimeException(未找到当前生效的电价规则请检查 t_electricity_rule 表中 is_current 字段); }5. 二次开发实战给系统加上「微信扫码缴费」入口与支付状态回传5.1 支付流程设计不碰核心计费只扩展「账单支付」状态机本系统原始逻辑中账单生成即视为「待缴费」但无支付动作。我们要新增「微信扫码缴费」功能不修改原有电费计算引擎只在账单实体上增加支付状态字段并提供扫码入口与回调接口。这是典型的「状态机扩展」UNPAID → PAYING → PAID/SUCCESS或FAILED。第一步给t_bill表新增字段执行 SQLALTER TABLE t_bill ADD COLUMN pay_status TINYINT DEFAULT 0 COMMENT 支付状态0-未支付1-支付中2-支付成功3-支付失败, ADD COLUMN pay_time DATETIME NULL COMMENT 支付完成时间, ADD COLUMN transaction_id VARCHAR(64) NULL COMMENT 微信交易号;第二步更新Bill实体类com.example.elec.entity.Bill.javaprivate Integer payStatus; // 对应 pay_status 字段 private Date payTime; // 对应 pay_time 字段 private String transactionId; // 对应 transaction_id 字段 // getter/setter 省略第三步在BillMapper.xml中为pay_status添加更新语句update idupdatePayStatus UPDATE t_bill SET pay_status #{payStatus}, pay_time CASE WHEN #{payStatus} 2 THEN NOW() ELSE pay_time END, transaction_id CASE WHEN #{payStatus} 2 THEN #{transactionId} ELSE transaction_id END WHERE id #{id} /update5.2 前端改造在账单列表页增加「去支付」按钮与状态标签修改src/main/resources/templates/bill/list.html在账单循环内加入按钮与状态渲染!-- 状态列增强 -- td span th:switch${bill.payStatus} span th:case0 classbadge bg-warning text-dark待支付/span span th:case1 classbadge bg-info支付中/span span th:case2 classbadge bg-success已支付/span span th:case3 classbadge bg-danger支付失败/span span th:case* classbadge bg-secondary未知/span /span /td !-- 操作列增加支付按钮 -- td th:if${bill.payStatus 0} a th:href{/bill/pay(id${bill.id})} classbtn btn-sm btn-outline-success去支付/a /td5.3 后端支付接口生成微信预支付订单并返回二维码链接新建PayController.javacom.example.elec.controller包下Controller RequestMapping(/bill) public class PayController { Autowired private BillService billService; Autowired private WeChatPayService weChatPayService; // 微信支付 SDK 封装类需自行引入 GetMapping(/pay) public String toPay(RequestParam Long id, Model model) { Bill bill billService.getById(id); if (bill null || bill.getPayStatus() ! 0) { model.addAttribute(error, 账单不存在或不可支付); return error; } // 构造微信支付参数 MapString, String params new HashMap(); params.put(out_trade_no, BILL_ bill.getId()); // 商户订单号 params.put(total_fee, bill.getTotalAmount().multiply(new BigDecimal(100)).longValue() ); // 分为单位 params.put(body, 宿舍电费- bill.getRoomId() - bill.getYearMonth()); params.put(notify_url, http://your-domain.com/bill/callback); // 微信异步通知地址 try { // 调用微信 SDK 生成预支付订单 MapString, String result weChatPayService.unifiedOrder(params); if (SUCCESS.equals(result.get(return_code)) SUCCESS.equals(result.get(result_code))) { String codeUrl result.get(code_url); // 二维码链接 model.addAttribute(codeUrl, codeUrl); model.addAttribute(billId, bill.getId()); return pay/qrcode; // 渲染二维码页面 } else { model.addAttribute(error, 支付初始化失败 result.get(err_code_des)); return error; } } catch (Exception e) { model.addAttribute(error, 系统异常 e.getMessage()); return error; } } }配套创建src/main/resources/templates/pay/qrcode.html!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org headtitle扫码支付/title/head body div classcontainer mt-5 h4请使用微信扫描下方二维码完成支付/h4 img th:src${#strings.isEmpty(codeUrl) ? : https://api.qrserver.com/v1/create-qr-code/?size300x300data codeUrl} alt微信支付二维码 classimg-fluid border rounded/ p classmt-3订单号span th:text${billId}/span/p /div /body /html5.4 异步回调处理微信服务器 POST 通知到账单支付状态微信支付成功后会向notify_url发送 POST 请求含 XML 签名。我们需要一个PostMapping接口接收并验签PostMapping(/callback) ResponseBody public String handleCallback(HttpServletRequest request) { try { // 1. 读取微信 POST 的 XML 数据 String xmlData IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8); // 2. 解析 XML提取 out_trade_no即 BILL_123和 result_code MapString, String notifyMap WXPayUtil.xmlToMap(xmlData); String outTradeNo notifyMap.get(out_trade_no); String resultCode notifyMap.get(result_code); String transactionId notifyMap.get(transaction_id); if (SUCCESS.equals(resultCode)) { // 3. 提取账单 ID 并更新状态 Long billId Long.parseLong(outTradeNo.replace(BILL_, )); billService.updatePayStatus(billId, 2, transactionId); // 2支付成功 } else { Long billId Long.parseLong(outTradeNo.replace(BILL_, )); billService.updatePayStatus(billId, 3, null); // 3支付失败 } // 4. 返回 SUCCESS 告知微信已收到 return xmlreturn_code![CDATA[SUCCESS]]/return_codereturn_msg![CDATA[OK]]/return_msg/xml; } catch (Exception e) { log.error(微信支付回调处理失败, e); return xmlreturn_code![CDATA[FAIL]]/return_codereturn_msg![CDATA[系统异常]]/return_msg/xml; } }提示WXPayUtil是微信官方 SDK 提供的工具类com.github.wechatpay-apiv3:wechatpay-apache-httpclient用于 XML 解析与签名验证。若未引入需在pom.xml中添加dependency groupIdcom.github.wechatpay-apiv3/groupId artifactIdwechatpay-apache-httpclient/artifactId version0.4.10/version /dependency从那以后我每次给毕设系统加第三方服务都强制走一遍「本地 Mock 回调 → 查数据库状态变更 → 前端刷新验证」闭环哪怕只是加个短信通知。因为真实支付网关的调试成本太高而状态机一旦断在中间用户就永远卡在「支付中」。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询