SpringBoot使用WebSocket收发实时离线消息

引入maven依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

WebScoket配置处理器

import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.servlet.ServletContext;


/**
 * WebScoket配置处理器
 */
@Configuration
public class WebSocketConfig implements ServletContextInitializer {
	 /**
     * ServerEndpointExporter 作用
     *
     * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
     *
     * @return
     */
	@Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    //设置websocket发送内容长度
    @Override
    public void onStartup(ServletContext servletContext)  {
        servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize","22428800");
    }
}

webScoket消息对象

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.Date;

/**
* @author: ws
* @date: 20223/10/26 15:59
* @Description: WebSocketMessage
*/
@Data
public class WebSocketMessage {

/**
* 用户ID
*/
private String fromId;

/**
* 对方ID
*/
private String toOtherId;
//消息内容
private String message;

//发送时间
@JSONField(format="yyyy-MM-dd HH:mm:ss")
public Date date;

}

WebSocket操作类

import cn.hutool.core.collection.ListUtil;
import com.alibaba.fastjson.JSON;
import com.ws.wxyinghang.entity.WebSocketMessage;
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.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @author: ws
 * @date: 20223/10/26 15:59
 * @Description: WebSocket操作类
 */
@ServerEndpoint("/websocket/{userId}")
@Component
@Slf4j
public class WebSocketSever {

    // 与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    private String userId;


    // session集合,存放对应的session
    private static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();

    // concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。
    private static CopyOnWriteArraySet<WebSocketSever> webSocketSet = new CopyOnWriteArraySet<>();
    // 用于存放离线消息
    private static ConcurrentHashMap<String, List<WebSocketMessage>> offlineMessageMap = new ConcurrentHashMap();

    /**
     * 建立WebSocket连接
     *
     * @param session
     * @param userId  用户ID
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId") String userId) {
        log.info("WebSocket建立连接中,连接用户ID:{}", userId);
        try {
            Session historySession = sessionPool.get(userId);
            // historySession不为空,说明已经有人登陆账号,应该删除登陆的WebSocket对象
            if (historySession != null) {
                webSocketSet.remove(historySession);
                historySession.close();
            }
        } catch (IOException e) {
            log.error("重复登录异常,错误信息:" + e.getMessage(), e);
        }
        // 建立连接
        this.session = session;
        this.userId = userId;
        webSocketSet.add(this);
        sessionPool.put(userId, session);
        //从离线消息队列里面获取消息
        if (offlineMessageMap.containsKey(userId)) {
            List<WebSocketMessage> list = offlineMessageMap.get(userId);
            Iterator it = list.iterator();
            while (it.hasNext()) {
                Object x = it.next();
                //离线消息接收成功后删除消息
                Boolean bb = sendOfflineMessageByUser(JSON.toJSONString(x));
                if (bb) {
                    System.out.println("从队列中删除离线消息" + x);
                    it.remove();
                }
            }
            offlineMessageMap.remove(userId);
        }
        log.info("建立连接完成,当前在线人数为:{}", webSocketSet.size());
    }

    /**
     * 发生错误
     *
     * @param throwable e
     */
    @OnError
    public void onError(Throwable throwable) {
        throwable.printStackTrace();
    }

    /**
     * 连接关闭
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        sessionPool.remove(this.userId);
        log.info("连接断开,当前在线人数为:{}", webSocketSet.size());
    }

    /**
     * 接收客户端消息
     *
     * @param message 接收的消息
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("收到客户端发来的消息:{}", message);
        sendMessageByUser(message);
    }

    /**
     * 推送消息到指定用户
     *
     * @param message 发送的消息
     */
    public static Boolean sendMessageByUser(String message) {
        WebSocketMessage msg = JSON.parseObject(message, WebSocketMessage.class);
        log.info("用户ID:" + msg.getToOtherId() + ",推送内容:" + message);
        Session session = sessionPool.get(msg.getToOtherId());
        //判断session是否正常
        if (session == null || !session.isOpen()) {
            log.info("用户ID:" + msg.getToOtherId() + ",离线,放入离线消息队列中");
            if (offlineMessageMap.containsKey(msg.getToOtherId())) {
                List<WebSocketMessage> list = offlineMessageMap.get(msg.getToOtherId());
                list.add(msg);
                offlineMessageMap.put(msg.getToOtherId(), list);
            } else {
                offlineMessageMap.put(msg.getToOtherId(), ListUtil.toList(msg));
            }
        }//发送消息
        else {
            try {
                session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                log.error("推送消息到指定用户发生错误:" + e.getMessage(), e);
                return false;
            }
        }
        return true;
    }

    //发送离线消息
    public static Boolean sendOfflineMessageByUser(String message) {
        WebSocketMessage msg = JSON.parseObject(message, WebSocketMessage.class);
        log.info("用户ID:" + msg.getToOtherId() + ",推送内容:" + message);
        Session session = sessionPool.get(msg.getToOtherId());
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error("推送消息到指定用户发生错误:" + e.getMessage(), e);
            return false;
        }
        return true;
    }

    /**
     * 群发消息
     *
     * @param message 发送的消息
     */
    public static void sendAllMessage(String message) {
        log.info("发送消息:{}", message);
        for (WebSocketSever webSocket : webSocketSet) {
            try {
                webSocket.session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                log.error("群发消息发生错误:" + e.getMessage(), e);
            }
        }
    }

}

启动项目,使用apiFox测试,新建webScoket接口

新建websocket1,连接后发送消息

新建webScoket2 ,可以看到连接后接收到了消息

如果webScoket2断开连接后, webScoket1继续发送消息,等webScoket2连接后就会收到离线的消息。

相关推荐
聪明的墨菲特i4 分钟前
Django前后端分离基本流程
后端·python·django·web3
hlsd#1 小时前
go mod 依赖管理
开发语言·后端·golang
陈大爷(有低保)1 小时前
三层架构和MVC以及它们的融合
后端·mvc
亦世凡华、1 小时前
【启程Golang之旅】从零开始构建可扩展的微服务架构
开发语言·经验分享·后端·golang
河西石头1 小时前
一步一步从asp.net core mvc中访问asp.net core WebApi
后端·asp.net·mvc·.net core访问api·httpclient的使用
2401_857439691 小时前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧6661 小时前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
阿华的代码王国1 小时前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
小码编匠2 小时前
领域驱动设计(DDD)要点及C#示例
后端·c#·领域驱动设计
德育处主任Pro2 小时前
『Django』APIView基于类的用法
后端·python·django