一.代码部分
相应模块里面添加3个类
WebSocketConfig,MyWebSocketHandler,WebSocketInterceptor
1.WebSocketConfig
package com.ruoyi.config;
import com.ruoyi.handler.MyWebSocketHandler;
import com.ruoyi.interceptor.WebSocketInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* 首先注入一个ServerEndpointExporterBean,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private static final Logger log = LoggerFactory.getLogger(WebSocketConfig.class);
@Autowired
private MyWebSocketHandler myWebSocketHandler;
@Autowired
private WebSocketInterceptor webSocketInterceptor;
@Value("#{'${websocket.wsHandlers}'.split(',')}")
private String[] paths;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myWebSocketHandler, paths)
.setAllowedOrigins("*")
.addInterceptors(webSocketInterceptor);
}
}
2.?MyWebSocketHandler
package com.ruoyi.handler;
import com.ruoyi.common.WebSocketCommon;
import com.ruoyi.common.core.constant.WebSocketConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import java.io.IOException;
@Component
public class MyWebSocketHandler implements WebSocketHandler {
private static final Logger log = LoggerFactory.getLogger(MyWebSocketHandler.class);
@Override
public void afterConnectionEstablished(WebSocketSession session) {
log.info("connect websocket successful!");
Object userId = session.getAttributes().get(WebSocketConstants.TOKEN);
if(userId==null){
return;
}
WebSocketSession oldSession = WebSocketCommon.CLIENT_SESSION.get(WebSocketCommon.getMapKey(session));
if (oldSession != null) {
log.info("close original session-start");
try {
oldSession.close();
} catch (IOException e) {
log.info("close original session failed");
}
}
//新的会话放入
WebSocketCommon.CLIENT_SESSION.put(WebSocketCommon.getMapKey(session),session);
}
/**
* 接收客户端发送的消息-用作客户端心跳
* @param session
* @param message
*/
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message){
log.info("handle message start");
try {
//String mess = (String) message.getPayload(); //获取客户端发送的消息
//这边可能需要处理更新map里session机制,防止map里面保存的失效,待定,等后面实际运行观察
if(session.isOpen()){
//心跳响应包
session.sendMessage(new PongMessage());
}
} catch (Exception e) {
log.error("e", e);
}
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
log.info("handle message start");
if (session.isOpen()) {
session.close();
}
log.error("connect error", exception);
Object userid = session.getAttributes().get(WebSocketConstants.TOKEN).toString();
if(userid==null){
return;
}
WebSocketCommon.CLIENT_SESSION.remove(WebSocketCommon.getMapKey(session));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus){
log.error("在线人数: {}" + WebSocketCommon.CLIENT_SESSION.size());
log.error("connection closed: " + closeStatus);
WebSocketCommon.CLIENT_SESSION.remove(WebSocketCommon.getMapKey(session));
log.error("在线人数: {}" + WebSocketCommon.CLIENT_SESSION.size());
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}
3.WebSocketInterceptor
package com.ruoyi.interceptor;
import com.ruoyi.common.core.constant.WebSocketConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
@Component
public class WebSocketInterceptor implements HandshakeInterceptor {
private static final Logger log = LoggerFactory.getLogger(WebSocketInterceptor.class);
//在握手之前执行该方法, 继续握手返回true, 中断握手返回false. 通过attributes参数设置WebSocketSession的属性
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes){
if (request instanceof ServletServerHttpRequest) {
String uri = request.getURI().getPath();
String token = uri.substring(uri.lastIndexOf("/")+1);
attributes.put(WebSocketConstants.TOKEN,token);
log.info("current token is:"+token);
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
log.info("coming webSocketInterceptor afterHandshake method...");
}
}
二.nacos中关键配置
在ruoyi-gateway-dev.yml 配置文件中添加如下,我是在ruoyi-collection这个模块中使用websocket的,所以配置路由到这个服务
# websocket模块
- id: ruoyi-websocket
uri: lb:ws://ruoyi-collection
predicates:
- Path=/ws/**
- id: ruoyi-websocket
uri: lb:ws://ruoyi-collection
predicates:
- Path=/ws1/**
这边是加了2个ws和ws1路径的配置,这样访问ws://ip+port/ws/xxx 和ws://ip+port/ws1/xxx都能连接上,这边注意
三.其它说明
1.在ruoyi-gateway-dev.yml中上面的配置中加了下面
filters:
- StripPrefix=1
这个配置,会导致连不上,要注意。
2.如果需要授权才能连接,我是这么实现的,我连接地址后面拼了一个token,例如ws://ip+port/ws/xxx?token=xxxxxx,这样在网关那边鉴权的时候,获取连接后面的token,拿到后正常往后面走,贴出关键代码如下
在gateway模块中
com.ruoyi.gateway.filter.AuthFilter#getToken
private String getToken(ServerHttpRequest request)
{
String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
if(StringUtils.isEmpty(token)){
//尝试从拼接参数中获取token,这步是为了websocket鉴权
Object authorization = request.getQueryParams().get(TokenConstants.AUTHENTICATION);
if(authorization!=null){
token = ((List<?>) authorization).get(0).toString();
}
}
// 如果前端设置了令牌前缀,则裁剪掉前缀
if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX))
{
token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
}
return token;
}
如果不需要授权的话,则在ruoyi-gateway-dev.yml中配置下白名单即可
# 不校验白名单
ignore:
whites:
- /auth/logout
- /auth/login
- /auth/register
- /*/v2/api-docs
- /csrf
- /collection/sendBoxStatus
- /ws/**
- /ws1/**
3.这边补上上面代码使用到的新加的类,供参考
WebSocketConstants
package com.ruoyi.common.core.constant;
public class WebSocketConstants {
//用户标识
public static String CLIENT_FLAG = "clientId";
//用户标识key
public static String TOKEN = "token";
//每个连接key前缀标识
public static String PREFIX = "prefix";
}
WebSocketCommon
package com.ruoyi.common;
import com.ruoyi.common.core.constant.WebSocketConstants;
import org.springframework.web.socket.WebSocketSession;
import java.util.concurrent.ConcurrentHashMap;
public class WebSocketCommon {
/**
* 保存已登录的会话session
*/
public static ConcurrentHashMap<String, WebSocketSession> CLIENT_SESSION = new ConcurrentHashMap();
public static String getMapKey(WebSocketSession session){
Object userId = session.getAttributes().get(WebSocketConstants.TOKEN);
if(userId==null){
return null;
}
String useridStr = userId.toString();
return useridStr;
}
}
最后非常感谢这篇文章的作者,参照这篇文章实现了功能,另外有什么问题欢迎留言,一起探讨。
SpringCloud Gateway 集成 WebSocket服务_哦哈哟小朋友的博客-CSDN博客_gateway整合websocket
|