RuoYi集成物联网平台:设备监控与数据采集全指南
一、物联网集成痛点与解决方案架构
你是否正面临这些挑战:工业传感器数据难以接入现有管理系统?设备状态监控缺乏统一视图?数据存储与分析成为性能瓶颈?本文将基于RuoYi框架,通过10个实战步骤,手把手教你构建企业级物联网集成方案,实现从设备接入到数据可视化的全流程管理。
读完本文你将获得:
- 3种主流物联网协议接入方案
- 高并发设备数据处理架构设计
- 基于Quartz的定时数据采集实现
- 设备权限精细化控制方案
- 完整的物联网数据可视化组件
技术架构总览
二、环境准备与依赖配置
2.1 必要依赖引入
在pom.xml中添加物联网相关依赖:
<!-- 物联网协议支持 -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
<!-- 时序数据库客户端 -->
<dependency>
<groupId>org.influxdb</groupId>
<artifactId>influxdb-java</artifactId>
<version>2.22</version>
</dependency>
<!-- WebSocket支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.2 数据源配置
修改application-druid.yml添加时序数据库连接:
spring:
datasource:
# 原有MySQL配置保持不变
# 新增时序数据库配置
influx:
url: http://192.168.1.100:8086
username: iotuser
password: iotpass
database: sensor_data
三、设备管理模块设计与实现
3.1 数据库设计
创建设备管理相关表结构:
-- 设备表
CREATE TABLE `iot_device` (
`device_id` bigint NOT NULL AUTO_INCREMENT COMMENT '设备ID',
`device_code` varchar(64) NOT NULL COMMENT '设备编码',
`device_name` varchar(128) NOT NULL COMMENT '设备名称',
`device_type` varchar(32) NOT NULL COMMENT '设备类型',
`protocol_type` varchar(20) NOT NULL COMMENT '协议类型(MQTT/HTTP/CoAP)',
`device_ip` varchar(64) DEFAULT NULL COMMENT '设备IP',
`port` int DEFAULT NULL COMMENT '端口号',
`status` char(1) NOT NULL DEFAULT '0' COMMENT '设备状态(0-离线 1-在线)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`device_id`),
UNIQUE KEY `uk_device_code` (`device_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物联网设备表';
-- 设备数据点表
CREATE TABLE `iot_device_point` (
`point_id` bigint NOT NULL AUTO_INCREMENT COMMENT '数据点ID',
`device_id` bigint NOT NULL COMMENT '设备ID',
`point_code` varchar(64) NOT NULL COMMENT '数据点编码',
`point_name` varchar(128) NOT NULL COMMENT '数据点名称',
`data_type` varchar(20) NOT NULL COMMENT '数据类型',
`unit` varchar(20) DEFAULT NULL COMMENT '单位',
`min_value` decimal(17,4) DEFAULT NULL COMMENT '最小值',
`max_value` decimal(17,4) DEFAULT NULL COMMENT '最大值',
PRIMARY KEY (`point_id`),
KEY `idx_device_id` (`device_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备数据点表';
3.2 实体类定义
创建设备和数据点实体类:
// 设备实体
public class IotDevice extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long deviceId;
private String deviceCode;
private String deviceName;
private String deviceType;
private String protocolType;
private String deviceIp;
private Integer port;
private String status;
// getter/setter省略
}
// 数据点实体
public class IotDevicePoint extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long pointId;
private Long deviceId;
private String pointCode;
private String pointName;
private String dataType;
private String unit;
private BigDecimal minValue;
private BigDecimal maxValue;
// getter/setter省略
}
四、设备接入核心实现
4.1 设备服务接口设计
public interface IIotDeviceService {
/**
* 查询设备列表
*/
public List<IotDevice> selectDeviceList(IotDevice device);
/**
* 新增设备
*/
public int insertDevice(IotDevice device);
/**
* 修改设备
*/
public int updateDevice(IotDevice device);
/**
* 批量删除设备
*/
public int deleteDeviceByIds(String ids);
/**
* 获取设备状态
*/
public String getDeviceStatus(String deviceCode);
/**
* 同步设备状态
*/
public void syncDeviceStatus();
}
4.2 MQTT协议接入实现
@Component
public class MqttClientComponent {
private static final Logger log = LoggerFactory.getLogger(MqttClientComponent.class);
@Value("${mqtt.broker}")
private String broker;
@Value("${mqtt.clientId}")
private String clientId;
@Value("${mqtt.username}")
private String username;
@Value("${mqtt.password}")
private String password;
private MqttClient client;
@PostConstruct
public void init() {
try {
client = new MqttClient(broker, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(username);
options.setPassword(password.toCharArray());
options.setConnectionTimeout(10);
options.setKeepAliveInterval(20);
options.setAutomaticReconnect(true);
client.setCallback(new MqttCallback() {
@Override
public void connectionLost(Throwable cause) {
log.error("MQTT连接丢失", cause);
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
// 处理接收到的设备数据
handleDeviceData(topic, new String(message.getPayload()));
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
// 消息发送完成回调
}
});
client.connect(options);
log.info("MQTT客户端连接成功");
// 订阅设备状态主题
client.subscribe("device/status/#", 1);
// 订阅设备数据主题
client.subscribe("device/data/#", 2);
} catch (MqttException e) {
log.error("MQTT客户端初始化失败", e);
}
}
/**
* 处理设备数据
*/
private void handleDeviceData(String topic, String payload) {
// 解析主题获取设备编码
String[] topicParts = topic.split("/");
if (topicParts.length < 3) {
log.warn("无效的MQTT主题格式: {}", topic);
return;
}
String deviceCode = topicParts[2];
log.info("收到设备[{}]数据: {}", deviceCode, payload);
// 解析数据并存储
try {
JSONObject json = JSONObject.parseObject(payload);
saveDeviceData(deviceCode, json);
} catch (Exception e) {
log.error("解析设备数据失败", e);
}
}
// 其他方法省略...
}
五、数据采集与存储方案
5.1 定时数据采集任务
利用RuoYi已有的Quartz定时任务框架,实现周期性数据采集:
@Component
public class DeviceDataCollectionJob extends AbstractQuartzJob {
@Autowired
private IIotDeviceService deviceService;
@Autowired
private IotDataService dataService;
@Override
protected void doExecute(JobExecutionContext context) throws Exception {
// 获取需要采集数据的设备列表
IotDevice deviceQuery = new IotDevice();
deviceQuery.setStatus("1"); // 只查询在线设备
List<IotDevice> devices = deviceService.selectDeviceList(deviceQuery);
if (CollectionUtils.isEmpty(devices)) {
log.info("没有需要采集数据的在线设备");
return;
}
// 遍历设备采集数据
for (IotDevice device : devices) {
try {
if ("HTTP".equals(device.getProtocolType())) {
// HTTP协议设备数据采集
collectHttpDeviceData(device);
} else if ("MQTT".equals(device.getProtocolType())) {
// MQTT协议设备主动上报,此处无需采集
} else if ("MODBUS".equals(device.getProtocolType())) {
// MODBUS协议设备数据采集
collectModbusDeviceData(device);
}
// 更新设备最后采集时间
deviceService.updateLastCollectTime(device.getDeviceId());
} catch (Exception e) {
log.error("设备[{}]数据采集失败", device.getDeviceCode(), e);
// 失败次数过多标记为离线
deviceService.increaseFailCount(device.getDeviceId());
}
}
}
/**
* HTTP协议设备数据采集
*/
private void collectHttpDeviceData(IotDevice device) {
String url = "http://" + device.getDeviceIp() + ":" + device.getPort() + "/data";
try {
String result = HttpUtils.sendGet(url);
if (StringUtils.isNotEmpty(result)) {
JSONObject data = JSONObject.parseObject(result);
dataService.saveDeviceData(device.getDeviceCode(), data);
}
} catch (Exception e) {
log.error("HTTP设备数据采集失败", e);
throw e;
}
}
// MODBUS采集实现省略...
}
5.2 时序数据库操作工具类
@Component
public class InfluxDbUtil {
@Value("${spring.datasource.influx.url}")
private String url;
@Value("${spring.datasource.influx.username}")
private String username;
@Value("${spring.datasource.influx.password}")
private String password;
@Value("${spring.datasource.influx.database}")
private String database;
private InfluxDB influxDB;
@PostConstruct
public void init() {
influxDB = InfluxDBFactory.connect(url, username, password);
// 检查数据库是否存在,不存在则创建
if (!influxDB.databaseExists(database)) {
influxDB.createDatabase(database);
}
influxDB.setDatabase(database);
// 设置保留策略,数据保留30天
influxDB.createRetentionPolicy("rp_30d", database, "30d", "1h", 1, true);
}
/**
* 保存设备数据到时序数据库
*/
public void saveDeviceData(String deviceCode, Map<String, Object> data, long timestamp) {
Point.Builder pointBuilder = Point.measurement("device_data")
.tag("device_code", deviceCode)
.time(timestamp, TimeUnit.MILLISECONDS);
for (Map.Entry<String, Object> entry : data.entrySet()) {
String field = entry.getKey();
Object value = entry.getValue();
if (value instanceof Number) {
pointBuilder.addField(field, (Number) value);
} else if (value instanceof Boolean) {
pointBuilder.addField(field, (Boolean) value);
} else {
pointBuilder.addField(field, value.toString());
}
}
influxDB.write(pointBuilder.build());
}
/**
* 查询设备历史数据
*/
public List<Map<String, Object>> queryDeviceHistoryData(String deviceCode, String pointCode,
long startTime, long endTime, String aggregation) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT ");
if (StringUtils.isNotEmpty(aggregation)) {
sql.append(aggregation).append("(").append(pointCode).append(") AS ").append(pointCode);
} else {
sql.append(pointCode);
}
sql.append(" FROM device_data WHERE device_code = '").append(deviceCode).append("'")
.append(" AND time >= ").append(startTime).append("ms")
.append(" AND time <= ").append(endTime).append("ms")
.append(" GROUP BY time(1m)")
.append(" ORDER BY time ASC");
QueryResult result = influxDB.query(new Query(sql.toString(), database));
return parseQueryResult(result, pointCode);
}
// 结果解析等方法省略...
}
六、设备监控与WebSocket实时推送
6.1 WebSocket配置
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 设备监控WebSocket端点,需要登录认证
registry.addHandler(deviceMonitorHandler(), "/ws/device/monitor")
.addInterceptors(new HttpSessionHandshakeInterceptor())
.setAllowedOrigins("*");
// 设备告警WebSocket端点
registry.addHandler(alarmNotifyHandler(), "/ws/device/alarm")
.addInterceptors(new HttpSessionHandshakeInterceptor())
.setAllowedOrigins("*");
}
@Bean
public WebSocketHandler deviceMonitorHandler() {
return new DeviceMonitorWebSocketHandler();
}
@Bean
public WebSocketHandler alarmNotifyHandler() {
return new AlarmNotifyWebSocketHandler();
}
}
6.2 设备监控WebSocket处理器
public class DeviceMonitorWebSocketHandler extends TextWebSocketHandler {
private static final Logger log = LoggerFactory.getLogger(DeviceMonitorWebSocketHandler.class);
// 存储用户WebSocket会话
private static final Map<Long, WebSocketSession> userSessions = new ConcurrentHashMap<>();
@Autowired
private ISysUserService userService;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 获取当前登录用户
SysUser user = ShiroUtils.getSysUser();
if (user != null) {
userSessions.put(user.getUserId(), session);
log.info("用户[{}]已连接设备监控WebSocket", user.getLoginName());
// 发送当前在线设备数量
Map<String, Object> message = new HashMap<>();
message.put("type", "online_count");
message.put("data", getOnlineDeviceCount());
session.sendMessage(new TextMessage(JSONObject.toJSONString(message)));
} else {
// 未登录用户关闭连接
session.close();
}
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 处理客户端发送的消息
String payload = message.getPayload();
JSONObject json = JSONObject.parseObject(payload);
String action = json.getString("action");
if ("subscribe_device".equals(action)) {
String deviceCode = json.getString("deviceCode");
// 订阅指定设备数据
subscribeDevice(session, deviceCode);
} else if ("unsubscribe_device".equals(action)) {
String deviceCode = json.getString("deviceCode");
// 取消订阅设备数据
unsubscribeDevice(session, deviceCode);
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// 移除用户会话
userSessions.entrySet().removeIf(entry -> entry.getValue().equals(session));
}
/**
* 向所有在线用户推送设备状态变化
*/
public static void broadcastDeviceStatus(String deviceCode, String status) {
Map<String, Object> message = new HashMap<>();
message.put("type", "device_status");
message.put("deviceCode", deviceCode);
message.put("status", status);
message.put("timestamp", System.currentTimeMillis());
String jsonMessage = JSONObject.toJSONString(message);
TextMessage textMessage = new TextMessage(jsonMessage);
for (WebSocketSession session : userSessions.values()) {
if (session.isOpen()) {
try {
session.sendMessage(textMessage);
} catch (IOException e) {
log.error("推送设备状态消息失败", e);
}
}
}
}
// 其他方法省略...
}
6.3 设备监控控制器
@RestController
@RequestMapping("/iot/monitor")
public class IotMonitorController extends BaseController {
@Autowired
private IIotDeviceService deviceService;
@Autowired
private InfluxDbUtil influxDbUtil;
/**
* 获取设备实时状态列表
*/
@GetMapping("/deviceStatus")
public AjaxResult getDeviceStatusList() {
IotDevice device = new IotDevice();
List<IotDevice> devices = deviceService.selectDeviceList(device);
List<Map<String, Object>> result = new ArrayList<>();
for (IotDevice d : devices) {
Map<String, Object> status = new HashMap<>();
status.put("deviceId", d.getDeviceId());
status.put("deviceCode", d.getDeviceCode());
status.put("deviceName", d.getDeviceName());
status.put("status", d.getStatus());
status.put("lastOnlineTime", d.getLastOnlineTime());
status.put("protocolType", d.getProtocolType());
result.add(status);
}
return AjaxResult.success(result);
}
/**
* 获取设备最新数据
*/
@GetMapping("/latestData")
public AjaxResult getDeviceLatestData(@RequestParam String deviceCode) {
// 查询设备数据点
List<IotDevicePoint> points = devicePointService.selectDevicePointByDeviceCode(deviceCode);
if (CollectionUtils.isEmpty(points)) {
return AjaxResult.error("设备未配置数据点");
}
// 查询最新数据
long endTime = System.currentTimeMillis();
long startTime = endTime - 5 * 60 * 1000; // 5分钟内
Map<String, Object> latestData = new HashMap<>();
for (IotDevicePoint point : points) {
String pointCode = point.getPointCode();
List<Map<String, Object>> data = influxDbUtil.queryDeviceHistoryData(
deviceCode, pointCode, startTime, endTime, null);
if (!CollectionUtils.isEmpty(data)) {
latestData.put(pointCode, data.get(data.size() - 1).get(pointCode));
latestData.put(pointCode + "_time", data.get(data.size() - 1).get("time"));
} else {
latestData.put(pointCode, null);
latestData.put(pointCode + "_time", null);
}
}
return AjaxResult.success(latestData);
}
/**
* 获取设备历史趋势数据
*/
@GetMapping("/historyTrend")
public AjaxResult getDeviceHistoryTrend(
@RequestParam String deviceCode,
@RequestParam String pointCode,
@RequestParam long startTime,
@RequestParam long endTime,
@RequestParam(required = false) String aggregation) {
List<Map<String, Object>> data = influxDbUtil.queryDeviceHistoryData(
deviceCode, pointCode, startTime, endTime, aggregation);
return AjaxResult.success(data);
}
// 其他接口省略...
}
七、权限控制与安全管理
7.1 设备权限设计
扩展RuoYi的权限模型,增加设备维度的权限控制:
/**
* 设备权限注解
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DevicePermission {
/**
* 权限标识
*/
String permission() default "";
/**
* 是否验证设备数据权限
*/
boolean verifyDataScope() default true;
}
/**
* 设备权限切面
*/
@Component
@Aspect
public class DevicePermissionAspect {
@Autowired
private ISysRoleService roleService;
@Pointcut("@annotation(com.ruoyi.iot.annotation.DevicePermission)")
public void devicePermissionPointCut() {
}
@Around("devicePermissionPointCut() && @annotation(permission)")
public Object around(ProceedingJoinPoint joinPoint, DevicePermission permission) throws Throwable {
// 检查用户是否有设备操作权限
if (!hasDevicePermission(permission.permission())) {
throw new NoPermissionException("没有操作权限,请联系管理员授权");
}
// 验证数据权限
if (permission.verifyDataScope()) {
verifyDeviceDataScope(joinPoint);
}
return joinPoint.proceed();
}
/**
* 检查设备权限
*/
private boolean hasDevicePermission(String permission) {
if (StringUtils.isEmpty(permission)) {
return true;
}
SysUser user = ShiroUtils.getSysUser();
// 管理员拥有所有权限
if (SysUser.isAdmin(user.getUserId())) {
return true;
}
// 检查用户是否拥有该权限
return ShiroUtils.hasPermission(permission);
}
/**
* 验证设备数据权限
*/
private void verifyDeviceDataScope(ProceedingJoinPoint joinPoint) {
SysUser user = ShiroUtils.getSysUser();
// 管理员不验证数据权限
if (SysUser.isAdmin(user.getUserId())) {
return;
}
// 获取方法参数中的设备ID
Long deviceId = getDeviceIdFromArgs(joinPoint.getArgs());
if (deviceId == null) {
return;
}
// 检查用户是否有权限操作该设备
if (!roleService.hasDeviceDataScope(user.getUserId(), deviceId)) {
throw new NoPermissionException("没有权限操作此设备数据");
}
}
// 参数解析等方法省略...
}
7.2 设备数据权限实现
@Service
public class SysRoleServiceImpl implements ISysRoleService {
// 原有代码...
/**
* 检查用户是否有设备数据权限
*/
@Override
public boolean hasDeviceDataScope(Long userId, Long deviceId) {
// 查询用户拥有权限的设备ID列表
List<Long> authorizedDeviceIds = roleMapper.selectAuthorizedDeviceIds(userId);
// 如果没有分配任何设备权限,则没有数据权限
if (CollectionUtils.isEmpty(authorizedDeviceIds)) {
return false;
}
// 如果有权限的设备包含当前设备ID,则有权限
return authorizedDeviceIds.contains(deviceId);
}
/**
* 获取用户有权限的设备列表
*/
@Override
public List<Long> getUserAuthorizedDeviceIds(Long userId) {
return roleMapper.selectAuthorizedDeviceIds(userId);
}
}
八、前端监控页面实现
8.1 设备列表页面
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('设备监控列表')" />
<link href="/css/iot/monitor.css" rel="stylesheet">
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="deviceForm">
<div class="select-list">
<ul>
<li>
设备名称: <input type="text" name="deviceName" class="form-control" placeholder="请输入设备名称">
</li>
<li>
设备编码: <input type="text" name="deviceCode" class="form-control" placeholder="请输入设备编码">
</li>
<li>
设备类型:
<select name="deviceType" class="form-control m-b">
<option value="">全部</option>
<option th:each="dict : ${@dict.getType('iot_device_type')}" th:value="${dict.dictValue}" th:text="${dict.dictLabel}"></option>
</select>
</li>
<li>
设备状态:
<select name="status" class="form-control m-b">
<option value="">全部</option>
<option value="0">离线</option>
<option value="1">在线</option>
</select>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i> 搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.table.reset()"><i class="fa fa-refresh"></i> 重置</a>
</li>
</ul>
</div>
</form>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="deviceTable" class="table table-hover table-bordered">
<thead>
<tr>
<th>设备编码</th>
<th>设备名称</th>
<th>设备类型</th>
<th>协议类型</th>
<th>IP地址</th>
<th>状态</th>
<th>最后在线时间</th>
<th>操作</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script type="text/javascript" src="/js/iot/device-monitor.js"></script>
</body>
</html>
8.2 设备监控JavaScript
$(function() {
var options = {
url: ctx + "iot/monitor/deviceStatus",
pagination: true,
pageSize: 10,
pageList: [10, 20, 30, 40, 50],
queryParams: function(params) {
return {
pageNum: params.pageNumber,
pageSize: params.pageSize,
deviceName: $.trim($("input[name='deviceName']").val()),
deviceCode: $.trim($("input[name='deviceCode']").val()),
deviceType: $("select[name='deviceType']").val(),
status: $("select[name='status']").val()
};
},
columns: [
{field: 'deviceCode', title: '设备编码', formatter: function(value, row, index) {
return '<a href="javascript:viewDeviceDetail(\'' + value + '\')">' + value + '</a>';
}},
{field: 'deviceName', title: '设备名称'},
{field: 'deviceType', title: '设备类型', formatter: function(value, row, index) {
return $.table.selectDictLabel('iot_device_type', value);
}},
{field: 'protocolType', title: '协议类型'},
{field: 'deviceIp', title: 'IP地址'},
{field: 'status', title: '状态', formatter: function(value, row, index) {
if (value === '1') {
return '<span class="label label-success">在线</span>';
} else {
return '<span class="label label-danger">离线</span>';
}
}},
{field: 'lastOnlineTime', title: '最后在线时间'},
{field: 'operate', title: '操作', formatter: function(value, row, index) {
var actions = [];
actions.push('<a href="javascript:viewDeviceDetail(\'' + row.deviceCode + '\')" class="btn btn-primary btn-xs"><i class="fa fa-eye"></i> 查看</a> ');
actions.push('<a href="javascript:showDeviceHistory(\'' + row.deviceCode + '\')" class="btn btn-info btn-xs"><i class="fa fa-line-chart"></i> 历史数据</a> ');
if (row.status === '1') {
actions.push('<a href="javascript:controlDevice(\'' + row.deviceCode + '\')" class="btn btn-success btn-xs"><i class="fa fa-cogs"></i> 控制</a>');
}
return actions.join('');
}}
]
};
$.table.init(options);
// 初始化WebSocket连接
initWebSocket();
});
// WebSocket连接
var websocket;
function initWebSocket() {
if ('WebSocket' in window) {
var wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
var wsUrl = wsProtocol + '//' + window.location.host + ctx + 'ws/device/monitor';
websocket = new WebSocket(wsUrl);
// 连接成功建立的回调方法
websocket.onopen = function() {
console.log("WebSocket连接成功");
};
// 连接关闭的回调方法
websocket.onclose = function() {
console.log("WebSocket连接关闭");
// 尝试重连
setTimeout(initWebSocket, 3000);
};
// 接收到消息的回调方法
websocket.onmessage = function(event) {
var message = JSON.parse(event.data);
handleWebSocketMessage(message);
};
// 连接发生错误的回调方法
websocket.onerror = function() {
console.error("WebSocket发生错误");
};
// 窗口关闭时,主动关闭WebSocket连接
window.onbeforeunload = function() {
websocket.close();
};
} else {
alert('当前浏览器不支持WebSocket,请使用Chrome、Firefox等现代浏览器');
}
}
// 处理WebSocket消息
function handleWebSocketMessage(message) {
if (message.type === 'device_status') {
// 更新设备状态
var deviceCode = message.deviceCode;
var status = message.status;
var $tr = $('table#deviceTable tbody tr').filter(function() {
return $(this).find('td:eq(0)').text() === deviceCode;
});
if ($tr.length > 0) {
var statusHtml = status === '1' ?
'<span class="label label-success">在线</span>' :
'<span class="label label-danger">离线</span>';
$tr.find('td:eq(5)').html(statusHtml);
// 添加闪烁效果
$tr.addClass('bg-highlight');
setTimeout(function() {
$tr.removeClass('bg-highlight');
}, 2000);
}
} else if (message.type === 'online_count') {
// 更新在线设备数量
$('#onlineDeviceCount').text(message.data);
}
}
// 查看设备详情
function viewDeviceDetail(deviceCode) {
var url = ctx + "iot/monitor/deviceDetail?deviceCode=" + deviceCode;
window.open(url, '_blank', 'width=1200,height=800,top=100,left=200');
}
// 其他函数省略...
九、项目部署与优化建议
9.1 部署架构
9.2 性能优化建议
-
数据采集优化
- 使用批量写入时序数据库,减少IO次数
- 对高频数据进行采样或降采样处理
- 使用缓存减少数据库查询次数
-
并发处理优化
- 设备连接使用NIO模型
- 数据处理使用线程池隔离不同设备类型
- 大流量场景使用Kafka消息队列削峰填谷
-
查询性能优化
- 时序数据库合理设计保留策略
- 热门设备数据添加缓存
- 使用数据聚合减少返回数据量
十、总结与扩展方向
通过本文介绍的方案,我们基于RuoYi框架构建了一个功能完善的物联网设备监控与数据采集系统,实现了设备接入、数据存储、实时监控和权限管理等核心功能。该方案具有以下特点:
- 高可扩展性:支持多种物联网协议,可灵活接入不同类型设备
- 高可靠性:通过定时任务和WebSocket实现设备状态实时监控
- 安全性:基于Shiro框架实现设备级别的权限控制
- 易维护性:充分利用RuoYi现有框架,降低开发和维护成本
未来扩展方向
- AI异常检测:集成机器学习算法,实现设备故障预测
- 边缘计算:在边缘节点实现数据预处理,减少中心服务器压力
- 移动端监控:开发APP实现设备移动监控与告警
- 3D可视化:结合WebGL实现设备三维可视化监控
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



