一般我们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],码字不易给个好评咯。