☰
毕业设计Java电费管理系统:环境配置、部署避坑与阶梯电价实现
2026/10/17 7:04:17 网站建设 项目流程

简介:基于Java的电费管理系统毕业设计资源包,面向计算机相关专业毕业生和Java Web方向学习者,帮助解决小区或企业电费自动计算、缴费与统计等业务开发难题。资源包内包含完整项目源码与配套页面,覆盖用户管理、电费计算、缴费管理、数据统计和提醒服务等核心模块,采用Spring Boot、MyBatis、MySQL等主流技术栈,前后端分离的MVC结构便于二次开发与毕业设计答辩讲解。压缩包共158个文件,主要由52个Java源码、24个JS脚本、19个HTML页面、14个CSS样式及10个XML配置组成,另含字体图标与项目说明文档,整体约1.78MB,目录结构清晰,可快速导入IDE运行。已有115人学习下载,适合研读电费阶梯计费算法、权限控制及在线支付集成等完整实现思路,同时可参考其目录划分与分层设计,学习Spring Boot与MyBatis整合和前端资源组织方式,也是一份可复用的课程设计与毕业设计参考资料。

1. 毕业设计电费管理系统(Java):这套源码能直接跑,但先搞清四个前提

下载过毕业设计电费管理系统(Java)源码的人通常有两种反应:一种是解压后看到一堆 XML 和 JSP 就懵了,另一种是导入 IDE 后按网上教程一顿操作,结果数据库就连不上。这套资源的本质是一个完整可运行的 Java Web 项目,包含用户管理、抄表录入、阶梯电费计算、账单生成和缴费记录,适合做课程设计复现、毕设二次开发或者练手 Spring/SpringMVC/MyBatis 的人。但拿到压缩包后别急着双击启动,先把四件事确认清楚:JDK 版本、数据库版本、Tomcat 版本和包内的框架类型。这四件事决定你接下来是 10 分钟跑通还是 2 小时翻车。

2. 环境与启动:从解压到页面跑通的完整路径,三步走

对大多数人来说,毕业设计源码包最大的价值是“能跑的现成系统”,但前提是运行环境对得上。我自己的习惯是先把四个前提确认完,再按建库、导入、部署三步走,每一步踩的坑都写在对应小节里。

2.1 技术栈判断与 JDK 版本选定

解开压缩包后先别急着找 README,直接看根目录结构。这一步决定了后面所有操作方式。

如果根目录有 pom.xml,这是 Maven 工程,依赖会自动从仓库下载,你只需要确认 IDEA 里的 Maven 仓库路径是通的。如果没有 pom.xml,而有 WebContent/WEB-INF/lib 目录,这是普通动态 Web 工程,lib 下的 jar 包要手动引入。这类毕业设计资源的 jar 包版本通常停留在 2015 到 2018 年区间,跟 JDK 8 配合最稳,如果你电脑装的是 JDK 17,很多旧波次的 jar 直接编译不过。第二看 web.xml 里加载的是什么:如果配的是 DispatcherServlet,说明是 SSM 组合;如果配的是 FilterDispatcher,那更接近 SSH 老工程。两类项目的启动方式完全不一样,先看清楚再动手。

java -version # 期望输出是 1.8.x 系列,例如 java version "1.8.0_202" # 如果输出的是 17、21,建议先装一个 JDK 8,否则后续编译会遇到大量 jar 包版本异常 catalina version # 确认 Tomcat 版本。Tomcat 8.5 对 JDK 8 和 javax.servlet 规范兼容性最好 # Tomcat 10 之后把 javax 换成了 jakarta,老项目在里面基本跑不起来

这里参数没有太多玄学:JDK 版本直接决定 javac 编译时的字节码版本,Tomcat 版本决定它能加载哪种 Servlet API。老项目的 web.xml 里通常声明的是 servlet 3.0/3.1 规范,Tomcat 8.5 支持完整,Tomcat 9 部分支持,Tomcat 10 直接拒绝。所以这套电费管理系统如果依赖的是老版本 Spring 和 MyBatis,用 JDK 8 加 Tomcat 8.5 基本不存在版本拒绝。别迷信最新版,毕业设计资源要的是“能跑”,不是“跑在最新的环境里”。

2.2 数据库初始化:建库脚本的字符集、编码和账号

解压包的 sql 目录下一般会有 electric_bill.sql 这类脚本。不要双击就导入,先用编辑器打开看头 30 行。重点看三处:第一是 CREATE DATABASE 语句的字符集声明,第二是脚本里有没有 DROP TABLE,第三是后面有没有 INSERT INTO 初始化数据(比如管理员账号、阶梯电价档位)。

常见做法是把脚本从命令行导入,执行前把默认字符集改成 utf8mb4,避免后续 JSP 页面和数据库交互时中文变问号。我自己一般这么导入:

mysql -u root -p < electric_bill.sql # 执行后检查核心表是否创建成功: mysql -u root -p -e "USE electric_bill; SHOW TABLES;" # 预期看到 t_user、t_meter_record、t_bill、t_tier_config 等核心表

如果脚本里的建库语句写的是 latin1,建议先手动执行一段替代语句再导入剩余内容:

CREATE DATABASE IF NOT EXISTS electric_bill DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE electric_bill; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', user_no VARCHAR(32) NOT NULL UNIQUE COMMENT '用户编号', user_name VARCHAR(50) NOT NULL COMMENT '用户姓名', address VARCHAR(200) COMMENT '用电地址', meter_no VARCHAR(32) COMMENT '电表编号', role_type TINYINT DEFAULT 2 COMMENT '1管理员 2普通用户', password VARCHAR(64) COMMENT '登录密码', salt VARCHAR(16) COMMENT '密码盐值' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

这段 SQL 里 utf8mb4_general_ci 是大小写不敏感的排序规则,用户编号查询时不会因为大小写产生额外分支。密码字段和 salt 分开存是常见做法,毕业设计里的登录校验一般先查用户再比对 MD5 值。这里有个容易忽略的地方:如果原脚本里的字段类型是 varchar 且没设置长度,后续密码加盐后可能放不下,导入前先确认 password 字段留了 64 位或以上。

2.3 IDEA 导入与 Tomcat 部署:五个配置项逐个核对

导入 IDEA 时不要用 Open 直接选文件夹,建议用 File -> New -> Project from Existing Sources,然后选 Eclipse 或 Maven 模式。等工程索引完成后,逐个核对五个位置:

第一是 Project Structure 里的 Project SDK,必须选 JDK 8,不能选 11 或 17,否则编译期直接报错。第二是 Modules 面板的 Language Level,保持在 8。第三是 Artifacts,Web 工程需要有一个 Web Application Exploded 类型的 artifact,输出目录指向 Tomcat 的 webapps 下的某个名称。第四是 Facets,确认 Web 模块勾选并且 Web Root 指向 WebContent 或 src/main/webapp。第五是 lib 依赖,如果工程没有 pom.xml,需要在 Artifacts 里把 WEB-INF/lib 下的 jar 加入打包列表。

web.xml 里比较关键的配置是 Spring 和 SpringMVC 的加载位置。老资源里常见这么写:

<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>

这里有两个容易踩的点。第一个是 contextConfigLocation 里的 classpath 路径必须和 src 下实际文件名一致,如果包里的 XML 叫 applicationContext-dao.xml,这里就要对得上,否则 Spring 容器启动时报 FileNotFound。第二个是 DispatcherServlet 的 url-pattern 如果配成/,它会拦截所有请求包括静态资源,所以 springmvc.xml 里必须配静态资源放行,具体写法在第 5 章排查部分展开。把上述五项确认完,Tomcat 启动后访问 http://localhost:8080/项目名/login.jsp,能看到登录页,环境这步就算过关了。

3. 核心流程拆解:登录鉴权、抄表录入到账单生成的链路里藏着什么

环境跑通只是第一步,真正有价值的理解在业务流程里。这一章拆解三个核心环节:登录鉴权如何做角色控制、抄表录入如何校验数据合法性、账单生成如何与缴费记录形成闭环。读代码时对照数据库表结构看,理解会快很多。

3.1 登录鉴权:角色模型与登录校验逻辑

这套系统的用户模型一般是两张逻辑角色:管理员和普通住户。管理员可以抄表、调价、查统计;普通住户只能看自己的账单和缴费记录。登录校验的 Service 层代码大体长这样:

public User login(String userNo, String password, Integer roleType) { User user = userMapper.findByUserNo(userNo); if (user == null) { throw new BizException("用户编号不存在"); } String encoded = MD5Util.encode(password + user.getSalt()); if (!encoded.equals(user.getPassword())) { throw new BizException("密码错误"); } if (!roleType.equals(user.getRoleType())) { throw new BizException("角色类型不匹配"); } return user; }

这段逻辑有三个细节值得注意。第一是密码比对用的是password + salt再 MD5,而不是直接 MD5 密码,加盐是为了防彩虹表,虽然毕业设计级别可能没有攻击模型,但这个习惯是对的。第二是抛异常而不是返回 null,Controller 层可以直接通过异常拿到中文提示,避免每个接口都写判空。第三是 roleType 的校验放在密码校验之后,意思是先验证身份再验证权限,顺序反了可能会泄露“这个账号存在”的信息。

参数说明:userNo 是表里的唯一索引,对应 t_user 表的 user_no 字段;roleType 用 TINYINT,1 是管理员,2 是普通住户;MD5Util 是工具类,一般在 common 包下,如果包里没有这个类,可以用 Spring 自带的 DigestUtils.md5DigestAsHex 替代。登录成功后把 user 对象放入 session,后面 Controller 层用拦截器统一判断 session 是否为空,而不是每个方法都写一遍。

3.2 抄表录入:读数校验与表结构设计

抄表是整个业务的数据源头,录入质量直接决定电费算得对不对。表结构里最核心的是 t_meter_record,关键字段是 last_reading(上月表底)、current_reading(本月表底)和 record_month(账单月份)。

录入接口的核心校验逻辑需要处理三个边界:同一月份不能重复录入、本次读数不能小于上次读数、读数不能是负数。

public MeterRecord saveRecord(Integer userId, String month, BigDecimal currentReading) { // 1. 查出该用户最近一次抄表记录,用于校验和计算用量 MeterRecord last = meterRecordMapper.findLastByUserId(userId); if (last != null && last.getRecordMonth().equals(month)) { throw new BizException("该月份已存在抄表记录"); } // 2. 本次读数必须大于等于上次读数 if (last != null && currentReading.compareTo(last.getCurrentReading()) < 0) { throw new BizException("本次读数不能小于上次读数"); } // 3. 计算本月用量 BigDecimal usage = currentReading.subtract( last == null ? BigDecimal.ZERO : last.getCurrentReading()); // 4. 调用阶梯电价计算服务 BigDecimal amount = tierService.calcCharge(usage.setScale(0, RoundingMode.HALF_UP).intValue()); return meterRecordMapper.insert(userId, month, last == null ? BigDecimal.ZERO : last.getCurrentReading(), currentReading, usage, amount); }

这里要特别说一个数值比较的坑:BigDecimal 的 equals 方法会比较精度位数,new BigDecimal("100").equals(new BigDecimal("100.0"))返回 false,所以比较数值时一律用 compareTo,不要用 equals。上面的currentReading.compareTo(last.getCurrentReading()) < 0就是基于这个原则。同时注意抄表读数通常会有小数,但阶梯电价按度算,这里把 usage 四舍五入到整数再参与计算,避免小数分档时出现跨档误差。

参数说明:month 的格式是yyyy-MM,例如2025-06。因为月份字符串是两位补零的,"2025-06".compareTo("2024-12")的结果是正确的,所以表里用 VARCHAR(7) 存月份在毕业设计里是可以接受的。findLastByUserId 的 SQL 用 ORDER BY record_month DESC LIMIT 1 取最近一条,也可以用 id DESC,因为自增 ID 的顺序与录入时间一致。

3.3 账单生成与缴费记录:状态流转如何形成闭环

抄表记录产生应收金额之后,下一环是账单确认和缴费记录。设计上常见的是两张表:t_bill 存储每月应收账单,t_payment 存储缴费流水。账单生成时机有两种做法:一种是录入抄表记录时同步生成账单,另一种是月底定时任务统一生成。毕业设计里前者居多,因为演示时点击抄表就能看到账单,效果直观。

账单状态一般用状态机管理。这里给出一个典型的流转:草稿 0 -> 已出账 1 -> 已缴费 2。录入抄表记录时状态为 0,管理员确认后变为 1,用户缴费后变为 2。每次状态变更都插入一条缴费记录,保证账单的金额变动有迹可循。

-- 查询某个月份未缴费的账单及用户信息 SELECT b.id, u.user_no, u.user_name, b.bill_month, b.amount, b.status FROM t_bill b LEFT JOIN t_user u ON b.user_id = u.id WHERE b.bill_month = '2025-06' AND b.status = 1 ORDER BY u.user_no;

这条 SQL 是缴费页面的核心列表查询。LEFT JOIN 保证即使 t_user 被逻辑删除(如果做了软删除的话),账单记录仍然能显示出来。status = 1 表示已出账未缴费,缴费动作完成后 UPDATE status = 2,同时往 t_payment 插入一条 payment_no、amount、pay_time。这里最容易被忽视的是事务,缴费包含“账单状态更新”加“插入缴费记录”两个写操作,必须放在同一个事务里,否则可能出现钱收了状态没改的脏数据。在 Spring 配置里给这个 Service 方法加上@Transactional注解即可。

4. 电费计算与调参:阶梯分段模型、精度坑和验证方法

电费管理系统最核心的算法是阶梯电价计算。第 2 章建库时我提到了 t_tier_config 表,这一章把它的设计理由、Service 层实现和验证方法讲透。如果你只是把源码跑起来看效果,不关心算法,后面可能被线下答辩问倒,因为导师大概率会追问“阶梯电价的分段参数存在哪里”。

4.1 阶梯电价的数据模型:分段表比硬编码可维护得多

电费阶梯算法常见的两种实现:一种是把档位阈值和价格硬编码在 Java 代码里,另一种是存到数据库配置表。毕业设计源码里两种都有,但比较规范的是后者,因为调价时不用改代码重新部署,在数据库里 UPDATE 一行就行。

阶梯配置表的结构一般长这样:

CREATE TABLE t_tier_config ( id INT PRIMARY KEY AUTO_INCREMENT, tier_no INT NOT NULL COMMENT '档位序号', lower_limit INT NOT NULL COMMENT '该档下限(度)', upper_limit INT COMMENT '该档上限(度),最后一档可为空', price DECIMAL(6,4) NOT NULL COMMENT '该档电价(元/度)', enabled TINYINT DEFAULT 1 COMMENT '是否启用' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='阶梯电价配置表'; -- 示例数据 INSERT INTO t_tier_config (tier_no, lower_limit, upper_limit, price, enabled) VALUES (1, 0, 180, 0.5580, 1), (2, 181, 400, 0.6080, 1), (3, 401, NULL, 0.8580, 1);

这里设计上有讲究:lower_limit 和 upper_limit 是两个独立字段而不是只存 upper_limit,是因为每个档位的“下限”对分段判断非常重要。价格字段用 DECIMAL(6,4),支持到小数点后四位,电价以后每度 0.0001 元的调整也能容纳。最后一档的 upper_limit 置为 NULL,表示超过 400 度都按三档计算,而不是数据库里存个 999999 之类的魔法数。

参数说明:示例电价是某地居民阶梯电价的常用口径,实际使用以你所在地区价格为准。tier_no 从 1 开始递增,Service 层按 tier_no 排序取列表。enabled 字段用来做临时停用,比如某个档位调整时不希望被新账单使用。

4.2 Service 层算法实现:从分段判断到金额累加

阶梯算法的思路是:先把总用量按阈值切成若干段,每段用对应的价格相乘再累加。一档 0 到 180 度,二档 181 到 400 度,三档 401 度以上。举例来说,如果某户本月用电 350 度,则应缴电费是 180 度按一档价格加 170 度按二档价格,而不是整个 350 度按二档价格。两者相差挺大,这块算法错了,电费账单会翻车。

public BigDecimal calcCharge(int usage) { List<TierConfig> tiers = tierConfigMapper.findEnabledList(); if (tiers == null || tiers.isEmpty()) { throw new BizException("阶梯电价未配置,请先初始化 t_tier_config"); } BigDecimal total = BigDecimal.ZERO; int remaining = usage; TierConfig lastTier = tiers.get(0); for (int i = 0; i < tiers.size(); i++) { TierConfig tier = tiers.get(i); lastTier = tier; int lower = tier.getLowerLimit(); int upper = tier.getUpperLimit() == null ? Integer.MAX_VALUE : tier.getUpperLimit(); int segment = 0; if (usage <= lower) { continue; } segment = Math.min(usage, upper) - lower; total = total.add(BigDecimal.valueOf(segment) .multiply(tier.getPrice())); if (usage <= upper) { break; } } // 超出所有档位上限的部分,按最后一档价格计算 if (usage > lastTier.getUpperLimit()) { int extra = usage - lastTier.getUpperLimit(); total = total.add(BigDecimal.valueOf(extra) .multiply(lastTier.getPrice())); } return total.setScale(2, RoundingMode.HALF_UP); }

这段代码的关键逻辑是:先用usage <= lower跳过没达到起征点的档位,再用Math.min(usage, upper) - lower计算当前档位内的用量段。循环里每次累加是分段加法而不是乘法最后加总,这样每个档位“用了多少度”被忠实反映。超出所有档位上限的部分,以最后一档价格为兜底,避免了 NULL upper 导致计算中断。

参数说明:usage 是整型,因为前面的抄表服务已经把用量四舍五入到整数度。price 从数据库读取时是 BigDecimal,直接参与乘法运算,整个链路避免使用 double。返回前用 setScale(2, RoundingMode.HALF_UP) 保留两位小数,这是金额要求的标准做法。RoundingMode.HALF_UP 是四舍五入,而不是银行家算法。

这里有一个容易被忽略的边界:如果某个档位的 upper_limit 为空,上面的循环内usage <= upper会变成usage <= Integer.MAX_VALUE,看起来没问题但会导致 break 条件失效,所以我在循环外补了兜底逻辑。更稳妥的做法是在 SQL 层就把 NULL 转成一个大数,我一般在 Mapper XML 里写IFNULL(upper_limit, 9999999999) AS upper_limit,这样 Service 层不用特殊判断。

4.3 测试数据构造与结果核对

算法写完,必须验证。找三个有代表性的输入数据:90 度(应该全部在一档)、350 度(横跨一二档)、500 度(横跨三个档位)。

-- 手工构造验证数据并核对金额 SELECT 90 AS usage_deg, ROUND(90 * 0.5580, 2) AS expect_amount UNION ALL SELECT 350, ROUND(180 * 0.5580 + (350 - 180) * 0.6080, 2) UNION ALL SELECT 500, ROUND(180 * 0.5580 + (400 - 180) * 0.6080 + (500 - 400) * 0.8580, 2);

预期结果分别是 50.22、203.44、306.80。把这组 SQL 的结果和界面上的电费金额对比,如果对不上,问题通常不在算法本身而在数据传入:检查 Service 层是不是拿 double 做运算,或者抄表时用的度数没取整。我一般会在 calcCharge 方法里临时加一行 System.out 打印 usage 的实际值,快速定位是入参问题还是计算问题。这种“打印一行日志定位”的做法看起来简单,但在源码调错时比断点还快,尤其是 JSP 页面和 Service 层之间隔了好几层调用时。

5. 避坑与排查:导入部署中最容易翻车的五个位置

这套系统虽然是毕业设计资源,但部署时该踩的坑一个不少。这一章写五个最常见的问题,每一个我都按“现象、原因、解决”的顺序说明。如果你照着第 2 章的步骤操作还起不来,优先来这一章找答案。

5.1 数据库连接失败:时区、编码与驱动版本

现象:Tomcat 启动时日志报Cannot create PoolableConnectionFactory或者Unknown database,页面上一查数据就 500。

原因:数据库连接 URL 里的参数和实际环境不匹配。最常见的三种情况:一是 MySQL 8 需要指定 serverTimezone,不指定会报 CST 时区错误;二是 jdbc.url 里的数据库名和实际建库名不一致;三是驱动类写错,MySQL 5.x 的驱动类是com.mysql.jdbc.Driver,MySQL 8 是com.mysql.cj.jdbc.Driver。

解决:打开工程里的 jdbc.properties 或 db.properties,核对以下配置:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/electric_bill?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=你的密码

注意useSSL=false是为了避免 MySQL 8 默认 SSL 握手时报错;serverTimezone=Asia/Shanghai必须和数据库时区一致。如果包里的 jar 是 MySQL 5 的老驱动,而本机装的是 MySQL 8,建议把 mysql-connector-java 的版本换到 8.0.x,这样驱动类名和时区参数都支持。

5.2 JSP 页面中文乱码:从请求到响应的全链路排查

现象:页面显示中文全是问号,或者表单提交中文到数据库后乱码。

原因:JSP 页面编码、Servlet 请求编码、数据库连接编码三层没有统一。最常见的情况是 JSP 页面声明的是 GBK 而数据库连接用了 UTF-8,提交时中文变问号。表单 POST 到后台的编码取决于 CharacterEncodingFilter 是否配置,不配置时 Tomcat 默认按 ISO-8859-1 解析表单参数,中文必乱。

解决:三层统一为 UTF-8。第一,JSP 头部写<%@ page contentType="text/html;charset=UTF-8" language="java" %>。第二,web.xml 里确认配置了 Spring 的字符编码过滤器:

<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

第三,数据库连接 URL 里保留characterEncoding=utf8参数。这三个位置全部统一后乱码才能根治,只改其中一两个位置,某个页面还是会出问题。

5.3 静态资源 404:DispatcherServlet 拦截了 CSS 和 JS

现象:登录页能打开但样式全丢,F12 看 Console 里一堆 CSS 404。

原因:DispatcherServlet 的 url-pattern 配置为/,拦截了所有请求包括 css、js、图片。SpringMVC 默认没有处理静态资源的 HandlerMapping,所以请求直接 404。

解决:在 springmvc.xml 里加静态资源放行配置:

<mvc:default-servlet-handler/>

如果工程里静态资源放在 src/main/webapp/static 下,也可以按路径映射:

<mvc:resources mapping="/static/**" location="/static/"/>

配完后重启 Tomcat,刷新页面样式恢复。这个问题在老资源的论坛答疑里出现频率极高,原因是很多入门者不清楚 url-pattern 为/和/*的区别:/只拦截非 JSP 请求,/*连 JSP 都拦截,两种配置的静态资源放行方式也不同。

5.4 账单周期错位:跨月录入和跨年判断

现象:某用户 1 月的抄表记录没有被算进 1 月账单,或者 2 月录入时把 1 月的数据覆盖了。

原因:抄表时只校验了“月份不重复”,没考虑用户上一次抄表是否跨月。比如用户 1 月没抄表,2 月抄表时记录的是 2 月读数,但用量应包含 1 月和 2 月累计,直接按单月计算会少算。另一种原因是跨年时用了MONTH(date)提取月份而忽略年份。

解决:计算用量前先查该用户最近一次抄表记录的 record_month,再和本次传入的 month 比较。如果中间隔了月份,需要在界面提示“该用户上次抄表是 2024-12,本次应包含 2025-01 的用量”。跨年判断不要用字符串子串截取月份,直接用完整字符串比较record_month.compareTo(month),因为2025-01和2024-12在字符串比较下结果是正确的。如果表里存的是 DATE 类型,则用YEAR()和MONTH()组合判断,不要只比月份。

5.5 编译失败:依赖下载中断和文件路径问题

现象:IDEA 编译时报红,提示找不到 javax.servlet 或者某个 jar 包,有时整个项目图标上全是错误标记。

原因:Maven 工程依赖没下载完整,本地仓库里有 .lastUpdated 后缀的残留文件;或者普通 Web 工程的 jar 包路径没有被 IDEA 识别。另一个常见原因是 JDK 版本过高,老 jar 在 JDK 17 编译期报cannot access class。

解决:先确认工程类型。Maven 工程在 IDEA 的 Maven 面板里执行clean compile,如果下载中断,手动删除本地仓库 ~/.m2/repository 下对应目录的.lastUpdated文件再重新导入。普通 Web 工程则检查 Project Structure -> Libraries 里是否把 WEB-INF/lib 下的所有 jar 加入,加入后还要在 Artifacts 的 Output Layout 里确认这些 jar 被带进打包目录。最后确认 Project SDK 是 JDK 8。这三步做完 90% 的编译问题都能解决。如果还不行,看 IDEA 的 Error 窗口第一个报错类名,百分之八九十是 jar 冲突,优先排查重复引用的旧版本。

6. 进阶改造:把人工抄表改成 Excel 批量导入,完整思路和实现骨架

系统跑通之后,用得最多的功能是抄表录入。但一个表一个表录太慢,尤其是几十户的小区场景。我拿到这套源码后做的第一个改造就是把人工录入改成 Excel 批量导入。这个需求的实现思路不仅适用于电费系统,也适用于所有“表单逐条录入”类管理系统的性能优化。

6.1 模板与校验设计

先设计一个 Excel 模板,三列:用户编号、账单月份、本次表底读数。第一行是标题,第二行起是数据。导入前的校验规则有三条:一是用户编号必须存在于 t_user 表,二是账单月份格式必须是 yyyy-MM,三是本次读数必须大于该用户上次抄表读数。原则上先整表校验再逐条入库,避免导入到一半失败导致部分数据写进去。

6.2 导入骨架代码实现

public ImportResult importMeterRecords(MultipartFile file) { ImportResult result = new ImportResult(); // try-with-resources 确保 Excel 工作簿被正确关闭 try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { Sheet sheet = wb.getSheetAt(0); for (int i = 1; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); if (row == null) { continue; } String userNo = getCellValue(row.getCell(0)); String month = getCellValue(row.getCell(1)); String readingStr = getCellValue(row.getCell(2)); // 逐行校验:用户存在、月份格式合法、读数大于上次 if (userNo == null || month == null || readingStr == null) { result.addError(i, "第" + (i + 1) + "行存在空单元格"); continue; } if (!month.matches("\\d{4}-\\d{2}")) { result.addError(i, "第" + (i + 1) + "行月份格式错误"); continue; } try { BigDecimal reading = new BigDecimal(readingStr.trim()); meterRecordService.saveRecordByUserNo(userNo, month, reading); result.addSuccess(userNo); } catch (BizException e) { result.addError(i, "第" + (i + 1) + "行导入失败:" + e.getMessage()); } } } catch (Exception e) { throw new BizException("文件解析失败:" + e.getMessage()); } return result; }

几个关键点说明:try-with-resources 比手动关流更安全,避免文件句柄泄漏。getCellValue 需要区分字符串单元格和数字单元格,POI 里用 cell.getCellType() 判断后分别取值。逐行处理时把异常捕获到行级别而不是中断整个文件,这样一次导入能返回所有错误行,操作员可以一次性修改。返回的 ImportResult 里包含成功数和错误列表,页面展示时直接渲染成表格,比只给一个“导入失败”的提示体验好得多。

从那以后我每次拿到一套毕业设计源码,都会强制走一遍环境确认、建库导入、跑通登录、看核心算法、改一个需求的流程。这个流程能让你在答辩或演示时对系统真正做到心里有底,而不是被导师问到底层实现时支支吾吾。这份电费管理系统的资源本身质量不错,框架结构清晰,阶梯电价算法有配置表支撑,适合作为二次开发起点。希望帮到你。

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

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

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

立即咨询