
简介面向高校计算机、电子信息工程、数学专业学生的智能水表系统课程设计与毕业设计参考项目资源包含完整工程源码覆盖从传感器数据采集、无线通信到Web管理界面的实现链路适合需要完成相关课题设计或学习物联网系统开发的学生参考。压缩包共80个文件以Java源码、JSP页面和Class文件为主辅以JS/CSS前端资源、XML配置及字体文件等整体约532KB目录结构清晰便于按模块阅读和复用。目前已有68人学习使用。通过该源码可掌握智能水表远程自动抄表、流量数据采集与上传、后台数据展示等核心环节的编码实现理解GPRS、NB-IoT等通信方式的选型逻辑同时为异常检测、水费管理等扩展功能开发提供可复用的基础框架。项目代码与配置完整适合作为课题设计、毕业论文撰写的技术参考。1. 智能水表系统毕设资源先搞清楚它到底是个什么项目不少同学拿到智能水表系统.zip第一反应是打开来找硬件电路图结果发现里面是一堆.java、.jsp、pom.xml心里咯噔一下这跟我以为的智能水表不一样。其实这套资源里的智能水表系统落点是一个 Java Web 管理平台——硬件端的抄表数据通过无线网络传到服务器之后由这套系统负责接收、存储、展示和统计也就是整个智能抄表架构里的软件大脑。它的核心价值不是教你怎么焊电路板而是让你把远程抄表 水务管理这条完整业务链在电脑上跑通从登录、数据看板到测试模块都齐活。适合计算机、电子信息工程、物联网专业拿来当课设或毕设底座替换成自己的数据库表名和前端样式就是一份能答辩、能演示、能讲清楚原理的作品。2. 架构与抄表链路从传感器到 JSP 页面的数据流向2.1 一个完整智能水表系统的三段式结构智能水表系统从来不是单机软件它至少分成三层感知层、传输层、平台层。感知层是水表终端里头有流量传感器常见是霍尔元件或干簧管水流推动叶轮转动时产生脉冲信号微控制器STM32、51 系列都有人用负责统计脉冲次数换算成流量再把数据暂存在本地。传输层负责把数据送出去这也是毕设答辩时老师最爱问的点——你用的是什么通信方案为什么不用别的。平台层就是本项目——Java Web 后台负责接收、解析、存储、展示数据还要支撑管理员登录、用户管理、账单统计这些业务动作。这套资源结构里能看到典型的 Java Web 工程痕迹src/main/java存放后台逻辑webapp下首页是index.jsp登录页是login.jsp另外还有一个TestCommunity.jsp。也就是说它虽然是智能水表系统但交付的是平台层代码感知层和传输层以业务背景和数据模拟的方式存在这一点先想清楚后面很多疑惑就解开了。2.2 传输层选型四种通信方案怎么选传输层的通信协议选择直接决定了系统的覆盖范围、功耗和运营成本这个知识点在论文里通常要占一节。梳理下来课设阶段最常被比较的四类方案是这样通信方式覆盖范围典型速率功耗适用场景GPRS/4G广依托运营商基站高高城区分散表计、实时性要求高的场景LoRa中远需要自建网关低低小区/园区集中部署网关覆盖NB-IoT广蜂窝网络中低目前智能抄表的主流海量连接有线 RS-485短需布线中低楼栋内部的集中器到主站如果是毕设论文我一般建议你选 NB-IoT 作为推荐方案理由很充分覆盖广、穿透强、单表功耗低而且运营商在推跟你未来智慧城市的展望能自然衔接。但如果只是课设演示实际操作中完全可以把传输层抽象成一个 HTTP POST 请求模拟水表终端往后台塞数据这也是第 6 章会做的事。论文里写 NB-IoT代码里用模拟请求不冲突反而是完整闭环。2.3 平台层内部的分层结构与对应文件回到这套源码本身把它拆开看平台层内部走的是很标准的 Web 分层表现层是 JSP 页面webapp/login.jsp负责登录入口webapp/index.jsp是登录成功后的主工作台TestCommunity.jsp看起来是独立测试或社区展示页。控制层在src/main/java里用 Servlet 实现负责接收 HTTP 请求、调用业务层、决定跳转哪个页面。业务层封装抄表记录的增删改查逻辑。数据访问层用 JDBC 或 MyBatis 操作 MySQL。最外面的pom.xml声明了所有依赖.classpath、.settings、.project这些是 Eclipse 的工程配置说明原项目是在 Eclipse 里开发的 Maven 工程导入时最好也用 Eclipse 或 IDEA 的 Maven 导入方式。需要特别注意的是src/main/webapp/WEB-INF目录下面一般放着web.xml和 JSP 的配置文件resources/META-INF里可能放了数据库配置或上下文配置。拿到项目后第一步不是急着打开 JSP 改页面而是先把pom.xml里的依赖列表看一遍确认里面有没有数据库驱动、JSTL 标签库、Servlet API因为这几个是后面最容易翻车的点。2.4 数据链路对照一帧抄表数据从哪来到哪去把这套系统的数据流串一遍大致是这样水表终端的传感器产生脉冲微控制器换算成用水量并按设定周期打包通过 NB-IoT 模块以 CoAP/UDP 方式发到运营商平台运营商平台再通过 HTTP 回调把 JSON 数据推送到你的后台接口。后台 Servlet 收到 JSON 后解析出表号、读数、时间戳插入数据库的抄表记录表然后index.jsp从数据库查出来渲染成表格或图表。如果你答辩时被问到系统延迟怎么办可以回答实时性要求不高的场景抄表数据每小时上报一次就够后台用定时任务批量处理如果要求实时告警后台接口得支持并发接收数据库连接也要换成连接池。这套课设代码里未必实现了连接池但你把这个思路讲出来老师会觉得你考虑过生产环境的问题。3. 登录模块落地从 login.jsp 到数据库权限校验的完整实现3.1 登录页的表单结构与提交方式打开login.jsp里面核心是一个表单用户名密码通过 POST 提交到LoginServlet。常见的写法是这样form action${pageContext.request.contextPath}/login methodpost input typetext nameusername placeholder请输入用户名 / input typepassword namepassword placeholder请输入密码 / button typesubmit登 录/button /form这段代码里最关键的是${pageContext.request.contextPath}它动态获取项目部署路径避免了上下文路径写死导致请求 404 的问题。很多同学在本地跑通、换一台电脑部署就登录失败往往是这里写死了路径。action 指向的/login要和web.xml里 Servlet 的url-pattern完全匹配大小写也得一致。3.2 LoginServlet用 PreparedStatement 挡住 SQL 注入后台接收登录请求的 Servlet核心逻辑是查库比对。常见的课设写法是这样WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); try (Connection conn DBUtil.getConnection()) { String sql SELECT id, username, role FROM sys_user WHERE username? AND password?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs ps.executeQuery(); if (rs.next()) { HttpSession session request.getSession(); session.setAttribute(userId, rs.getInt(id)); session.setAttribute(role, rs.getString(role)); response.sendRedirect(index.jsp); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } } catch (Exception e) { e.printStackTrace(); } } }这里有两个细节我想强调。第一密码比对直接在 SQL 里用WHERE username? AND password?完成这种写法胜在简单课设够用但严格来说生产环境肯定要存哈希值。答辩时如果老师问密码安全你就说当前为了演示采用明文比对生产环境会换成 BCrypt 加密存储这一句话就能体现你懂安全边界。第二用了PreparedStatement而不是拼接字符串这是刻意为之——?占位符让数据库引擎把参数当成值而不是 SQL 片段能有效挡住 or 11这类注入。3.3 DBUtil连接参数集中管理别把密码写死在 JSP 里登录必须要有一个数据库连接工具类常见做法是写一个DBUtil把驱动加载、连接获取、资源关闭都封装起来public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/smart_meter?useUnicodetruecharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }URL 里那串参数值得注意useUnicodetruecharacterEncodingutf8是解决中文乱码的第一道关卡。如果不加数据库连接默认用的字符集可能和表结构不一致读出来的用户名变成问号登录永远失败。驱动类名有两个版本MySQL 5.x 用com.mysql.jdbc.DriverMySQL 8.x 用com.mysql.cj.jdbc.Driver下载依赖时看清楚 pom.xml 里写的版本否则会报 ClassNotFoundException。3.4 登录成功后的 Session 处理和页面跳转登录成功不能只是跳个页面至少要做三件事把用户标识写进 Session、记住用户角色、跳转到主页面。上面代码里session.setAttribute(userId, ...)和setAttribute(role, ...)就是干这个的。后续index.jsp里可以用${sessionScope.role}判断显示哪些菜单比如管理员能看到用户管理普通用户只看到自己的水表读数。跳转用sendRedirect还是forward也讲究。登录成功用sendRedirect让浏览器地址栏变成index.jsp用户按 F5 刷新不会重复提交表单登录失败用forward回到login.jsp好处是request域里的errorMsg还能带到页面上显示。如果用sendRedirect带错误消息就得拼 URL 参数比较麻烦。这一点的取舍面试和答辩都有人问。4. 数据看板与查询index.jsp 怎么把抄表数据用起来4.1 抄表数据表设计先搞清楚你要展示什么index.jsp是登录后的主工作台它的数据来源是抄表记录表。课设里这张表的设计直接决定后面查询好不好写。我见过很多项目把用户信息和抄表数据揉在一张表里结果想按小区统计水量时 SQL 写得非常痛苦。建议至少拆成两张表sys_user管用户和角色meter_data管抄表记录。meter_data的常见字段是这样字段名类型说明idBIGINT主键自增meter_noVARCHAR(20)水表编号每块表唯一user_nameVARCHAR(50)用户户名readingDECIMAL(10,2)当前累计读数吨flowDECIMAL(10,2)本次周期用水量statusTINYINT0 正常1 异常疑似泄漏collect_timeDATETIME抄表时间reading存累计值而不是单次用量这个和实际智能水表的工作方式一致——表端只记录累计读数后台用两次读数的差值算周期用水量。你也可以在插入时直接算好flow存进去省得查询时再做减法。4.2 DAO 层按时间范围查抄表记录的典型写法查询逻辑集中在 DAO 里一个常见需求是查最近 N 天的抄表记录并按时间倒序显示在首页表格里public ListMeterData findRecentRecords(int limit) throws SQLException { ListMeterData list new ArrayList(); String sql SELECT meter_no, user_name, reading, flow, status, collect_time FROM meter_data ORDER BY collect_time DESC LIMIT ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, limit); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { MeterData d new MeterData(); d.setMeterNo(rs.getString(meter_no)); d.setUserName(rs.getString(user_name)); d.setReading(rs.getBigDecimal(reading)); d.setFlow(rs.getBigDecimal(flow)); d.setStatus(rs.getInt(status)); d.setCollectTime(rs.getTimestamp(collect_time)); list.add(d); } } } return list; }这里LIMIT ?的参数也是用setInt传入的和登录模块的PreparedStatement是同一个套路。要注意 MySQL 的LIMIT参数不能用字符串拼接有些版本直接拼数字没问题但统一用占位符最安全。ORDER BY collect_time DESC倒序排列配合LIMIT就能实现首页只显示最近 20 条数据量大了也不会让页面卡死。4.3 index.jsp 用 JSTL 循环渲染表格DAO 查出来的数据最终要展示在 JSP 上。Servlet 把 List 塞进 request 域之后JSP 里用 JSTL 的c:forEach循环输出。常见写法如下% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table tr th水表编号/thth户名/thth累计读数/th th周期用量/thth状态/thth抄表时间/th /tr c:forEach items${recentRecords} varrecord tr td${record.meterNo}/td td${record.userName}/td td${record.reading}/td td${record.flow}/td td c:choose c:when test${record.status 1} span stylecolor:red;异常/span /c:when c:otherwise正常/c:otherwise /c:choose /td td${record.collectTime}/td /tr /c:forEach /table这一段的items${recentRecords}对应 Servlet 里request.setAttribute(recentRecords, list)的名字必须完全一致。${record.meterNo}用到的是 EL 表达式它默认调用record对象的 getter 方法所以实体类MeterData里的属性名和 getter 要符合 JavaBean 规范——属性meterNo对应getMeterNo()否则页面上就显示空。很多同学改字段名时只改了数据库和实体类忘了改 getter结果页面一片空白这个问题在第 5 章还会再提。4.4 再加一个趋势查询按天统计用水量如果想让首页更丰满一点可以加一个近 7 天用水量趋势统计这个功能特别适合答辩演示因为能直观展示系统不只是展示明细还能做数据分析。SQL 按天分组汇总SELECT DATE_FORMAT(collect_time, %Y-%m-%d) AS day, SUM(flow) AS total_flow FROM meter_data WHERE collect_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE_FORMAT(collect_time, %Y-%m-%d) ORDER BY day这段 SQL 的逻辑是先用DATE_SUB把时间范围卡在最近 7 天再用DATE_FORMAT把时间戳转成天并以天分组最后SUM(flow)汇总出每天的用水总量。%Y-%m-%d的格式串里%Y是四位年份%m是两位月份%d是两位日期大小写不能写反。这个查询结果可以在 JSP 里渲染成柱状图用隐藏 canvas 或直接输出到表格里都行或者更简单点直接放在一个近 7 日用水表格里展示同样用c:forEach循环即可。5. 避坑指南JSPMaven 项目导入、依赖与中文乱码排查5.1 Eclipse 导入后 JSP 全部报错少配置了 Tomcat 运行时现象把.zip解压后用 Eclipse 的 Import → Existing Maven Projects 导入src/main/java里的类和webapp下的 JSP 大片标红HttpServlet、HttpServletRequest全部找不到。原因项目本来是动态 Web 工程Servlet相关 API 由 Tomcat 提供Maven 的pom.xml里如果用的是scopeprovided/scope意思是运行时由容器提供那么 IDE 里必须手动指定一个 Tomcat 运行时否则编译环境里根本没有这些类。解决在 Eclipse 里 Window → Preferences → Server → Runtime Environments 里 Add 一个 Tomcat版本跟项目匹配常见是 Tomcat 8 或 9然后在项目上右键 Properties → Targeted Runtimes勾上这个 Tomcat重新 Maven → Update Project红色报错就会消失。如果在 IDEA 里确认 Project Structure → SDK 选对 JDK并且 Facets 里 Web 模块的 Deployment Descriptor 路径指向src/main/webapp/WEB-INF/web.xml即可。5.2 运行时报 ClassNotFoundException: com.mysql.cj.jdbc.Driver现象启动 Tomcat 后访问login.jsp点登录按钮后台报ClassNotFoundException: com.mysql.cj.jdbc.Driver或者No suitable driver found。原因数据库驱动 Jar 包只在编译期存在运行期没有同步到 Tomcat 的 WEB-INF/lib 下。用 Eclipse 直接 Run on Server 时如果 Maven 依赖没有 deploy 到 webapp就会出现这种情况。另外 MySQL 8.x 和 5.x 的驱动类名不一样也可能是因为DBUtil里写错了驱动类。解决先确认 pom.xml 里有没有 mysql-connector-java 依赖然后把 Maven 工程 clean 后再 package看target目录下最终生成的 war 里WEB-INF/lib有没有驱动 Jar。如果 IDE 直接运行可以在项目的 Deployment Assembly 里把 Maven Dependencies 加进去。我一般还会建议直接把 Tomcat 的运行方式改成使用本地安装的 Tomcat而不是使用工作空间元数据能少踩很多依赖不同步的坑。5.3 数据库数据变成问号三层字符集至少统一两处现象登录时输入的密码含中文后台查不到用户或者往meter_data表插入的户名在 MySQL 客户端看是正常的但 JSP 页面上显示问号。原因典型的字符集链路断裂。JSP 文件本身的编码、request.setCharacterEncoding、JDBC URL 的characterEncoding、MySQL 表结构的字符集这四个环节只要有任何一个不是 UTF-8中文就会出乱子。最常见的是 JSP 页面没有声明% page contentTypetext/html;charsetUTF-8 %Tomcat 按 ISO-8859-1 解析了请求参数。解决我拿到这类项目第一件事就是全局搜索charset确保每个 JSP 头部都有 UTF-8 声明LoginServlet的doPost第一行写上request.setCharacterEncoding(UTF-8)JDBC URL 带上useUnicodetruecharacterEncodingutf8最后 MySQL 建库时显式写CREATE DATABASE smart_meter DEFAULT CHARACTER SET utf8mb4。这条链路全走通乱码基本绝迹。5.4 8080 端口被占用Tomcat 起不来现象启动 Tomcat 时控制台报Port 8080 required by Tomcat v9.0 Server at localhost is already in use。原因本机有其他进程占用了 8080 端口常见的是之前启动过的 Tomcat 实例没被杀掉或者别的开发工具如某个前端调试服务占了端口。这不是项目代码问题但没处理过的新手很容易卡在这一步。解决Windows 下用netstat -ano | findstr 8080找到 PID再到任务管理器结束对应进程也可以改 Tomcat 的端口。如果只是想快速跑通项目改端口最省事——在path-to-tomcat/conf/server.xml里把Connector port8080改成 8081。但注意如果项目代码里有写死的路径或 Ajax 请求地址指向http://localhost:8080/xxx改端口后这些请求也会受牵连所以我更推荐直接杀进程。5.5 TestCommunity.jsp 单独访问 404项目上下文路径没弄清楚现象Tomcat 启动成功login.jsp能访问但直接访问http://localhost:8080/TestCommunity.jsp时返回 404而在 Eclipse 里明明能看到这个文件。原因Web 应用的访问路径是http://localhost:8080/项目上下文路径/资源路径而不是http://localhost:8080/资源路径。Eclipse 里工程的上下文路径默认是工程名如果你在浏览器直接敲 JSP 文件名少了工程名这一段Tomcat 自然找不到。解决先看 Tomcat Server 配置里 Modules 那一栏显示的 Path 是什么假设是/smart-meter那么访问首页应该是http://localhost:8080/smart-meter/index.jsp。为了避免手写路径出错代码里尽量用${pageContext.request.contextPath}拼绝对路径上面登录表单的例子就是这么处理的。如果你确认路径没问题还是 404再检查TestCommunity.jsp是否放在了src/main/webapp根目录下而不是误放进WEB-INF里——WEB-INF下面的资源是禁止直接通过 URL 访问的。6. 进阶验证用一条模拟数据打通信道到展示的全链路代码跑通和逻辑跑通是两回事。这里我给你一个验证套路在本地往meter_data插入一条模拟抄表数据然后操作前端页面确认这个数据能显示出来。这个过程看起来简单实际上把数据库、DAO、Servlet、JSP 整条链路都验证了一遍比对着页面空谈功能扎实得多。先写一个模拟数据写入的 Servlet访问它就往库里塞一条记录。简单实现如下WebServlet(/simulate) public class SimulateDataServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String meterNo MTR-1001; BigDecimal flow new BigDecimal(Math.round(Math.random() * 50 * 10) / 10.0); String sql INSERT INTO meter_data (meter_no, user_name, reading, flow, status, collect_time) VALUES (?, ?, ?, ?, 0, NOW()); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, meterNo); ps.setString(2, 测试住户); ps.setBigDecimal(4, flow); ps.executeUpdate(); response.getWriter().write(ok); } catch (Exception e) { e.printStackTrace(); response.getWriter().write(error); } } }这里Math.round(Math.random() * 50 * 10) / 10.0是生成一位小数的随机用水量范围控制在 0.0 到 50.0 吨之间。status直接写 0 表示正常。访问一次http://localhost:8080/项目路径/simulate浏览器显示 ok然后打开index.jsp最新的记录应该出现在列表第一行。你可能会问为什么要把reading留空实际上你插入数据时应该补上reading——它表示当前累计读数我这里是为了演示批量插入时的字段取舍。更严谨的写法是把当前表里该水表最近一条reading查出来加上flow得到新的累计值。你可以自己改造成读取上一条 → 累加 → 插入三步这其实就是远程抄表后台真实的数据处理逻辑表端上报的是累计读数后台计算增量。从那以后我每次拿到这类毕设项目不管什么技术栈都强制走一遍模拟数据全链路验证写一条测试数据、跑一次查询、看一次页面渲染、查一次数据库结果。四步走完项目能不能用、哪个环节断了心里一清二楚。做课设最怕的不是代码写不出来而是数据从哪来、往哪去、页面凭什么显示这三件事你亲手验证过一遍答辩时随便老师怎么问都接得住。希望这套资源能帮你把智能水表系统从能跑做到能讲透少走点弯路。本文还有配套的精品资源点击获取