说到工业数字化转型,设备数据出不来、系统各管一摊、开发周期拖到以月为单位,这三件事几乎是所有产线改造绕不开的坎。最近我在评估轻量级物联网方案时,重点研究了UNIHH-IOT这个前后端全开源的项目,自己从源码拉取、环境准备到模拟设备接入完整跑了一遍,整体感受是:该有的能力一个不少,落地成本却比传统组态软件低一个量级。这篇文章把我拆解这套平台的过程、选型逻辑和实操踩坑记录整理出来,给正在选型或准备二开的团队做个参考。
UNIHH-IOT的定位很明确,就是给工业场景提供一个能装进普通服务器、能接多类协议设备、能快速搭出可视化大屏的物联网底座。它解决的并不是“做一个监控系统”这种单点问题,而是把设备接入、数据采集、存储转发、告警触发、权限管理、可视化展示这一整条链路打通,让做业务的人不用再从零开始造轮子。如果你面临类似场景——产线上有一批PLC、仪表、传感器等着统一上网,或者客户要求几天内拿出一个可演示的设备管理Demo,这套平台值得你花半小时把下文看完。
1. 项目定位与核心需求拆解
1.1 工业数字化转型到底缺什么底座
传统工厂搞数字化,最常见的路径是找集成商定制一套组态软件,或者用国外重量级平台慢慢改。这两种方案各有各的痛:定制项目前期调研、开发、联调,周期轻松按季度算,后期业务一调整又要重新走流程;重量级平台功能确实全,但架构复杂、license贵,一台网关几十个测点都要单独算授权。我见过不少工厂上了一套大平台,结果只用了不到两成功能,剩下全在吃资源。
UNIHH-IOT这类轻量级平台瞄准的正是这个空档。它解决的问题可以拆成三层看:
- 连接层:能用标准协议把现场设备拉进系统,而不是每接一个品牌就写一套驱动。
- 数据层:能稳定采集、存储、转发时序数据,为上层分析提供干净的数据源。
- 应用层:用可视化大屏、告警、权限等手段,把数据变成管理者真正看得懂的东西。
三层对应到平台上,就是设备接入模块、数据服务模块和运维展示模块。UNIHH-IOT把这三块做成了相对独立的子系统,这对后续二次开发非常友好——你想替换存储层,不用动接入逻辑;你想新增一种协议,也不用碰前端页面。
1.2 “轻量级”和“高性能”不是嘴上的概念
先说说“轻量级”指什么。我在一套4核8G内存的测试服务器上部署了完整版UNIHH-IOT,包含前端、后端、数据库和消息中间件,剩余内存还有将近3G空闲。这个资源占用放在工业现场是很有意义的,因为很多工厂的IT机房条件并不好,能挤出一台旧服务器跑业务已经不错了,你不可能要求每一条产线都配一套高配集群。
再说“高性能”。平台宣称的高性能主要体现在接入并发和数据处理链路。我压测过单节点同时在线500个模拟设备、每秒上报2000条数据,后端CPU占用率稳定在40%上下,数据入库无明显积压。这个数值虽然不能和大型分布式物联网平台比,但对于中小规模工厂、园区级项目,完全是够用的。更关键的是,单节点够用意味着你不需要一开始就上集群,省下的运维成本是看得见的。
1.3 前后端全开源的价值在哪里
开源这件事,在工业领域尤其要单独拎出来讲。工厂侧最担心的就是被某个商业平台绑定,后面想改个数据字段、加个协议、接个内部系统,全要跟原厂提需求、排期、付费。全开源等于把钥匙交到了自己手里。
实际开发中,我依赖全开源源码做过几类改动:调整采集点缓存的淘汰策略,让高波动的测点数据不丢;在接入层加了一个自定义的CRC校验,适配某品牌的非标报文;前端大屏里改了默认的主题色和Logo,让交付给客户时更像定制产品。这些改动如果是闭源平台,每一条都得走商务流程。所以“前后端全开源”不只是代码开放,而是把项目的可演进性交给了使用团队,这一点对做项目交付的公司来说是实打实的竞争力。
2. 技术栈选型与整体架构设计
2.1 前后端选型逻辑:为什么是这套组合
UNIHH-IOT的仓库里,前端是一个独立工程,后端是另一个独立工程,部署时可以完全分离。我拉代码后第一件事就是看技术栈,因为技术栈直接决定了二次开发的成本和团队招人难度。
前端部分选择了当前生态比较成熟的一套:Vue3 + TypeScript + Vite,UI层面用了Element Plus,可视化图表基于ECharts封装,页面状态管理用的Pinia。这套组合在国产化适配、开发效率、组件生态上都很成熟,社区里踩坑记录也多,遇到问题基本一搜就有答案。
后端部分没有跟风微服务,而是采用了单体应用+模块化拆分的思路。核心框架是Spring Boot,接入层用Netty处理高并发的长连接,调度层用过Quartz,数据层通过JPA操作关系库,同时引入Redis做缓存。单体架构在项目初期是收益最高的选择——部署简单、调试方便、链路追踪容易,等业务量真上来了,再按接入模块、告警模块把服务单独拆出去也不晚。这一点上我的判断和项目设计者是一致的:工业场景多数项目的规模远没有到必须拆微服务的程度,强制拆只会白白增加复杂度。
有一个细节值得提,就是消息中间件没有硬编码死。平台的数据链路里通过消息队列做削峰填谷,但它的实现封装了一层接口,你在配置里可以切换内置MQTT Broker或者外部MQTT服务。这个设计让平台既能开箱即用演示,也能嵌入到客户已有的架构中,属于很实用的工程考量。
2.2 整体架构如何分层,数据流向怎么走
我梳理UNIHH-IOT的代码结构时,基本可以按五层来看:
- 接入层:负责设备连接与报文收发,支持MQTT、Modbus TCP、HTTP回调、TCP自定义协议。设备接入后统一抽象成“设备-点位-报文”模型。
- 服务层:拆分为设备管理、产品管理、告警管理、用户权限、规则配置等模块,通过标准REST接口与前端交互。
- 处理层:数据经过格式校验、点位映射、单位换算后,进入消息队列做异步处理。
- 存储层:结构化数据存MySQL,缓存和临时状态存Redis,时序类测点采用批量写库方式落到MySQL分区表。
- 展示层:Vue前端负责可视化大屏、设备列表、告警记录、系统配置等页面的渲染。
数据流向大概是这样的:现场设备通过MQTT或Modbus把报文推到接入层,接入层解析后交给处理层,处理层过滤、转换、缓存,再异步写入数据库。前端页面通过REST接口查询数据,配置了实时刷新的大屏则通过WebSocket接收推送。
这套架构最值得称道的地方在于异步解耦。上报高峰时,接入层只负责收数据、简单解析、丢进队列就返回,真正的入库动作在消费端批量执行。这样即使前端页面查询再慢,也不会阻塞设备接入这条主干道,避免了“展示拖垮采集”的经典故障。
2.3 为什么前后端分离,单体筒仓式页面不好吗
传统单体Web应用,页面由后端渲染,前端和后端强耦合。UNIHH-IOT选择前后端分离,首先是因为工业项目交付时经常要面对“前端单独部署到客户内网机器、后端留在服务器”这种拓扑。分离后,前端可以通过Nginx或者任意静态服务器托管,后端只要保证接口可达,两者生命周期互不干扰。
另一点是可维护性。拆成两个工程,前端组和后端组并行开发的冲突明显减少。而且大屏可视化这类页面有强交互、高渲染需求,用前后端分离后可以针对性地做过路由懒加载、组件按需加载,普通jsp模板页面做这些优化会吃力得多。
从部署资源角度看,前端是纯静态资源,部署在轻量Nginx上几乎不占CPU;后端服务作为Java应用独占进程,内存可控。这个拆分和轻量级定位是互相呼应的。
3. 核心功能模块与实现细节
3.1 设备接入层:多协议兼容的工程实现
设备接入是整个物联网平台最脏最累的部分。UNIHH-IOT把“产品”和“设备”做了两级抽象:产品定义了设备的型号、协议类型、点位表;设备是产品的具体实例,绑定某个产品后才拥有了完整的解析规则。这个设计在工程上很聪明,同一批同型号设备,你只需要定义一次产品,批量接入时省掉大量重复配置。
协议层面,我重点试了MQTT和Modbus TCP两条路线:
- MQTT:适合网络条件较好、设备本身支持TCP/IP的智能仪表和网关。配置时主要关注Topic约定和报文格式。我在测试时用Python脚本模拟了200台设备,每台设备在
/{productKey}/{deviceKey}/datapoint这个Topic下发布JSON格式的测点数据,平台能稳定接收。 - Modbus TCP:适合PLC、电表、温控器等传统工业设备。配置时先建立设备IP和端口映射,再按功能码配置寄存器地址、数据类型、字节顺序。这里有个我踩过的坑:有些设备的寄存器数据是大端模式,有些是小端,平台里每个点位都有“字节序”配置项,如果对不上,读上来的数值就是乱的。
接入层的代码封装层次也是清晰的。ProtocolService接口定义了连接、解析、上报的统一入口,新增协议时可以继承接口做扩展。我实际改过一个自定义协议适配器,只动了协议解析类和点位映射配置,没有影响其他模块。这一点对有非标接入需求的团队相当重要。
3.2 数据采集、清洗与存储链路
设备数据进了平台,接下来要过三关:
第一关是合法性校验。报文里数据类型、量程范围、单位标识不对的会被丢弃或标记异常。UNIHH-IOT可以给每个点位配上下限和变化率阈值,瞬时跳变超过阈值的值会被拦截,这个功能在工业场景里很实用——传感器偶尔飘出来一个超物理范围的值,不该入库。
第二关是转换计算。原始报文常常是裸的工程值,比如4~20mA电流信号需要换算成温度。平台支持在点位配置里写简单的换算公式,读取原始值后自动套公式得到最终值。这个环节我在接入某型号温湿度传感器时用到过,省去了在业务层重复计算。
第三关是存储优化。UNIHH-IOT的默认策略是把测点数据攒批后批量写入数据库,批量大小和落库时间间隔都在配置里可以调。MySQL侧按时间对采集表做分区,避免单表数据无限膨胀导致查询越来越慢。实际使用时我建议把batch.size设为500到1000之间,flush.interval设在5到10秒,这样既能减少IO次数,又不至于太脏读数据。
数据链路里还有个容易忽视的设计:点位数据值都带时间戳和质量戳,即便采集端延迟到达,写库时也以设备自带时间戳为准。这个设计对工业时序数据的准确性很重要,因为网络抖动造成的报文乱序太常见了。
3.3 告警规则怎么配置才能不“狼来了”
一通接好设备,告警配置就成了运维最关注的模块。告警配置里最重要的三个参数是阈值、持续时间、防抖窗口。
以温度告警为例,阈值设为80摄氏度,持续时间设为30秒,防抖窗口设为1分钟。意思是温度超过80度必须持续30秒才触发告警,1分钟内同一测点不会重复告警。这两个参数看着简单,实际工程价值巨大:没有持续时间约束,瞬时波动就能把告警刷屏;没有防抖窗口,同一个故障会生成几十条重复工单。
UNIHH-IOT的告警规则可以把多个测点条件组合,比如“温度大于80度且湿度小于30%”同时满足才触发,还可以设置告警级别。通过WebSocket,告警记录实时推到前端页面上。邮件通知和WebHook需要你根据平台预留的接口自行扩展,我在一个水处理项目中就写了一个把告警转发到企业微信机器人的扩展,整个过程只用了半天。
3.4 可视化大屏:拖拽配置背后的实现
工业项目交付里,大屏做完就等于验收完成了一半。UNIHH-IOT的可视化部分支持通过拖拽组件库搭建页面,内置了折线图、柱状图、仪表盘、实时表格、地图等常见组件。每个组件的数据源可以绑定一个或多个点位,支持聚合方式和刷新频率设置。
实际使用中我发现,大屏性能的关键不在组件本身,而在数据通道。默认情况下大屏通过WebSocket接收服务端推送,服务端把变化后的点位值按订阅关系分发到对应页面,页面局部更新组件,而不是整页刷新。我在带200个点位、10个图表组件的大屏上测试,切换页面和数据刷新都比较流畅。如果你把刷新频率调到500ms以下,浏览器渲染就会开始吃力,建议工业大屏的刷新间隔设在1到5秒之间,既有实时感又不卡顿。
4. 本地部署与实操记录
4.1 环境准备:需要哪些基础组件
UNIHH-IOT的部署依赖不算多,但对版本有隐性要求。我在测试时用的环境组合可以参考这套:
- JDK 1.8以上(我是用OpenJDK 11跑的)
- MySQL 5.7或8.0,建议用8.0,字符集用utf8mb4
- Redis 5.0以上
- Maven 3.6+(后端源码打包用)
- Node.js 16+(前端工程构建用)
如果你不想在本机装全套,也可以用Docker Compose把MySQL、Redis、后端、前端编排起来。官方部署文档里有docker-compose示例,但我实际用下来发现,数据库初始化脚本需要先执行完成,后端服务再启动才连得上,编排时要注意依赖顺序。
4.2 Docker编排快速启动示例
我这里给出一份我在测试环境调整后的docker-compose参考片段,注意版本号以你拉取的仓库为准,不要直接抄:
version: "3.8" services: mysql: image: mysql:8.0 container_name: unihh-mysql environment: MYSQL_ROOT_PASSWORD: unihh123456 MYSQL_DATABASE: unihh_iot volumes: - ./sql:/docker-entrypoint-initdb.d ports: - "3306:3306" redis: image: redis:6.2 container_name: unihh-redis ports: - "6379:6379" unihh-backend: image: unihh-backend:latest container_name: unihh-backend depends_on: - mysql - redis environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/unihh_iot SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: unihh123456 REDIS_HOST: redis ports: - "8080:8080" unihh-frontend: image: unihh-frontend:latest container_name: unihh-frontend depends_on: - unihh-backend ports: - "80:80"如果用这个编排,数据初始化脚本不要和容器启动放在同一时刻去写库,建议先启动mysql容器,等初始化完成后,再构建并启动后端。
4.3 关键配置项逐项解读
部署完成后,有两处配置最影响运行效果:
第一处是数据库连接池。后端application.yml里默认的HikariCP参数偏保守,如果你设备量比较大,可以调大maximum-pool-size到30左右,connection-timeout保持30秒。工业项目里数据库连接池太小,高并发写入时会出现获取连接超时的报错,日志里会有Connection is not available,这个错我排查了挺久才定位到。
第二处是JVM参数。后端服务默认启动内存可能不够用,特别是同时开启设备接入和大屏服务时。我习惯在启动命令里加:
java -Xms512m -Xmx1024m -jar unihh-backend.jar如果现场同时在线设备超过1000台,建议把-Xmx提到2048m。内存给太小不会立刻崩,但会频繁触发Full GC,表现为前端页面刷新明显变慢,设备日志上报延迟拉高。
4.4 跑通全流程:从接入一个模拟设备开始
部署完平台,第一步不是接真实PLC,而是先跑通一个模拟设备。这一步能帮你快速验证整体链路。我用的是一段Python脚本,模拟温湿度传感器通过MQTT上报数据:
import json import random import time from paho.mqtt import client as mqtt_client broker = "127.0.0.1" port = 1883 topic = "unihh/demo001/device001/datapoint" client = mqtt_client.Client("simulator_001") client.connect(broker, port) while True: payload = { "temperature": round(random.uniform(20, 35), 2), "humidity": round(random.uniform(40, 70), 2), "ts": int(time.time() * 1000) } client.publish(topic, json.dumps(payload), qos=0) time.sleep(2)在平台上先创建好产品“模拟温湿度计”,添加温度、湿度两个点位,然后注册设备“device001”,脚本运行后就能在设备详情页看到实时数据曲线。如果看不到,优先检查Topic路径里的产品标识和设备标识是否与平台上一致,这是新手最容易配错的地方。
5. 常见问题与调优经验
5.1 设备频繁掉线:心跳参数与网络状态
运行一段时间后最闹心的问题就是设备频繁显示离线又上线。我遇到过两种典型原因:
第一种是MQTT心跳超时配置太短。设备端默认心跳间隔如果是30秒,平台端的心跳超时不能小于60秒,否则网络一抖动就判定离线。UNIHH-IOT的连接参数配置里可以设置心跳超时和会话过期时间,我建议在线设备类型为网关时设置300秒,避免短时间网络波动造成误报。
第二种是设备在NAT网络后面,长连接会被中间设备空闲回收。解决思路是设备端启用MQTT的Keep Alive机制,让它定时发心跳,保持连接活跃。如果是用TCP自定义协议接入的设备,则要在协议解析层透传心跳包,并让平台侧的“最后在线时间”随的心跳包更新。
5.2 消息积压和入库慢的排查思路
设备数量一多,最容易出现“采集正常,但历史数据查询延迟高”的问题。我的排查顺序是:
先看消息队列的消费速率有没有追上生产速率。如果积压严重,优先调整消费端批量入库参数,把batch.size调到1000、flush.interval降到3秒,在吞吐和实时性之间找到平衡。再看数据库写入情况,如果是机械硬盘且数据库并发高,写入就会触顶,可以考虑把采集表放到单独的磁盘分区或者换成SSD。
还有一个容易被忽略的点:数据库表索引。UNIHH-IOT默认对时间字段建了索引,但如果查询经常按照“设备ID+时间范围”过滤,建议再加一个联合索引(device_id, ts)。这个优化我在上数据量到百万级的时候做了一次,查询耗时从三秒降到了毫秒级。
5.3 大屏页面卡顿和数据延迟
大屏卡顿主要看两处:数据推送频率和前端渲染开销。如果页面里图表组件太多,且每个组件都单独订阅了WebSocket数据流,浏览器会被消息风暴打满。我的调优做法是:
- 把刷新周期从500ms调到2000ms,肉眼几乎感知不到差别,CPU占用明显下降。
- 多个点位共用同一订阅通道,在后端聚合数据后统一下发,前端再做分发,减少连接数。
- 历史曲线组件不要直接绑定原始点位,而是查询聚合接口,比如按分钟平均值展示,这需要后端提供一个聚合查询函数,我是自己在服务层补了一个接口完成的。
5.4 问题排查速查表
| 现象 | 可能原因 | 解决动作 |
|---|---|---|
| 设备不在线 | 心跳超时过短 | 调大超时时间,检查设备侧心跳机制 |
| 报文明明发了但页面无数据 | Topic路径或点位标识不匹配 | 核对产品标识、设备标识、点位Key |
| 数据值异常偏大或负数 | 字节序或数据类型配置错误 | 检查点位寄存器配置,切换大小端 |
| 历史查询越来越慢 | 采集表数据量过大 | 启用分区、加联合索引、归档旧数据 |
| 告警不触发或重复触发 | 防抖窗口、持续时间设置不合理 | 按工况且配置,先持续后防抖 |
| 前端页面接口超时 | 后端线程池或数据库连接池不足 | 调整HikariCP和JVM参数 |
6. 后续扩展与个人实操感受
项目部署稳定后,我把它接到了一个小型车间的能耗监控场景里:12块电能表通过Modbus TCP接入,20个温湿度传感器通过MQTT上报,一套大屏展示所有电力参数和曲线。从硬件调试到最终交付,前后花了不到一周,这个速度在传统定制化开发里几乎不可能。
我个人实操中最大的体会是,这套平台的定位不是挑战重型工业软件,而是在中小规模的数字化转型项目里当一个高效的基础设施。它把最耗时间的接入、存储、展示工作标准化了,让实施团队能把精力集中在业务本身。如果你准备基于它做二次开发,建议先花一个下午把设备接入Demo跑通,再对照源码把接入模块的类结构读一遍,后面扩展协议或者定制告警逻辑会顺手很多。
拿到源码后续最值得做的三件事:把前端Logo和配色换成自己项目的,体验定制化交付流程;在告警模块里接一个企业微信或钉钉WebHook,把通知闭环补全;最后,把设备点位表按照自己行业的管理习惯重新组织一遍,这会让系统真正长成自己需要的样子。