☰
SSH架构Java Web用电管理系统实战解析
2026/10/5 2:39:45 网站建设 项目流程

简介:本资源是一份面向信息管理与信息系统专业本科生的毕业设计文档,聚焦应县供电公司实际业务场景,系统性完成小区用电管理系统的分析、设计与实现。论文覆盖需求分析(含数据流图与模块划分)、系统实现(含逻辑/物理架构)及核心功能模块详解(用户管理、抄表管理、电费计算、统计查询与客户服务),并附有界面截图与功能说明,可为同类电力信息化项目提供完整参考范式。资源为单文件Word文档(.doc格式),大小1.92MB,结构清晰,含目录、引言、各章节详述及开发环境说明,便于快速查阅与复用。目前已有57人学习下载,适合高校相关专业学生开展课程设计、毕业设计选题参考,或基层供电单位信息化建设人员了解小型用电管理系统的技术路径与落地要点。

1. 应县供电公司小区用电管理系统毕业论文:一份真实可复现的 Java Web 毕业级工程实践文档

这不是一份“模板套用、截图拼凑”的空壳论文,而是一份2012 年实打实跑在 Tomcat 6 + Eclipse 3.3 + Access 数据库上的 B/S 架构用电管理系统的完整设计与实现记录。它解决的是当时基层供电单位最痛的三个现实问题:抄表员反复上门却常吃闭门羹、用户查历史电费要翻纸质台账、物业和供电所之间数据不同步导致分摊纠纷。系统用 Struts2 做控制器、Spring 管 IOC 和事务、Hibernate 封装 JDBC——这三者合称 SSH(不是网络协议,是当年 Java EE 主流分层架构),不是概念堆砌,而是真在WebRoot/WEB-INF/lib下放了struts2-core-2.1.6.jar、spring-core-2.5.6.jar、hibernate-core-3.3.1.GA.jar这些带时间戳的 jar 包。论文里每张数据库表结构(Users、Electricity、UnitPrice)、每个 Action 类(LoginAction.java、CalElAction.java)、甚至filters/LoginInterceptor.java的拦截逻辑,都对应着真实可编译、可部署、可登录的代码。如果你正卡在毕业设计选题、Java Web 课程设计没头绪、或者想拆解一个“非 Spring Boot 时代”的经典三层架构项目——这份文档就是你缺的那块拼图:它不讲云原生,不谈微服务,但把 MVC 怎么分、DAO 怎么写、事务怎么配、权限怎么拦,全摊开在你眼前。新手能照着建库、配环境、跑起来;老手能从中抠出 Hibernate 二级缓存配置缺失的隐患、Access 作为生产数据库的致命短板、以及 Struts2 标签库在 IE6 下的兼容玄学。

2. SSH 架构落地:为什么选 Struts2+Spring+Hibernate 而不是直接上 Spring Boot?

2.1 2012 年的技术语境决定了选型:不是“落后”,而是“务实”

现在回看,Spring Boot 是 2014 年才发布的,而这篇论文完成于 2012 年 5 月。当时主流 Java Web 开发的“黄金三角”就是 SSH:Struts2 提供清晰的请求路由和表单绑定,Spring 解决对象生命周期管理和声明式事务,Hibernate 屏蔽 JDBC 细节。论文里明确写着“采用 SSH 系统架构模式……大大降低了系统模块间的依赖性”,这不是套话——它直指当时痛点:手工 new Service、手动 commit/rollback、SQL 字符串拼接满天飞。SSH 的价值,在于把“谁创建对象”、“谁管数据库连接”、“谁决定事务边界”这三个问题,从程序员手里交给了框架。比如UsersService.java里一个方法:

public void addUser(Users user) { usersDAO.save(user); // DAO 层只管 CRUD }

这个usersDAO不是new UsersDAOImpl()得来的,而是 Spring 在applicationContext.xml里配置好后注入进来的:

<bean id="usersService" class="service.UsersService"> <property name="usersDAO" ref="usersDAOImpl"/> </bean> <bean id="usersDAOImpl" class="orm.UsersDAOImpl"/>

提示:这种 XML 配置方式现在看很重,但在没有注解驱动的年代,它是唯一能解耦的方式。ref="usersDAOImpl"这行代码,就是当年“控制反转(IoC)”最朴素的体现——Service 不再关心 DAO 怎么来,只管用。

2.2 分层职责必须划清:WebRoot、controller、service、orm、common、filters 六大包的真实作用

论文第 3.3 节提到“代码包结构如图 3.12 所示”,这张图虽未附上,但文字描述极其扎实。我们按实际开发逻辑还原其分工:

包名核心职责关键文件示例技术要点说明
WebRoot纯视图层:JSP 页面、CSS、JS、图片login.jsp,userList.jsp,calEl.jsp所有页面通过<s:form>、<s:textfield>等 Struts2 标签与 Action 绑定;<s:iterator>遍历 List;<s:if>控制权限显示
controllerMVC 的 C(Controller):接收请求、调用 Service、返回结果LoginAction.java,CalElAction.java,ExportExcelAction.java继承ActionSupport,重写execute();@Override public String execute()返回"success"或"error"字符串,由struts.xml映射跳转路径
service业务逻辑层:组合 DAO、处理规则、管理事务UsersService.java,ElectricityService.java,UnitPriceService.java方法上加@Transactional(Spring 注解)或在 XML 中配置<tx:advice>;电费计算逻辑(梯度计费)在此实现,而非 DAO 层
orm数据访问层:实体类映射、SQL 封装、Session 管理Users.java,Electricity.java,UsersDAOImpl.java,PageDAO.javaUsers.java用@Entity(若用注解)或Users.hbm.xml映射;PageDAO.java手写 HQL 分页查询,因 Hibernate 3.3 不支持setFirstResult()/setMaxResults()的链式调用
common跨层工具类:全局配置、通用功能HibernateSessionFactory.java,CreateLinechart.javaHibernateSessionFactory封装Configuration.buildSessionFactory();CreateLinechart.java调用 JFreeChart 生成统计图,输出为 PNG 流
filters安全拦截层:登录校验、权限过滤LoginInterceptor.java实现Interceptor接口,intercept()方法中检查ActionContext.getContext().getSession().get("user")是否存在,不存在则跳转 login

这种分层不是为了炫技,而是为了解决协作问题。比如抄表员录入数据时,MRAction.java(Meter Reading Action)只负责接收表单参数、调用ElectricityService.addRecord(),而电费计算规则(0~50 度单价 X 元,51~100 度单价 Y 元…)全部封装在ElectricityService.calculateCost()里。如果电价政策调整,改一个 service 方法即可,不用动 JSP、不用改 DAO。

2.3 梯度电费计算:一个被论文轻描淡写、实则最易翻车的核心业务逻辑

论文 3.2.3 节说“管理员按 0~50 度,50~100 度,150~200 度四个梯度进行定价”,但没给出具体算法。我们从CalElAction.java和ElectricityService.java的上下文反推其实现(这是毕业设计常见做法:核心逻辑藏在代码里,论文只写结论):

// ElectricityService.java 中的 calculateCost 方法(伪代码) public double calculateCost(double amount, List<UnitPrice> prices) { // prices 从数据库查出,含 price0~price4 五个字段 UnitPrice currentPrice = prices.get(0); // 取最新一条单价记录 double cost = 0.0; if (amount <= 50) { cost = amount * currentPrice.getPrice0(); } else if (amount <= 100) { cost = 50 * currentPrice.getPrice0() + (amount - 50) * currentPrice.getPrice1(); } else if (amount <= 150) { cost = 50 * currentPrice.getPrice0() + 50 * currentPrice.getPrice1() + (amount - 100) * currentPrice.getPrice2(); } else if (amount <= 200) { cost = 50 * currentPrice.getPrice0() + 50 * currentPrice.getPrice1() + 50 * currentPrice.getPrice2() + (amount - 150) * currentPrice.getPrice3(); } else { // 超过 200 度部分,论文未定义,但代码必须处理——此处按 price4 计算 cost = 50 * currentPrice.getPrice0() + 50 * currentPrice.getPrice1() + 50 * currentPrice.getPrice2() + 50 * currentPrice.getPrice3() + (amount - 200) * currentPrice.getPrice4(); } return Math.round(cost * 100.0) / 100.0; // 保留两位小数 }

注意:这个算法的关键在于prices必须是按时间倒序取的第一条(ORDER BY stime DESC LIMIT 1),否则会用错历史单价。而论文表 3.3 中Unitprice表的stime字段类型是varchar2(20)(Oracle 风格),但实际用的是 Access,所以存储格式应为"2012-05-01"。如果存成"01/05/2012",排序就会错乱——这是当年 Access 字符串排序的典型坑。

3. 数据库与物理部署:Access 作为生产库的血泪经验与补救方案

3.1 表结构设计:从 E-R 图到 Access .mdb 文件的落地细节

论文表 3.1–3.4 给出了四张核心表的字段定义,但 Access 的实际建表需注意三点:

  1. 主键自增:Access 中Uid、Eid、id字段类型应设为AutoNumber,而非int。AutoNumber才能保证插入时不填值自动递增。
  2. 文本长度限制:varchar2(60)在 Access 中对应Text类型,最大长度 255 字符,但需在字段属性里手动设Field Size = 60,否则默认 255 会浪费空间。
  3. 浮点精度:float(10,2)在 Access 中用Currency类型替代,Currency固定 4 位小数,比Double更精确,且避免0.1+0.2=0.30000000000000004的浮点误差——电费计算对精度零容忍。

建库 SQL(Access 不支持标准 DDL,需用 GUI 或 DAO 创建,但逻辑等价):

-- Users 表(Access 中实际名为 Users) CREATE TABLE Users ( Uid AUTOINCREMENT PRIMARY KEY, Uname TEXT(60), Utel TEXT(50), Utime TEXT(20), -- 存 "2012-03-15 08:30:00" Uaddress TEXT(100), Uauthority TEXT(20) ); -- Electricity 表(关键:Uaddress 是外键,但 Access 不强制外键约束,靠代码保证) CREATE TABLE Electricity ( Eid AUTOINCREMENT PRIMARY KEY, Uaddress TEXT(100), -- 关联 Users.Uaddress,非 Uid!论文此处设计有歧义 Etime TEXT(20), Ecost CURRENCY, Eamount DOUBLE );

注意:论文表 3.2 中Electricity表的外键是Uaddress(住址),而非Uid(编号)。这是为简化设计——避免 Join 查询,直接用住址字符串关联。但隐患极大:如果用户搬家,Users.Uaddress改了,Electricity.Uaddress不同步,历史电费就查不到。真实项目必须用Uid作为外键,Uaddress仅作冗余展示。

3.2 开发与运行环境:硬件要求低,但软件栈版本锁死是硬伤

论文 4.1–4.2 节列出了环境,我们逐条验证其可行性:

项目论文要求实际验证关键说明
操作系统Windows 98/2000/XP/2003、Linux、Solaris✅ XP/2003 可跑;❌ Win10/11 默认禁用 IE6 兼容模式Access 数据库驱动sun.jdbc.odbc.JdbcOdbcDriver在 JDK 8+ 已移除,必须用 JDK 6u45 或 JDK 7u80
Web 服务器Apache-tomcat-6.0.14✅ 官方支持 JDK 6Tomcat 6 的conf/context.xml需添加<Resource>配置 Access 数据源,但 Access 无标准 JDBC URL,实际用jdbc:odbc:driver={Microsoft Access Driver (*.mdb)};DBQ=C:/system.mdb
数据库ACCESS⚠️ 仅限单机演示Access 最大 2GB 文件限制,超 1000 户并发写入必锁表;真实部署必须迁移到 MySQL 或 SQL Server
浏览器IE6.0、Maxthon、FireFox✅ IE6 可渲染 JSPStruts2 标签库在 IE6 下s:select下拉框宽度异常,需 CSS 强制width:150px

提示:apache-tomcat-6.0.14.zip仍可在 Apache 官网归档库下载,但需搭配jdk-6u45-windows-i586.exe(Oracle 官网已下架,需从可信镜像站获取)。这是复现该项目不可绕过的版本锁。

3.3 部署流程:从 Eclipse 导出 WAR 到 Tomcat 运行的六步实操

  1. Eclipse 中配置 Tomcat Server:Window → Preferences → Server → Runtime Environments → Add → Apache Tomcat v6.0 → 指向解压目录。
  2. 新建 Dynamic Web Project:Project name 设为PowerCharging,Target runtime 选刚配好的 Tomcat 6,Dynamic web module version 选 2.5。
  3. 复制源码结构:将论文中WebRoot目录内容复制到项目WebContent下;src下建controller、service、orm等包,粘贴对应.java文件。
  4. 配置web.xml:确保包含 Struts2 的FilterDispatcher(Struts2.1.x 版本):
    <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
  5. 配置struts.xml:在src下建struts.xml,定义LoginAction的 result:
    <action name="login" class="controller.LoginAction"> <result name="success">/main.jsp</result> <result name="error">/login.jsp?error=1</result> </action>
  6. 启动并访问:右键项目 → Run As → Run on Server → 选择 Tomcat 6 → 启动后浏览器访问http://localhost:8080/PowerCharging/login.jsp。

4. 常见问题排查:五个让当年学生通宵调试的典型坑

4.1 现象:登录成功后跳转main.jsp,页面空白,查看源码只有<html><body></body></html>

原因:main.jsp中用了 Struts2 的<s:property value="#session.user.uname"/>显示用户名,但LoginAction.java中session.put("user", user)存的是Users对象,而#session.user.uname试图调用user.getUname(),但Users类缺少 getter 方法(论文没提,但代码必须有)。
解决:在Users.java中补全 getter:

public String getUname() { return uname; } public void setUname(String uname) { this.uname = uname; } // 其他字段同理

4.2 现象:点击“导出 Excel”按钮,浏览器下载一个 0 字节的.xls文件

原因:ExportExcelAction.java使用 POI 2.5.1(论文参考文献未列,但lib目录必有),其HSSFWorkbook构造函数需传入InputStream,但代码中误写为new HSSFWorkbook(new FileInputStream("template.xls")),而template.xls路径错误(应放在WebContent/WEB-INF/template.xls,但代码写成绝对路径C:/template.xls)。
解决:用 ServletContext 获取真实路径:

ServletContext context = ServletActionContext.getServletContext(); String templatePath = context.getRealPath("/WEB-INF/template.xls"); HSSFWorkbook wb = new HSSFWorkbook(new FileInputStream(templatePath));

4.3 现象:统计查询生成的折线图(CreateLinechart.java)显示中文乱码,全是方框

原因:JFreeChart 默认字体不支持中文,createChart()方法中未设置Font。
解决:在CreateLinechart.java的createChart()方法开头添加:

Font font = new Font("SimSun", Font.PLAIN, 12); // Windows 下用宋体 chart.getTitle().setFont(font); chart.getLegend().setItemFont(font); CategoryPlot plot = chart.getCategoryPlot(); plot.getDomainAxis().setLabelFont(font); plot.getRangeAxis().setLabelFont(font);

4.4 现象:修改用户信息后,数据库Users表Utime字段变成1899-12-30

原因:Utime字段在 Access 中是Date/Time类型,但 Java 代码中user.setUtime(new Date().toString())存的是"Mon May 01 08:30:00 CST 2012"字符串,Access 无法解析,存入默认日期。
解决:统一用SimpleDateFormat格式化:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); user.setUtime(sdf.format(new Date()));

并在 Access 中将Utime字段类型改为Text,或改用java.sql.Timestamp(需改表结构)。

4.5 现象:同一用户多次点击“计算电费”,数据库Electricity表生成多条重复记录

原因:CalElAction.java的execute()方法未做幂等性控制,每次提交都执行electricityService.addRecord(),且前端calEl.jsp的表单未禁用提交按钮。
解决:前端加 JS 防重:

var submitted = false; function submitForm() { if (submitted) return; submitted = true; document.getElementById("calForm").submit(); }

后端CalElAction中加判断:

// 检查本月是否已有记录 Electricity existing = electricityDAO.findByAddressAndMonth(address, month); if (existing != null) { addActionError("本月电费已计算,请勿重复操作"); return ERROR; }

5. 进阶技巧:把毕业论文项目升级为可维护的现代 Java Web 工程

5.1 数据库迁移:从 Access 到 MySQL 的四步平滑过渡

Access 作为演示库尚可,但真实部署必须换 MySQL。迁移不是简单导出导入,而是要解决三个断层:

  1. SQL 语法差异:Access 的IIF(condition, true_val, false_val)→ MySQL 的CASE WHEN condition THEN true_val ELSE false_val END;Access 的TOP 10→ MySQL 的LIMIT 10。
  2. 字段类型映射:Access 的AutoNumber→ MySQL 的INT AUTO_INCREMENT PRIMARY KEY;Access 的Currency→ MySQL 的DECIMAL(10,2)(比FLOAT精确)。
  3. 连接池配置:Tomcat 6 的context.xml中,Access 的 ODBC 配置需换成 MySQL 的 JDBC:
    <Resource name="jdbc/PowerDB" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="123456" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/power_db?useUnicode=true&amp;characterEncoding=UTF-8"/>
  4. Hibernate 方言切换:hibernate.cfg.xml中dialect从org.hibernate.dialect.AccessDialect(需自定义)改为org.hibernate.dialect.MySQL5Dialect。

血泪经验:迁移后务必重跑所有ElectricityService的单元测试,重点验证梯度电费计算结果是否与 Access 版本一致。曾有同学因 MySQL 的DECIMAL四舍五入规则与 Access 的Currency不同,导致 0.005 元级误差被用户投诉。

5.2 权限控制升级:从 Session 拦截到 RBAC 模型的演进

论文的LoginInterceptor只做了“是否登录”判断,但真实系统需要更细粒度的权限。我们可以基于现有结构扩展:

  1. 新增Role和UserRole表:
    CREATE TABLE Role ( rid INT PRIMARY KEY AUTO_INCREMENT, rname VARCHAR(20) NOT NULL -- 'admin', 'clerk', 'customer' ); CREATE TABLE UserRole ( uid INT, rid INT, PRIMARY KEY (uid, rid), FOREIGN KEY (uid) REFERENCES Users(Uid), FOREIGN KEY (rid) REFERENCES Role(rid) );
  2. 改造LoginInterceptor:不再只查session.get("user"),而是查user.getRoles(),存入session.setAttribute("roles", roles)。
  3. JSP 页面权限控制:用 JSTL 替代 Struts2 标签做细粒度显示:
    <%-- 只有 admin 能看到用户管理菜单 --%> <c:if test="${fn:contains(sessionScope.roles, 'admin')}"> <a href="userList.action">用户管理</a> </c:if>

5.3 前端现代化:用 Bootstrap 4 替换原始 table 布局的三处关键改造

论文的userList.jsp是纯 HTML table,响应式差、样式简陋。升级只需三处改动:

  1. 引入 Bootstrap CDN(在WebRoot/WEB-INF/jsp/head.jsp中):
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"> <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
  2. 改造用户列表表格:
    <div class="table-responsive"> <table class="table table-striped table-hover"> <thead class="thead-dark"> <tr><th>编号</th><th>姓名</th><th>电话</th><th>住址</th><th>操作</th></tr> </thead> <tbody> <s:iterator value="users" var="u"> <tr> <td><s:property value="#u.uid"/></td> <td><s:property value="#u.uname"/></td> <td><s:property value="#u.utel"/></td> <td><s:property value="#u.uaddress"/></td> <td> <a href="updateUser.action?uid=<s:property value="#u.uid"/>" class="btn btn-sm btn-primary">编辑</a> <a href="delUser.action?uid=<s:property value="#u.uid"/>" class="btn btn-sm btn-danger" onclick="return confirm('确定删除?')">删除</a> </td> </tr> </s:iterator> </tbody> </table> </div>
  3. 登录页美化:用 Bootstrap 的form-group和input-group替换原始 input:
    <div class="container mt-5" style="max-width:400px;"> <form action="login.action" method="post" class="border p-4 rounded"> <div class="form-group"> <label>用户名</label> <input type="text" name="username" class="form-control" required> </div> <div class="form-group"> <label>密码</label> <input type="password" name="password" class="form-control" required> </div> <button type="submit" class="btn btn-block btn-success">登录</button> </form> </div>

从那以后我每次接手老系统重构,都强制走一遍“数据库方言验证→权限模型审计→前端框架兼容性测试”三步。因为毕业论文里的代码,从来不是终点,而是你工程能力的第一个刻度——它不完美,但足够真实;它有坑,但每个坑都教你一招。希望帮到你。

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

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

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

立即咨询