基于RuoYi+Spring Boot的在线智能IOT管理系统开发实战
2026/9/7 7:03:32 网站建设 项目流程

在开发在线智能 IOT 管理系统时,很多同学会遇到同一个问题:设备端数据能收到,业务接口也能写,但后台管理端、权限体系、菜单配置、用户角色这些基础能力都要从零手写,开发周期被拉得很长。尤其是刚接触物联网项目的开发者,往往把精力耗在管理框架上,真正核心的设备接入与数据规则反而没有时间打磨。本文围绕“基于 Idea + HTML/CSS + RuoYi + Java + Spring Boot + MySQL”这套技术栈,完整拆解一个在线智能 IOT 管理系统的落地过程,包含环境搭建、设备管理模块实现、数据上报接口设计、告警规则触发以及常见排错思路。无论是准备课设,还是企业项目前期快速搭建原型,这套方案都可以直接参考。

1. 项目背景与核心概念

1.1 什么是在线智能 IOT 管理系统

在线智能 IOT 管理系统,本质上是一个面向物联网设备的管理后台。它需要完成几类核心工作:

  • 维护设备的基础信息,比如设备编号、设备名称、设备类型、安装位置、运行状态。
  • 接收设备上报的实时数据,例如温度、湿度、电压、开关状态。
  • 对设备数据进行存储、检索、可视化展示。
  • 根据预设的规则判断设备是否异常,触发告警通知。
  • 通过后台管理界面完成用户、角色、权限、菜单的可视化配置。

物联网项目通常包含“设备端—接入层—业务层—展示层”几个环节。设备端可能是传感器、网关、摄像头或工业控制器,接入层负责接收设备上报的数据,业务层对数据进行解析和规则判断,展示层则面向管理人员提供操作界面。而管理系统的开发重点往往集中在业务层和展示层。

1.2 RuoYi 在项目中的定位

RuoYi(若依)是一个基于 Spring Boot + Vue 的快速开发平台,它内置了用户管理、角色管理、菜单管理、部门管理、字典管理、操作日志、登录认证等后台系统的基础功能。对于 IOT 管理系统来说,这些基础功能并不需要重复开发。

使用 RuoYi 作为项目基底,可以带来几个直接的好处:

  • 权限模型开箱即用,不需要自己设计用户表、角色表、菜单表。
  • 自带代码生成器,可以从数据库表直接生成 Controller、Service、Mapper 和前端 Vue 页面。
  • 统一封装了 AjaxResult、TableDataInfo、BaseController,写接口时不用重复处理返回格式。
  • 提供操作日志、登录日志、定时任务等通用能力,适合业务系统的快速迭代。

在 IOT 项目中,RuoYi 解决的是“管理端怎么搭”的问题,而我们自己的开发重点,则放在“设备数据怎么接入”“数据规则怎么判断”“设备状态怎么展示”这些物联网特有的环节上。

1.3 IOT 管理系统与传统后台系统的差异

传统后台管理系统处理的数据主要来自人工维护,比如用户填单、编辑资料、上传文件。而 IOT 管理系统的数据来源是设备自动上报,这就带来几个差别:

对比项传统后台系统IOT 管理系统
数据来源用户手动录入设备自动上报
数据量特征中小规模,增长平缓高频写入,可能突增
实时性要求一般较高,需要快速展示和告警
故障关注点业务逻辑错误设备离线、数据异常、网络延迟
设备管理能力不需要需要设备档案与上下线管理

因此在设计 IOT 管理系统的数据库表结构时,往往要把“设备基础信息表”和“设备实时数据表”分开。设备基础信息表的数据量相对稳定,实时数据表则按时间不断增长,需要考虑索引、归档和按时间分区。

2. 环境准备与版本说明

2.1 开发环境总览

在开始搭建项目之前,先梳理一套完整的开发环境。版本方面不需要追求最新,重点是稳定兼容。以下以常见的 RuoYi-Vue 开发环境为例:

工具推荐说明
JDKJDK 8 或 JDK 11,RuoYi 3.x/4.x 使用较多的版本
Maven3.6.3 以上,负责依赖管理
MySQLMySQL 5.7 或 MySQL 8.0,建议使用 8.0 并注意时区配置
RedisRuoYi 登录验证码与会话缓存需要 Redis
Node.js前端 Vue 项目使用,建议 14.x 到 16.x
Idea开发后端代码,Community 版或 Ultimate 版均可
Navicat / DBeaver数据库客户端,用于初始化 SQL 和调试

需要说明的是,RuoYi 新版本和老版本在依赖版本上差别比较大。如果使用的是 Spring Boot 2.x 对应的版本,JDK 8 足够;如果使用 Spring Boot 3.x,则 JDK 需要 17 及以上。实际开发时,先确认下载的 RuoYi 版本,再选择 JDK,避免环境不兼容导致启动失败。

2.2 RuoYi 项目获取与结构说明

RuoYi 分为 RuoYi-Vue(前后端分离)、RuoYi(单体版)、RuoYi-App、RuoYi-Cloud 等版本。搭建在线智能 IOT 管理系统时,如果没有微服务需求,选择 RuoYi-Vue 或者 RuoYi 单体版都可以。为了方便部署,本文以单体版思路为主,纯前端页面放在 templates 或 static 目录下。

获取项目后,先了解后端目录结构:

ruoyi ├── ruoyi-admin // 启动模块,控制层入口 ├── ruoyi-common // 通用工具类,注解,资源配置 ├── ruoyi-framework // 框架核心,安全、拦截器、配置 ├── ruoyi-generator // 代码生成器,用于根据表结构生成代码 ├── ruoyi-quartz // 定时任务模块 ├── ruoyi-system // 系统模块,用户、角色、菜单等 └── pom.xml // 父级依赖管理

在实际开发中,IOT 相关业务代码可以新建一个ruoyi-iot模块,或者直接在ruoyi-system中增加业务包。新建模块的优点是与系统模块隔离,便于后续沿用代码生成能力和独立编译。

2.3 数据库初始化

RuoYi 自带 SQL 脚本,在项目中的sql目录下可以找到ry_xxxx.sql。使用 MySQL 客户端创建数据库后,导入脚本:

CREATE DATABASE IF NOT EXISTS ruoyi_iot DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE ruoyi_iot; source /your/path/ry_2024xxxx.sql;

这里需要特别提醒:utf8mb4可以支持完整的 UTF-8 字符,包括中文和大部分特殊字符。如果数据库字符集使用utf8,部分生僻字或 emoji 符号可能无法写入。

导入完成后,修改后端application-druid.yml中的数据库连接配置:

spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ruoyi_iot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: your_password

MySQL 8.0 与 MySQL 5.7 的驱动类略有不同,8.0 使用com.mysql.cj.jdbc.Driver,5.7 通常使用com.mysql.jdbc.Driver。另外serverTimezone参数需要根据本地时区调整,国内环境一般配置为GMT+8

2.4 启动验证

配置完成后,启动ruoyi-admin模块下带Application后缀的启动类。看到类似下面的日志就说明启动成功:

Started RuoYiApplication in 12.54 seconds (JVM running for 13.20)

访问后端接口路径http://localhost:8080,如果前端使用 Vue 开发,则单独启动前端项目,访问http://localhost:80。默认管理账号为admin,密码admin123,登录后先修改密码,避免暴露在公网环境后出现安全问题。

3. 核心技术栈与配置拆解

3.1 RuoYi 分层架构理解

RuoYi 采用经典的三层架构:Controller、Service、Mapper。每一层的职责非常清晰:

  • Controller:接收请求,参数校验,调用 Service,封装返回结果。
  • Service:业务逻辑处理,事务控制。
  • Mapper:数据持久层,与数据库交互。

如果使用代码生成器生成代码,生成的逻辑也是按照这套结构生成的。理解分层之后,添加 IOT 业务模块时,只需要按同样的方式写 Controller、Service、Mapper 即可,不需要额外引入复杂的架构设计。

Controller 通常会继承BaseController,这样可以直接使用startPage()分页方法、getDataTable()返回分页数据、toAjax()转换操作结果:

/** * 文件路径:ruoyi-iot/src/main/java/com/ruoyi/iot/controller/DeviceController.java */ @RestController @RequestMapping("/iot/device") public class DeviceController extends BaseController { @Autowired private IDeviceService deviceService; @GetMapping("/list") public TableDataInfo list(DeviceInfo device) { startPage(); List<DeviceInfo> list = deviceService.selectDeviceList(device); return getDataTable(list); } @PostMapping("/add") public AjaxResult add(@RequestBody DeviceInfo device) { return toAjax(deviceService.insertDevice(device)); } @PutMapping("/edit") public AjaxResult edit(@RequestBody DeviceInfo device) { return toAjax(deviceService.updateDevice(device)); } @DeleteMapping("/remove/{ids}") public AjaxResult remove(@PathVariable Long[] ids) { return toAjax(deviceService.deleteDeviceByIds(ids)); } }

这里需要注意的是@RequestBody:RuoYi 的 Vue 前端提交数据时,axios默认使用application/json格式,所以 Controller 接收对象参数需要加@RequestBody。如果使用表单提交,可以不添加。

3.2 Spring Boot 配置文件说明

RuoYi 项目中,配置文件分为application.ymlapplication-druid.yml,有的版本还有application-log.yml。IOT 项目中的自定义配置,比如设备上报密钥、设备报文版本号、告警阈值默认值,建议放到application.yml的自定义前缀下:

iot: upload: api-key: your-secret-key max-connection: 100 alert: default-stream-id: 1 mqtt: enabled: false host: localhost port: 1883

在 Java 代码中,使用@ConfigurationProperties绑定配置:

/** * 文件路径:ruoyi-iot/src/main/java/com/ruoyi/iot/config/IotProperties.java */ @Component @ConfigurationProperties(prefix = "iot") public class IotProperties { private Upload upload = new Upload(); private Alert alert = new Alert(); private Mqtt mqtt = new Mqtt(); public static class Upload { private String apiKey; private Integer maxConnection; // 省略 getter/setter } public static class Alert { private Integer defaultStreamId; } public static class Mqtt { private boolean enabled; private String host; private int port; } // 省略 getter/setter }

使用配置绑定的好处是不需要在业务代码里到处读取@Value,配置集中管理,后续调整设备接入密钥时,只改动配置文件,不需要重新编译代码。

3.3 MyBatis 与数据源配置

RuoYi 使用 MyBatis 作为持久层框架,在application-druid.yml中除了配置数据源,还需要注意 MyBatis 关于 Mapper XML 文件扫描路径的配置。如果新建了ruoyi-iot模块,需要确认Mapper接口能被扫描到,XML 文件能被打包到对应目录。

在实际开发中,一个常见的报错是Invalid bound statement (not found),根本原因是 Mapper 接口找到了,但 XML 文件没有映射上。排错顺序如下:

  1. 检查 Mapper 接口的@Mapper注解是否添加。
  2. 检查 Mapper XML 的namespace是否与接口全限定名一致。
  3. 检查application.yml中的mybatis.mapperLocations是否包含对应路径。
  4. 检查编译后的 classes 目录是否包含 XML 文件。

RuoYi 的 Mapper XML 通常放在resources/mapper目录下。新建 IOT 业务模块时,推荐保持同样的目录约定。

4. 智能 IOT 管理系统实战:设备管理模块

设备管理模块是整个 IOT 管理系统的核心。下面从数据库设计开始,到后端接口、前端页面、设备数据上报,完整走一遍流程。

4.1 数据库表设计

设备管理模块需要两张基础表:设备信息表iot_device和设备数据表iot_device_data

设备信息表负责记录设备的静态属性:

CREATE TABLE `iot_device` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `device_code` varchar(50) NOT NULL COMMENT '设备编码,全局唯一', `device_name` varchar(100) DEFAULT NULL COMMENT '设备名称', `device_type` varchar(50) DEFAULT NULL COMMENT '设备类型:温湿度/电压/开关', `location` varchar(200) DEFAULT NULL COMMENT '安装位置', `status` char(1) DEFAULT '1' COMMENT '状态:0离线 1在线', `last_online_time` datetime DEFAULT NULL COMMENT '最后在线时间', `create_by` varchar(64) DEFAULT '' COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT '' COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`), UNIQUE KEY `uk_device_code` (`device_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备信息表';

设备数据表负责记录设备上报的实时数据:

CREATE TABLE `iot_device_data` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `device_code` varchar(50) NOT NULL COMMENT '设备编码', `temperature` decimal(10,2) DEFAULT NULL COMMENT '温度', `humidity` decimal(10,2) DEFAULT NULL COMMENT '湿度', `voltage` decimal(10,2) DEFAULT NULL COMMENT '电压', `status` char(1) DEFAULT '0' COMMENT '设备告警状态:0正常 1告警', `report_time` datetime NOT NULL COMMENT '上报时间', PRIMARY KEY (`id`), KEY `idx_device_code_report_time` (`device_code`, `report_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备实时数据表';

数据表设计时有两个重点。第一个是device_code建立唯一索引,防止重复设备编码。第二个是device_code + report_time建立联合索引,因为查询设备趋势图、查询设备最近一条数据都依赖这两个字段。

从 IOT 项目长期运行的角度看,设备实时数据表的增长速度很快。生产环境建议按月份做分区,或者定时把超过 90 天的历史数据归档到独立的统计库中。

4.2 编写后端设备管理接口

使用代码生成器可以快速生成设备信息表的 CRUD 代码。生成之后,核心的 Service 逻辑可以继续完善。

DeviceInfo实体类中的状态字段,建议用 Integer 或 String 表示,不要用 Boolean。原因是设备状态可能不止“在线、离线”两种,后续可能扩展“维护中”“异常”“升级中”等状态。

下面是一个简化的IDeviceService接口:

/** * 文件路径:ruoyi-iot/src/main/java/com/ruoyi/iot/service/IDeviceService.java */ public interface IDeviceService { List<DeviceInfo> selectDeviceList(DeviceInfo device); DeviceInfo selectDeviceByCode(String deviceCode); int insertDevice(DeviceInfo device); int updateDevice(DeviceInfo device); int deleteDeviceByIds(Long[] ids); void updateDeviceOnlineStatus(String deviceCode, boolean online); }

Service 实现类中,insertDevice方法里需要校验设备编码是否重复,设备编码不能为空,并且可以设置默认状态:

/** * 文件路径:ruoyi-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceServiceImpl.java */ @Service public class DeviceServiceImpl implements IDeviceService { @Autowired private DeviceInfoMapper deviceInfoMapper; @Override public int insertDevice(DeviceInfo device) { // 设备编码不能为空 if (StringUtils.isEmpty(device.getDeviceCode())) { throw new ServiceException("设备编码不能为空"); } // 设备编码唯一校验 DeviceInfo exist = deviceInfoMapper.selectDeviceByCode(device.getDeviceCode()); if (exist != null) { throw new ServiceException("设备编码已存在"); } if (StringUtils.isNull(device.getStatus())) { device.setStatus("0"); } device.setCreateTime(DateUtils.getNowDate()); return deviceInfoMapper.insertDevice(device); } @Override public void updateDeviceOnlineStatus(String deviceCode, boolean online) { DeviceInfo device = new DeviceInfo(); device.setDeviceCode(deviceCode); device.setStatus(online ? "1" : "0"); device.setLastOnlineTime(online ? DateUtils.getNowDate() : null); deviceInfoMapper.updateDeviceStatus(device); } }

实际项目里,设备上下线状态通常不是人工修改的,而是由设备接入层根据心跳数据自动更新。例如设备每隔 30 秒上报一次心跳,如果超过 90 秒没有收到心跳,就把设备状态置为离线。

4.3 前端设备列表页面

RuoYi-Vue 前端使用 Vue + Element UI。设备列表页面通常包含搜索表单、按钮区域、表格、分页组件几个部分。

核心页面结构如下:

<template> <div class="app-container"> <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch"> <el-form-item label="设备名称" prop="deviceName"> <el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable @keyup.enter="handleQuery" /> </el-form-item> <el-form-item label="设备类型" prop="deviceType"> <el-select v-model="queryParams.deviceType" placeholder="请选择设备类型" clearable> <el-option v-for="dict in deviceTypeOptions" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button> </el-form-item> </el-form> <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button type="primary" plain icon="Plus" @click="handleAdd">新增设备</el-button> </el-col> </el-row> <el-table v-loading="loading" :data="deviceList"> <el-table-column label="设备编码" prop="deviceCode" /> <el-table-column label="设备名称" prop="deviceName" /> <el-table-column label="设备类型" prop="deviceType" /> <el-table-column label="安装位置" prop="location" /> <el-table-column label="状态" align="center"> <template #default="scope"> <el-tag :type="scope.row.status === '1' ? 'success' : 'danger'"> {{ scope.row.status === '1' ? '在线' : '离线' }} </el-tag> </template> </el-table-column> <el-table-column label="操作" align="center"> <template #default="scope"> <el-button type="text" icon="Edit" @click="handleUpdate(scope.row)">编辑</el-button> <el-button type="text" icon="Delete" @click="handleDelete(scope.row)">删除</el-button> </template> </el-table-column> </el-table> <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> </div> </template>

页面中引用pagination组件,直接传入总数、当前页、每页条数即可。新增和编辑弹窗可以沿用 RuoYi 代码生成器生成的dialog表单模板。

4.4 设备数据上报接口设计

设备端上报数据是 IOT 系统的“活水”。为了演示方便,这里先实现一个基于 HTTP 协议的 JSON 上报接口。设备端可以定时向这个接口发送 POST 请求。

/** * 文件路径:ruoyi-iot/src/main/java/com/ruoyi/iot/controller/DeviceReportController.java */ @RestController @RequestMapping("/api/device") public class DeviceReportController { @Autowired private IDeviceDataService deviceDataService; @Autowired private IDeviceService deviceService; @Autowired private IotProperties iotProperties; @PostMapping("/data/report") @Anonymous public AjaxResult reportData(@RequestBody DeviceDataDTO data) { // 1. 校验设备编码 if (StringUtils.isEmpty(data.getDeviceCode())) { return AjaxResult.error("设备编码不能为空"); } // 2. 校验设备是否存在 DeviceInfo device = deviceService.selectDeviceByCode(data.getDeviceCode()); if (device == null) { return AjaxResult.error("设备未注册"); } // 3. 保存设备数据 DeviceData deviceData = new DeviceData(); BeanUtils.copyProperties(data, deviceData); deviceData.setReportTime(DateUtils.getNowDate()); deviceDataService.insertDeviceData(deviceData); // 4. 更新设备在线状态 deviceService.updateDeviceOnlineStatus(data.getDeviceCode(), true); return AjaxResult.success(); } }

这里使用@Anonymous注解,是为了让设备上报接口不经过登录认证。但要注意,生产环境中不能裸奔,至少要在设备端请求头添加api-key,再配合签名校验,避免非法设备伪造数据上报。

设备上报的数据对象DeviceDataDTO可以按实际设备定义:

/** * 文件路径:ruoyi-iot/src/main/java/com/ruoyi/iot/domain/dto/DeviceDataDTO.java */ public class DeviceDataDTO { /** 设备编码 */ private String deviceCode; /** 温度 */ private BigDecimal temperature; /** 湿度 */ private BigDecimal humidity; /** 电压 */ private BigDecimal voltage; /** 设备状态码 */ private Integer deviceStatus; // 省略 getter/setter }

实际上报时,设备端发送的 POST Body 示例:

{ "deviceCode": "DEV001", "temperature": 36.5, "humidity": 65.2, "voltage": 220.1, "deviceStatus": 0 }

4.5 模拟设备上报与验证

没有真实设备时,可以使用 Idea 自带的 HTTP Client 或 Postman 模拟上报。在测试阶段,也可以写一个简单的 Java Main 方法模拟设备端循环上报:

/** * 模拟设备上报,可以直接运行这个方法 */ public class MockDeviceClient { public static void main(String[] args) throws Exception { String url = "http://localhost:8080/api/device/data/report"; JSONObject data = new JSONObject(); data.put("deviceCode", "DEV001"); data.put("temperature", 36.5); data.put("humidity", 65.2); data.put("voltage", 219.8); data.put("deviceStatus", 0); HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "application/json") .POST(BodyPublishers.ofString(data.toJSONString())) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); } }

如果控制台输出:

{ "code": 200, "msg": "操作成功" }

说明设备数据已经成功写入数据库。此时可以在设备列表中看到设备 DEV001 的状态变为在线。

5. 规则引擎与告警通知

IOT 管理系统不能只做数据展示,还需要对数据做规则判断。例如温度超过 45 度、电压低于 180V,系统需要自动生成一条告警记录,并通知相关运维人员。

5.1 告警规则设计

最简单的方式是把规则配置化。在数据库中添加一张告警规则表iot_alert_rule

CREATE TABLE `iot_alert_rule` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `rule_name` varchar(100) NOT NULL COMMENT '规则名称', `device_type` varchar(50) DEFAULT NULL COMMENT '设备类型', `field_name` varchar(50) NOT NULL COMMENT '字段名:temperature/humidity/voltage', `operator` varchar(10) NOT NULL COMMENT '比较符:> < = >= <=', `threshold` decimal(10,2) NOT NULL COMMENT '阈值', `alert_level` char(1) DEFAULT '1' COMMENT '告警级别:1普通 2重要 3严重', `status` char(1) DEFAULT '1' COMMENT '启用状态:0停用 1启用', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='告警规则表';

规则字段的设计与代码的可扩展性密切相关。device_typefield_name一起决定了这条规则作用于哪种设备的哪个属性。通过operatorthreshold组合出判断条件,比如temperature > 45

5.2 实时告警检测

在设备数据上报接口中,保存完设备数据后,立即调用告警规则检测方法。这样能做到秒级响应。

/** * 文件路径:ruoyi-iot/src/main/java/com/ruoyi/iot/service/impl/AlertServiceImpl.java */ @Service public class AlertServiceImpl implements IAlertService { @Autowired private IotAlertRuleMapper alertRuleMapper; @Autowired private IotAlertLogMapper alertLogMapper; @Override public void checkAlert(DeviceData data) { // 1. 根据设备类型查询启用状态的规则 List<IotAlertRule> rules = alertRuleMapper.selectEnabledRules(); for (IotAlertRule rule : rules) { boolean triggered = false; BigDecimal fieldValue = getFieldValue(data, rule.getFieldName()); if (fieldValue == null) { continue; } // 2. 比较判断 BigDecimal threshold = rule.getThreshold(); switch (rule.getOperator()) { case ">" : triggered = fieldValue.compareTo(threshold) > 0; break; case "<" : triggered = fieldValue.compareTo(threshold) < 0; break; case ">=" : triggered = fieldValue.compareTo(threshold) >= 0; break; case "<=" : triggered = fieldValue.compareTo(threshold) <= 0; break; case "=" : triggered = fieldValue.compareTo(threshold) == 0; break; default: break; } // 3. 触发告警则写入日志 if (triggered) { insertAlertLog(data, rule); } } } private BigDecimal getFieldValue(DeviceData data, String fieldName) { switch (fieldName) { case "temperature": return data.getTemperature(); case "humidity": return data.getHumidity(); case "voltage": return data.getVoltage(); default: return null; } } }

告警检测的逻辑是写在数据接收链路上的,因此需要考虑性能。如果规则数量很大,可以先把规则缓存到 Redis,只在后台修改规则时刷新缓存,避免每次上报都查数据库。

5.3 告警记录与通知

告警日志表iot_alert_log用于记录完整告警信息。字段包括告警时间、设备编码、设备名称、规则名称、告警字段、实际值、阈值、告警级别、处理状态、处理人、处理时间等。

通知方式可以采用站内消息、邮件或钉钉/企业微信机器人。如果项目初期不需要接入复杂通知渠道,可以先把告警记录展示在告警列表中,由运维人员登录后台查看和确认。

钉钉机器人通知的通用思路是:在告警产生后,向钉钉自定义机器人 Webhook 发送一个 POST 请求,消息内容包含设备编码、告警原因、实际数值。如果有多套环境(测试、生产),通知地址放在配置文件中,不要写死在代码里。

6. 常见问题与排查思路

在开发在线智能 IOT 管理系统的过程中,无论使用 RuoYi-Vue 还是单体版,都会遇到一些相似的问题。下面整理一份高频问题排查表。

问题现象常见原因解决思路
项目启动报数据库连接失败MySQL 未启动、密码错误、时区错误检查 MySQL 服务,确认密码;URL 添加serverTimezone=GMT+8
登录页面验证码不显示Redis 未安装或未启动启动 Redis,确认application.yml中的 Redis 配置正确
启动时报Invalid bound statementMapper XML 路径不对或 namespace 错误检查mapperLocations配置和 XML namespace
页面中文乱码数据库字符集不是 utf8mb4修改数据库和表字符集,连接串加characterEncoding=utf8
设备上报接口 404接口路径写错或模块未被扫描检查@RequestMapping路径,确认启动类扫描范围
MySQL 8.0 报 Public Key Retrieval 错误连接串缺少allowPublicKeyRetrieval=true在 JDBC URL 中添加参数
端口被占用本机其他程序占用 8080修改server.port或关闭占用进程
前端访问接口跨域前后端分离项目,未配置代理或跨域开发环境配置 Vite/Webpack 代理,生产环境用 Nginx 反向代理

其中,MySQL 8.0 的Public Key Retrieval is not allowed是很多新手最容易踩的坑。解决方式是在 JDBC 连接串中追加:

url: jdbc:mysql://localhost:3306/ruoyi_iot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true

如果遇到项目无法启动,优先查看控制台最底层的Caused by内容,不要只看第一行异常描述。大多数问题,根因都在最下面的Caused by中。

7. 最佳实践与工程建议

7.1 设备接入接口的安全设计

设备上报接口必须做鉴权,不能允许任何客户端随意上报数据。推荐在 Header 中传递api-key,并在后端拦截器统一校验。如果有更高的安全要求,可以增加签名规则,例如由设备编码、时间戳、随机数、密钥拼接后做 MD5/SHA256 签名,时间戳偏差超过 5 分钟的数据直接拒绝。

合法的接口校验流程:

  1. 从请求头获取api-key,与配置的密钥对比。
  2. 校验请求时间戳,防止重放攻击。
  3. 校验设备编码是否已注册。
  4. 校验数据字段格式是否合法。
  5. 写入数据库并触发规则判断。

7.2 数据库写入性能优化

IOT 系统的高频数据写入是最容易产生瓶颈的地方。如果单台 MySQL 每秒写入量很大,可以从几个方向优化:

  • 批量插入:设备数据先积攒到一定数量,或每隔几秒批量刷入数据库。
  • 索引精简:实时数据表的索引不要建太多,保留高频查询字段即可。
  • 分表分区:按月或按设备分组分表,避免单表数据量过大。
  • 异步落库:使用 MQ 或线程池异步处理设备数据,先返回接收成功,再异步写入数据库。

但异步化会带来数据丢失的可能,需要在代码中做补偿机制,比如记录接收日志、定时核对数据量。

7.3 使用 RuoYi 代码生成器加速开发

RuoYi 的代码生成器适合生成标准 CRUD 代码。新建好数据库表后,在系统工具—代码生成中导入表结构,配置生成信息,然后下载代码。生成的文件直接放入项目对应模块中,可以减少大量重复工作。

不过代码生成器生成的 Controller、Service、Mapper 只是模板。IOT 项目中复杂的业务逻辑,例如设备上下线判断、告警规则匹配、数据统计聚合,仍然需要手写实现。

7.4 日志记录与监控

设备上报接口属于高频请求,不能打大量普通日志,否则会把磁盘写满。推荐做法:

  • 正常接收的数据不打 INFO 日志,只记录异常情况。
  • 设备状态变化(上线、离线)记录一条日志。
  • 告警触发记录完整日志,包含设备编码、规则名称、实际值、阈值。
  • 日志输出格式统一,包含时间、接口、耗时、设备编码、结果。

监控方面,RuoYi 自带系统监控,可以看到服务器 CPU、内存、磁盘信息。IOT 项目还需要关注设备在线率、今日上报量、当前告警数等业务指标,这些指标可以通过 SQL 统计后在首页大屏展示。

7.5 事务与异常处理

设备数据上报链路比较长,如果“保存数据”和“更新设备状态”之间没有事务,可能出现数据写成功了但设备状态没有更新的情况。

推荐在核心业务中增加事务控制:

@Transactional(rollbackFor = Exception.class) public void processDeviceReport(DeviceDataDTO data) { // 保存设备数据 deviceDataMapper.insert(deviceData); // 更新设备在线状态 deviceInfoMapper.updateDeviceStatus(device); // 告警判断 alertService.checkAlert(deviceData); }

同时要注意,告警通知如果走 HTTP 调用外部接口,不应该和主链路放在同一个事务中,否则外部接口响应慢会拖垮数据上报接口。可以把通知逻辑放到事务提交之后执行,或者使用异步线程池处理。

7.6 前端展示优化

设备列表页面对数据实时性要求较高,可以通过 RuoYi 前端项目中的useInterval或者定时器定时刷新接口。比如每 10 秒刷新一次设备状态列表。刷新频率不宜过高,否则会给后端接口造成压力。

设备详情页可以展示历史趋势图,使用 ECharts 折线图展示温度和湿度变化曲线。后端提供一个查询设备历史数据的接口,前端按时间范围查询并渲染图表。数据量较大时,聚合查询可以在 SQL 中按小时取平均值,减少返回数据量。

8. 总结与学习路线

围绕在线智能 IOT 管理系统的开发,本文主要完成了以下内容:

  • 梳理了 IOT 管理系统与普通后台系统的差别,说明了 RuoYi 在项目中的定位。
  • 介绍了从环境准备到 RuoYi 项目初始化的完整流程。
  • 设计了设备信息表与设备实时数据表,并实现了设备管理模块。
  • 实现了设备数据上报接口,通过模拟客户端验证了数据接入流程。
  • 增加了告警规则检测机制。
  • 整理了数据库、前端、接口鉴权、优化实践等工程建议。

接下来可以从几个方向继续深入:

  1. 接入 MQTT 协议,替换 HTTP 设备上报,适应更复杂的物联网网络场景。
  2. 增加数据可视化大屏,通过 ECharts 展示设备分布、实时数据、在线率等指标。
  3. 学习 Docker 容器化部署,把 MySQL、Redis、后端服务、前端页面打包成容器,提高部署效率。
  4. 深入 RuoYi 的权限控制模型,为 IOT 平台建立“租户隔离”或“多项目隔离”能力。

实际项目中,IOT 系统真正的难点不只在于页面和接口,更在于设备接入的稳定性、海量数据存储的容量规划、告警规则的灵活性。建议从一台模拟设备跑通全链路开始,先关注业务闭环,再逐步优化性能和安全。如果本文对你有帮助,可以收藏备用,也欢迎在评论区交流设备接入和告警联动方面的实践经验。

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

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

立即咨询