springboot集成websocket实战:站内消息实时推送

背景

    现有一个类似boss直聘的招聘小程序,求职端和招聘端可以根据身份进行切换.要求实现两个问题:
    1.求职端或是招聘端上线时,如果有未读消息需要显示未读消息数;
    2.求职端和招聘端同时在线时,求职端投递简历之后,要求招聘端能够实时显示有新投递简历的消息信息;招聘端发送面试邀请时,求职端消息列表中实时显示出面试要求的消息信息.

处理方案梳理

    对于第一个问题,可以在进入到小程序页面之后,服务端提供一个获取用户未读消息数据查询的接口.
    对于第二个问题想到的处理方案是可以在小程序里面做一个轮询处理,间隔一定的时间去不断去调用获取用户消息列表接口.存在的问题就是请求中有大半是无用,浪费带宽和服务器资源。所以考虑的处理方案是使用websocket处理,websocket的优点是服务端可以主动向客户端发送消息.能处理客户端轮询查询带来的问题.
    自己实现了springboot+websocket进行消息推送的简单demo,使用在线的websocket地址用作客户端进行测试.有同样需求的同学可以参考并加入到自己的业务逻辑中.下面说下实现过程;

实现过程

    主要展示服务端实现方案,前端只需要创建websocket连接,每种客户端创建连接的方式不同,这里不提供实现,测试的时候使用在线websocket测试网站作为客户端.

服务端配置

    需要引入的依赖:

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

websocket配置类:

@Configuration
public class WebSocketConfig {

    // 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

websocket服务端主要逻辑:

@Component
@Slf4j
@Service
@ServerEndpoint("/webSocket/{login}/{type}")  // login表示用户唯一标识,type表示用户类型:1.求职身份;2.面试身份
public class WebSocketServer {

    //当前在线连接数
    private static int onlineCount = 0;

    // 每个在线用户会创建一个WebSocketServer对象
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

    // 存放所有在线的客户端 key为用户的唯一标识:login,value为每个会话连接
    private static Map<String, Session> clients = new ConcurrentHashMap<>();
//    private Session session;

    // 用户login
    private String login = "";

    // 处理使用@Autowire注入为空的问题,使用静态变量处理
    private static NewsMapper newsMapper= SpringUtils.getBean(NewsMapper.class);


    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("login") String login, @PathParam("type") Integer type) {
        clients.put(login, session);
//        this.session = session;
        webSocketSet.add(this);     //加入set中
        this.login = login;
        addOnlineCount();           //在线数加1
        try {
          	// 查询用户未读消息数
            Integer unReadMsg=0;
            List<Long> noReadingNewsIds = newsMapper.findNoReadingNewsId(type, login);
            if(CollectionUtil.isNotEmpty(noReadingNewsIds)){
                unReadMsg=noReadingNewsIds.size();
            }

           // 用户上线提醒
            sendMessage("用户"+login+"已上线("+("1".equals(login) ? "求职者)":"招聘者)")+",未读消息数:"+unReadMsg,session);
            log.info("有新窗口开始监听用户详情id:" + login +",当前在线人数为:" + getOnlineCount());
        } catch (IOException e) {
            log.error("websocket IO Exception");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        clients.remove(login);
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1

        log.info("释放的login为:"+login);

        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());

    }

    /**
     * 收到客户端消息后调用的方法
     * @ Param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自窗口" + login + "的信息:" + message);
        //群发消息
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message,session);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @ Param session
     * @ Param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message,Session session) throws IOException {
        if(session != null){
            session.getBasicRemote().sendText(message);
        }

    }

    /**
     * 校验是否在线,在线需要返回用户session信息
     */
    public Session checkIsOnline(String login) throws IOException {
        for (String onLineLogin : clients.keySet()) {
            if(login.equals(onLineLogin)){
                return clients.get(login);
            }
        }
        return null;

    }


    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }

    public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
        return webSocketSet;
    }
}

模拟业务场景:求职端发送简历给求职者

@RestController
@RequestMapping("/webSocket")
public class WebSocketServerController {

    @Autowired
    private WebSocketServer webSocketServer;

    @GetMapping("/serverToClient")
    public ResultVo sendServerToClient(String login) throws IOException {

        System.out.println("求职者给招聘者发送简历操作..............");
        // 判断用户是否在线
        Session session = webSocketServer.checkIsOnline(login);
        if(ObjectUtil.isNotNull(session)){
            webSocketServer.sendMessage("求职者给招聘者发送简历,招聘者已接收",session);
        }
        return ResultVoUtil.success();
    }
}

模拟客户端

    这里客户端使用在线websocket测试网站,地址:http://www.websocket-test.com/,测试数据:

login:1,userType:1 模拟求职端;
login:2,userType:2 模拟招聘端;

    请求地址为websocket服务器所在项目ip以及端口和请求参数,本地项目参数如下:

ws://172.16.0.131:8080/webSocket/1/1

模拟用户1登录上线:
在这里插入图片描述

模拟用户2登录上线:
在这里插入图片描述
    用户上线之后可以获取到未读消息数!
模拟用户1求职者发送简历给用户2应聘者
这里提供模拟接口:
在这里插入图片描述
以上是模拟客户端,不刷新情况下可以接收到服务端消息.
在这里插入图片描述

    以上是站内消息推送的实现方案,希望对有同样需求的同学有所帮助!

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
要实现Spring Boot集成WebSocket实现消息推送,需要进行以下步骤: 1. 添加Spring Boot WebSocket依赖 2. 创建WebSocket配置类 3. 创建WebSocket处理器类 4. 创建WebSocket拦截器类 5. 创建WebSocket消息模型类 6. 在Spring Boot中使用WebSocket 下面是一个简单的示例代码: 1. 添加Spring Boot WebSocket依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 创建WebSocket配置类 创建一个WebSocket配置类,用于配置WebSocket相关的参数,如下所示: ``` @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new WebSocketHandler(), "/websocket").addInterceptors(new WebSocketInterceptor()); } } ``` 3. 创建WebSocket处理器类 创建一个WebSocket处理器类,用于处理WebSocket连接、断开连接和接收消息等操作,如下所示: ``` public class WebSocketHandler extends TextWebSocketHandler { private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { for (WebSocketSession s : sessions) { s.sendMessage(message); } } } ``` 4. 创建WebSocket拦截器类 创建一个WebSocket拦截器类,用于拦截WebSocket连接请求,如下所示: ``` public class WebSocketInterceptor implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } } ``` 5. 创建WebSocket消息模型类 创建一个WebSocket消息模型类,用于封装WebSocket消息,如下所示: ``` public class WebSocketMessage { private String content; public String getContent() { return content; } public void setContent(String content) { this.content = content; } } ``` 6. 在Spring Boot中使用WebSocketSpring Boot中使用WebSocket非常简单,只需要在Controller中注入WebSocketSession即可,如下所示: ``` @Controller public class WebSocketController { @Autowired private WebSocketSession session; @MessageMapping("/send") public void send(WebSocketMessage message) throws Exception { session.sendMessage(new TextMessage(message.getContent())); } } ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卖柴火的小伙子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值