在创建Vue+Springboot前后端分离项目时,需要使用Websocket进行通讯,但是后端报了如下错误,不是每次但是经常
java.lang.IllegalStateException: WebSocketSession not yet initialized
at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.19.jar:5.3.19]
at org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession.getPrincipal(WebSocketServerSockJsSession.java:87) ~[spring-websocket-5.3.19.jar:5.3.19]
前端Websocket设置
vue.config.js
//配置websocket代理
proxyObj["/elitehrserver/ws"]={
/*
目前比较来看,无论是http还是ws都会后端都会偶尔出现WebSocketSession not yet initialized错误
*/
target: "http://localhost:8081",
changeOrigin: true
};
websocket的相关使用
//websocket连接到后端服务器,并订阅消息
connect(context){
//创建连接后台服务器并返回实例
context.state.stomp=Stomp.over(new SockJS(baseUrl+"/ws/ep"));
let token=window.sessionStorage.getItem("token");
//连接
context.state.stomp.connect({"Auth-Token":token},success=>{
//订阅消息(接口)并打印消息 /queue/chat 前面必须加 /user 这是规定
context.state.stomp.subscribe("/user/queue/chat",msg=>{
let recieveMsg=JSON.parse(msg.body);
//如果不是当前对话用户或当前用户没和谁对话则展示消息通知
if(!context.state.currentSession||recieveMsg.from!==context.state.currentSession.username){
Notification({
title: "【"+recieveMsg.fromNickName+"】",
message: recieveMsg.content.length>10?recieveMsg.content.substr(0,10):recieveMsg.content,
position: 'bottom-right',
iconClass:"fa fa-comments"
});
//添加消息红点
Vue.set(context.state.idDot,context.state.currentAdmin.username+"#"+recieveMsg.from,true);
}
recieveMsg.notSelf=true;
recieveMsg.to=recieveMsg.from;
//添加同步消息
context.commit("addMessage",recieveMsg);
})
},error=>{
})
}
后端配置
/**
* websocket配置类
* @author 刘昌兴
*
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Value("${jwt.tokenHead}")
private String tokenHead;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
/**
* 添加endpoint,这样网页可以通过websocket连接上服务器
* 也就是我们配置websocket的服务地址,并可以指定是否可以使用socketJS
* @author 刘昌兴
*
* @param registry
* @return
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
/*
* 1.将/ws/ep作为stomp的端点,用户连接了这个端点就可以进行websocket通讯,支持socketJS
* 2.配置允许跨域
* 3.支持socketJS访问
*/
registry.addEndpoint("/ws/ep").setAllowedOriginPatterns("*").withSockJS();
}
/**
* 输入通道参数配置
* @author 刘昌兴
*
* @param registration
* @return
*/
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor= MessageHeaderAccessor.getAccessor(message,StompHeaderAccessor.class);
//判断是否为连接,如果是则需要获取token,并设置用户对象,用于后续发送消息是获得发送来源等,而不是进行token认证,因为已经放行了/ws/**
if(StompCommand.CONNECT.equals(accessor.getCommand())){
String token=accessor.getFirstNativeHeader("Auth-Token");
if(StringUtils.hasLength(token)){
String authToken=token.substring(tokenHead.length());
String username=jwtTokenUtil.getUserNameFromToken(authToken);
//如果token中存在用户名
if(StringUtils.hasLength(username)){
UserDetails userDetails=userDetailsService.loadUserByUsername(username);
//验证token是否有效,重新设置用户对象
if(jwtTokenUtil.validateToken(authToken,userDetails)){
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
accessor.setUser(authenticationToken);
}
}
}
}
return message;
}
});
}
/**
* 配置消息代理
* @author 刘昌兴
* @param registry
* @return
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//配置代理域,可以配置多个,配置代理目的地前缀为/queue,可以在配置域上向客户端推送消息
registry.enableSimpleBroker("/queue");
}
}
WebSocketSession尚未初始化
换一种思路换一种写法,代码如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* 注入ServerEndpointExporter,
* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.annotation.Resource;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.jeecg.common.base.BaseMap;
import org.jeecg.common.constant.WebsocketConst;
import org.jeecg.common.modules.redis.client.JeecgRedisClient;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
/**
* @Author scott
* @Date 2019/11/29 9:41
* @Description: 此注解相当于设置访问URL
*/
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}") //此注解相当于设置访问URL
public class WebSocket {
private Session session;
private String userId;
private static final String REDIS_TOPIC_NAME = "socketHandler";
@Resource
private JeecgRedisClient jeecgRedisClient;
/**
* 缓存 webSocket连接到单机服务class中(整体方案支持集群)
*/
private static CopyOnWriteArraySet<WebSocket> webSockets = new CopyOnWriteArraySet<>();
private static Map<String, Session> sessionPool = new HashMap<String, Session>();
@OnOpen
public void onOpen(Session session, @PathParam(value = "userId") String userId) {
try {
this.session = session;
this.userId = userId;
webSockets.add(this);
sessionPool.put(userId, session);
log.info("【websocket消息】有新的连接,总数为:" + webSockets.size());
} catch (Exception e) {
}
}
@OnClose
public void onClose() {
try {
webSockets.remove(this);
sessionPool.remove(this.userId);
log.info("【websocket消息】连接断开,总数为:" + webSockets.size());
} catch (Exception e) {
}
}
/**
* 服务端推送消息
*
* @param userId
* @param message
*/
public void pushMessage(String userId, String message) {
Session session = sessionPool.get(userId);
if (session != null && session.isOpen()) {
try {
log.info("【websocket消息】 单点消息:" + message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 服务器端推送消息
*/
public void pushMessage(String message) {
try {
webSockets.forEach(ws -> ws.session.getAsyncRemote().sendText(message));
} catch (Exception e) {
e.printStackTrace();
}
}
@OnMessage
public void onMessage(String message) {
//todo 现在有个定时任务刷,应该去掉
log.debug("【websocket消息】收到客户端消息:" + message);
JSONObject obj = new JSONObject();
//业务类型
obj.put(WebsocketConst.MSG_CMD, WebsocketConst.CMD_CHECK);
//消息内容
obj.put(WebsocketConst.MSG_TXT, "心跳响应");
for (WebSocket webSocket : webSockets) {
webSocket.pushMessage(message);
}
}
/**
* 后台发送消息到redis
*
* @param message
*/
public void sendMessage(String message) {
log.info("【websocket消息】广播消息:" + message);
BaseMap baseMap = new BaseMap();
baseMap.put("userId", "");
baseMap.put("message", message);
jeecgRedisClient.sendMessage(REDIS_TOPIC_NAME, baseMap);
}
/**
* 此为单点消息
*
* @param userId
* @param message
*/
public void sendMessage(String userId, String message) {
BaseMap baseMap = new BaseMap();
baseMap.put("userId", userId);
baseMap.put("message", message);
jeecgRedisClient.sendMessage(REDIS_TOPIC_NAME, baseMap);
}
/**
* 此为单点消息(多人)
*
* @param userIds
* @param message
*/
public void sendMessage(String[] userIds, String message) {
for (String userId : userIds) {
sendMessage(userId, message);
}
}
}
/**
* 监听消息
*/
@Slf4j
@Component
public class SocketHandler
{
@Autowired
private WebSocket webSocket;
public void onMessage(BaseMap map) {
log.info("【SocketHandler消息】Redis Listerer:" + map.toString());
String userId = map.get("userId");
String message = map.get("message");
if (ObjectUtil.isNotEmpty(userId)) {
webSocket.pushMessage(userId, message);
//app端消息推送
webSocket.pushMessage(userId+CommonSendStatus.APP_SESSION_SUFFIX, message);
} else {
webSocket.pushMessage(message);
}
}
}
若有帮助,谢谢采纳~
在开发环境中将
//配置websocket代理
proxyObj["/elitehrserver/ws"]={
/*
目前比较来看,无论是http还是ws都会后端都会偶尔出现WebSocketSession not yet initialized错误
*/
target: "http://localhost:8081",
changeOrigin: true
};
替换成
//配置websocket代理
proxyObj["/elitehrserver/ws"]={
/*
ws:false, //如果使用ws://则需要将ws改为false,否则会抛出Invalid frame header异常,影响通讯
目前比较来看,只有一下配置行得通
*/
ws:false,
target: "ws://localhost:8081",
changeOrigin: true
};
就可以正常访问