RuoYi-Vue-fast集成WebSocket:实时通信功能实现

RuoYi-Vue-fast集成WebSocket:实时通信功能实现

【免费下载链接】RuoYi-Vue-fast :tada: (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue & Element 的前后端分离权限管理系统 【免费下载链接】RuoYi-Vue-fast 项目地址: https://gitcode.com/GitHub_Trending/ru/RuoYi-Vue-fast

你是否在开发后台管理系统时遇到过这些困扰?管理员修改配置后用户页面无法实时更新,重要通知需要用户手动刷新才能看到,在线聊天功能实现复杂?本文将详细介绍如何在RuoYi-Vue-fast框架中集成WebSocket(套接字),通过5个步骤快速实现实时通信功能,让你的系统具备即时消息推送、在线状态同步等能力。

一、WebSocket简介与环境准备

WebSocket是一种在单个TCP连接上进行全双工通信的协议,它允许服务器主动向客户端推送数据,非常适合实时通知、在线聊天、实时监控等场景。与传统的HTTP轮询相比,WebSocket具有更低的延迟和更高的效率。

在开始集成前,请确保你的开发环境满足以下要求:

  • JDK 1.8或更高版本
  • Maven 3.0+构建工具
  • RuoYi-Vue-fast框架基础环境

项目核心配置文件参考:

二、添加WebSocket依赖

首先需要在项目的pom.xml文件中添加WebSocket相关依赖。由于RuoYi-Vue-fast使用Spring Boot框架,我们可以直接使用Spring提供的WebSocket starter。

打开pom.xml文件,在<dependencies>节点下添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

添加位置建议放在Spring Boot核心依赖区域,与其他starter保持一致,例如放在spring-boot-starter-security依赖之后。

三、配置WebSocket服务器

接下来需要创建WebSocket配置类,注册WebSocket端点并配置相关参数。

src/main/java/com/ruoyi/framework/config/目录下创建WebSocketConfig.java文件,内容如下:

package com.ruoyi.framework.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import com.ruoyi.framework.interceptor.WebSocketHandshakeInterceptor;

@Configuration
public class WebSocketConfig {

    /**
     * 注册WebSocket端点处理器
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
    
    /**
     * 注册WebSocket握手拦截器
     */
    @Bean
    public HandshakeInterceptor handshakeInterceptor() {
        return new WebSocketHandshakeInterceptor();
    }
}

四、实现WebSocket服务端点

创建WebSocket服务类,处理连接建立、消息接收、连接关闭等事件。在src/main/java/com/ruoyi/project/web/controller/目录下创建WebSocketController.java文件:

package com.ruoyi.project.web.controller;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.LogUtils;
import com.ruoyi.common.utils.StringUtils;

/**
 * WebSocket服务端点
 * 注意:@ServerEndpoint注解中的value属性定义了WebSocket连接的URL路径
 */
@ServerEndpoint(value = "/ws/message")
@Component
public class WebSocketController {

    // 存储当前在线连接
    private static ConcurrentHashMap<String, Session> onlineSessions = new ConcurrentHashMap<>();
    
    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        String userId = getUserIdFromSession(session);
        if (StringUtils.isNotEmpty(userId)) {
            onlineSessions.put(userId, session);
            LogUtils.info("用户{}建立WebSocket连接,当前在线人数:{}", userId, onlineSessions.size());
        }
    }
    
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        String userId = getUserIdFromSession(session);
        if (StringUtils.isNotEmpty(userId)) {
            onlineSessions.remove(userId);
            LogUtils.info("用户{}关闭WebSocket连接,当前在线人数:{}", userId, onlineSessions.size());
        }
    }
    
    /**
     * 收到客户端消息后调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        String userId = getUserIdFromSession(session);
        LogUtils.info("收到用户{}的消息:{}", userId, message);
        // 可以在这里处理消息,如广播给其他用户或存储消息等
    }
    
    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        String userId = getUserIdFromSession(session);
        LogUtils.error("用户{}WebSocket连接发生错误:{}", userId, error.getMessage());
        error.printStackTrace();
    }
    
    /**
     * 从Session中获取用户ID
     */
    private String getUserIdFromSession(Session session) {
        // 实际项目中需要根据你的认证方式从Session中获取用户ID
        // 这里仅作示例,实际实现需结合你的登录认证机制
        return session.getId();
    }
    
    /**
     * 发送消息给指定用户
     */
    public static void sendMessageToUser(String userId, String message) throws IOException {
        Session session = onlineSessions.get(userId);
        if (session != null && session.isOpen()) {
            session.getBasicRemote().sendText(message);
        }
    }
    
    /**
     * 广播消息给所有在线用户
     */
    public static void broadcastMessage(String message) throws IOException {
        for (Session session : onlineSessions.values()) {
            if (session.isOpen()) {
                session.getBasicRemote().sendText(message);
            }
        }
    }
}

五、配置安全拦截器与握手处理

为了确保WebSocket连接的安全性,需要创建握手拦截器,验证用户身份。在src/main/java/com/ruoyi/framework/interceptor/目录下创建WebSocketHandshakeInterceptor.java文件:

package com.ruoyi.framework.interceptor;

import java.util.Map;
import javax.servlet.http.HttpSession;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.LoginUser;

public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, 
                                   WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpSession session = servletRequest.getServletRequest().getSession(false);
            
            // 获取当前登录用户
            LoginUser loginUser = SecurityUtils.getLoginUser();
            if (loginUser == null) {
                // 用户未登录,拒绝握手
                return false;
            }
            
            // 将用户信息存入attributes,供WebSocketController使用
            attributes.put("userId", loginUser.getUserId());
            attributes.put("username", loginUser.getUsername());
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, 
                              WebSocketHandler wsHandler, Exception exception) {
        // 握手完成后的处理
    }
}

同时,需要修改Spring Security配置,允许WebSocket连接的URL访问。打开安全配置类src/main/java/com/ruoyi/framework/config/SecurityConfig.java,在configure方法中添加WebSocket相关URL的放行规则:

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
        // ... 其他配置
        .authorizeRequests()
            // 放行WebSocket连接
            .antMatchers("/ws/**").permitAll()
            // ... 其他URL配置
}

六、集成Redis实现分布式WebSocket

在分布式系统中,单个WebSocket服务实例无法处理所有节点的连接,需要使用Redis的发布订阅功能实现跨节点的消息广播。

首先在Redis配置类src/main/java/com/ruoyi/framework/config/RedisConfig.java中添加Redis消息监听容器配置:

@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    // 可以添加多个消息监听器
    return container;
}

然后创建Redis消息监听器,用于接收其他节点发送的消息并广播给当前节点的WebSocket连接:

@Component
public class WebSocketRedisListener extends KeyExpirationEventMessageListener {

    public WebSocketRedisListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
        String messageBody = new String(message.getBody(), StandardCharsets.UTF_8);
        
        if ("webSocket:broadcast".equals(channel)) {
            try {
                // 将消息广播给当前节点的所有WebSocket连接
                WebSocketController.broadcastMessage(messageBody);
            } catch (IOException e) {
                LogUtils.error("广播WebSocket消息失败:{}", e.getMessage());
            }
        }
    }
}

七、前端Vue集成WebSocket

在Vue前端项目中创建WebSocket服务,实现与后端的连接和消息处理。创建src/utils/websocket.js文件:

import store from '@/store'

let websocket = null

/**
 * 初始化WebSocket连接
 */
export function initWebSocket() {
  if (typeof (WebSocket) === 'undefined') {
    console.error('您的浏览器不支持WebSocket')
    return
  }
  
  // 获取当前用户token,用于认证
  const token = store.getters.token
  if (!token) {
    console.error('用户未登录,无法建立WebSocket连接')
    return
  }
  
  // WebSocket连接地址,根据实际情况修改
  const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
  const wsPath = `${wsProtocol}//${window.location.host}/ws/message?token=${token}`
  
  // 创建WebSocket实例
  websocket = new WebSocket(wsPath)
  
  // 连接成功事件
  websocket.onopen = function() {
    console.log('WebSocket连接成功')
    // 可以在这里发送初始化消息,如用户上线通知等
  }
  
  // 收到消息事件
  websocket.onmessage = function(e) {
    console.log('收到WebSocket消息:', e.data)
    const message = JSON.parse(e.data)
    // 处理消息,如显示通知、更新数据等
    handleWebSocketMessage(message)
  }
  
  // 连接关闭事件
  websocket.onclose = function() {
    console.log('WebSocket连接关闭')
    // 连接关闭后可以尝试重连
    setTimeout(() => {
      initWebSocket()
    }, 3000)
  }
  
  // 连接错误事件
  websocket.onerror = function() {
    console.error('WebSocket连接发生错误')
  }
  
  // 监听窗口关闭事件,主动关闭WebSocket连接
  window.onbeforeunload = function() {
    closeWebSocket()
  }
}

/**
 * 关闭WebSocket连接
 */
export function closeWebSocket() {
  if (websocket) {
    websocket.close()
  }
}

/**
 * 发送WebSocket消息
 */
export function sendWebSocketMessage(message) {
  if (websocket && websocket.readyState === WebSocket.OPEN) {
    websocket.send(JSON.stringify(message))
  } else {
    console.error('WebSocket连接未建立或已关闭')
  }
}

/**
 * 处理WebSocket消息
 */
function handleWebSocketMessage(message) {
  switch (message.type) {
    case 'NOTIFICATION':
      // 处理通知消息
      store.dispatch('app/addNotification', message.data)
      break
    case 'SYSTEM_CONFIG':
      // 处理系统配置更新消息
      store.dispatch('settings/updateSystemConfig', message.data)
      break
    // 其他类型消息处理...
    default:
      console.log('未知类型的WebSocket消息:', message)
  }
}

八、测试与验证

完成上述配置后,启动应用程序,使用WebSocket测试工具(如wscat)连接到WebSocket服务端点进行测试:

# 安装wscat
npm install -g wscat

# 连接到WebSocket服务
wscat -c ws://localhost:8080/ws/message

发送消息测试:

{"type":"MESSAGE","content":"Hello, WebSocket!"}

在实际项目中,你可以结合业务需求,实现如实时通知、在线聊天、实时监控等功能。例如,在系统管理模块中添加消息推送功能,当有新的系统公告发布时,通过WebSocket实时推送给所有在线用户。

系统通知相关代码参考:

九、常见问题与解决方案

  1. 连接失败问题:检查WebSocket端点URL是否正确,防火墙是否阻止了WebSocket端口,以及用户认证是否通过。相关配置可参考src/main/java/com/ruoyi/framework/security/config/SecurityConfig.java

  2. 消息发送不出去:确认WebSocket连接状态是否为OPEN,可在发送前检查websocket.readyState属性。

  3. 分布式环境下消息不同步:确保Redis服务正常运行,检查Redis消息监听器是否正确配置。相关代码参考src/main/java/com/ruoyi/framework/config/RedisConfig.java

  4. 大量连接时性能问题:可考虑使用Nginx作为WebSocket反向代理,配置适当的连接超时时间,并对WebSocket连接进行负载均衡。

十、总结与扩展

通过本文介绍的方法,我们成功在RuoYi-Vue-fast框架中集成了WebSocket功能,实现了服务器与客户端的实时通信。你可以基于此扩展更多实用功能:

  • 在线用户列表与状态显示
  • 实时数据监控面板
  • 即时通讯系统
  • 协同编辑功能
  • 实时日志输出

项目完整文档参考:doc/若依环境使用手册.docx,更多高级功能实现可参考官方文档和社区教程。

希望本文能帮助你快速掌握WebSocket在RuoYi-Vue-fast框架中的应用,为你的项目增添实时通信能力。如有任何问题或建议,欢迎在项目社区中交流讨论。

【免费下载链接】RuoYi-Vue-fast :tada: (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue & Element 的前后端分离权限管理系统 【免费下载链接】RuoYi-Vue-fast 项目地址: https://gitcode.com/GitHub_Trending/ru/RuoYi-Vue-fast

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

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

抵扣说明:

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

余额充值