RuoYi集成物联网平台:设备监控与数据采集全指南

RuoYi集成物联网平台:设备监控与数据采集全指南

【免费下载链接】RuoYi :tada: (RuoYi)官方仓库 基于SpringBoot的权限管理系统 易读易懂、界面简洁美观。 核心技术采用Spring、MyBatis、Shiro没有任何其它重度依赖。直接运行即可用 【免费下载链接】RuoYi 项目地址: https://gitcode.com/gh_mirrors/ruoyi/RuoYi

一、物联网集成痛点与解决方案架构

你是否正面临这些挑战:工业传感器数据难以接入现有管理系统?设备状态监控缺乏统一视图?数据存储与分析成为性能瓶颈?本文将基于RuoYi框架,通过10个实战步骤,手把手教你构建企业级物联网集成方案,实现从设备接入到数据可视化的全流程管理。

读完本文你将获得:

  • 3种主流物联网协议接入方案
  • 高并发设备数据处理架构设计
  • 基于Quartz的定时数据采集实现
  • 设备权限精细化控制方案
  • 完整的物联网数据可视化组件

技术架构总览

mermaid

二、环境准备与依赖配置

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>&nbsp;搜索</a>
                                <a class="btn btn-warning btn-rounded btn-sm" onclick="$.table.reset()"><i class="fa fa-refresh"></i>&nbsp;重置</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 部署架构

mermaid

9.2 性能优化建议

  1. 数据采集优化

    • 使用批量写入时序数据库,减少IO次数
    • 对高频数据进行采样或降采样处理
    • 使用缓存减少数据库查询次数
  2. 并发处理优化

    • 设备连接使用NIO模型
    • 数据处理使用线程池隔离不同设备类型
    • 大流量场景使用Kafka消息队列削峰填谷
  3. 查询性能优化

    • 时序数据库合理设计保留策略
    • 热门设备数据添加缓存
    • 使用数据聚合减少返回数据量

十、总结与扩展方向

通过本文介绍的方案,我们基于RuoYi框架构建了一个功能完善的物联网设备监控与数据采集系统,实现了设备接入、数据存储、实时监控和权限管理等核心功能。该方案具有以下特点:

  1. 高可扩展性:支持多种物联网协议,可灵活接入不同类型设备
  2. 高可靠性:通过定时任务和WebSocket实现设备状态实时监控
  3. 安全性:基于Shiro框架实现设备级别的权限控制
  4. 易维护性:充分利用RuoYi现有框架,降低开发和维护成本

未来扩展方向

  1. AI异常检测:集成机器学习算法,实现设备故障预测
  2. 边缘计算:在边缘节点实现数据预处理,减少中心服务器压力
  3. 移动端监控:开发APP实现设备移动监控与告警
  4. 3D可视化:结合WebGL实现设备三维可视化监控

【免费下载链接】RuoYi :tada: (RuoYi)官方仓库 基于SpringBoot的权限管理系统 易读易懂、界面简洁美观。 核心技术采用Spring、MyBatis、Shiro没有任何其它重度依赖。直接运行即可用 【免费下载链接】RuoYi 项目地址: https://gitcode.com/gh_mirrors/ruoyi/RuoYi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值