Java毕设实战:电费管理系统从零跑通与二次开发指南 简介这份资源是面向高校计算机相关专业学生与Java初学者的一份毕业设计完整项目源码主题为基于Java的电费管理系统可用于课程设计参考、毕设选题落地或Spring Boot全栈练手。压缩包共158个文件以52个java源文件为核心业务代码辅以24个js、19个html、14个css构成前端页面与交互另有10个xml配置、若干字体图标文件及properties、json等配置资源整体约1.78MB体量轻便、结构完整。项目围绕用户管理、电费计算、缴费管理、数据统计与缴费提醒等模块展开采用MVC分层与Spring Boot、MyBatis、MySQL技术组合并涉及权限控制、报表导出与第三方支付、短信服务集成思路适合对照学习接口设计、计费算法与前后端协作方式。目前已有117人学习可作为毕设答辩演示与二次开发的基础工程。1. 从一份“能跑起来的毕设”说起电费管理系统到底解决什么问题每年毕业季后台总有人问同一句话有没有那种下载下来、改改数据库密码就能跑、答辩时还能讲出点架构的 Java 项目这份电费管理系统压缩包就是冲着这个需求来的。它把“小区/宿舍/商户抄表—计费—缴费—欠费提醒”这条最典型的业务线做成了一个完整的 Java Web 单体应用前端页面、后端接口、数据库脚本、登录鉴权一应俱全。你拿到手不是一堆散落的类而是一个能启动、能登录、能录入电表读数、能自动算出阶梯电费并生成缴费单的闭环系统。适合两类人一是赶毕设、需要一套结构清晰可讲解的底子的同学二是刚接触 Java Web、想找一个业务不空泛的练手项目的开发者。电费这个场景的好处是业务规则明确——单价、阶梯、抄表周期、欠费状态全是能落到代码里的硬逻辑不像某些“管理系统”只有增删改查的空壳。2. 技术栈与目录结构一个标准 Java Web 单体是怎么搭起来的2.1 技术选型为什么是这套组合这类毕设级 Java 项目最常见的做法是 Spring Boot MyBatis MySQL Thymeleaf或原生 JSP/HTML 少量 JS打包成可执行 jar 直接java -jar启动。选 Spring Boot 而不是传统 SSM 的原因很实际省掉 web.xml、省掉一堆 XML 配置application.yml里写几行数据库连接就能跑答辩演示时不会因为 Tomcat 版本对不上翻车。持久层用 MyBatis 而不是 JPA是因为电费系统里“按用户查账单”“按月统计用电量”这类查询需要手写 SQLMyBatis 的 Mapper XML 写起来直观也方便你在答辩时指着 SQL 讲业务。下面是一个典型的pom.xml核心依赖片段我按这个项目场景补全了最可能用到的版本区间具体版本以你解压后看到的为准dependencies !-- Web 容器与 MVC提供 Controller 和内置 Tomcat -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 模板引擎服务端渲染页面避免前后端分离的额外部署成本 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency !-- MyBatis 整合 Spring Boot -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Lombok省掉实体类 getter/setter -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies逻辑说明spring-boot-starter-web负责把 Controller 暴露成 HTTP 接口并内嵌 Tomcatthymeleaf让后端直接把数据塞进 HTML 模板返回适合毕设这种“页面不多但要能看”的场景mybatis-spring-boot-starter负责把 Mapper 接口扫描成 Bean。参数上要注意mybatis-spring-boot-starter的版本要和 Spring Boot 大版本匹配2.x 配 Boot 2.x3.x 配 Boot 3.x配错了启动直接报NoSuchMethodError这是新手最容易踩的第一坑。2.2 目录结构每个包负责什么解压后典型的src/main结构大致如下我按职责标注了每个目录的作用src/main/java/com/example/electricity/ ├── ElectricityApplication.java # 启动类main 方法入口 ├── config/ # 拦截器、跨域、MyBatis 配置 │ └── LoginInterceptor.java # 登录态校验拦截器 ├── controller/ # 接收 HTTP 请求参数校验后调 service │ ├── LoginController.java │ ├── UserController.java # 用户/住户管理 │ ├── MeterController.java # 电表读数录入 │ └── BillController.java # 账单查询与缴费 ├── service/ # 业务逻辑计费规则在这里 │ ├── BillService.java │ └── impl/BillServiceImpl.java ├── mapper/ # MyBatis 接口 │ └── BillMapper.java ├── entity/ # 与数据库表对应的实体 │ ├── User.java │ ├── MeterReading.java │ └── Bill.java └── common/ # 统一返回、常量、工具类 └── Result.java src/main/resources/ ├── application.yml # 数据库连接、端口、MyBatis 路径 ├── mapper/ # Mapper XML写 SQL │ └── BillMapper.xml ├── templates/ # Thymeleaf 页面 │ ├── login.html │ ├── bill_list.html │ └── meter_input.html └── static/ # css/js/图片这个分层是标准的 Controller → Service → Mapper 三层。Controller 只做参数接收和返回封装Service 里放计费这种“有规则”的逻辑Mapper 只管 SQL。你二次开发时改业务基本只动 Service改查询动 Mapper XML改页面动 templates职责边界清楚不会一改就牵一发动全身。2.3 数据库设计三张核心表撑起整条业务线电费系统的数据模型不复杂核心就是“人—表—账”三张表。下面是我按这类项目常见做法整理的建表语句字段名和类型你可以对照解压包里的.sql文件核对-- 住户/用户表 CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 密码常见做法是 MD5 或 BCrypt, real_name VARCHAR(50) COMMENT 住户姓名, room_no VARCHAR(20) COMMENT 房号, phone VARCHAR(20), status TINYINT DEFAULT 1 COMMENT 1正常 0停用 ); -- 电表读数表每次抄表一条记录 CREATE TABLE t_meter_reading ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, read_value DECIMAL(10,2) NOT NULL COMMENT 本次读数(kWh), read_date DATE NOT NULL COMMENT 抄表日期, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 账单表由读数计算生成 CREATE TABLE t_bill ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, period VARCHAR(7) NOT NULL COMMENT 账期如 2024-06, usage_amount DECIMAL(10,2) COMMENT 用电量, unit_price DECIMAL(6,2) COMMENT 单价, total_amount DECIMAL(10,2) COMMENT 应缴金额, pay_status TINYINT DEFAULT 0 COMMENT 0未缴 1已缴, pay_time DATETIME );逻辑说明t_meter_reading存的是“每次抄表的绝对读数”用电量要靠“本次读数减上次读数”算出来这是电费系统区别于普通 CRUD 的关键点t_bill把算好的账固化下来避免每次查询都重算也方便对账。参数上DECIMAL(10,2)是金额和电量的常用精度别用FLOAT浮点误差在钱上会出洋相。3. 核心模块拆解计费逻辑、登录拦截与账单查询3.1 计费 Service阶梯电价怎么落到代码里整个系统最值得讲的就是计费。居民阶梯电价通常是“第一档多少度以内一个价超出部分另一个价”这类项目里常见做法是分段累加。下面这段BillServiceImpl的核心方法是我按业务规则补全的典型实现Service public class BillServiceImpl implements BillService { Autowired private MeterReadingMapper readingMapper; Autowired private BillMapper billMapper; // 阶梯规则0-180 度 0.5 元181-400 度 0.55 元400 以上 0.8 元 private static final BigDecimal TIER1_LIMIT new BigDecimal(180); private static final BigDecimal TIER2_LIMIT new BigDecimal(400); private static final BigDecimal PRICE1 new BigDecimal(0.50); private static final BigDecimal PRICE2 new BigDecimal(0.55); private static final BigDecimal PRICE3 new BigDecimal(0.80); Override public Bill calculateBill(Long userId, String period) { // 1. 取本月读数和上月读数 MeterReading current readingMapper.findByUserAndPeriod(userId, period); MeterReading previous readingMapper.findPrevious(userId, period); if (current null) { throw new BizException(该账期没有抄表记录); } BigDecimal lastValue previous null ? BigDecimal.ZERO : previous.getReadValue(); // 2. 用电量 本次读数 - 上次读数 BigDecimal usage current.getReadValue().subtract(lastValue); if (usage.compareTo(BigDecimal.ZERO) 0) { throw new BizException(读数小于上次请核对电表); } // 3. 分段累加计费 BigDecimal total BigDecimal.ZERO; BigDecimal remaining usage; BigDecimal tier1 remaining.min(TIER1_LIMIT); total total.add(tier1.multiply(PRICE1)); remaining remaining.subtract(tier1); if (remaining.compareTo(BigDecimal.ZERO) 0) { BigDecimal tier2 remaining.min(TIER2_LIMIT.subtract(TIER1_LIMIT)); total total.add(tier2.multiply(PRICE2)); remaining remaining.subtract(tier2); } if (remaining.compareTo(BigDecimal.ZERO) 0) { total total.add(remaining.multiply(PRICE3)); } // 4. 落库 Bill bill new Bill(); bill.setUserId(userId); bill.setPeriod(period); bill.setUsageAmount(usage); bill.setTotalAmount(total.setScale(2, RoundingMode.HALF_UP)); bill.setPayStatus(0); billMapper.insert(bill); return bill; } }逻辑说明第一步先拿到本月和上月读数findPrevious按账期倒序取上一条没有上月记录就按 0 起算新装表场景。第二步算用电量并做负数校验这是血泪经验——抄表员手滑把读数录小了不校验就会算出负账单。第三步是阶梯累加的核心先用min截取第一档扣掉后再截第二档剩下的全按第三档。参数上setScale(2, HALF_UP)保证金额两位小数四舍五入别用默认的ROUND_DOWN否则用户会觉得你少算他钱。这段逻辑答辩时能讲出“分段累加”四个字比背 CRUD 强太多。3.2 登录拦截器一个 Session 校验把未登录请求挡在门外毕设项目很少上 Spring Security常见做法是写个HandlerInterceptor校验 Session。下面这段是LoginInterceptor的典型实现Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录页和静态资源 String uri request.getRequestURI(); if (uri.contains(/login) || uri.startsWith(/static)) { return true; } // 校验 Session 中是否有登录用户 Object user request.getSession().getAttribute(loginUser); if (user null) { // 未登录重定向到登录页 response.sendRedirect(/login); return false; } return true; } }逻辑说明preHandle在 Controller 之前执行返回false就中断请求。这里用 URI 白名单放行登录接口和静态资源其余一律查 Session。参数上request.getSession()默认会创建 Session如果只想读不创建可以用getSession(false)但登录场景一般无所谓。注册拦截器要在config里实现WebMvcConfigurer的addInterceptors把/login和/static/**排除掉否则登录页自己也被拦形成死循环——这是新手最常见的翻车点之一。3.3 账单查询Mapper XML 里的一条多表关联账单列表页要同时显示住户姓名、房号和账单金额单表查不出来得关联。下面这条 SQL 是BillMapper.xml里最典型的一条select idselectBillPage resultTypecom.example.electricity.entity.BillVO SELECT b.id, u.real_name AS realName, u.room_no AS roomNo, b.period, b.usage_amount AS usageAmount, b.total_amount AS totalAmount, b.pay_status AS payStatus FROM t_bill b LEFT JOIN t_user u ON b.user_id u.id where if testperiod ! null and period ! AND b.period #{period} /if if testpayStatus ! null AND b.pay_status #{payStatus} /if /where ORDER BY b.period DESC, u.room_no ASC /select逻辑说明LEFT JOIN保证即使住户被删了账单也能查出来实际业务里住户一般软删除但关联查询用左连接更稳。where配合if做动态条件前端传了账期就按账期筛传了缴费状态就按状态筛不传就查全部。参数上#{period}是预编译占位符能防 SQL 注入别图省事用${}拼接。resultType指向一个BillVO视图对象把关联出来的字段接住这样 Controller 直接返回给页面就行不用在 Java 里再拼。4. 跑起来从解压到登录成功的完整步骤4.1 环境准备与数据库导入先把环境对齐JDK 8 或 11看pom.xml里的java.version、Maven 3.6、MySQL 5.7 或 8.0。这三样版本对不上后面全是玄学报错。数据库这块解压包里一般有个sql目录或根目录下的.sql文件先建库再导入# 登录 MySQL 后执行 CREATE DATABASE electricity_db DEFAULT CHARACTER SET utf8mb4; USE electricity_db; SOURCE /你的路径/electricity.sql;逻辑说明utf8mb4而不是utf8是因为住户姓名里可能有生僻字或表情utf8三字节存不下会报错。SOURCE命令在 MySQL 客户端里执行脚本路径用绝对路径最稳。导入完SHOW TABLES;确认三张核心表都在再往下走。4.2 改配置、启动、验证打开src/main/resources/application.yml把数据库连接改成你自己的server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/electricity_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.electricity.entity逻辑说明serverTimezoneAsia/Shanghai必须加MySQL 8 驱动不加时区会报The server time zone value is unrecognized这是最高频的启动失败原因。mapper-locations告诉 MyBatis 去哪找 XML路径写错启动不报错但查询时提示Invalid bound statement很隐蔽。改完在项目根目录执行mvn clean package -DskipTests java -jar target/electricity-0.0.1-SNAPSHOT.jar看到Started ElectricityApplication就成功了浏览器打开http://localhost:8080/login用 SQL 脚本里预置的账号常见是admin/123456登录。进去后按“住户管理 → 录入读数 → 生成账单 → 缴费”的顺序走一遍整条链路通了这个项目就算真正跑起来了。5. 避坑与排查那些启动和运行时的常见问题5.1 启动报数据库连接失败现象控制台刷Communications link failure或Access denied for user。原因通常是 MySQL 没启动、端口不是 3306、或者application.yml里的密码没改。解决先用命令行mysql -uroot -p确认能连上再核对 yml 里的 url、username、password 三处注意 yml 对缩进敏感datasource下的字段多一个空格就解析不到。5.2 页面 404 或静态资源加载不出来现象登录页能开但 css/js 全是 404页面裸奔。原因多半是拦截器把/static/**也拦了或者 Thymeleaf 模板里引用路径写成了绝对路径。解决检查addInterceptors里有没有excludePathPatterns(/static/**)模板里资源引用用{/css/style.css}这种 Thymeleaf 语法别硬写/static/css/style.css。5.3 查询报 Invalid bound statement现象点账单列表后台抛Invalid bound statement (not found)。原因就两个Mapper XML 没被扫描到或者 XML 里的namespace和接口全限定名对不上。解决先看application.yml的mapper-locations路径对不对再看 XML 头部namespacecom.example.electricity.mapper.BillMapper是否和接口包名完全一致一个字母都不能差。5.4 计费结果和手算对不上现象账单金额总是差几毛。原因通常是阶梯分段时用了FLOAT或者setScale的舍入模式不对也可能是“上月读数”取错了记录。解决把findPrevious的 SQL 打出来看是不是按read_date倒序取的第一条金额全程用BigDecimal最后统一setScale(2, HALF_UP)。我一般会在 Service 里加一行日志把用电量和各档金额打出来对账时一眼就能看出是哪一档算歪了。6. 二次开发把这份毕设改成能讲出彩的系统拿到这套代码最忌讳的就是原封不动交上去。答辩老师一眼就能看出是模板项目问两句就露馅。我的建议是挑一个方向做深而不是到处加功能。下面给三个改造方向按投入产出比排序。第一个方向是把计费规则做成可配置。现在单价和阶梯是写死在BillServiceImpl里的常量你可以抽一张t_price_rule表存档位上限和单价Service 启动时加载或每次查询时读这样答辩时能讲“规则与代码解耦”。改造点集中在calculateBill方法把三个PRICE常量换成从PriceRuleMapper查出来的列表循环累加即可。验证方法很简单改数据库里的单价不重启应用重新生成账单看金额变没变。第二个方向是加一个用电量趋势接口。前端用 ECharts 画个折线图后端在BillMapper里加一条按账期GROUP BY求SUM(usage_amount)的 SQLController 返回 JSON。这个改动小、演示效果好答辩时一张趋势图比十页 CRUD 页面有说服力。注意返回的日期要按period升序排否则折线是乱的。第三个方向是把密码存储从明文/MD5 换成 BCrypt。很多毕设模板密码是明文存的这是个硬伤。引入spring-security-crypto依赖只引 crypto 包不引整个 Security登录时用BCryptPasswordEncoder.matches校验注册/改密时encode。改造点在LoginController和UserServiceImplSQL 脚本里的初始密码也要重新生成密文。改造方向改动文件难度答辩加分点计费规则可配置BillServiceImpl、新增 PriceRule 相关中规则与代码解耦用电量趋势图BillMapper.xml、BillController、前端页面低数据可视化密码 BCrypt 加密LoginController、UserServiceImpl、pom.xml低安全意识最后说个习惯。我以前改这类项目总喜欢一口气把功能全加上再启动结果一报错就不知道是哪处改坏的排查半天。后来我强制自己每改一个模块就mvn clean package跑一次、登录点一遍对应页面确认这条链路没断再动下一处。这份电费系统你按“先跑通原版、再单点改造、每步验证”的节奏来基本不会翻车。希望帮到你。本文还有配套的精品资源点击获取