最近在了解springboot整合websocket的许多文章,然后根据自己的想法及理解下搭建的了下列的框架。
本人第一次写文章 , 转载 请注明。
1.引入Pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置WebSocketAutoConfig 建立请求的通道
@Slf4j
@Configuration
@EnableWebSocket
public class WebSocketAutoConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// webSocket通道
registry.addHandler(new MessageHandler(), "/xx/xxx")
// 指定自定义拦截器
.addInterceptors(new WebSocketInterceptor())
// 允许跨域
.setAllowedOrigins("*");
// sockJs通道
registry.addHandler(new MessageHandler(), "/xxx/xxx")
.addInterceptors(new WebSocketInterceptor())
.setAllowedOrigins("*")
// 开启sockJs支持
.withSockJS();
}
}
第一种:
var webscoket = new WebScoket('ws://ip:端口/通道');
第二种:
var webscoket = new SockJs('http://ip:端口/通道');
注意:
1.以上两种不同的通道,他所请求的方式也有所不同,比较建议大家选择第二种;
2.在有些情况下则需要不同的模块推送不同的消息,且需要分开进行推送,所以可以在这里注册多个不同路径的通道;
3.若项目使用token的操作,可以把这个路径让他不走鉴权,目前还没发现websocket的鉴权的操作。
值得注意的是:若使用两个不同的通道的是,handler是需要分开的,HandshakeInterceptor 可以用同一个
3.建立处理handler
@Slf4j
public class MessageHandler implements WebSocketHandler {
/**
* 存储sessionId和webSocketSession
* 需要注意的是,webSocketSession没有提供无参构造,不能进行序列化,也就不能通过redis存储
* 在分布式系统中,要想别的办法实现webSocketSession共享
*/
private static Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();
private static Map<String, Set<String>> userMap = new ConcurrentHashMap<>();
/**
* webSocket连接创建后调用
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) {
// 获取参数
String user = String.valueOf(session.getAttributes().get("user"));
Set<String> us = (Objects.isNull(userMap.get(user))||userMap.get(user).size()==0)?new HashSet<>():userMap.get(user);
us.add(session.getId());
userMap.put(user, us);
sessionMap.put(session.getId(), session);
}
/**
* 接收到消息会调用
*/
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
String user = String.valueOf(session.getAttributes().get("user"));
if (message instanceof TextMessage) {
sendMessage(user,((TextMessage) message).getPayload());
} else if (message instanceof BinaryMessage) {
} else if (message instanceof PongMessage) {
} else {
System.out.println("Unexpected WebSocket message type: " + message);
}
}
/**
* 连接出错会调用
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
removeSessionUser(session);
}
/**
* 连接关闭会调用
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
removeSessionUser(session);
}
@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 移除用户所存的信息
* @param session
*/
public void removeSessionUser(WebSocketSession session){
String user = String.valueOf(session.getAttributes().get("user"));
//获取sessionId
Set<String> sessionIds = userMap.get(user);
if(Objects.nonNull(sessionIds)&&sessionIds.size()>0){
for(String sessionId : sessionIds){
if(Objects.equals(sessionId,session.getId())){
//移除该端的id
sessionIds.remove(sessionId);
//将sessionMap中的数据也移除
sessionMap.remove(sessionId);
}
}
}
userMap.put(user,sessionIds);
}
/**
* 后端发送消息-推送至某一个用户
* return 推送失败的用户
*/
public static boolean sendMessage(String user, Object sendMap){
log.info("进入8081....");
Set<String> sessionIds = userMap.get(user);
boolean flag = true;
if(Objects.nonNull(sessionIds)&&sessionIds.size()>0){
for(String sessionId : sessionIds){
if(Objects.nonNull(sessionId)){
WebSocketSession session = sessionMap.get(sessionId);
try {
TextMessage textMessage = new TextMessage(JSONUtil.toJsonStr(sendMap));
session.sendMessage(textMessage);
} catch (IOException e) {
e.printStackTrace();
flag = false;
}
}else{
flag = false;
}
}
}else{
log.info("不存在用户!");
flag = false;
}
return flag;
}
/**
* 后端发送消息-推送至某一组用户
* @param userIds
* @param sendMap
* @return 返回推送未成功的用户信息
*/
public static List<String> sendMessage(List<String> userIds,Object sendMap){
List<String> resultUserIds = new ArrayList<>();
if(Objects.nonNull(userIds)&&userIds.size()>0){
for(String userId : userIds){
boolean s = sendMessage(userId, sendMap);
if(!s){
resultUserIds.add(userId);
}
}
}
return resultUserIds;
}
}
4.创建HandshakeInterceptor处理的机制
public class WebSocketInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;
// 获取请求路径携带的参数
String user = serverHttpRequest.getServletRequest().getParameter("user");
attributes.put("user", user);
return true;
} else {
return false;
}
}
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, @Nullable Exception e) {
}
}
至此 webscoket推送消息的模块的算是基本完成
测试
下面为SockJs的测试用例
var sock = new SockJS('http://localhost:8081/xxx/xxx?user=jack');
sock.onopen = function(){
console.log('open');
showText('open');
};
sock.onmessage = function(e){
console.log(e);
console.log('message',e.data);
showText(e.data);
}
sock.close = function(data){
console.log("colse");
showText(data.data);
}
//监听窗口关闭事件,当窗口关闭,主动关闭连接
window.onbeforeunload = function(data){
showText(data.data);
sock.close();
}
function showText(data){
document.getElementById("message").innerHTML += data+"<br/>";
}
function send(){
var text = document.getElementById("content").value;
console.log(text);
sock.send(text);
}
若有两个不同的处理handler,可以采用下列方式
1.创建一个枚举
public enum RedisTypes {
/**
* 保存websocket数据
*/
MESSAGE_HANDLER(1,"消息处理",处理类1.class,"sendMessage"),
MESSAGE_NOTICE(2,"工作流消息处理", 处理类2.class,"sendMessage");
private Integer val;
private String message;
private Class handlerClass;
private String method;
public Class getHandlerClass() {
return handlerClass;
}
public Integer getVal() {
return val;
}
public String getMessage() {
return message;
}
public String getMethod(){
return method;
}
RedisTypes(Integer val, String message,Class handlerClass,String method){
this.val = val;
this.message = message;
this.handlerClass = handlerClass;
this.method = method;
}
public static Class getSendClass(Integer val){
for(RedisTypes types : values()){
if(Objects.equals(types.getVal(),val)){
return types.getHandlerClass();
}
}
return null;
}
public static String getSendMethod(Integer val){
for(RedisTypes types : values()){
if(Objects.equals(types.getVal(),val)){
return types.getMethod();
}
}
return null;
}
}
使用java的反射机制处理
/**
* 发送指定的用户
* @param val
* @param userIds
* @param sendMap
*/
public Object sendMessage(Integer val, String userIds, Object sendMap) {
boolean flag = true;
try {
Class sendClass = RedisTypes.getSendClass(val);
Method sendMessage = sendClass.getMethod(RedisTypes.getSendMethod(val),String.class,Object.class);
Object invoke = sendMessage.invoke(sendClass, userIds, sendMap);
return invoke;
} catch (NoSuchMethodException e) {
e.printStackTrace();
flag = false;
} catch (IllegalAccessException e) {
e.printStackTrace();
flag = false;
} catch (InvocationTargetException e) {
e.printStackTrace();
flag = false;
}
return flag;
}