java后端 微信小程序 websocket 获取不到 session,比如:三次握手拦截器获取不到session中的参数?

前言

一般我们web网站都会有cookie来保存session ID,将用户和服务器保持在一次会话中,但是很遗憾,微信小程序不支持cookie,他的每一次请求就是一次会话,这样就会产生一个问题,每次请求都需要确定当前的用户是谁,但是我们又不能在每次请求的数据中携带用户的信息,这样是不安全的。
如果要让小程序保持session一直,所以每次请求的时候wx.request   header 加上cookie,
保持session 一致是否非常关键的,对取session 中的值非常重要,不然每次取到的都是空值
我是在app.js 中的onLaunch 中向服务器请求获取sessionId.

服务端不单单可以给小程序用,网页端(网页端也有很多坑,比如跨域的时候ajax不传一个重要参数 叫啥凭证值一定要为true,那么是获取不到握手前的session的,同理也是为了保持session一致,如果你遇到了问题,请联系我)也可以,我还有websocket 智能心跳,保证连接一直存活,有需要联系我,下方有邮件地址。

好吧,来介绍一下我的websocket 的实现思路:
1.要使用websocket 首先使用需要请求登录 下面是我的登录方法,每次请求把能能够标识这个小程序用户的唯一标识传过来,这个对之后单独给这个用户发送消息很重要。然后放入session中。

@RequestMapping("login")
    public ReturnValue login(HttpServletRequest request, HttpSession session) throws Exception {
    	logger.info("登录websokcet.....sessionId:"+session.getId());
        String userId = request.getParameter("userId");
        String clientType=request.getParameter("clientType");
        if(Strings.isBlank(userId)||Strings.isBlank(clientType)) {
        	return new ReturnValue(false,"参数异常");
        }
        if(null!=session) {
        	logger.info("登陆WebSocket成功:"+("1".equals(clientType)?"P"+userId:"U"+userId));
            session.setAttribute("WEBSOCKET_SESSSION_ID", "1".equals(clientType)?"P"+userId:"U"+userId); //一般直接保存user实体
        }else {
        	logger.info("登陆WebSocket失败,session==null:"+("1".equals(clientType)?"P"+userId:"U"+userId));
        	logger.info("登录websocket+login时,获取session失败");
        }
        return new ReturnValue(true);
    }

2.websocket 是基于tcp 3次握手的,所以我使用的是springboot 一个握手前的拦截器,来吧1中放入session中的唯一标识,放到websocket 会话中。就是下面的session获取不到,得不到其中的参数,为什么呢?握手前获取到session,如果小程序不传sessionId,也会获取不到session的,所以在小程序连接websocket时,也要传sessionId.

app.globalData.webSocket=wx.connectSocket({
      url: "" + socketUrl + "/ptt/websocket/socketServer",
      header: {
        'Cookie': 'JSESSIONID=' + wx.getStorageSync("SessionId")
      },
      success() {
        console.log('连接成功');
        that.init();
      }
    });
package com.zzd.ptt.comm.websocket;

import java.util.Map;

import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
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.support.HttpSessionHandshakeInterceptor;

public class SpringWebSocketHandlerInterceptor extends HttpSessionHandshakeInterceptor {
    private static Logger logger = Logger.getLogger(SpringWebSocketHandler.class);

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Map attributes) throws Exception {
    	logger.info("Before Handshake");
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpSession session = servletRequest.getServletRequest().getSession(false);
            HttpSession session2 = servletRequest.getServletRequest().getSession();
            logger.info("握手时,获取到的session:"+(session==null?session:session.getAttributeNames()));
            logger.info("握手时,获取到的session2:"+(session2==null?session2:session2.getId()));
            if (session != null) {
                //使用拼接ID区分WebSocketHandler,以便定向发送消息
                String sessionId = (String) session.getAttribute("WEBSOCKET_SESSSION_ID");  //一般直接保存user实体
                if (sessionId!=null) {
                    attributes.put("WEBSOCKET_SESSSION_ID",sessionId);
                }

                //使用拼接ID区分WebSocketHandler,以便定向发送消息  出来处理工单也,在主页面的socket
                String sessionNo_workOrderId = (String) session.getAttribute("WEBSOCKET_NO_WORKORDER_WAIT_MESSAGE_SESSSION_ID");  //一般直接保存user实体
                if (sessionNo_workOrderId!=null) {
                    attributes.put("WEBSOCKET_NO_WORKORDER_WAIT_MESSAGE_SESSSION_ID",sessionNo_workOrderId);
                }
            }
        }
        return super.beforeHandshake(request, response, wsHandler, attributes);

    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                               Exception ex) {
        super.afterHandshake(request, response, wsHandler, ex);
    }
    
}
//

3.编写websocket 处理类了 这个我是继承了 spring的websocket 的工具类

package com.zzd.ptt.comm.websocket;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import org.apache.log4j.Logger;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;


@Component
public class SpringWebSocketHandler extends TextWebSocketHandler {
    
    private static Logger logger = Logger.getLogger(SpringWebSocketHandler.class);

    private static final Map users;  //Map来存储WebSocketSession,key用USER_ID 即在线用户列表
 
    //用户标识
    private static final String USER_ID = "WEBSOCKET_SESSSION_ID";   //对应监听器从的key
 
    //没有工单的用户标识 等待消息时
    private static final String No_WORKORDER_USERID="WEBSOCKET_NO_WORKORDER_WAIT_MESSAGE_SESSSION_ID";
    static {
        users =  new ConcurrentHashMap();
    }
 
    public SpringWebSocketHandler() {}
 
    /**
     * 连接成功时候,会触发页面上onopen方法
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
 
    	logger.info("成功建立websocket连接!");
        String userId = (String) session.getAttributes().get(USER_ID);
        String no_workOrderId_userId = (String) session.getAttributes().get(No_WORKORDER_USERID);
        logger.info("成功建立websocket连接 userId=!"+userId);
        logger.info("成功建立websocket连接no_workOrderId_userId=!"+no_workOrderId_userId);
        if(Strings.isNotBlank(userId)) {
//        	if(users.containsKey(userId)) {//如果已包含将关闭之前的session,发送消息到新的session
//        		WebSocketSession wss=users.get(userId);
//        		wss.close();
//        	}
            users.put(userId,session);
        }
        if(Strings.isNotBlank(no_workOrderId_userId)) {
//        	if(users.containsKey(no_workOrderId_userId)) {//如果已包含将关闭之前的session,发送消息到新的session
//        		WebSocketSession wss=users.get(no_workOrderId_userId);
//        		wss.close();
//        	}
            users.put(no_workOrderId_userId,session);
        }
        logger.info("当前线上用户数量:"+users.size());
        logger.info(users.entrySet().stream().collect(Collectors.toList()));
    	logger.info(".......以上为用户session数量");
        //这块会实现自己业务,比如,当用户登录后,会把离线消息推送给用户
        //TextMessage returnMessage = new TextMessage("成功建立socket连接,你将收到的离线");
        //session.sendMessage(returnMessage);
    }
 
    /**
     * 关闭连接时触发
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        String userId= (String) session.getAttributes().get(USER_ID);
        logger.info("用户"+userId+"已退出!");
        if(Strings.isNotBlank(userId)) {
           users.remove(userId);
        }
        logger.info("剩余在线用户"+users.size());
    }
 
    /**
     * js调用websocket.send时候,会调用该方法
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
 
        super.handleTextMessage(session, message);
        logger.info("websocket session:"+session.getAttributes().entrySet().stream().collect(Collectors.toList()));
        sendMessageToUser(session.getId(),message);
//        session.getAttributes()
        /**
         * 收到消息,自定义处理机制,实现业务
         */
        logger.info("服务器收到消息:"+message);
 
        if(message.getPayload().startsWith("#anyone#")){ //单发某人
 
             sendMessageToUser((String)session.getAttributes().get(USER_ID), new TextMessage("服务器单发:" +message.getPayload())) ;
 
        }else if(message.getPayload().startsWith("#everyone#")){
 
             sendMessageToUsers(new TextMessage("服务器群发:" +message.getPayload()));
 
        }else{
 
        }
 
    }
 
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        if(session.isOpen()){
            session.close();
        }
        logger.info("传输出现异常,关闭websocket连接... ");
        String userId= (String) session.getAttributes().get(USER_ID);
        users.remove(userId);
    }
 
    public boolean supportsPartialMessages() {
 
        return false;
    }
 
 
    /**
     * 给某个用户发送消息
     *
     * @param userId
     * @param message
     */
    public void sendMessageToUser(String userId, TextMessage message) {
    	logger.info("开始发送消息!给->"+userId +"   信息"+message);
        for (String id : users.keySet()) {
        	logger.info("目前有:键值"+id+"  会话"+users.get(id));
            if (id.equals(userId)) {
                try {
                    if (users.get(id).isOpen()) {
                        users.get(id).sendMessage(message);
                        logger.info("成功发送消息给:"+userId);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }
 
    /**
     * 给所有在线用户发送消息
     *
     * @param message
     */
    public void sendMessageToUsers(TextMessage message) {
        for (String userId : users.keySet()) {
            try {
                if (users.get(userId).isOpen()) {
                    users.get(userId).sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //判断用户是否在线
    public boolean checkUserInline(String uorpId) {
    	if(users.containsKey(uorpId)) {
    		WebSocketSession wss=users.get(uorpId);
    		return wss!=null&&wss.isOpen();
    	}
    	return false;
    }
}
//

4.编写发送消息的方法 ,为什么要单独写一个发送消息的方法呢?因为在处理类中使用autowired 不能够实例化bean,这个没有去仔细看为什么不能?下面是我写的handler发送方法。上面可以放很多bean

 @Bean//这个注解会从Spring容器拿出Bean
    public SpringWebSocketHandler infoHandler() {
 
        return new SpringWebSocketHandler();
    }
 @RequestMapping("send")
    public ReturnValue send(HttpServletRequest request,HttpSession session) {
    	String userId = request.getParameter("userId");
        String proxyId=request.getParameter("proxyId");
        String workOrderId=request.getParameter("workOrderId");
        String clientType=request.getParameter("clientType");
        String message=request.getParameter("message");
        WorkOrderLeavMsg workOrderLeaeMsg=new WorkOrderLeavMsg();
        workOrderLeaeMsg.setContent(message);
        workOrderLeaeMsg.setWorkOrderId(Integer.parseInt(workOrderId));
        if("1".equals(clientType)) {
        	workOrderLeaeMsg.setProxyId(Integer.parseInt(proxyId));
        }else if("0".equals(clientType)) {
        	workOrderLeaeMsg.setUserId(Integer.parseInt(userId));
        }
        String lastId="1".equals(clientType)?"U"+userId:"P"+proxyId;
        SpringWebSocketHandler wshandler=infoHandler();
        EnumCode enumCode=new EnumCode();
        enumCode.setCode("0");//未读
        workOrderLeaeMsg.setLeaveMsgType(enumCode);
        //入库
        if(wos.addWorkOrderLeavMsg(workOrderLeaeMsg)) {
          //给还在处理页面的用户发送消息
        	wshandler.sendMessageToUser(lastId,new TextMessage("A+"+"**"+workOrderId+"**"+message));
        }
        //修改工单状态为待反馈
        WorkOrder wo=new WorkOrder();
        wo.setId(Integer.parseInt(workOrderId));
        Product p=new Product();
        p.setId("1".equals(clientType)?Long.parseLong(proxyId):Long.parseLong(userId));
        wo.setProduct(p);
        wo.setState("2");
        wos.updateWorkOrder(wo);
        return new ReturnValue(true);
    }

按照这几部绝对可以行的通,我走了太多的弯路,所以才写下这篇,希望能帮到其他人,如果按照我的步骤还不行,请联系我 给我发邮件有空一定回[email protected],码字不易给个好评咯。

你可能感兴趣的:(java,websocket,session)