Websocket异常 WebSocketSession not yet initialized

问题遇到的现象和发生背景

在创建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
};

就可以正常访问