WebSocket的定义:
WebSocket 是独立的、创建在 TCP 上的协议。
Websocket 通过HTTP/1.1 协议的101状态码进行握手。
为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”
浏览器发出请求
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //浏览器随机生成的Base64 加密值
Sec-WebSocket-Protocol: chat, superchat //用户定义的字符串, 用来区分相同url下, 不同服务所需要的协议
Sec-WebSocket-Version: 13 //版本
Origin: http://example.com
服务器响应请求
HTTP/1.1 101 Switching Protocols
Upgrade: websocket //已经成功切换websocket协议
Connection: Upgrade //已经成功切换websocket协议
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
至此,HTTP已经完成它所有工作了,接下来就是完全按照Websocket协议进行了
引入依赖
org.springframework.boot
spring-boot-starter-websocket
WebSocketController
import cn.gzccc.wx.expertUser.domain.WxExpertUser;
import cn.gzccc.wx.expertUser.service.IWxExpertUserService;
import cn.gzccc.wx.utils.SecurityUtils;
import cn.gzccc.wx.utils.SpringContextUtil;
import cn.hutool.crypto.CryptoException;
import cn.hutool.json.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
/**
* 微信小程序聊天的websocket
* token --> 微信小程序用户openId Aes加密后的Hex密文
*/
@ServerEndpoint("/wx/api/websocket/{token}") //@ServerEndpoint主要是将目前的类定义成一个websocket服务器端,
//注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
public class WebSocketController {
/**
* 与某个客户端的连接对话,需要通过它来给客户端发送消息
*/
private Session session;
/**
* 标识当前连接客户端的用户标识
*/
private String token;
/**
* 用于存所有的连接服务的客户端,这个对象存储是安全的
*/
public static ConcurrentHashMap map = new ConcurrentHashMap<>();
public ConcurrentHashMap content(){
return map;
}
/**
* 连接建立成功调用的方法
*
* @param session 与某个客户端的连接会话,需要通过它来给客户端发送数据
* @param token 用户token
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "token") String token){
// 判断当前token 是否属于User中所存在的真实用户
this.session = session;
this.token = token;
if (isUser()){
map.put(this.token,this);
log.info("[WebSocket] 连接成功,当前连接人数为:={}", map.size());
appointSending(token,"连接成功");
}else{
map.put(this.token,this);
log.info("[WebSocket] 异常用户连接,token为:={}", this.token);
appointSending(token,"异常用户,已记录该行为");
map.remove(this.token);
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(){
// 设置该用户下线
String openId = SecurityUtils.decryption(token); //解密
IWxExpertUserService wxExpertUserService = SpringContextUtil.getContext().getBean(IWxExpertUserService.class);
WxExpertUser wxExpertUser = wxExpertUserService.selectWxUserByOpenId(openId);
if (wxExpertUser !=null){
wxExpertUser.setOnline(0); //设置下线
wxExpertUserService.updateWxExpertUser(wxExpertUser);
}
map.remove(this.token);
log.info("[WebSocket] 退出成功,当前连接人数为:={}", map.size());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message){
if (isUser()){
try{
ObjectMapper mapper =new ObjectMapper();
Message mess = null;
try{
mess = mapper.readValue(message, Message.class);
}catch (Exception e){
// 用户付费时间已过期
map.put(this.token,this);
appointSending(token,"消息格式错误,无法为您转发信息");
map.remove(this.token);
}
// 设置发送者
mess.setSendToken(this.token);
String toToken = mess.getToToken(); // 获取接收者Token
String messageContent = mess.getMessageContent(); // 获取消息内容
String messageType = mess.getMessageType(); // 获取消息类型
JSONObject jsonObject = new JSONObject();
jsonObject.set("sendToken",mess.getSendToken());
jsonObject.set("toToken",mess.getToToken());
jsonObject.set("messageType",mess.getMessageType());
jsonObject.set("messageContent",mess.getMessageContent());
String json = jsonObject.toString();
// websocket提供了发送信息的方法
appointSending(toToken,json);
}catch (Exception e){
e.printStackTrace();
}
}else{
map.put(this.token,this);
appointSending(token,"您的账号不存在,系统无法为您传达此信息");
map.remove(this.token);
}
log.info("[WebSocket] 收到消息:{}","发送者Token:" + this.token + "," + message);
}
/**
* 发生错误时调用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
error.printStackTrace();
}
/**
* 指定发送
*
* @param token 用户token
* @param message 要发送的信息
*/
public void appointSending(String token,String message){
// 判断token 是否在线
if (!"连接成功".equals(message)) {
WebSocketController webSocketController = map.get(token);
if (webSocketController == null){
// 不在线 公众号发送消息
log.info("指定发送 message: "+message);
//sendWxTemplate(message,token);
}
}
try {
if (map.get(token) != null) {
// websocket提供了发送信息的方法, this.session.getBasicRemote()对象的方法.
map.get(token).session.getBasicRemote().sendText(message);
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 判断当前用户是否存在
* @return
*/
public boolean isUser(){
try{
String openId = SecurityUtils.decryption(token); //解密
IWxExpertUserService wxExpertUserService = SpringContextUtil.getContext().getBean(IWxExpertUserService.class);
WxExpertUser wxExpertUser = wxExpertUserService.selectWxUserByOpenId(openId);
if (wxExpertUser == null){
return false;
}else{
// 设置该用户上线
wxExpertUser.setOnline(1);
wxExpertUserService.updateWxExpertUser(wxExpertUser);
return true;
}
}catch (CryptoException e){
return false;
}
}
}
SecurityUtils
import cn.hutool.crypto.symmetric.AES;
public class SecurityUtils {
// 加密
public static String encryption(String data){
AES aes = new AES("CBC", "PKCS7Padding",
// 密钥,不可更改
"GzcccPCall012345".getBytes(),
// iv加盐
"GzcccCallDYgjCE0".getBytes());
// 加密为16进制表示
String encryptHex = aes.encryptHex(data);
return encryptHex;
}
// 解密
public static String decryption(String data){
AES aes = new AES("CBC", "PKCS7Padding",
// 密钥,不可更改
"GzcccPCall012345".getBytes(),
// iv加盐
"GzcccCallDYgjCE0".getBytes());
// 加密为16进制表示
String decryptStr = aes.decryptStr(data);
return decryptStr;
}
}
微信线上小程序测试
打开小程序
退出小程序