接到物联网平台需求的时候,我第一反应不是去调研市面上的成熟IoT产品,而是打开了一个熟悉的骨架——若依(RuoYi)。别急着笑,这个选择背后其实有很现实的逻辑:我们团队是Java技术栈,如果从零搭建后台管理系统,光权限、菜单、用户、部门、日志这一套就要折腾两周起步,但用若依做底座,这些全部开箱即用,我只需要把精力花在物联网特有的设备接入、物模型、告警规则、数据通道这些核心模块上。
这篇文章不是讲若依基础教程,而是记录基于若依二次开发一个物联网平台时,从选型、设备接入、物模型建模、规则引擎到音视频集成的完整思路,以及过程中踩过的坑和优化方案。如果你正打算用若依或者其他通用后台管理系统去搭建IoT项目,这篇文章会比较对胃口。
1. 为什么偏偏选若依来做物联网底座:选型背后的真实考量
很多技术人一听到“物联网”三个字,第一反应是EMQ X、ThingsBoard、云厂商IoT套件这些重型方案。但真实的项目现场里,80%的物联网平台功能其实是后台管理系统的活儿——设备管理、用户权限、经销商体系、工单售后、报表统计。拿一个通用后台管理系统做底座,把这些通用能力直接托管,再把精力集中在物联网特有的模块上,这套路在中小团队里特别实用。
1.1 若依省下的不是开发量,而是技术债
若依本身就是一套非常成熟的前后端分离后台管理系统,Spring Boot + Vue + MyBatis + Redis + Shiro(或Sa-Token),这些技术栈对大多数Java研发来说几乎零学习成本。这里面最值钱的不是那几套页面,而是沉淀好的权限模型和代码结构:
- RBAC权限模型:用户、角色、菜单、部门这套体系是现成的,我只需要往里面塞“设备数据的查看权限”“设备控制的操作权限”这些资源点,不用自己去设计权限表结构。
- 操作日志与登录日志:物联网平台最怕出事情的时候查不到是谁操作了设备。若依的日志切面是AOP实现的,我在设备控制接口上打一个
@Log注解,每一次“开闸”“重启”“远程升级”操作都能落日志,这个在项目验收和运维排障时帮了我大忙。 - 定时任务:若依自带Quartz封装,我直接在后台界面写个Cron表达式就能调度任务。物联网场景里,定期同步设备状态、定时生成日报告、定时清理离线设备,全靠这个模块撑起来。
很多人看不上这些“通用功能”,但恰恰是这些功能,如果从零写干净,至少是一个半月的工时。
1.2 选型分界线:什么样的物联网项目不能硬套若依
当然,若依不是万能的。我在项目启动前划定了一条红线:
- 如果设备接入量在千级以内,消息频率不高,用若依单体架构完全没问题;
- 如果涉及十万级设备、每秒上万条消息上报,那若依这套单体架构和数据库设计需要大改,或者干脆换成若依微服务版(RuoYi-Cloud),把设备接入、规则引擎单独拆成微服务。
这个判断很重要,因为它决定了后续所有架构设计的方向。我们的项目属于前者——中试车间的设备联网,设备量在五百台左右,数据上报频率5秒一条,这种规模用若依单体完全扛得住,没必要为了“微服务”而微服务。
1.3 若依-Plus和若依-Vue版本,我为什么选了后者
搜“若依”的时候,你会发现有RuoYi-Vue、RuoYi-Cloud、RuoYi-Plus这些分支。我最终选择的是RuoYi-Vue(前后端分离的单体版本),理由有三条:
- 部署简单:单体版本一个Jar包加一个Vue的Nginx站点,Docker跑起来就完事,不需要引入Nacos、Gateway、Sentinel这些基础设施,运维压力小很多;
- 社区生态:RuoYi-Vue的教程和踩坑文章最多,遇到问题基本都能搜到解决方案;
- 改造空间足够:如果将来设备量上去了,RuoYi-Vue可以平滑向RuoYi-Cloud迁移,因为两者的业务代码风格一致。
如果项目一开始就要支撑多租户、海量设备,建议直接上RuoYi-Plus或者RuoYi-Cloud,省得后面迁移时还得改业务代码。这个选择没有对错,只有是否契合当前阶段。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 物联网平台的骨架:设备接入层和物模型建模
后台管理系统的骨架是用户和权限,物联网平台的骨架则是设备接入与设备数据建模。这一层做得扎不扎实,直接决定后续功能的上限。
2.1 设备接入层:MQTT协议与Broker选型
设备接入是物联网平台和传统后台系统最大的分水岭。我们这里的设备大部分是Modbus RTU、Modbus TCP协议的老工业设备,网关把Modbus转成MQTT,再上报到平台。所以平台侧最核心的接入协议是MQTT。
Broker选型上,我用了EMQX(开源版),原因很简单:
- 支持MQTT 3.1.1和5.0,单机轻松扛十万级连接;
- 有完整的HTTP API可以管理设备连接状态;
- 配置了认证回调,设备连接时回调若依的后端接口做鉴权。
设备接入的大致流程是:
- 设备(或网关)上电后,携带设备ID和密钥连接EMQX Broker;
- EMQX通过认证回调,请求若依系统的
/device/auth接口校验设备凭证; - 校验通过后,设备订阅
/device/{deviceId}/cmd主题,并上报数据到/device/{deviceId}/data主题; - 若依后端通过MQTT客户端订阅
/device/+/data,把设备数据接进来。
在EMQX回调里,我对接了若依的SysUserService来复用设备的认证逻辑,也做了一层设备状态缓存。设备连接上后,立即把在线状态写入Redis,断开时通过EMQX的clients.disconnected事件更新状态。
2.2 物模型:把设备抽象成“属性、事件、服务”
设备接进来了,数据也上来了,但上层业务怎么理解这些数据?总不能直接操作JSON里的{"temp": 25.5, "humidity": 60}这种裸字段。所以我在项目里引入了“物模型”的概念,把所有设备的通信数据抽象成三类东西:
- 属性:设备的状态量,比如温度、湿度、电压、开关状态;
- 事件:设备主动上报的事情,比如故障告警、门禁打开、离线;
- 服务:平台下发到设备的控制指令,比如远程重启、调节温度、打开阀门。
物模型的定义我存在MySQL表里,包括product_id、model_type(属性/事件/服务)、identifier(英文标识)、name(中文名称)、data_type(int/float/bool/string)、unit(单位)、rw_flag(只读/可写)。界面直接用若依的数据字典渲染字段类型和单位的下拉框,一套代码生成器生成的CRUD页面就够了。
这里有一个关键点:物模型不是只给展示层用的,它还定义了解析规则。设备上报的原始数据到了后端后,会根据物模型配置做一次数据清洗,把不合法的数据丢弃,把数据格式统一,再进行后续的规则判断和存储。
2.3 设备影子与在线状态管理
设备网络不稳定的情况在物联网场景里是常态。为了应对设备离线期间指令丢失的问题,我引入了“设备影子”机制:
- 设备每次上报数据后,更新Redis中该设备的影子数据;
- 平台下发指令时,先写一条待确认指令到数据库,再发送给设备;
- 设备上线后,先比对影子数据和实际状态,再决定是否需要重新下发指令。
影子数据用的是Redis Hash结构,key是device_shadow:{deviceId},字段是各个物模型属性的标识符。这样一来,即使设备断网十分钟,重新上线后也能快速同步状态,不会出现“平台觉得设备是开的,设备其实是关的”这种尴尬情况。
在线状态管理我做了两层:一层是EMQX的连接会话,存储实时的online/offline标记;另一层是业务侧的心跳机制,如果设备超过N个周期没有上报数据,即使MQTT连接还在,业务上也会判断为“通信异常”。这是纯MQTT连接状态判断不出来的,因为有些设备是网络假死,TCP连接没断但数据已经不来了。
3. 实时数据处理链路:从MQTT到MySQL的管道搭建
设备数据接入只是开始,更关键的是如何把高频上报的数据流稳定地存进数据库,同时不影响前台页面的正常CRUD。这里的核心矛盾是:若依默认的数据访问链路是为“人操作”设计的,扛不住高频写入。
3.1 为什么不能直接把MQTT数据写入MySQL
刚开始我的设想很简单:后端订阅到MQTT消息后,直接deviceDataMapper.insert(deviceData)写入库。但实测下来发现,当五十台设备同时每5秒上报一次数据时,MySQL的写入就明显吃紧,而且设备数据写入和后台业务查询(比如查设备列表、查用户、查日志)互相争抢数据库连接池,导致后台界面也卡。
问题出在“高频小数据量写入”和“后台界面同步查询”这两类负载完全不同。设备数据是持续写入的,后台界面是间歇查询的,混在一起时,数据库的连接池、缓冲池都会被写入请求占满。
3.2 引入消息队列做削峰填谷
后来我参照了若依-Cloud里RocketMQ的集成方式,在本地引入消息队列作为缓冲区。设备数据到达后端后,先发送到消息队列,再由独立的消费者批量写入数据库。
当前用的是RocketMQ,主要包括两个主题:
IOT_DEVICE_DATA:设备上报的原始数据;IOT_DEVICE_EVENT:设备上报的事件和告警。
消费者端做了批量攒批的逻辑:每2秒或者每攒够500条,才执行一次批量插入。用这个方式,数据库的写入压力从原来的“实时高频写入”变成了“稳定的批量写入”,同样的设备量下,MySQL的负载直接降了一大截。
这里的核心思想是把“流式写入”变成“微批写入”。对物联网平台来说,数据的实时性要求通常在秒级,2秒的批处理窗口完全不影响业务,但对数据库的友好程度却是天壤之别。
3.3 时序数据不能全塞MySQL:引入轻量时序存储
设备数据存MySQL这件事,在数据量小的时候够用,但一旦数据量涨起来,查询会越来越慢。尤其是做设备数据曲线图的时候,要在几百万条数据里做GROUP BY time这种聚合查询,MySQL的性能并不理想。
虽然按项目规模看来不至于用上专门的时序数据库,但我还是把设备历史数据单独抽了一个模块:关系型数据(设备信息、用户、告警记录、工单)继续留在MySQL;时序数据(设备历史属性值)转存到ClickHouse(因为这个项目没有引入新的中间件,但生产环境更推荐根据团队技术栈选择TDengine或InfluxDB等时序数据库),并在接入层做了一层路由。
这里分享给大家一个比较务实的实践:如果设备量不大,把时序数据单独建表,按天分表,配合定时清理,MySQL也能顶得住。但不要一开始就把所有数据都塞在一张表里,不然后面做报表分析的时候想死的心都有。
实际上,更通用、更稳妥的做法是引入一款轻量级时序数据库。如果团队已经熟悉MySQL,可以考虑用TDengine替代ClickHouse;如果更看重生态兼容性和SQL灵活度,ClickHouse是常见选择;如果数据量和团队运维能力都有限,InfluxDB单机版也能满足需求。总之,选型不要贪大,够用就好。
3.4 数据链路埋点和积压监控
消息队列引入之后,新的问题也来了:如果消费者挂了,消息积压怎么办?如果设备数据量突然暴涨,队列会不会撑爆?
我给物联网数据链路加了三层监控:
- MQ消费延迟监控:定时检查消费者消费位点和生产位点的差距,超过阈值就告警;
- 数据库写入耗时监控:批量插入的平均耗时和P99耗时,出现异常及时分析;
- 昨日数据量对比:统计每天的数据总量,如果同比波动超过50%,很可能有异常设备或数据上报风暴。
这些监控都做成了若依后台里的独立页面,直接复用若依的定时任务和图表插件,开发量不大,但运维时非常有安全感。监控的意义不在于提前预知所有故障,而在于故障发生时能快速定位问题环节——到底是Broker崩了、队列积压了,还是数据库慢了,一眼就能看出来。
4. 规则引擎与告警联动:平台真正的“大脑”
设备数据接入只是基础,物联网平台真正值钱的地方在于对数据的实时处理和业务联动。我花了不少时间设计和实现的,就是这个规则引擎模块。
4.1 告警规则不是写死的“如果大于阈值”
很多初版方案会把告警逻辑直接写在业务代码里,比如:
java复制if (temperature > 80) {
alertService.send("温度过高");
}
听起来没问题,但实际情况远没有这么简单——不同设备类型有不同的告警阈值,同一个设备在不同时间段可能有不同的阈值,厂长和车间主任对“高温”的定义也可能不一样。写死在代码里,每次调整阈值都要发版本,运维和业务的体验都很糟。
我最终采用的是“规则配置化”方案:把规则存到数据库表里,由后台界面维护。一条规则包含:
- 适用范围:指定产品型号或指定设备;
- 触发条件:字段、运算符、阈值(如
温度 > 80); - 持续时间:连续满足多长时间才触发(防止瞬时抖动误报);
- 恢复条件:什么情况下恢复状态,是否需要通知恢复;
- 动作列表:生成告警记录、推送钉钉/短信、触发语音播报等。
这样业务人员在界面就能自助配置告警规则,不需要研发介入。整个规则解析和执行用了Cron表达式来调度扫描任务,扫描频率可配置,一般场景下每10秒扫描一次就够了,规则触发延迟最多10秒,业务上完全能接受。
4.2 用Aviator表达式解析动态规则
规则条件的解析,我用了Aviator——一个轻量级的Java表达式引擎。规则的“触发条件”字段存的就是类似temperature > 80 && humidity < 30这样的表达式,设备数据进来后,把当前设备的属性值作为上下文变量传入,用Aviator执行表达式,返回true或false。
这样做的优势很明显:
- 规则条件可以变得非常灵活,不仅支持简单比较,还支持逻辑运算、正则匹配,甚至数学函数;
- 表达式以字符串形式存储在数据库,后台界面编辑方便;
- 执行性能很好,单次表达式执行是微秒级,几百条规则扫描一轮用不到1秒。
Aviator的集成方式,核心代码如下:
java复制// 创建Aviator表达式执行器
Expression compiledExp = AviatorEvaluator.compile(rule.getExpression());
Map<String, Object> env = new HashMap<>();
env.put("temperature", 82.5);
env.put("humidity", 20.0);
Boolean result = (Boolean) compiledExp.execute(env);
设备数据进来后,先做数据清洗和物模型转换,再循环遍历该设备对应的规则集,执行表达式,命中后触发告警动作。
4.3 告警风暴抑制:防止“一告警就刷屏”
物联网场景里,最招人烦的问题就是“告警风暴”。一台设备网络抖动,一分钟内上报了几十条“离线”事件,群里瞬间被刷屏,真正重要的告警反而被淹没了。
我从两个方面做了抑制:
- 告警去重:相同设备、相同规则、相同内容,在未恢复前只生成一条告警,重复触发只更新触发次数和最后触发时间;
- 告警聚合:设置一个时间窗口(比如5分钟),窗口内同一设备的同类型告警合并为一条,窗口结束后才推送通知。
这两个机制上线后,告警通知的数量从原来的每天几百条降到几十条,运维人员的“告警疲劳”问题基本解决。
4.4 告警动作:钉钉/短信/电话/语音播报
告警触发的动作,我实现成了可插拔的“处理器”模式。目前实现了四类:
- 钉钉机器人:通过自定义机器人Webhook,推送告警详情到指定的工作群;
- 阿里云短信:调用短信服务API发送到值班人员手机;
- 声光报警器:通过集成Modbus控制的继电器,在车间现场触发声光报警;
- WEB端实时消息:通过WebSocket往后台推送告警提示,前端右上角弹窗。
这套可插拔的设计,让我在后期给客户部署时,能非常方便地根据现场条件增删通知渠道。比如有的工厂不方便用手机短信,我可以只保留钉钉和现场声光报警,也不用改代码。
5. 若依代码生成器在物联网场景中的改造与升级
若依的代码生成器能帮你自动生成单表CRUD的后端代码和Vue页面,这个功能我在通用管理模块上用得飞起。但在物联网场景下,直接生成的代码离“能用”还差一段距离,得做几处改造,才能真正适配业务需求。
5.1 代码生成器给物联网开发省了多少事
先说实话:在非物联网项目里,代码生成器生成完,微调一下就能上线。但在物联网平台里,单表CRUD只是基础,设备数据、告警记录、规则配置这些表之间还有复杂的关联关系,直接生成的代码不一定够用。
我的做法是:用代码生成器生成“基础版本”,再在这个基础上做二次开发。生成器主要给我省了两个工作量:
- 实体类、Mapper、Service、Controller、Vue页面的标准结构不用手写了;
- 若依自带的分页、搜索、导入导出、权限校验都自动接好了。
生成完成后,我再针对物联网的特殊需求进行升级,这样做既利用了生成器的高效,又保证了业务的灵活性。
5.2 物联网场景下的几个定制化改造点
改造一:把“设备状态”字段做成多状态而不是CRUD状态
若依生成器默认生成的状态字段一般是0/1两种取值,用于表示“正常/停用”。但设备状态远不止这两种——在线、离线、故障、维护中、报废。所以在生成的代码里,我把设备状态字段改成了自定义数据字典,通过若依的数据字典模块来维护设备状态的枚举值和样式类。
改造二:关联查询的优化
若依生成器生成的单表查询只能查本表字段。但设备列表页面要显示“产品名称”“所属分组”“最近在线时间”,这些字段分布在多张表里。所以我改造了DeviceServiceImpl的selectDeviceList方法,通过LEFT JOIN关联产品表、设备分组表,并对分页查询做了性能优化,避免每次查询都走全表扫描。
改造三:搜索条件的时间范围
物联网数据查询最常见的操作是按时间范围筛选——查某台设备从某天到某天的上报数据。若依生成器默认只支持某个字段的精确匹配和模糊查询,我增加了时间范围查询的参数解析,前端页面对应增加起始时间、结束时间两个日期选择器,后端把这两个参数转换成SQL的BETWEEN条件。
5.3 改造代码生成器的正确姿势:模板定制
如果你的物联网平台有大量类似的页面,每页都手动改,效率太低。更优的思路是直接改若依的代码生成模板,把上面提到的改造也固化到模板里。
比如修改vm/java/domain.java.vm模板,让生成的所有实体类都自动带上@JsonFormat时间格式化注解;修改vm/java/serviceImpl.java.vm模板,让生成的所有ServiceImpl都自动继承自定义的BaseServiceImpl,从而自动拥有一些物联网平台特有的基础能力。
这个思路和我们“用若依做底座”的初衷是一致的——通用能力自动化,特殊能力模板化,业务能力定制化。把这个改完,后面每新增一种设备类型的管理页面,基本就是走一遍配置表、点几下生成按钮、改两个字段的事。我在实际项目中,新增一个设备型号的完整后台管理页面,从建表到能测试,差不多半天就能搞定,比从零开发快了至少两倍。
6. 现场音视频监控与Web组件集成:最让人头疼的一环
物联网平台做到底,往往不只是看数据,还要看现场画面。设备告警了,光看温度和震动数据还不够,最好能直接调出车间里的摄像头看一眼现场情况。所以我在后台里集成了海康威视的Web视频插件WebControl,集成过程中踩了好几个坑,这里单独拎出来说说。
6.1 若依Vue项目中集成海康WebControl插件的两种思路
先明确一点:海康WebControl插件(老版是WebControls,新版是WebControl)是基于浏览器ActiveX或NPAPI的技术,现代浏览器基本不支持了。现在用的比较多的是海康的“无插件播放”方案,也就是基于H5的Video.js播放HLS/RTMP流。但很多老项目中,客户现场的设备还是只支持WebControl插件模式。
在若依Vue项目里集成WebControl,我走了两条路:
-
方案一:iframe嵌入海康官方Demo页面
快速但不优雅,在正式项目里会出现视频画面被弹窗遮挡的问题,因为iframe里的内容层叠上下文是独立的,和主页面之间互相不可控。 -
方案二:在Vue组件里直接引入WebControl的JS文件
最终采用的是这个方案。在public/index.html里引入海康的webControl.js,然后在Vue组件里初始化插件,配置插件地址、端口、appKey等信息。这种方法集成度高,视频画面可以和页面元素交互,但需要处理大量的插件生命周期事件。
核心初始化代码如下:
javascript复制// 在Vue组件中初始化WebControl
const WebControl = window.WebControl;
const options = {
szPluginVerify: sessionStorage.getItem('you-app-key'),
iType: 2, // 0: 生产环境, 2: 测试环境
iPort: 443,
iLang: 1,
};
WebControl.init(options, () => {
WebControl.JS_StartPlugin();
// 创建播放窗口
WebControl.JS_OpenWindow(videoDivId, '0,0,1280,720');
// 登录设备
WebControl.JS_RequestLogin({
szIP: '192.168.1.64',
szPort: '8000',
szUserName: 'admin',
szPassword: 'password',
});
});
6.2 视频画面被下拉框遮挡的层级问题:两个关键修复
这个坑应该是搜索热词里“海康web插件webcontrol窗口层级遮挡个人中心下拉框”这个问题的出处来源。WebControl插件本质是一个ActiveX控件或者独立窗口,天然悬浮在浏览器所有DOM元素之上,普通CSS的z-index根本管不住它。
我实操中遇到的情况是:打开视频监控页面后,点击右上角的用户头像,个人中心的下拉框被视频画面盖住了,完全点不到“退出登录”按钮。
解决方案有两个,我强烈建议直接用第二种:
方案一(治标不治本):跳过插件,改用主动隐藏插件区域
视频区域通过插件显示时,动态修改插件窗口的位置和大小,让它挪到看不到的位置。比如打开下拉框时,把整个插件的div瞬移到屏幕外,关闭下拉框再移回来。这样做的问题是:视频画面会闪断重连,体验很差。
方案二(推荐):不再直接嵌插件,改为通过海康“无插件取流”中间件
海康部分新设备型号支持“安全认证网关”或“HikCentral”等中间件,可以输出HLS/WebRTC流,前端直接播放H5流,完全绕开WebControl的层级问题。如果现场设备支持,优先走这个方案,省心很多。
如果现场设备确实只能走WebControl插件,那退而求其次的兜底方案是:在若依的顶部导航栏,不用普通的下拉框组件,改成点击后跳转到独立路由页,从物理上避免弹出层和视频插件的层级冲突。这是最稳妥的纯前端策略,不依赖后端,也不需要改插件配置。
6.3 视频播放区和告警联动的设计
视频监控和告警不是一个孤立功能,我把它和规则引擎做了联动:
- 设备告警触发后,告警详情页自动关联该设备附近的摄像头;
- 点击“打开视频”按钮,直接调起WebControl,播放关联摄像头的实时画面;
- 同时支持按时间回放录像,方便告警后的现场取证。
这样用户在处理告警时,不用从一个系统跳到另一个系统,在同一个后台界面就能完成“查看报警数据—打开现场画面—确认现场情况”的完整闭环。
7. 文件存储与部署交付的细节坑
物联网平台往往不只是软件,还涉及现场部署。项目上线时,会遇到一些与开发环境完全不同的情况:没有公网环境、无法访问云存储、内网穿透、权限隔离等。这一章节我把部署和文件存储的细节经验一并分享了,都是实际踩过坑换来的。
7.1 MinIO在若依项目里的正确配置姿势
若依默认集成了本地文件上传,但在物联网场景中,设备图片、产品说明书、告警截图这些文件往往需要集中存储和备份。我用MinIO替换了默认的文件存储方案。
首先在application.yml中配置MinIO相关参数:
yaml复制minio:
endpoint: http://192.168.1.100:9000
access-key: ruoyi
secret-key: ruoyi123
bucket-name: iot-platform
这里要特别提醒的是,千万不要把MinIO的endpoint配置成公网IP的HTTP地址,除非你有SSL证书和配置HTTPS。MinIO默认生成的分享链接是基于配置的endpoint生成的,如果配了公网HTTP,MinIO生成的链接在外部网络访问时会被浏览器拦截为“不安全内容”,上传轻松,下载却谜之失败。
7.2 Docker Compose一键部署整套物联网平台
为了让交付变得简单,我把整个平台打成了一个Docker Compose编排,包括:
- MySQL 8.0:存储业务数据;
- Redis 7.0:缓存和状态存储;
- EMQX:设备接入Broker;
- RocketMQ:消息管道;
- MinIO:文件存储;
- 若依后端服务(自定义镜像);
- 若依前端(Nginx镜像)。
生产环境的部署步骤就变成了:把那台装了Docker的服务器给运维,然后把docker-compose.yml和.env环境变量文件丢过去,执行docker-compose up -d,整套平台就起来了。
当然这里有一个前提——内网部署时,所有中间件镜像要提前离线拉取好,否则现场没有公网,镜像拉不下来,整个交付就卡住了。我吃过这个亏,第一次去工厂部署时,现场网络没有外网权限,当场傻眼。后来我在离线镜像仓库里提前存好了所有镜像,并用docker save打包好镜像tar包,部署时docker load导入即可。
7.3 多环境配置管理:开发、测试、生产分离
若依默认只有application.yml,做多环境部署时容易混乱。我重新整理了配置结构:
application-dev.yml:本地开发环境,连本地MySQL、Redis;application-test.yml:测试环境,连测试库;application-prod.yml:生产环境,配置文件通过环境变量注入。
启动命令里用--spring.profiles.active=prod来选择环境。生产环境的所有密码、密钥统一放docker-compose的.env文件里,后台服务运行时动态读取,这样既保证环境隔离,又避免配置文件泄露敏感信息。
8. 权限与多租户:物联网平台不能绕过的一道坎
若依的权限模型是标准的RBAC,这对内部后台管理系统够用,但物联网平台往往要对接多个角色,比如供应商能看到自己设备的运行数据,客户只能看到自己工厂的产线。如果不做数据隔离,权限漏洞会变成事故。
8.1 在若依RBAC基础上扩展“数据范围”
若依本身有数据权限的概念:按部门、按自定义SQL来限制数据可见范围。在物联网平台上,我把“部门”语义扩展成了“组织/客户”,给设备和产品表都增加了dept_id字段,让每个设备都能归属到某个组织。
这样,供应商A的账号登录后,只能看到自己组织下的设备和对应的数据;管理员账号可以看到全局数据。这个扩展完全复用若依的原生数据权限API,不需要重新造轮子。
8.2 设备控制权限:操作级别的最小化控制
看数据还好说,更关键的是设备控制权限。一个操作员不小心按了“远程重启”,可能导致整条产线停机。我把设备控制操作单独做了权限点:
iot:device:view:查看设备数据;iot:device:control:下发控制指令;iot:device:config:修改设备参数;iot:rule:manage:管理告警规则。
在控制接口上,我添加了若依的@PreAuthorize("@ss.hasPermi('iot:device:control')"),并配合二次确认弹窗。这样每个操作都能追溯到人,操作日志里记录下设备ID、指令内容、结果,出了问题能直接定位责任。
权限这块,我在实际项目里见过不少翻车的案例,大多是“只做了菜单权限,没做数据权限”。在物联网平台上,数据权限的粒度一定要比一般后台系统更细,因为数据直接关联现场设备和真实的生产安全。
8.3 主数据管理:设备与物模型的版本控制
设备量少的时候,改设备型号、改物模型属性无所谓。但当设备量大了、历史数据多了,改一个物模型的字段名,可能会把历史数据查询直接搞崩。所以在权限之外,我加了一个“物模型版本”的字段。
每次修改物模型定义,都会生成一个新的版本号。设备上报数据时,带上的标识符对应的是当前生效版本的物模型。历史的物模型版本保留,用于回溯数据和兼容存量设备。
这个设计在平台运营中起了大作用。有一次业务部门说“温度精度要改成小数点后两位”,如果直接改字段定义,历史数据全是小数点后一位,报表会出现格式不统一的问题。有了版本控制之后,新旧数据各归各的,查询时按版本号分别格式化即可。
9. 持续迭代的方向:从若依单体到若依微服务的演进路径
这个物联网平台上线运行后,现在已经在稳定支撑车间的生产监控业务。从一个更长的视角看,基于若依的物联网平台不是一次性交付就完事,它和产线一样,需要持续迭代和演进。
如果将来设备量扩张到几千甚至上万台,有几个模块一定会成为瓶颈。我也提前梳理了对应的演进路径:
- 设备接入模块:从若依单体里拆出来,独立成“设备接入服务”,使用RuoYi-Cloud的注册中心统一管理,Broker也从EMQX开源版升级到EMQX集群;
- 数据处理模块:消息队列从RocketMQ升级到Kafka或Pulsar,支持更大的吞吐量和更多Topic;
- 规则引擎模块:从定时扫描升级为基于CEP(复杂事件处理)的流式规则引擎,支持毫秒级告警和更复杂的时序模式匹配;
- 报表服务:从MySQL聚合查询改造为独立的数据仓库加定时ETL任务,支撑BI分析。
在演进过程中,若依的微服务版(RuoYi-Cloud)可以作为基础底座,把鉴权、网关、日志、监控这些通用能力平滑迁移过去,业务代码只需调整依赖和调用方式,不需要重写。这是当初选型若依时就看中的一条后备路线。
不过话说回来,技术选型永远要匹配业务阶段。如果你的项目规模还比较小,没必要提前上微服务架构;如果项目已经明确要大流量高并发,那就应该一开始就选择RuoYi-Cloud或其他微服务底座,而不是单体架构跑一段时间再改造。架构演进永远比从零设计更痛苦,所以选型时就要有长期意识,不能只看眼前的开发效率。
