☰
智慧能耗管理系统后端源码解析:九大模块架构与核心链路实现
2026/10/26 7:54:53 网站建设 项目流程

简介:面向毕业设计或课程作业场景的智慧能耗管理系统后端源码包,聚焦能耗数据采集、分析与智能优化,适合具备一定Java/Spring基础并希望实践物联网与人工智能应用的计算机专业学生。包体共481个文件、约53.56MB,以Java源码与XML配置为主,另有JAR依赖、属性配置、YAML及部署相关文件,整体覆盖业务逻辑、接口定义、数据库脚本、系统配置与容器化部署等完整后端工程结构。压缩包内按client、common、dao、interface、report等模块组织,便于按分层架构理解项目与二次开发。目前已有67人学习下载,适合作为毕业设计参考或课程项目实战素材;通过研读源码可掌握API设计、数据持久化、服务分层及能耗预测等AI辅助功能的落地思路,同时积累从需求分析到部署上线的完整项目经验。

1. 智慧能耗管理系统后端:这份 zip 里的九个模块到底答不答得完毕设

拿到这份智慧能耗管理系统(后端)的 zip,我第一反应不是解压,而是先看文件列表。九个 .iml 后缀的模块文件整整齐齐躺在里面,说明这是一个标准的 IDEA 多模块 Java 后端工程,不是随手丢进来的散代码。对做毕设或者课程作业的人来说,这种结构比单模块项目更接近真实的前后端分离项目实战:common、dao、service、interface、web 分层明确,还带着 report 和 work 这类业务模块。整套系统要解决的事,听名字就能猜个大概——把能耗数据接进来、存下来、算清楚,再给前端展示出去,顺带用一点人工智能手段做趋势预测。无论你是想拆着学架构,还是想把后端直接跑起来做演示,这份源码都值得先花十分钟读懂它的模块地图。

2. 从 .iml 反推项目架构:九个模块各管哪一段,依赖怎么理

拿到陌生工程先读代码?我习惯先扫一遍 .iml 文件。后缀 .iml 是 IntelliJ IDEA 的模块描述文件,全称 IntelliJ Module。它不是业务源码,但记录了模块名、模块类型、源目录、依赖关系这些关键信息。一个多模块 Java 工程长什么样,不用跑代码,看这几个文件就能还原出七八成。

2.1 为什么 .iml 文件能暴露项目全貌

每个 .iml 文件本质是 XML,里面存着模块的元数据。我贴一段典型的片段,你对照自己解压出来的文件看,结构基本一致:

<?xml version="1.0" encoding="UTF-8"?> <module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4"> <component name="NewModuleRootManager" LANGUAGE_LEVEL="JAVA_1_8"> <content url="file://$MODULE_DIR$" /> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="module" module-name="service" /> </component> </module>

这段是示意,实际文件以你解压后为准。两个字段最关键:一是LANGUAGE_LEVEL="JAVA_1_8",直接告诉你编译语言级别是 Java 8,后面配置 JDK 时不能乱用 17 或 21;二是MavenProjectsManager.isMavenModule="true",说明这个工程是 Maven 管理的多模块项目,而不是 Gradle——Gradle 多模块一般只保留 .gradle 文件,不会给每个模块单独生成 .iml。

想不打开 IDEA 就快速摸清模块依赖,我一般直接跑一条命令:

grep -h 'orderEntry type="module"' *.iml | sed 's/.*module-name="//;s/".*//' | sort -u

逻辑说明:这条命令扫描当前目录下所有 .iml,把引用其它模块的 orderEntry 行抓出来,再用 sed 剥掉前后缀,只留下module-name的值,最后排序去重。跑完你就能得到一张「谁依赖谁」的简表,比在 IDEA 里逐个点开模块设置快得多。参数上注意grep -h是去掉文件名前缀,sort -u是去重,如果你的文件不在同一目录,先把它们拷到临时目录再跑。

拿到这份依赖简表后,再配合 IDEA 的导入步骤,项目就跑起来了:File → New → Project from Existing Sources,选中工程根目录的 pom.xml,IDEA 会自动识别全部九个模块。如果你只选中某个 .iml 文件导入,模块间的依赖引不到,后面全是飘红报错。

2.2 common / dao / service:三层架构的模块化拆分

先把三门基础课讲清楚,这三个模块是整套系统的骨架。common 放的是工具类、常量、统一返回体 Result、自定义异常、分页参数;dao 层放 MyBatis 的 Mapper 接口和 SQL XML,或者 JPA 的 Repository;service 层放业务规则、事务边界、统计逻辑和预测逻辑。调用方向是单向的:web/interface → service → dao,common 可以被任意层引用,但 dao 绝不能反过来调用 service,这是分层的底线。

实际工程里代码会长这样:

// web 层:接收前端请求,不写业务逻辑 @RestController @RequestMapping("/api/energy") public class EnergyController { private final EnergyService energyService; public EnergyController(EnergyService energyService) { this.energyService = energyService; } @GetMapping("/daily") public Result<List<DailyEnergyVO>> daily(@RequestParam String date) { return Result.ok(energyService.getDailyReport(date)); } }

逻辑说明:Controller 只做两件事——接收参数、包装返回结果,真正的统计逻辑在EnergyService里。参数用@RequestParam String date表示按天查询,格式建议yyyy-MM-dd,Controller 不做格式校验,格式合法性在 service 层统一校验,这样多个入口调用 service 时校验逻辑不会重复。为什么强调构造器注入而不是在 Controller 里new一个 Service?因为构造器注入能让你在单元测试时把 Service 替换成 mock 对象,这是答辩时聊「可测试性」的加分点。

dao 层对应的是 Mapper 接口加 XML,你会在 dao 模块里看到类似EnergyRecordMapper.java和EnergyRecordMapper.xml成对出现的文件。XML 里只写 SQL,不写业务判断,连表查询和聚合都在这一层解决。

2.3 web / interface / report:接入层、接口层和报表层怎么分工

新手最容易懵的是 web 和 interface 的区别。同一个工程里同时出现这两个模块,说明设计者把「HTTP 接入」和「接口契约」分开了。web 模块放 Controller、拦截器、跨域配置、全局异常处理,是真正对外提供 HTTP 服务的地方;interface 模块放请求参数对象、响应对象、以及可能存在的 RPC 接口定义——比如 Dubbo 的接口,或者模块间调用的 Feign 接口契约。

怎么快速判断 interface 在工程里的真实角色?看 web 模块里 Controller 的 import 列表:如果 import 的全是 interface 包下的 Request/Response 对象,说明 interface 是契约层,前后端约定和模块间约定都沉淀在这;如果 import 的是 service 的接口,那 interface 只是业务接口定义层。两种设计都常见,能在答辩时说清你选的是哪一种,本身就是对架构意识的证明。

report 模块专门处理报表。能耗系统里报表是重头戏,日报、月报、区域汇总、分项占比,全是给人看图表的。report 模块通常不直接暴露 HTTP 接口,而是被 web 层调用,或者被定时任务触发——注意,这个「定时任务」大概率就在 work 模块里。

2.4 ql / qlzh / work:核心业务模块的命名规律

ql 和 qlzh 这两个名字,一看就是「擎联」「擎联智慧」的拼音缩写。q 系列模块承载的是业务域代码:设备档案、计量点管理、能耗读数、分项统计。qlzh 大概率是核心业务域,ql 是基础数据域,比如组织架构和字典配置。work 模块对应工作台或定时任务,每天凌晨跑统计、跑预测,都归它管。命名规律说明开发者在拆模块时用的是「业务域」思维而不是单纯的技术分层——既有 common/dao/service 这种技术层模块,又有 qlzh/report/work 这种业务域模块,混合分层在真实项目里很常见,这也是这份资源值得学的地方。

按文件名反推,职责地图大概是这样的:

模块文件职责推测典型内容
common.iml公共基础设施工具类、常量、统一返回体、全局异常
dao.iml数据访问层Mapper 接口、SQL XML、实体类
service.iml业务逻辑层事务逻辑、统计逻辑、预测逻辑
web.imlHTTP 入口Controller、拦截器、跨域配置
interface.iml接口契约层DTO/VO、RPC 接口定义
report.iml报表域聚合 SQL、报表模板
ql.iml / qlzh.iml核心业务域设备、能耗读数、分项计量
work.iml任务/工作台定时任务、调度、待办

这个表只是依据文件名做的推测,不要当成文档用,但按这个地图去读代码,能少走不少弯路。我拆这类工程有个习惯:把表格贴在 IDE 旁边,每读完一个包的代码,就回去核对更新一次职责描述,读到一半发现职责地图与实际不符,那往往就是代码里有彩蛋或坏味道的地方。

读代码的顺序建议:先读 common 的 Result 和异常定义,知道接口返回格式长什么样;再读 interface 的 DTO,知道前端传什么、后端回什么;接着读 dao 的表结构,知道数据从哪来;最后读 service 和 web,把调用链串起来。按这个顺序走,三十分钟能把这套工程的骨架摸清楚。

3. 能耗数据从采集到预测:一套数据模型与两条核心链路

模块结构看完了,接下来要回答的是「这套系统到底在做什么」。能耗管理系统的后端,核心就是两条链路:采集链路——设备把读数上报进来;分析链路——把明细数据聚合成报表,再喂给预测模块。下面从数据模型讲到具体实现。

3.1 能耗数据模型:设备、计量点、读数、账单的关系

数据模型是最先要从抽象里想清楚的事。能耗场景里,设备是物理载体(一台空调、一块电表),计量点是采集单位(一块表可以有几个计量点),读数是最小记录(某个时刻的用量),报表是聚合结果。最小化设计四张表就能撑起整个系统,核心两张如下:

-- 设备表:物理设备注册信息 CREATE TABLE device_info ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', device_no VARCHAR(32) NOT NULL COMMENT '设备编号,通常由厂商下发', device_name VARCHAR(128) NOT NULL COMMENT '设备名称', location VARCHAR(128) COMMENT '安装位置,如 A 栋 3 层', status TINYINT NOT NULL DEFAULT 1 COMMENT '1 启用,0 停用', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_device_no (device_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备档案表'; -- 能耗读数表:每次采集产生的明细数据 CREATE TABLE energy_record ( id BIGINT NOT NULL AUTO_INCREMENT, device_id BIGINT NOT NULL COMMENT '关联 device_info.id', collect_time DATETIME NOT NULL COMMENT '采集时间,业务时间而非入库时间', energy_type TINYINT NOT NULL COMMENT '1 电,2 水,3 气', energy_value DECIMAL(12,4) NOT NULL COMMENT '本次读数,累计值或增量值由采集协议约定', unit VARCHAR(8) NOT NULL DEFAULT 'kWh', PRIMARY KEY (id), KEY idx_device_time (device_id, collect_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能耗采集明细表';

参数说明:energy_record是整个系统的数据地基,collect_time必须用业务采集时间而不是数据库入库时间——否则设备断网后补报数据,你会按错误时间去聚合,报表直接错位。energy_value的单位和口径要在采集协议里写死,累计值还是增量值必须统一,混用了后面所有统计全部翻车。索引idx_device_time支撑的是「按设备按时间段」查询,这是报表模块最高频的查询模式。

报表统计结果一般再落一张energy_stat表,字段就是device_id + stat_date + energy_type + total_value,主键加唯一键(device_id, stat_date, energy_type)。有没有这张表,决定了报表查询是秒开还是几十秒。

3.2 数据接入:采集接口的设计与校验逻辑

数据怎么进来?常见做法是设备侧通过上报接口推数据,后端暴露一个接收接口。这个接口只做接收和结构校验,业务加工全扔给 service。用 Spring Boot 写一个采集接收接口,骨架长这样:

@RestController @RequestMapping("/api/collect") public class CollectController { private final EnergyRecordService recordService; public CollectController(EnergyRecordService recordService) { this.recordService = recordService; } /** * 设备上报单个读数 * body 示例:{"deviceNo":"MTR-001","collectTime":"2025-06-01 12:00:00", * "energyType":1,"value":10086.5} */ @PostMapping("/record") public Result<Void> record(@RequestBody CollectRequest req) { recordService.saveRecord(req); return Result.ok(); } }

参数说明:CollectRequest里四个核心字段——deviceNo设备编号、collectTime业务采集时间、energyType能源类型、value读数。接收端只做字段非空、时间格式、value 范围这类结构校验;设备是否存在、时间是否合理、是否重复上报这些业务校验放 service。这样设计的理由:采集接口会被设备高频调用,校验逻辑必须快,业务校验放在 service 层可以复用给其它入口。

真正要小心的是幂等。设备断网恢复后会把积压的数据重新上报一遍,同一个deviceNo + collectTime + energyType可能被推送多次。我一般会在energy_record表上直接加唯一键(device_no, collect_time, energy_type),然后在saveRecord里捕获DuplicateKeyException后直接返回成功。这样设备重试不会污染统计数据,也不用引入额外缓存。这个坑在毕设答辩时主动讲出来,比背十页文档都有说服力。

3.3 统计报表:按日/月/区域聚合的 SQL 思路

能耗报表的核心是聚合,第一反应就是GROUP BY,但有两个口径问题要先想清楚:如果读数是增量值,直接SUM没问题;如果是累计值,得用相邻记录差分或者「区间最大值减最小值」,这个口径必须和采集协议对齐。日报表典型写法:

<select id="sumDailyByDevice" resultType="com.qlzh.report.dto.DailyStatDTO"> SELECT t.device_id AS deviceId, t.stat_date AS statDate, t.energy_type AS energyType, SUM(t.energy_value) AS totalValue FROM ( SELECT device_id, DATE_FORMAT(collect_time, '%Y-%m-%d') AS stat_date, energy_type, energy_value FROM energy_record WHERE collect_time BETWEEN #{beginTime} AND #{endTime} ) t GROUP BY t.device_id, t.stat_date, t.energy_type ORDER BY t.device_id, t.stat_date </select>

逻辑说明:内层子查询先按时间范围把数据卡住,再用DATE_FORMAT把时间泛化成天,外层GROUP BY按设备按天按能源类型聚合。三个分组字段支撑「哪个设备、哪天、什么能源用了多少」这个最小报表粒度。参数beginTime和endTime是查询区间,我一般会在 service 层把时间边界按自然日算好再传进来,而不是在 SQL 里对collect_time做函数处理——否则字段上的索引就失效了。

提示:数据量上来之后,别每次报表都扫明细表。常见做法是每天凌晨用定时任务——就是你工程里的 work 模块——把前一天的聚合结果写进energy_stat统计表,报表查询只查统计表,明细表只保留近 90 天。答辩时主动说出这个设计,比堆 CRUD 有说服力得多。

3.4 人工智能预测模块:时序数据怎么落到 service

摘要里挂着「人工智能」标签,落到后端代码里大概率是两件事:一是规则引擎做节能策略,二是时序模型做能耗趋势预测。毕设场景我不会建议上深度学习,数据量不够、训练时间也不允许。常见做法是用移动平均或线性回归做未来几小时、几天的预测,效果够用,也好解释。

public class EnergyPredictor { /** * 基于最近 N 个历史点做移动平均预测 * @param history 历史值列表,按时间升序 * @param window 取最近几个点,建议按业务周期设置 * @return 下一个时间点的预测值 */ public double predictNext(List<Double> history, int window) { if (history == null || history.isEmpty()) { return 0d; } int size = history.size(); int start = Math.max(0, size - window); double sum = 0d; for (int i = start; i < size; i++) { sum += history.get(i); } return sum / (size - start); } }

参数说明:history是某设备某能源类型最近一段时间的序列,window是窗口大小,取值 7 表示取最近 7 个点平均。如果数据是按小时采集,想预测工作日的用电高峰,窗口可以改成「过去 7 天同一时刻的值」——把history预先过滤成同一时刻的序列再传入,预测含义就变成了周期叠加。移动平均是最稳的兜底方案,换线性回归也简单,用最小二乘拟合斜率再外推,趋势型曲线会更贴合。

预测结果往哪用?两个地方:一是给报表模块的「未来 24 小时能耗趋势」曲线图用,二是给规则引擎做阈值预判——预测值超过设定阈值就触发告警。这就是「人工智能」在后端最常见的落地姿态:不炫模型,把预测结果接进业务链路,让告警和报表都活起来。我习惯把预测结果缓存起来设 30 分钟过期,预测计算本身轻,但前端每次刷新图表都重新拉历史数据算一遍,就没必要了。

4. 本地复现一个能跑的后端:JDK/Maven/MySQL 版本选择与三步启动

代码看懂了,接下来是把它跑起来。这类毕设后端工程,环境版本踩的坑往往比代码本身多。老工程不是越新的环境越好,先按稳妥组合跑通,再考虑升级。

4.1 环境清单:JDK、Maven、MySQL 的版本选择

先给一张我实测过比较稳的组合表,照着装能省掉一半玄学问题:

组件推荐版本选择理由
JDK1.8(8u202 及以上)老毕设工程最常见的编译级别,高版本 JDK 容易触发 Lombok、cglib 兼容问题
Maven3.6.3 ~ 3.8.8太老的 Maven 拉不动较新依赖,太新的对老 pom 有兼容告警
MySQL5.7 或 8.0.36+5.7 兼容性最好,8.0 要注意时区和认证插件问题
IDEA2021.3 及以上对 Maven 多模块工程支持稳定
Redis2.6 以上(可选)代码里有缓存注解才需要,没有就跳过

这张表的逻辑是「向老版本靠」。课程作业和毕设工程大多是两到三年前写的,依赖版本普遍不高,直接用 JDK 17 编译经常会遇到javax.*包名迁移和 Lombok 版本不兼容这类问题。先按 1.8 跑通再升级,顺序不要反。

装完环境先做三连确认:

java -version # 确认输出是 1.8.0_xxx,而不是 17 或 21 mvn -v # 确认 Maven 版本在 3.6 以上 mysql --version # 确认 MySQL 可执行,再确认服务已启动

参数说明:java -version重点看 major version,如果显示 17 或 21,要么换 JDK,要么进 IDEA 的 Project Structure 把 Language Level 调回 8。mvn -v还要看它读取的settings.xml路径,确认本地仓库位置和你 IDEA 里配置的一致,否则命令行能编译、IDEA 里却飘红,就是两边仓库没对上。

4.2 client.conf 与配置文件:从配置到数据库初始化

压缩包里有个client.conf文件,按名字判断,它管的是设备接入侧或采集客户端的参数——服务器地址、上报周期、连接超时、密钥。这类配置文件在毕设工程里两种用法都有:一是给后端集成测试用,二是给模拟采集端用。我更推荐第二种:演示时让「模拟设备」按这个配置去上报数据,没有真硬件也能让系统完整流转起来。典型内容如下,具体以你解压后的实际文件为准:

# 采集客户端连接后端的配置 server.host=127.0.0.1 server.port=8080 # 上报间隔,单位秒 collect.interval=30 # 模拟设备编号与能源类型 device.no=MTR-001 energy.type=1

参数说明:server.host和server.port指向后端服务的地址,collect.interval是模拟采集端的上报间隔。演示时我会把 interval 调成 10 秒,前端曲线刷新快、观感好;正式验证时调回 300 秒,减轻数据库写入压力。device.no和energy.type控制在演示里造哪一类数据。

数据库初始化靠自己找 SQL 脚本。工程里数据库脚本一般放在db/或doc/目录下,找到后执行:

mysql -uroot -p < db/init.sql # 导入表结构 mysql -uroot -p < db/data.sql # 导入基础数据:设备、区域、用户

说明:两条命令分别导入表结构和基础数据。data.sql里通常是设备档案、区域字典、用户账号这类「没有业务也能跑起来」的初始化数据。如果工程里建库语句用了utf8mb4而连接串用的是utf8,导入后中文可能乱码,检查点放在字符集。

4.3 启动顺序与接口自测

多模块工程的启动顺序一条铁律:先基础设施,再后端服务。MySQL 先起来,Redis 有也先起来,然后才是后端 jar。首次构建建议跳过测试,毕设工程的测试用例经常依赖测试库或特定环境,直接跑会被环境卡住,不是代码有问题。

# 工程根目录执行,跳过测试打包 mvn clean package -DskipTests # 查看各模块打出的 jar ls */target/*.jar # 启动 web 模块,jar 名以 target 下实际文件为准 java -jar web/target/web-1.0.0.jar --spring.profiles.active=dev

参数说明:-DskipTests表示只跳过测试执行,但会编译测试代码;如果想连测试代码编译都跳过,用-Dmaven.test.skip=true。启动参数--spring.profiles.active=dev会加载application-dev.yml里的配置,数据源、端口、日志级别都从那里读。如果启动日志报「Failed to configure a DataSource」,八成是数据库连接串或账号密码不对,先别查代码,先查配置。

启动成功后不要急着开前端,先用 curl 做接口自测:

# 健康检查接口,路径以工程实际为准 curl -X GET http://127.0.0.1:8080/api/health # 登录接口,确认数据库连接和用户表正常 curl -X POST http://127.0.0.1:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'

逻辑说明:健康检查确认进程起来了、能响应 HTTP;登录接口确认数据库连接正常、用户表有数据。健康检查通但登录 500,优先查数据库连接串和账号权限,这是两个最常见的翻车点。登录返回的 token 记下来,后续接口在 header 里带Authorization: Bearer <token>,具体鉴权方式看工程里的拦截器实现。

前端联调时记得确认后端跨域配置允许本地开发地址,否则前后端分离的项目浏览器里会被 CORS 拦死。这个问题频率太高,下章专门展开。

5. 复现路上的避坑记录:四条血泪经验从导入报错到接口不通

以下四个问题是我在复现同类型毕设后端时踩过、也帮别人排过的,每条按「现象 → 原因 → 解决」写,希望能帮你绕开。

5.1 IDEA 导入后模块飘红:Maven 依赖没进来

现象:IDEA 导入根 pom.xml 后,项目树里每个模块都标红波浪线,提示「Cannot resolve symbol」或找不到SpringBootApplication。

原因:最常见两种——本地 Maven 仓库没有 pom 里声明的依赖版本,IDEA 不会自动帮你全量拉取;或者 Maven 配置的镜像仓库失效,依赖下到一半就断了。IDEA 默认的 Maven 配置还容易和你命令行装的 Maven 指向不同的settings.xml,依赖就「一会儿有一会儿没有」。

解决:先在命令行把依赖拉下来:

mvn dependency:resolve -DskipTests

然后回到 IDEA,右键根 pom → Maven → Reload Project(旧版本叫 Reimport)。如果还是飘红,检查 IDEA 的 Maven 设置里 User settings file 是否指向你实际使用的settings.xml,跟命令行保持一致。这条处理完,百分之八十的飘红能消掉。

5.2 MySQL 8 时区导致时间错乱:日期差 8 小时

现象:接口返回的统计日期比实际晚 8 小时,或者按天查询查不到当天数据,只有下午以后的数据能对上。

原因:MySQL 8 默认时区跟 Java 进程时区不一致,JDBC 连接串里又没指定serverTimezone。Java 侧按本地时区 UTC+8 传参,数据库侧按 UTC 存储或返回,两头一对就偏了 8 小时。这个偏移跟DATETIME类型无关,纯粹是连接串设置问题。

解决:在数据源连接串里显式指定时区:

jdbc:mysql://127.0.0.1:3306/energy?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false

说明:serverTimezone=Asia/Shanghai是关键参数,必须显式写明。顺带一个隐藏坑:如果 MySQL 是 5.7 且系统时区是 UTC,同样偏移也会出现,解法一致。改完连接串记得重启后端进程,只改配置文件不重启是没用的。

5.3 接口 404 或跨域拦截:前后端分离联调的老翻车

现象:后端起了,curl 能通,但前端页面的请求全部失败。控制台要么报跨域 CORS,要么前端请求/api/xxx后端路由是/xxx,全部落到 404。

原因:两个独立问题。跨域是因为前端地址(比如localhost:5173)与后端地址(localhost:8080)不同源,后端没配 CORS 过滤器,浏览器直接拦截响应;404 是因为前端没把/api前缀代理到后端,或者后端设置了 context-path 和前端约定不一致。

解决:跨域在 web 模块加一个全局配置类:

@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); // 开发环境放开,生产必须改成具体域名 config.addAllowedMethod("*"); config.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }

参数说明:addAllowedOriginPattern比addAllowedOrigin宽容,支持带端口的具体域名;生产环境把*改成https://your-domain,不要图省事。类里的 import 按你工程包名补全。404 那个问题去前端工程里看vite.config.ts或vue.config.js的 proxy 配置,把/api前缀代理到 8080 端口;如果后端设置了 context-path,比如/energy,proxy 里的路径要不就带全、要不就统一去掉,前后必须一致。

5.4 报表数据为空:SQL 聚合分组字段不一致

现象:分项统计接口返回成功,但totalValue全是 0,或者某些设备在报表里查不到数据,前端折线图一条线也没有。

原因:最常见是两个——energy_type字段在表里是数字(1、2、3),查询条件里传的是字符串'1',MySQL 隐式转换有时能通、有时对不上;另一种更隐蔽,采集端上报的是设备编号device_no,报表查询用的却是主键device_id,两套标识对不上,数据自然查不出来。

解决:先把一条明细捞出来核对:

SELECT id, device_id, energy_type, energy_value, collect_time FROM energy_record ORDER BY id DESC LIMIT 5;

说明:先看采集数据有没有进库。有数据再看统计查询条件,重点核对device_id传的是主键还是编号。我的习惯是查询接口只接收device_no,在 service 层先转成device_id再查,报表层和采集层永远用一套标识体系。这个习惯从那以后一直保留着,能避免大量「数据明明有却查不到」的尴尬。

6. 毕设演示的加分技巧:给后端造一批像样的能耗数据

后端能跑只是及格,演示有说服力才是加分。我见过太多毕设演示翻车,不是系统崩了,而是数据太假——一条水平直线,看不出任何波动,预测模块拿什么预测?正确的做法是先造一批「看着像真的」能耗数据:工作日白天高、凌晨低、周末低、整体有缓慢趋势、带随机噪声。这套数据既能让报表曲线有峰有谷,又让预测模块有规律可循。

我习惯用独立脚本造数,不污染业务代码,直接写进 MySQL:

import random import datetime import pymysql conn = pymysql.connect(host="127.0.0.1", user="root", password="123456", database="energy", charset="utf8mb4") cur = conn.cursor() device_id = 1 # 假设设备主键是 1 start = datetime.datetime(2025, 5, 1) for i in range(24 * 90): # 90 天,每小时一条 ts = start + datetime.timedelta(hours=i) hour = ts.hour # 工作日白天高、夜间低,周末整体偏低 base = 50 if (ts.weekday() < 5 and 8 <= hour <= 20) else 20 # 叠加随机噪声和缓慢上升趋势 value = base + random.uniform(-5, 5) + (i / 1000) cur.execute( "INSERT INTO energy_record(device_id, collect_time, energy_type, energy_value, unit) " "VALUES(%s, %s, %s, %s, %s)", (device_id, ts, 1, round(value, 2), "kWh"), ) conn.commit() cur.close() conn.close()

参数说明:脚本造 90 天、每小时一条的用电数据。工作日 8 点到 20 点基线 50,夜间基线 20,random.uniform(-5, 5)模拟测量噪声,(i / 1000)模拟缓慢上升的能耗趋势。三个参数按需调:24 * 90改数据量,基线值改负载形态,噪声幅度改曲线毛刺程度。如果设备不止一台,外层再加个device_id循环。

验证方法:造完数先查统计接口,确认聚合值有波动;再看告警阈值会被触发——把阈值设成 60,没有噪声数据告警永远不会触发,演示就没有互动性;加了噪声后偶尔触发的告警反而让答辩节奏更自然。

从那以后,我每次拿到别人的毕设后端,都先不急着跑业务功能,而是先把造数脚本写好,让演示数据能讲故事,再对着故事去演示模块。这份 zip 里的后端如果跑通了,也建议你照这个顺序走一遍——先造数,再跑接口,最后录演示。希望帮到你。

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

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

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

立即咨询