1. 风电物联网平台:不止是“源码”那么简单
先说结论:一个真正能落地的风电物联网平台,绝不是把“数据采集、状态显示、故障管理”这三个词堆砌起来就能交差的。做这个项目时我最大的体会是——它是典型的“工业物联网平台层”产物,本质上解决的是“设备怎么连、数据怎么来、异常怎么发现、问题怎么闭环”这一整条链路的问题。
我来拆解一下这套系统的价值点:数据采集解决的是“感知”问题,要搞定Modbus TCP、OPC UA、MQTT这类协议接入,还得处理PLC、传感器、风机主控等异构设备的数据上行;状态显示解决的是“可视化”问题,得让运行值班人员一眼看清每台风机的发电功率、转速、温度、风速、舱内状态,而不是面对一堆原始报文发呆;故障管理解决的是“闭环”问题,从报警触发、故障记录、派单维修到消缺确认,必须形成业务闭环,否则“告警”只是纸面上的红点。
值得单独说明的是,这个项目选择基于 Java + Spring Boot 实现,而不是用 C++ 或 Python,背后是有现实考量的。风电场的运维监控系统通常要接入企业已有的 ERP、EAM(资产管理系统)或集中监控中心,Spring Boot 在这类企业级系统集成上有天然优势:生态成熟、部署简单(内嵌 Tomcat,一键 jar 包落地)、社区资料丰富、招人好招。另外,Java 生态对“高并发下的数据接入”有非常成熟的方案,比如 Netty、Kafka、线程池调度,这些在风电场的多风机并发采集场景下至关重要。
这个内容适合谁学习?我想大致有三类人:第一类是物联网工程或软件工程专业的毕业生,拿它当毕业设计或课程项目的骨架,绝对比做一个“学生管理系统”要有含金量得多;第二类是刚入门的 Java 后端工程师,想找一个能体现综合能力的真实业务场景;第三类则是做工业物联网或新能源监控的从业者,可以把它当成一套可复用的基础平台,在此基础上改造成水电、光伏、储能甚至智慧园区场景。接下来,我会从技术选型、核心模块、数据建模和故障排查几个维度,把整个项目掰开了讲清楚。
2. 从业务需求反推技术选型:为什么是 Spring Boot + 物联网三层架构
2.1 物联网三层架构在风电场景下的真实映射
很多人听到“物联网三层架构”就条件反射地背出“感知层、网络层、应用层”,但要真正设计一个风电平台,你需要知道每一层在这个具体场景里到底对应什么硬件、什么协议、什么数据。
感知层对应的是风机上的各种传感器和执行器:风速仪、风向标、转速传感器、齿轮箱温度传感器、发电机绕组温度传感器、振动传感器、偏航编码器、变桨角度传感器、电能表等。这些设备有的走模拟量输出(4-20mA / 0-10V),有的走数字量输出(Modbus RTU/TCP、CAN总线),有的直接接入风机主控 PLC。这一层的数据特点非常明确:点位多、类型杂、部分传感器工作在非常恶劣的环境下(高空、低温、雷暴区),所以采集模块必须做“断线重连”和“数据质量标记”。
网络层在风电场里通常是一条复杂的链路:风机内部传感器 → 风机主控PLC → 风机内部的以太网交换机 → 光纤环网(或4G/5G无线专网)→ 升压站或集控中心的网关服务器。网络层有一个工业场景特有的问题:风电场地处偏远,网络带宽常常有限,而且有些老旧风机只支持2G/3G无线传输,时延和稳定性都很差。这意味着你的数据采集模块不能无脑用“长连接+高频率轮询”,需要设计“本地缓存+批量上报”机制。
应用层就是本项目的核心范围了:后端微服务(或者单体应用)+ 前端可视化平台 + 数据库 + 消息中间件。这里需要对接的用户角色包括运行值班员(看状态、看告警)、检修工程师(查看故障详情、消缺)、运营经理(看发电量统计、可利用率分析)、系统管理员(配置阈值、管理设备档案)。
2.2 为什么 Spring Boot 能胜任这个“脏活累活”
Spring Boot 在这个项目里不只是做一个“提供 RESTful API 的壳子”,它承担了四类关键任务,这也是我最终确定技术栈的核心逻辑:
第一,协议接入层。虽然很多工业数据采集推荐用 Node-RED 或 IoTDB 自带网关,但自己用 Netty(Spring Boot 里可以方便地集成 Netty 服务端)写一个 Modbus TCP 采集服务,你能精确控制采样周期、异常重试、报文解析等细节。尤其是风电场可能有不同厂商的风机(金风、远景、明阳或者国外厂商),每家对 Modbus 寄存器地址的定义都不同,你需要一套高度可配置的“点位表”,这是对平台扩展能力的核心考验。
第二,消息分发层。设备采集上来的数据需要同时送给实时监控页面、持久化存储、告警判断引擎和数据分析模块。如果所有消费者都直接去数据库查询,数据库马上就会变成瓶颈。Spring Boot 项目里用 RabbitMQ 或 Kafka 把“采集数据”作为消息源进行分发,是非常成熟的套路。我实际用的是 RabbitMQ,因为它的路由规则灵活、部署相对轻量,对于中小规模风电场(几十台风机)绰绰有余。
第三,业务应用层。状态显示、告警规则、故障工单管理这些都是典型的业务接口,Spring Boot 的开发效率极高,特别是配合 MyBatis-Plus 或 Spring Data JPA,CRUD 能做得非常工整。而且它有非常成熟的权限框架 Spring Security + JWT,可以轻松实现“不同角色看到不同内容”——运维值班员只能确认告警,检修工程师才能发起工单,管理员才能修改阈值参数。
第四,部署运维层。风电场的服务器环境往往不太友好,尤其是一些场站只有一台普通 PC 级服务器,没有 Docker 也没有 K8s。Spring Boot 打出一个可执行 jar,配合外部配置文件(application.yml),任何一台装有 JDK 的机器都能跑起来。这一点比微服务全家桶要更适合现场环境,这也是我在设计架构时没有强行上 Spring Cloud 的原因——不是越复杂越好,适合现场落地才是硬道理。
小项目也可以这样扩展:我曾经在这个基础架构上,用同样的 Spring Boot 骨架改造成了一个光伏电站运维平台,只换了协议适配器和点位模型,平台层和显示层几乎没动。这说明只要核心架构的“设备接入层”和“业务层”解耦做得好,平台的复用价值会超出你的预期。
3. 数据采集模块:核心中的核心,从协议到存储的全链路实现
3.1 数据采集架构:轮询调度 + 异步解耦 + 断线重连
数据采集模块是整套系统的“发动机”。它的核心难点不在“读数据”本身,而在于“稳定地、实时地、不丢不重地读多台设备的数据”。我设计的采集架构分四个层次:
采集调度层(定时任务 / 轮询调度器) ↓ 协议解析层(Modbus TCP 客户端 / OPC UA 客户端 / 自定义报文解析) ↓ 数据清洗与转换层(量程换算、单位转换、异常值剔除、质量码标记) ↓ 数据分发层(写入时序数据库 + 发送 RabbitMQ 消息 + 触发告警规则引擎)很多新手写采集任务时有一个常见错误:直接在@Scheduled定时方法里写同步的 socket 读写和数据库插入,结果就是当前一批设备还没采集完,下一轮调度就启动了,会导致数据堆积、线程阻塞、甚至内存溢出。
我推荐的做法是使用一个独立的线程池来处理 IO 操作(Netty 本身就是异步非阻塞的),而调度层只负责任务触达。每个设备或设备组对应一个独立的采集任务,用ScheduledExecutorService或者 Quartz 来管理 cron 表达式,这样每台风机的采集周期可以独立配置——比如有功功率、风速等模拟量按 5 秒周期采集,而电能累计值按 1 分钟周期采集。
关于协议层,对于绝大多数风机主控或 PLC 系统,Modbus TCP 是最常见的选择。这里有一个非常关键的前置工作:梳理点位表(Point Table),它定义了每个传感器数据的寄存器地址、数据类型、数据长度、换算系数。举个例子,风机风速的风速计连接到 PLC 后,映射到 Modbus 保持寄存器的地址可能是40001,数据类型是 Float(32位),单位是 m/s,量程是 0~60m/s,换算系数是 0.1(即寄存器的原始值 250,代表 25.0m/s)。这些信息我一般存到数据库里的点位配置表而非硬编码到代码里,这样增加一台新风机或更换传感器时,只需要在后台配置,不需要重新编译发布。
3.2 数据持久化:该用关系型还是时序数据库?
谈到数据存储,这是我在项目中做了比较长时间权衡的地方,也是很多类似项目容易“翻车”的点。
最开始我图省事,把所有采集数据都直接写入 MySQL,结果才接了 20 台风机的 100 多个点位,数据量就很惊人——按 20 台 × 100 点 × 每 10 秒一条,一天就是 1728 万条记录,MySQL 的单表很快就吃不消了。后来我把 MySQL 的表拆成按天分区,保留最近 3 个月热数据,勉强能跑,但查询“某台风机某一天的温度变化区间”这种分析型 SQL 时延迟很高。
最终我的方案是**“双库并行”**:热数据写入 TDengine(开源的时序数据库,用 SQL 查询,支持自动分区和降采样),用于实时监控和趋势分析;而业务数据(告警记录、工单、设备档案、用户信息)保存在 MySQL。如果你不想引入额外的数据库组件,也可以选择 MySQL + 定时任务把旧数据归档到冷表,通过“按风场编号和采集时间复合分区 + 索引”来优化查询,但说实话,时序场景下 TDengine 是更贴合的选择。它一条 SQL 就能按时间窗口做聚合(avg、max、min),做曲线的效率高得多。
需要注意一个细节:写 TDengine 时不要逐条 INSERT,要走“参数绑定 + 批量写入”的方式。比如每 5 秒积攒 200 条,一次性批量提交,能把写入性能提升一个数量级。我在项目中设定了一个简单的批处理窗口——积累到 500 条或者时间超过 2 秒就批量 flush 一次,实际运行效果很稳定。
3.3 实操要点:点位配置表的设计与常见坑
点位配置表是整个采集模块的“灵魂”,它的设计好坏直接决定了你后续做状态显示和故障管理时的数据一致性。我在项目里用了这样一张模型:
| 字段名 | 说明 | 示例 |
|---|---|---|
| point_id | 点位唯一编号 | WND_001_SPD |
| device_id | 所属设备编码 | FJ01(1号风机) |
| point_name | 点位显示名称 | 风速 |
| point_type | 点位类型(模拟量/开关量/累计量) | analog |
| register_addr | Modbus 寄存器地址 | 40001 |
| data_type | 数据类型(float/int/bool) | float32 |
| data_length | 数据长度 | 2(寄存器个数) |
| scale_factor | 换算系数 | 0.1 |
| unit | 单位 | m/s |
| alarm_enabled | 是否参与告警判断 | true |
| normal_min / normal_max | 正常范围边界 | 0 / 57 |
| point_sort | 展示排序 | 1 |
这张表建好后,采集服务启动时一次性加载到本地缓存(ConcurrentHashMap),后续实时查询不走数据库,性能更快。
这里分享几个我在实际调试中踩过的重要的坑:
坑一:大小端模式(ByteOrder)不匹配。Modbus 报文中 32 位浮点数的字节顺序有两种:有的设备是高位在前(Big Endian),有的是低位在前(Little Endian),有的四个字节还会互换。如果你不加验证直接解析,风速 25.3 可能变成几千甚至负数。解决办法就是配置表里加一个byte_order字段,解析时按此配置转换,换设备时不用改代码。
坑二:状态量的位映射。风机主控的状态字(比如“运行/停机/故障”状态)通常不是一个寄存器一个数值,而是某个寄存器里的某几位(bit)组合表示。比如寄存器 40010 的第 3 位代表“齿轮箱故障”。这种情况下要把“按位解析”写在协议层里,配置表支持bit_offset和bit_length,解析为布尔开关量后,后续告警逻辑才能直接使用。
坑三:采集数据质量码。当从风机主控读取的值是0xFFFF或者NaN时,它不是真实测量值,而是设备侧“无效数据”的标志。数据清洗层必须给每条数据打上质量码(0=正常,1=无效,2=超量程),否则无效数据会进入统计报表,把平均功率、温度统计全部带偏。
坑四:网络断线重建。风机通信链路不稳定是常态。断线后不能只是记录一条日志就完事,要有状态机管理:“在线→断线→重试→离线→恢复在线”,每次状态变化都应该推送给“状态显示”模块和告警模块,让值班人员知道“是采集断了”还是“风机真停了”。
4. 状态显示模块:大屏看板、实时曲线与前端方案选型
4.1 实时数据推送到浏览器:WebSocket 还是 SSE?
状态显示模块是值班人员的“眼睛”,它希望达到的效果是:值班员盯着大屏,不用手动刷新,每台发电机的功率、转速、温度等数据就像“直播”一样实时变动。这就要解决服务端到浏览器的数据推送问题。
在技术选型上,WebSocket 和 SSE(Server-Sent Events)各有优劣。WebSocket 是双向通信,功能强大但实现复杂一些;SSE 是单向服务端推送,基于 HTTP 实现,简单可靠。考虑到业务场景主要是“服务端把状态变化推给前端”,我最终采用了WebSocket,原因有两个:一是故障确认、工单处理这类操作时前端需要即时把操作结果回传给服务端并广播给其他在线用户(比如集控室多个值班员同时看到“某条告警已被张三确认”);二是后续如果做远程控制(如复位风机、启动偏航)时,WebSocket 天然支持双向命令下发。
在 Spring Boot 里用 WebSocket 非常方便:引入spring-boot-starter-websocket依赖,实现WebSocketHandler或者用@ServerEndpoint注解(注意@ServerEndpoint的类会被 Servlet 容器管理,Spring 无法直接注入 Service 依赖,需要一个静态工具类或构造器注入方式来间接获取 Bean)。我实际用的是 Spring 封装的TextWebSocketHandler,它天然支持 Spring 的依赖注入,处理起来更方便。
推送策略上,不要每收到一条采集数据就推一条消息——这样 20 台风机每 5 秒推上百条消息,浏览器直接卡死。我采取的策略是聚合推送 + 低频差量推送:服务端每 3 秒聚合一次最近的所有有效点位数据,只把状态值变化超过死区(比如功率变化超过 1kW,或所有开关量状态变化)的点位推给前端;状态无变化的点位不推送,前端沿用上一次缓存。这样既保证了实时性,也极大地降低了页面渲染压力。
4.2 可视化看板:从“数据”到“图形”的最后一公里
前端可视化的核心工作是“把点位数据转成人能一眼看懂的形式”。我的看板设计分成四个维度:
场站总览层:地图或拓扑图展示整个风场风机分布,每台风机用一个循环色块表示——绿色运行、灰色停机、红色故障、黄色告警。点击某一台即可下钻到机组详情页。这部分如果不想引入重型 GIS 框架,直接使用 canvas 或 SVG 绘制风机分布即可。
机组详情层:展示单台风机的核心指标(发电功率、风轮转速、发电机转速、风速、风向角、机舱温度、齿轮箱油温等),用仪表盘组件展示实时数值,配合趋势曲线(近 1 小时、近 24 小时、近 7 天切换)。
数据曲线层:这是运行分析最看重的部分,用 ECharts 的动态数据 + 时间轴缩放功能,可以把历史数据的曲线拖拽放大缩小。需要注意,前端的本地时间和服务端存储的时间必须统一用“毫秒时间戳 + 时区标准”处理,否则会出现曲线在边界处断连或错位的问题。我项目里前后端统一使用 UTC 时间戳存储,仅在展示时用浏览器本地时区格式化,这样就能避开绝大多时区显示错乱的坑。
告警与提示层:页面顶部固定一条滚动的实时告警条,右下角弹出“告警浮窗”,配合声音提示。这里的核心是“告警合并”:同一台风机同一类型的故障在短时间内重复触发(比如振动值在阈值附近反复波动),应合并为同一条告警并更新“触发次数”和“最后触发时间”,而不是连弹 10 条框,否则值班人员会疯掉。
4.3 实测中的“状态显示”问题清单
在开发联调阶段,我遇到并解决了几个典型的显示问题:
数据显示延迟的“假死”现象。WebSocket 连接看似还在,但页面数据 5 分钟不动了。排查发现是 Nginx 默认对长时间空闲连接有 60 秒超时,WebSocket 连接被上游关闭后,服务端和客户端都没有及时感知。解决方案是在 WebSocket 服务端配置心跳检测(每 30 秒发一个 ping 帧),前端也在onclose里做重连补偿。
历史曲线查询慢。如果不给 TDengine 的 key 字段建索引,或者查询的标签范围过大,历史曲线的响应会变得特别慢。我采用的办法是按“设备编号 + 点位编号”建组合标签,并且查询曲线时使用 TDengine 的时间降采样(interval)功能,例如查询近 24 小时的温度趋势时,只需要返回每 5 分钟的平均值,根本不需要拉全部几万行。
前端渲染性能瓶颈。如果一次性推送所有点位的数据并全量刷新表格组件,页面会有明显卡顿。在做了“差量推送 + 前端按 key 更新”策略后,CPU 占用从 60% 降到 20% 左右。你可以用浏览器的 performance 面板来定位,哪个方法耗时最长基本就是渲染卡顿的原因。
5. 故障管理模块:从“报警触发”到“工单闭环”的完整设计
5.1 告警规则引擎:阈值判断、状态跃迁与告警风暴抑制
故障管理模块是整个平台里业务逻辑最重的部分,也是最能体现平台价值的部分。风力发电设备最典型的故障包括:齿轮箱油温过高、发电机轴承温度过高、振动超限、偏航电机过载、变桨系统故障、电网电压波动导致的脱网等。这些故障告警从“发生”到“处置”的完整链路是:
采集数据 → 规则引擎判断 → 生成告警记录 → 实时推送前端 → 值班确认 → 生成工单 → 检修执行 → 消缺验收 → 告警关闭告警规则引擎的设计上,我没有采用复杂的 Drools 规则库,而是用可配置的阈值规则 + 表达式组合来覆盖大多数场景。每条规则包含以下要素:关联点位、比较运算符(大于/小于/区间/不等于)、阈值或阈值组、持续时间(比如“持续超过 85°C 长达 30 秒才触发,避免瞬间尖峰误报”)、告警级别(提示/次要/重要/紧急)。
“持续时间”这个参数非常值得强调。风电设备的传感器数据本身噪声比较大,直接搞一个瞬时阈值触发会带来大量误报。我设计了一个“确认窗口”机制——当数据超过阈值,先进入“预报警”状态(提醒级),如果在窗口期(如 30~60 秒)持续越界,再升级为“真实告警”;如果窗口期内恢复正常,则只记录一条极短的事件日志,不打扰值班人员。这个机制落地后,误报率从 30% 降到了 5% 以内。
告警风暴抑制也值得一提。在强风雷暴或电网电压波动时,一个风场的几十台风机可能同时报“电网频率异常”或“振动超限”,如果全部推送到值班室,反而会掩盖关键故障。我用两种策略解决:第一种是告警去重合并,相同设备、相同告警类型在 10 分钟内重复触发时,合并到原有告警记录中,仅增加触发次数;第二种是告警聚合,对同一原因导致的批量告警,生成一条“场站级聚合告警”,便于值班员看到整体态势。
5.2 故障工单:状态机驱动的业务流程闭环
“故障”不等同于“工单”,这是很多初做物联网项目的同学容易混淆的点。有些故障只要远程复位就能解决,只有确认需要现场处理的故障才生成工单。我给工单模块设计了一个简单的状态机:
待派单 → 已派单 → 进行中 → 已完成(待验收) → 已验收 └→ 已取消 / 已驳回(填写驳回原因)这里有一个容易被忽视的业务细节:工单和告警必须双向关联。检修人员在现场处理完故障后,在系统里录入“处理措施”和“更换备件”,系统要自动把“处理结果”回写到对应的告警记录上,形成完整的运维履历。过了一个月你再想查“这台风机三月份为啥停机了两天”,通过工单号就能把前后数据串联起来。
在权限控制上,Spring Security + JWT 在这里派上了大用场,我规划了三类角色,每类角色看到的操作按钮不同:
| 角色 | 可执行操作 |
|---|---|
| 值班员 | 查看告警、确认告警、发起工单、查看状态 |
| 检修工程师 | 接单、处理、填写措施、申请验收 |
| 系统管理员 | 配置阈值规则、管理用户权限、修改设备档案 |
实操中注意一件事:确认告警和关闭告警不能是同一个人。值班员确认告警表示“我知道了”,检修工程师处理完提交“消缺申请”后,再由值班长或者系统管理员验收关闭。这样能避免“自己报警自己处置自己销号”的管理盲区。
5.3 告警通知:除了平台弹窗,还要有电话和短信兜底
最后说一下告警通知的“最后一公里”。风电场往往不是时刻都有值班员盯屏幕,特别是夜间无人值守或少人值守模式。因此告警除了在平台界面展示,还需要通过短信甚至语音电话的方式通知到相关人员。这部分的实现不难:在生成“紧急”级别告警时,通过消息队列异步调用短信网关服务,同时做“升级机制”——若紧急告警触发后 10 分钟未被确认,自动再发一次通知给上级值班负责人。这个不起眼的功能,在实际运行中挽救了好几次“凌晨风机长时间无人问津”的事故。
6. 工程化落地:核心代码骨架、前后端联调与优化笔记
6.1 Spring Boot 项目核心代码骨架
如果只看标题,很多人以为这只是一个 CRUD 的“玩具项目”,但真实的工程化代码实现下来,你的项目骨架应该长这样:
wind-power-platform/ ├── pom.xml // 依赖管理 ├── src/main/java │ ├── WindPowerApplication.java // 启动类 │ ├── config/ // 配置类(WebSocket、RabbitMQ、TDengine、跨域等) │ ├── controller/ // 后端 API 接口 │ ├── service/ // 业务逻辑层(含告警规则引擎) │ ├── mapper/ // MyBatis 或 MyBatis-Plus 数据访问层 │ ├── entity/ // 数据库实体类 │ ├── dto/ // 数据传输对象 │ ├── mqtt/ 或 modbus/ // 协议接入层(Modbus TCP 客户端、MQTT 客户端) │ ├── task/ // 定时采集任务调度 │ └── utils/ // 工具类(ByteUtils、DateUtils、JsonUtils) └── src/main/resources ├── application.yml // 主配置 ├── mapper/*.xml // SQL 映射 └── static/ 或 templates/ // 前端静态资源(Vue 打包产物)采集调度层的核心伪代码可以这样写(我用定时任务 + 线程池的方式加载设备点位):
@Component public class CollectScheduler { @Autowired private PointService pointService; @Autowired private ModbusTcpClient modbusClient; @Autowired private DataPublishService dataPublishService; // 本地缓存点位表,避免每次采集都查数据库 private Map<String, List<PointConfig>> devicePointCache = new ConcurrentHashMap<>(); @PostConstruct public void init() { // 初始化加载所有设备及对应点位 List<Device> devices = deviceService.listAll(); for (Device device : devices) { List<PointConfig> points = pointService.listByDevice(device.getId()); devicePointCache.put(device.getCode(), points); } } @Scheduled(fixedRate = 5000) // 每5秒触发一次调度 public void collectAll() { devicePointCache.forEach((deviceCode, points) -> { // 每个设备提交到采集线程池,避免阻塞调度线程 collectExecutor.execute(() -> { try { // 1. 建立/复用连接(含断线重连逻辑) if (!modbusClient.isConnected(deviceCode)) { modbusClient.reconnect(deviceCode); } // 2. 批量读取寄存器数据 Map<String, Object> rawValues = modbusClient.readRegisters(deviceCode, points); // 3. 数据清洗:量程换算、无效值剔除 List<CollectData> cleanData = dataCleanService.process(deviceCode, rawValues, points); // 4. 分发:写时序库并发送MQ消息触发告警引擎 dataPublishService.publish(cleanData); } catch (Exception e) { // 记录断线日志,并且推送到状态显示模块 log.error("设备采集异常: {}", deviceCode, e); } }); }); } }6.2 数据库模型与关键 SQL 优化
数据库设计上,我最终保留了这几张核心表,它们之间通过外键或逻辑关联串联起整个业务闭环:
device:设备表(风机编号、风场编号、设备厂商、安装日期、经度纬度等)point_config:点位配置表(前文已详述)alarm_rule:告警规则表(点位 ID、阈值、持续时间、告警级别)alarm_record:告警记录表(设备、规则、开始时间、结束时间、状态)work_order:工单表(关联告警 ID、处理人、处理措施、完成时间)sys_user:用户表(账号、角色、姓名、手机号)
在告警记录查询优化上,有一个典型的索引设计建议:alarm_record表按(device_code, alarm_status, create_time)建立联合索引,这样“查询某台设备未关闭的告警记录”就能走索引,千万不能只把 id 当主键,否则两张表一 join 就慢得不行。
另外关于敏感权限的一个细节:系统里的删除操作必须是“假删除”。告警记录和已完成的工单都属于审计追踪数据,不能物理删除。我用逻辑删除标记(deleted=1)来屏蔽查询,避免误删导致运维履历缺失。
6.3 前后端联调与部署:三个最容易踩的“隐性坑”
项目开发完最后一步就是前后端联调、打包部署,这个阶段我这边的经验是——真正的坑往往在“环境不在场”时出现。
坑一:跨域配置只做了一半。开发环境下前端是 Vue 独立起的localhost:8080,后端是 Spring Bootlocalhost:8081,跨域问题必须解决。很多同学只知道在 Spring Boot 里加@CrossOrigin或者配置CorsFilter,但部署时如果前后端用的是 Nginx 反向代理,配置方式完全不同,项目里应该保留两套环境配置(dev 和 prod),通过spring.profiles.active切换。
坑二:Vue 打包后放进 Spring Boot 的 jar 里访问路径不对。如果你希望把前端打包后的 dist 目录放到src/main/resources/static下,随 Spring Boot 一起启动,必须修改 Vue 的publicPath为相对路径./,否则打出来的包会请求/js/app.js,配合后端/api前缀的接口路径容易出问题。
坑三:现场服务器时间不同步。风电场现场有大量设备,服务器时间很可能比标准时间慢几分钟。如果采集设备的时间戳和服务器时间不一致,你存进 MySQL 和 TDengine 的时间就会错乱。建议在数据库写入和前端展示时统一以“服务器时间为基准”,采集数据到达网关时打上服务器时间戳,而不是直接使用设备自带时间。这个细节在现场走查时是最容易出问题、也最容易被忽略的一处。
7. 部署落地:从开发机到风电场的最后一公里
7.1 现场部署的硬件环境与资源估算
风电场现场的服务器条件往往比较紧张。我遇到过的典型环境是:一台 8 核 16G 内存的工控机或服务器,需要同时运行 MySQL、TDengine、RabbitMQ、Spring Boot 服务、Nginx。这样的配置其实够用,但资源规划必须做在前面。
以 30 台风机、每台 100 个采集点位、5 秒一个采集周期来测算:每秒产生 600 条原始数据(30 × 100 / 5)。每个点位仅按 20 字节计算,写入消息队列和时序数据库的流量并不大,带宽完全够用。真正的瓶颈在于告警规则引擎的实时计算和前端 WebSocket 的广播压力,这两个组件建议单独分配堆内存,避免在同一 JVM 里和采集服务抢资源。
7.2 数据库备份、日志策略与现场调试常用命令
数据库备份是工业系统里非常容易“被忽略但后果很严重”的事情。我的现场规范是:MySQL 每天凌晨 2 点定时全量备份(mysqldump输出到 NAS 或移动硬盘),TDengine 提供内置的taosdump工具,同样按天备份增量。不要以为“内部系统数据不重要”——设备运维数据积累到一定规模后,是故障分析和发电量优化的唯一依据。
现场调试时建议多使用这些命令组合:
# 查看某台服务器端口占用(确认服务是否正常监听) netstat -tunlp | grep 8081 # 查看 RabbitMQ 消息积压情况 rabbitmqctl list_queues name messages # TDengine 查询某台设备最近10条原始记录(排除无效数据) SELECT ts, point_id, value FROM device_data WHERE device_code = 'FJ01' AND point_id = 'WND_SPD' AND quality = 0 ORDER BY ts DESC LIMIT 10;7.3 上线前的测试清单(照做一遍心里就有底了)
我给这个项目列过一份上线测试清单,每一条都是踩过坑后的经验沉淀,这里分享出来:
| 测试项目 | 验证方法 | 预期结果 |
|---|---|---|
| 协议接入测试 | 用 Modbus Slave 模拟器模拟风机主控,检查点位值解析正确性 | 模拟器设置的数值与平台显示一致 |
| 断线重连测试 | 拔掉网线 5 分钟再插回,观察采集是否自动恢复 | 数据恢复采集,日志出现“重连成功”且历史数据无重复 |
| 阈值得确认测试 | 把报警阈值调低到当前正常值以下,观察预报警、真实告警、恢复通知三个阶段 | 三个阶段状态变化正确,且没有瞬间尖峰误报 |
| 大流量冲击测试 | 模拟 50 台设备同时上报,观察消息队列、入库延迟、WebSocket 推送成功率 | 消息队列无明显积压,页面更新延迟不超过 3 秒 |
| 跨天时间切换测试 | 修改服务器时区与时间,观察历史曲线与告警记录时间是否错乱 | 已存储数据时间正确,新建数据使用修改后的新时间 |
每一版发布前,我都会把这份清单完整跑一遍。有几次改动觉得“就加了几个字段,不会影响采集”,结果恰恰是因为修改实体类少加了一个注解,导致 TDengine 的数据映射全部错位。工业系统最怕的不是复杂逻辑,而是“你以为没变的部分悄悄变了”。
8. 项目复盘与扩展设想
这个项目的完整实现,我的核心体会是:源码只是骨架,真正值钱的是从数据采集到业务闭环的完整思路,以及在现场环境下的抗风险设计。你拿着这套代码,换个协议适配器,把点位表里的“风速、齿轮箱温度”换成光伏场景下的“组件电压、箱变温度”,把告警规则调整成光伏逆变器故障类型,那它就从一个“风电平台”变成了“光伏运维平台”。这是这类工业物联网平台最让人着迷的地方——它不是为单台设备定制的,而是为一整类设备提供了统一接入和运维管理的范式。
后续还可以在几个方向上深度扩展:
第一,接入实时计算引擎。将 Kafka 对接到 Flink,做流式计算—比如实时计算全风场的“理论发电量 vs 实际发电量偏差”,一旦偏差超过阈值就推送“发电效能异常”诊断建议。Spring Boot 生态中整合 Flink 有不少现成方案,但确实存在一定的复杂度,关键是 Job 定义和 Spring 容器资源隔离要做好。
第二,增加预测性维护能力。利用存储的历史数据(齿轮箱温度、振动、功率曲线),用 Python 训练一个温度趋势异常检测模型,把预测结果通过 API 对接回 Spring Boot。比如预测“这台风机齿轮箱温度在未来 2 小时内有超过 90°C 的概率达到 70%”,就能提前生成检修建议,把“故障后维修”变成“故障前维护”。
第三,全面拥抱边缘计算。目前采集服务在中心化服务器上运行,如果风场网络中断,中心平台会“失明”。可以把 Spring Boot 压缩成轻量边缘服务部署在场站网关,本地做数据缓存、本地做告警判断,网络恢复后再把缓存数据批量补传到中心平台。这样即使网络断开,现场运行值班人员也有系统可用。
第四,移动端支持。现在的运维场景早就不是“坐在集控室看大屏”了。给 Spring Boot 后端增加移动端 H5 接口,哪怕不做原生 App,一个适配手机的 Web 页面也能让检修人员在风机塔筒下面实时查看设备状态、接收紧急告警、提交消缺回执。这部分投入不多,对使用体验的提升却非常明显。
写在最后
做这类工业物联网项目,我的切身体会是:它考验的不是你懂多少框架和组件,而是你愿不愿意沉下心去理解业务现场的真实痛点。编码本身三天就能写完,但理解为什么风机要 5 秒采一次而非 1 秒、为什么告警不能立即弹出而要设置确认窗口、为什么维修工单和告警记录必须关联——这些问题的答案,来自现场设备,来自运维人员的反馈,来自一次次线上事故后的复盘。
我建议正在学这套项目的朋友,拿到源码后不要急着跑起来,先花一周时间把整个数据链路(传感器→PLC→采集服务→消息队列→数据库→WebSocket→前端大屏)画出来,把每个节点上的异常情况(断网、重启、脏数据、时钟漂移)都标注好,再开始动手改代码。脑子里有了全链路的地图,你修改任何一个模块时才知道牵扯哪些上下游。
最后分享一个小技巧:给所有采集数据表和告警记录表统一加上“设备编码 + 时间”二级索引,并做好半年一次的历史归档清理。别小看这些基础工作,工业系统跑上两三年,数据库还能保持流畅响应,靠的就是这些隐藏在代码之外的日常维护。希望这个项目能成为你进入工业物联网领域的踏实的起点。