RuoYi-Cloud-Plus集成 WebSocket

学习地址:https://blog.youkuaiyun.com/qq_43898141/article/details/123744468

  1. 添加websocket 依赖
 <!--添加websockert依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId><exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </exclusion>
    </exclusions>
  1. 添加配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
    public class WebSocketConfig {

        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    }

  1. webSocket 代码
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.ruoyi.common.core.config.systemConfig;
import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.redis.service.RedisService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


@ServerEndpoint(value = "/websocket/{userName}")
@Component
public class WebSocketService implements Serializable {

    //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
    @JsonBackReference
    private static Map<String, WebSocketService> webSocketMap = new ConcurrentHashMap<>();

    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 接收userName
     */
    private String userName = "";

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userName") String userName) {
        this.session = session;
        this.userName = userName;
        webSocketMap.put(userName, this);
        System.err.println("----------------------------------------------------------------------------");
        System.err.println("用户连接:" + userName + ":" + userName + ",当前在线人数为:" + webSocketMap.size());
        sendMessage("来自后台的反馈:连接成功");
        webSocketMap.forEach((k, v) -> System.err.println(k));
    }


    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(@PathParam("userName") String userName) {
        if (webSocketMap.containsKey(userName)) {
            webSocketMap.remove(userName);
        }
        System.err.println("----------------------------------------------------------------------------");
        System.err.println(userName + "用户退出,当前在线人数为:" + webSocketMap.size());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.err.println("收到用户消息:" + userName + ",报文:" + message);
        //可以群发消息
        //消息保存到数据库、redis
        if (message != null) System.err.println("");
    }

    /**
     * 连接失败调用方法
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.err.println("用户错误:" + this.userName + ",原因:" + error.getMessage());
        error.printStackTrace();
    }


    /**
     * 连接服务器成功后主动推送
     */
    public void sendMessage(String message) {
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



    /**
     * 向指定客户端发送消息
     * <p>
     * // * @param userName
     * //* @param message
     */
    public static void sendMessage(String userName, String message) {
        try {
            WebSocketService webSocketService = webSocketMap.get(userName);
            if (webSocketService != null) {
                webSocketService.getSession().getBasicRemote().sendText(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }


  //下面方法根据自己情况 删 留
    public Session getSession() {
        return session;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public static Map<String, WebSocketService> getWebSocketMap() {
        return webSocketMap;
    }

    public static void setWebSocketMap(Map<String, WebSocketService> webSocketMap) {
        WebSocketService.webSocketMap = webSocketMap;
    }

    public static void put(String key, WebSocketService data) {
        webSocketMap.put(key, data);
    }

    public static WebSocketService get(String key) {
        return webSocketMap.get(key);
    }

    public static void del(String key) {
        webSocketMap.remove(key);
    }

  1. 网管配置服务转发
# websocket模块
        #服务名称
- id: ruoyi-chat-websocket
  #转发的服务
  uri: lb:ws://ruoyi-chat
  #转发设置
  predicates:
    - Path=/websocket/**
  #请求地址后一位,如:/socket/xxx/xx  去掉socket = /xxx/xx
  filters:
    - StripPrefix=1

网关配置

  1. 重要: 如果你的webSocket 没有Token 也没有任何认证的话 需要开放白名单。

配置

 # 不校验白名单
  ignore:
    whites:
      - /code
      - /auth/logout
      - /auth/login
      - /auth/smsLogin
      - /auth/xcxLogin
      - /auth/mobileLogin
      - /websocket/**
  1. **开始测试 **http://www.jsons.cn/websocket/

image.png

  1. 如果出现连接成功 立马断开的问题 排查思路如下
    1. 排查网关里面的转发url 是否使用的ws=> lb:ws://ruoyi-chat
    2. 是否开启了白名单

如果以上都可以了 还是不行,那就是因为他的子模块默认也是需要Token 权限的 我在解决了两天的情况下发现 需要加上以下配置

  1. 在 SecurityConfiguration implements WebMvcConfigurer 文件下面增加 放过
/**
* 校验是否从网关转发
*/
@Bean
    public SaServletFilter getSaServletFilter() {
    return new SaServletFilter()
        .addInclude("/**")
        .addExclude("/actuator/**")
        .addExclude("/socket/**")
        .setAuth(obj -> SaIdUtil.checkCurrentRequestToken())
        .setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
}

出现的错误提示

WebSocketClientHandshakeException Create breakpoint : Invalid handshake response getStatus: 200 OK

An exception has been observed post termination, use DEBUG level to see the full stack:io.netty . handler . codec . http . websocketx . webSocketClientHandshakeExcept ion: Connection prematurely closed BEFORE opening handshake is complete.

结论:

在子模块中也是需要鉴权的Token 的如果只在网管放开白名单 在子模块也是不行的 所以直接在这个地方加上

import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.id.SaIdUtil;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.util.SaResult;
import com.yawei.common.core.constant.HttpStatus;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 权限安全配置
 *
 * @author Suiquantong
 */
@AutoConfiguration
public class SecurityConfiguration implements WebMvcConfigurer {

    /**
     * 注册sa-token的拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注解拦截器
        registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
    }

    /**
     * 校验是否从网关转发
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
            .addInclude("/**")
            .addExclude("/actuator/**")
            .addExclude("/socket/**")
            .setAuth(obj -> SaIdUtil.checkCurrentRequestToken())
            .setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
    }

}

<think>嗯,用户想在RuoYi-Cloud项目中集成WebSocket实现实时通信。首先,我需要回忆一下RuoYi-Cloud的架构,它基于Spring Cloud,所以应该使用Spring Boot的WebSocket支持。我记得Spring提供了@ServerEndpoint注解,不过可能需要配置一个ServerEndpointExporter的Bean。不过,如果是Spring Boot,可能还需要考虑依赖管理,比如添加spring-boot-starter-websocket。 然后,用户提到RuoYi-Cloud的配置文件结构,引用[4]里提到了application.yml等文件的位置,可能需要配置Nginx作为反向代理。引用[1]提到Nginx配置WebSocket需要设置Upgrade和Connection头,还有proxy_read_timeout,这部分需要注意。另外,引用[2]提到后端需要获取Sec-WebSocket-Protocol头中的token,可能涉及安全验证,比如在握手时处理。 接下来,具体步骤应该是:添加依赖、配置WebSocket端点、实现服务层、集成到现有模块比如system模块,可能需要修改ruoyi-gateway的配置,确保WebSocket的路径被正确路由。然后前端部分,用户需要用JavaScript建立WebSocket连接,并处理消息。 还可能涉及权限验证,比如在连接建立时验证token,这可能需要在握手拦截器中处理,从请求参数或头中获取token,并验证用户身份。此外,集群环境下需要考虑会话共享问题,可能需要结合Redis或Spring Session实现。 用户可能遇到的问题包括Nginx配置不当导致连接断开,或者跨域问题,所以在配置Nginx时需要注意相关参数。另外,生产环境下的配置可能需要调整超时时间,避免TCP连接断开。 最后,生成相关问题的时候,需要覆盖配置优化、集群处理、安全验证以及异常处理等方面,帮助用户全面了解集成WebSocket的各个方面。</think>### RuoYi-Cloud集成WebSocket实现方案 #### 一、环境准备 1. 在`ruoyi-gateway`模块添加依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 修改Nginx配置支持WebSocket协议[^1]: ```nginx location /ws { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400; } ``` #### 二、WebSocket核心实现 1. 创建WebSocket配置类: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(webSocketHandler(), "/ws/message") .setAllowedOrigins("*") .addInterceptors(new HttpSessionHandshakeInterceptor()); } @Bean public WebSocketHandler webSocketHandler() { return new SystemWebSocketHandler(); } } ``` 2. 实现消息处理器(关键代码): ```java public class SystemWebSocketHandler extends TextWebSocketHandler { private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) { String token = session.getHandshakeHeaders().getFirst("Sec-WebSocket-Protocol"); if (validateToken(token)) { // 引用[2] sessions.put(getUserIdFromToken(token), session); } } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) { // 处理消息转发逻辑 } } ``` #### 三、业务集成步骤 1. 在`ruoyi-system`模块新增服务接口: ```java public interface ISysWebSocketService { void pushMessage(Long userId, String message); void broadcast(String message); } ``` 2. 在`application-dev.yml`添加配置: ```yaml spring: websocket: allowed-origins: "*" endpoint: /ws ``` #### 四、前端实现示例 ```javascript const socket = new WebSocket('ws://your-domain.com/ws/message', ['your-token']); socket.onmessage = function(event) { console.log('收到消息:', event.data); }; ``` #### 五、安全验证实现 ```java public class AuthHandshakeInterceptor extends HttpSessionHandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) { String token = request.getHeaders().getFirst("Sec-WebSocket-Protocol"); // 引用[2] return validateToken(token); } } ```
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值