news 2026/10/17 11:49:38

基于Web的城市能源管理系统:从架构设计到二次开发全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Web的城市能源管理系统:从架构设计到二次开发全解析

简介:这是一套面向城市能源管理场景的Web系统源码,适合计算机、物联网及能源相关专业学生与开发者用于课程设计、毕业设计或技术学习。系统以Bootstrap框架构建响应式界面,涵盖登录验证、能源数据可视化仪表盘、实时监测、异常预警以及设备入库、检修与查询等模块,可帮助读者理解从数据采集到策略调整的完整业务闭环。资源包共170个文件,约3MB,包含18个html页面、18个js脚本、13个css样式及14个less、14个scss预编译文件,另有64张png与14张jpg图片、字体图标文件及1个db数据库文件,前端资源与页面结构较为完整。目前已有899人学习下载。通过该源码,读者可参考AdminLTE后台布局与图表组件的组织方式,学习响应式页面搭建、能源监测数据展示及预警逻辑的实现思路,也可在此基础上进行二次开发或功能扩展。

1. 从一块电表到一张城市能源网:这套 Web 系统到底在管什么

我第一次接触城市级能源管理,是因为一个很具体的场景:园区里几十栋楼,每栋楼有独立的电表、水表、空调机组,物业每个月抄一次数,数据散在 Excel 里,谁也用不起来。后来有人提了个需求——能不能在一个浏览器页面里,把整片区域的用电、用水、用气实时看清楚,还能按楼栋、按时间段拉出报表?这就是「基于 Web 的城市能源管理系统」要解决的问题。它面向的是园区运维、能源服务商、以及做智慧城市二次开发的团队,核心价值是把分散的计量设备数据统一采集、存储、展示和分析,让能源消耗从一笔糊涂账变成可查、可比、可预警的数字资产。读完这篇,你能把它跑起来,也能顺着模块往下改。

2. 技术选型与整体架构:数据从设备到浏览器的完整链路

2.1 为什么是 Web 而不是桌面端

城市能源管理系统的用户角色很杂:运维人员要在值班室大屏上看实时曲线,能源分析师要在自己工位上导出月度报表,管理层可能只想在手机上瞄一眼总览。桌面端装客户端的方式在这种场景下基本活不过第一轮需求评审。Web 方案的优势在于一次部署、多端访问,浏览器就是客户端,升级只需要更新服务端。

从技术栈上看,这类系统常见做法是前后端分离。前端用 Vue 或 React 搭 SPA,图表库选 ECharts,因为能源行业对曲线图、饼图、热力图的需求非常密集,ECharts 的配置项丰富度和国内文档友好度都占优。后端主流是 Spring Boot,配合 MyBatis-Plus 做数据访问,原因很实际:能源项目往往要对接甲方已有的 Java 技术体系,招人、交接、二次开发都顺。数据库层面,业务数据用 MySQL,时序数据用 InfluxDB 或 TDengine,这个拆分后面会细说。

提示:如果你的团队更熟 Python,后端换成 FastAPI 或 Django 也能跑通同样的架构,核心链路不变,只是生态工具不同。

2.2 四层架构拆开看

整个系统我习惯按四层来理解,从下往上分别是:

设备采集层:电表、水表、气表、温湿度传感器通过 Modbus、MQTT 或 HTTP 协议上报数据。这一层的关键是协议适配,不同厂家的表计协议不一样,需要一个采集网关做归一化。

数据存储层:采集上来的原始数据先落到时序数据库,按设备 ID 和时间戳建索引。业务侧的设备档案、用户权限、报警规则存在 MySQL 里。这样拆的好处是时序库扛高频写入,MySQL 扛关系查询,各干各的活。

业务服务层:Spring Boot 应用,对外提供 RESTful 接口。核心模块包括设备管理、数据查询、报表统计、报警引擎、权限控制。报警引擎通常是一个定时任务或消息消费者,按规则扫描最新数据,触发阈值就写报警记录并推送。

展示层:Vue 前端,通过 Axios 调后端接口,ECharts 渲染图表。大屏页面会做自适应缩放,适配不同分辨率。

2.3 目录结构长什么样

一个典型的项目目录大致是这样组织的:

energy-management/ ├── energy-backend/ # 后端服务 │ ├── src/main/java/com/energy/ │ │ ├── controller/ # 接口层 │ │ ├── service/ # 业务逻辑 │ │ ├── mapper/ # 数据访问 │ │ ├── entity/ # 数据库实体 │ │ ├── config/ # 配置类 │ │ └── job/ # 定时任务 │ └── src/main/resources/ │ ├── application.yml # 主配置 │ └── mapper/ # MyBatis XML ├── energy-frontend/ # 前端工程 │ ├── src/ │ │ ├── views/ # 页面 │ │ ├── api/ # 接口封装 │ │ ├── components/ # 公共组件 │ │ └── router/ # 路由 │ └── package.json └── docker-compose.yml # 一键启动依赖服务

这个结构不是死的,但分层逻辑值得保留:controller 只做参数校验和响应封装,service 承载业务,mapper 只管 SQL。二次开发时,加一个新报表功能,基本就是在这三层各加一个文件,路径清晰,不容易改乱。

2.4 数据采集与存储的关键设计

采集层最容易被低估。我见过不少项目一开始直接用 HTTP 轮询电表,设备一多就崩。常见做法是引入 MQTT Broker,设备作为客户端发布数据,后端订阅主题。这样设备主动上报,服务端不用维护轮询列表,扩展性也好。

数据写入时序库时,表结构设计有个坑:不要把每个设备建一张表。正确做法是按设备类型建表,用 tag 区分设备 ID。比如电表数据统一写进meter_power表,device_id作为 tag,voltage、current、power作为 field。这样查询某栋楼所有电表某时间段的功率,一条 SQL 就能搞定。

-- 时序库中电表数据的典型查询(以类 InfluxDB 语法示意) SELECT mean("power") AS avg_power FROM "meter_power" WHERE "building_id" = 'B01' AND time >= now() - 24h GROUP BY time(1h), "device_id"

这段查询按小时聚合过去 24 小时的功率均值,building_id是设备档案里带过来的标签,GROUP BY time(1h)是时序库的时间分桶。参数怎么调:time(1h)改成time(15m)就是 15 分钟粒度,适合看负荷波动细节;mean换成max就是峰值统计,做需量分析时用得上。

3. 本地跑起来:从零到看见第一张能耗曲线

3.1 环境准备与依赖安装

跑这个项目之前,本机需要准备这些东西:JDK 8 或 11、Maven 3.6+、Node.js 14+、MySQL 5.7+、Redis(做缓存和会话)、以及一个时序数据库。如果不想一个个装,项目根目录的docker-compose.yml可以一键拉起 MySQL、Redis 和时序库。

# 启动基础依赖服务 docker-compose up -d mysql redis influxdb # 查看容器状态,确认三个服务都 healthy docker-compose ps

这里有个细节:docker-compose.yml里 MySQL 的初始化脚本通常放在./sql/init.sql,容器第一次启动时会自动执行建库建表。如果你本机已经有 MySQL 在跑,端口可能冲突,改一下 compose 文件里的端口映射就行,比如把3306:3306改成3307:3306。

3.2 后端启动与配置修改

后端启动前,先改application.yml里的连接信息。重点看这几个配置项:

spring: datasource: url: jdbc:mysql://localhost:3306/energy_db?useUnicode=true&characterEncoding=utf8 username: root password: your_password redis: host: localhost port: 6379 influx: url: http://localhost:8086 database: energy_ts retention-policy: autogen

url里的energy_db要和初始化脚本建的库名一致。influx那段是时序库连接,retention-policy控制数据保留策略,默认autogen是永久保留,生产环境一般会改成30d或90d,避免磁盘无限增长。

改完配置,在energy-backend目录下执行:

mvn clean package -DskipTests java -jar target/energy-backend-1.0.0.jar

看到控制台输出Started EnergyApplication就说明后端起来了,默认端口 8080。如果启动报错,先看是不是数据库连不上,再看 Redis 有没有开,这两个是最高频的翻车点。

3.3 前端启动与接口联调

前端在energy-frontend目录下,先装依赖再启动:

npm install npm run serve

启动后浏览器访问http://localhost:8081,默认会跳转到登录页。初始账号一般在init.sql里写死了,常见的是admin / admin123。登录进去后,如果页面图表空白,按 F12 看 Network 面板,大概率是接口 404 或跨域。跨域问题在开发环境用 devServer 的 proxy 解决:

// vue.config.js 中的代理配置 devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } }

target指向后端地址,pathRewrite把前端请求里的/api前缀去掉再转发。这样前端代码里统一写/api/device/list,实际打到后端就是/device/list,生产环境用 Nginx 做同样的转发即可。

3.4 造点模拟数据看效果

系统刚跑起来时数据库是空的,图表没东西可看。项目里一般会带一个模拟数据生成器,或者你可以手动往时序库写几条:

# 用 curl 往时序库写一条模拟电表数据 curl -i -XPOST 'http://localhost:8086/write?db=energy_ts' \ --data-binary 'meter_power,building_id=B01,device_id=M001 power=45.2,voltage=220.5 1700000000000000000'

meter_power是表名,逗号后面是 tag,空格后面是 field,最后是纳秒时间戳。多写几条不同设备、不同时间的数据,刷新前端页面就能看到曲线了。这一步看着简单,但它是验证「采集→存储→查询→展示」整条链路是否通畅的最快方式。

4. 核心模块拆解:设备管理、数据查询与报警引擎

4.1 设备管理模块的增删改查

设备管理是整个系统的地基,所有数据都挂在设备上。后端对应的实体类大概长这样:

@Data @TableName("t_device") public class Device { @TableId(type = IdType.AUTO) private Long id; private String deviceCode; // 设备编号,全局唯一 private String deviceName; // 设备名称 private Integer deviceType; // 1-电表 2-水表 3-气表 private String buildingId; // 所属楼栋 private String protocol; // 通信协议 private Integer status; // 0-离线 1-在线 private LocalDateTime createTime; }

deviceCode是业务主键,采集层上报数据时带的就是这个编号,所以它必须唯一且不可变。deviceType用整数枚举而不是字符串,查询时索引效率更高。status字段一般由心跳机制维护,采集网关每隔一段时间上报心跳,后端更新在线状态。

对应的 Controller 就是标准的 RESTful 风格,@GetMapping("/list")做分页查询,@PostMapping("/add")做新增,@PutMapping("/update")做修改。这里有个容易忽略的点:删除设备时不能物理删除,因为历史数据还挂在上面,正确做法是加一个is_deleted逻辑删除字段,MyBatis-Plus 配置一下就能自动处理。

4.2 能耗数据查询与聚合

数据查询模块是前端调得最频繁的接口。典型需求是「查某栋楼某天每小时的用电量」,后端收到请求后要拼时序库查询语句。核心 Service 方法逻辑如下:

public List<EnergyPoint> queryHourlyPower(String buildingId, LocalDate date) { // 1. 计算时间范围 Instant start = date.atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant end = start.plus(1, ChronoUnit.DAYS); // 2. 构建时序库查询 String flux = String.format( "from(bucket:\"energy_ts\") " + "|> range(start: %s, stop: %s) " + "|> filter(fn: (r) => r._measurement == \"meter_power\") " + "|> filter(fn: (r) => r.building_id == \"%s\") " + "|> aggregateWindow(every: 1h, fn: mean)", start, end, buildingId ); // 3. 执行查询并转换结果 return influxClient.query(flux, EnergyPoint.class); }

这段代码用 Flux 语法查时序库,range限定时间窗口,filter筛出指定楼栋的电表数据,aggregateWindow按小时做均值聚合。参数怎么改:every: 1h改成every: 1d就是按天聚合,适合做月度报表;fn: mean换成fn: sum就是求总和,算总用电量时用。注意时间范围要用 UTC 或明确时区,否则跨时区部署时会出现「查昨天数据查出前天」的玄学问题。

4.3 报警引擎的实现思路

报警引擎负责在数据异常时触发通知。常见实现是一个定时任务,每隔几分钟扫描一次最新数据,和预设阈值比对:

@Scheduled(fixedDelay = 60000) // 每分钟执行一次 public void checkAlarm() { // 1. 查出所有启用的报警规则 List<AlarmRule> rules = alarmRuleMapper.selectEnabled(); for (AlarmRule rule : rules) { // 2. 取该设备最近一条数据 EnergyPoint latest = influxService.getLatest(rule.getDeviceCode()); if (latest == null) continue; // 3. 阈值判断 boolean triggered = rule.getOperator().equals(">") ? latest.getValue() > rule.getThreshold() : latest.getValue() < rule.getThreshold(); // 4. 触发则写报警记录并推送 if (triggered) { alarmRecordMapper.insert(buildRecord(rule, latest)); notifyService.push(rule, latest); } } }

fixedDelay = 60000表示上一轮执行完后再等 60 秒,避免任务堆积。operator支持大于、小于两种比较,够用但不够灵活,二次开发时可以扩展成表达式引擎。notifyService.push是推送入口,常见做法是接邮件、短信或站内消息,具体渠道看项目配置。

注意:报警去重很重要。如果设备持续超阈值,每分钟都插一条报警记录,数据库很快就爆了。常见做法是加一个「最近报警时间」判断,同一设备同一规则在 N 分钟内只报一次。

4.4 权限控制与多角色视图

系统一般分三种角色:管理员、运维人员、普通用户。管理员能管设备和用户,运维能看数据和确认报警,普通用户只能看自己权限范围内的报表。实现上通常用 Spring Security 或 Shiro 做认证,JWT 做无状态令牌。

前端侧,路由守卫根据用户角色动态挂载菜单。比如运维登录后能看到「报警管理」菜单,普通用户看不到。这个逻辑在router/index.js的beforeEach钩子里做,从 localStorage 取角色信息,比对路由的meta.roles字段,不匹配就重定向到 403 页面。

5. 避坑与排查:那些让我加班到凌晨的坑

5.1 时序库写入报「field type conflict」

现象:采集程序运行一段时间后突然写不进数据,日志报 field type conflict。

原因:同一个 field 在不同时间写入了不同类型。比如power字段第一次写了整数45,第二次写了浮点45.2,时序库对 field 类型是强约束的,一旦确定就不允许改。

解决:采集端统一数据类型,所有数值字段强制转成 double 再写入。如果已经写乱了,只能删掉该 measurement 重建,或者改 field 名重新开始。

5.2 前端图表数据不刷新

现象:页面打开时图表正常,但过一会儿数据不更新了,手动刷新页面才恢复。

原因:定时刷新逻辑写在了mounted里,但组件被 keep-alive 缓存后,mounted不会再触发,定时器也没清理,导致要么不刷新要么内存泄漏。

解决:把定时器逻辑挪到activated和deactivated钩子里,或者用watch监听路由变化重新拉数据。Vue 3 的话用onActivated组合式 API。

5.3 跨域配置在开发环境生效、生产环境失效

现象:本地开发一切正常,部署到服务器后接口全部 404 或跨域报错。

原因:开发环境靠 devServer 的 proxy 转发,生产环境没有这个机制,需要 Nginx 配置反向代理。

解决:Nginx 里加一段location /api/ { proxy_pass http://backend:8080/; },注意proxy_pass末尾的斜杠,带斜杠和不带斜杠的转发路径不一样,这个细节坑过很多人。

5.4 数据库连接池耗尽

现象:系统运行几天后接口响应越来越慢,最后直接超时,重启后恢复。

原因:某个查询或定时任务没有正确关闭连接,或者连接池最大连接数设得太小,高并发时排队。

解决:先查代码里有没有手动获取 Connection 没 close 的地方,再调大 HikariCP 的maximum-pool-size,同时加上连接泄漏检测leak-detection-threshold: 60000,超过 60 秒没归还就打印堆栈。

5.5 时间戳时区错乱

现象:查当天数据查不到,或者报表日期和实际差一天。

原因:设备上报的时间戳是 UTC,数据库存的是 UTC,但前端展示按本地时区转换时没处理好,或者后端查询时用了本地时间字符串去匹配 UTC 存储。

解决:全链路统一用 UTC 存储,只在展示层做时区转换。Java 里用Instant而不是LocalDateTime做传输,前端用 dayjs 或 moment 统一格式化。

6. 二次开发怎么改:从加一个报表到换一套采集协议

二次开发最常遇到的需求是加报表。假设要加一个「楼栋月度能耗排名」,改动路径很清晰:后端在ReportController加一个接口,ReportService里写查询逻辑,按时序库聚合出每栋楼的总能耗,再排序返回;前端在views/report/下加一个页面,用 ECharts 的柱状图渲染。整个过程不用动核心模块,这就是分层架构的好处。

换采集协议稍微麻烦一点。如果原来用 MQTT,现在要接 Modbus 电表,需要在采集网关层加一个 Modbus 适配器,把读到的寄存器值转成统一的内部消息格式,再走原来的写入链路。关键是别让协议差异渗透到业务层,适配器做完归一化,后面的存储和查询完全不用改。

验证改动是否成功,我一般走三步:先用单元测试跑通 Service 层逻辑,再用 Postman 调接口确认返回结构,最后在前端页面点一遍看图表对不对。三步都过了才提交,少一步都可能埋雷。

有个习惯我坚持了很久:每次改完采集相关代码,都会手动往时序库写一条带边界值的数据,比如功率为 0 或负数,看系统会不会崩。能源数据里负值不一定非法——光伏倒送电时功率就是负的——但如果不处理,报表里就会出现莫名其妙的负数。从那以后我每次动数据链路,都强制走一遍边界值验证,希望这个习惯也能帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 11:48:08

数字艺术手作感:artcraft三层质感搭建法

artcraft这个词我最近在好几个创作社区里都看到有人在提&#xff0c;一开始我以为是某个软件的缩写&#xff0c;后来读了几篇帖子才明白&#xff0c;它其实是把 art 和 craft 捏在一起&#xff0c;说的是“让数字画面带手作感”的那一整套做法。我自己是画插画出身&#xff0c;…

作者头像 李华
网站建设 2026/10/17 11:47:28

车载以太网AVB协议簇解析:PTP时间同步与QoS测试实践

简介&#xff1a;这是一份面向车载以太网测试工程师与汽车电子研发人员的讲座型PDF&#xff0c;围绕智能驾驶与车联网背景下的车载以太网技术展开。内容从常见应用场景入手&#xff0c;涵盖软件OTA刷写、ADAS与360环视主干网络、信息娱乐系统与TBox交互等&#xff1b;随后重点剖…

作者头像 李华
网站建设 2026/10/17 11:47:09

Cursor 深度实践:AI 代码编辑器的功能拆解与落地避坑指南

用过一段时间 AI 代码编辑器之后&#xff0c;我越来越觉得 Cursor 已经不只是“带补全的 IDE”那么简单了。它属于那种你用久了会回不去的工具&#xff1a;代码写到一半&#xff0c;它替你补全&#xff1b;重构一个大函数&#xff0c;你跟它说人话&#xff0c;它直接改完&#…

作者头像 李华
网站建设 2026/10/17 11:46:38

UML用例图与顺序图核心考点解析:需求建模与动态交互

简介&#xff1a;本资源是一份聚焦UML核心建模技能的系统性试题汇编&#xff0c;专为软件工程专业学生、UML初学者及备考软考/课程设计的学习者设计&#xff0c;旨在帮助读者深入理解用例图、顺序图与协作图等关键交互建模技术&#xff0c;并夯实高内聚、可见性、领域建模、统一…

作者头像 李华
网站建设 2026/10/17 11:46:08

AD-E2E-JEPA:目标条件驾驶规划遇上潜空间预测,解锁零样本

做自动驾驶规划的朋友应该都有同感&#xff1a;工程上能跑闭环的方案&#xff0c;换个城市、换个天气、换一种没见过的路口&#xff0c;表现说崩就崩。最近某顶级AI研究团队放出的新作 AD-E2E-JEPA&#xff0c;方向挺有意思——把目标条件驾驶规划和 JEPA 那套潜空间预测思路拧…

作者头像 李华