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

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

背景


   现有一个类似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登录上线:

7918d07b420de3c312bbe080349ba3e7_f2fa14044aa04467a5c17033abcc16b5.png


模拟用户2登录上线:

4358f66884a230b2833fdd3d98323434_041748898bad4be9ad68eb6b541e925c.png

用户上线之后可以获取到未读消息数!

模拟用户1求职者发送简历给用户2应聘者

这里提供模拟接口:

f86a4ac07c91780d6642a92aa3e7ef96_642c9bc43e7b4f2093354aec9099600e.png

以上是模拟客户端,不刷新情况下可以接收到服务端消息.

c6c2e2ff59e66ae9b97133cba33f314b_dff2fbf1c20a44759789f973959c62a5.png

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


相关文章
|
11月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
1498 3
|
10月前
|
人工智能 自然语言处理 API
快速集成GPT-4o:下一代多模态AI实战指南
快速集成GPT-4o:下一代多模态AI实战指南
699 101
|
缓存 监控 安全
通义大模型与现有企业系统集成实战《CRM案例分析与安全最佳实践》
本文档详细介绍了基于通义大模型的CRM系统集成架构设计与优化实践。涵盖混合部署架构演进(新增向量缓存、双通道同步)、性能基准测试对比、客户意图分析模块、商机预测系统等核心功能实现。同时,深入探讨了安全防护体系、三级缓存架构、请求批处理优化及故障处理机制,并展示了实时客户画像生成和动态提示词工程。通过实施,显著提升客服响应速度(425%)、商机识别准确率(37%)及客户满意度(15%)。最后,规划了技术演进路线图,从单点集成迈向自主优化阶段,推动业务效率与价值持续增长。
655 8
|
9月前
|
监控 Cloud Native Java
Spring Boot 3.x 微服务架构实战指南
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Spring Boot 3.x与微服务架构,探索云原生、性能优化与高可用系统设计。以代码为笔,在二进制星河中谱写极客诗篇。关注我,共赴技术星辰大海!(238字)
1403 2
Spring Boot 3.x 微服务架构实战指南
|
10月前
|
消息中间件 Ubuntu Java
SpringBoot整合MQTT实战:基于EMQX实现双向设备通信
本教程指导在Ubuntu上部署EMQX 5.9.0并集成Spring Boot实现MQTT双向通信,涵盖服务器搭建、客户端配置及生产实践,助您快速构建企业级物联网消息系统。
3062 1
|
10月前
|
人工智能 Java API
Java与大模型集成实战:构建智能Java应用的新范式
随着大型语言模型(LLM)的API化,将其强大的自然语言处理能力集成到现有Java应用中已成为提升应用智能水平的关键路径。本文旨在为Java开发者提供一份实用的集成指南。我们将深入探讨如何使用Spring Boot 3框架,通过HTTP客户端与OpenAI GPT(或兼容API)进行高效、安全的交互。内容涵盖项目依赖配置、异步非阻塞的API调用、请求与响应的结构化处理、异常管理以及一些面向生产环境的最佳实践,并附带完整的代码示例,助您快速将AI能力融入Java生态。
1529 12
|
缓存 NoSQL Java
基于SpringBoot的Redis开发实战教程
Redis在Spring Boot中的应用非常广泛,其高性能和灵活性使其成为构建高效分布式系统的理想选择。通过深入理解本文的内容,您可以更好地利用Redis的特性,为应用程序提供高效的缓存和消息处理能力。
1562 79
|
人工智能 安全 Shell
Jupyter MCP服务器部署实战:AI模型与Python环境无缝集成教程
Jupyter MCP服务器基于模型上下文协议(MCP),实现大型语言模型与Jupyter环境的无缝集成。它通过标准化接口,让AI模型安全访问和操作Jupyter核心组件,如内核、文件系统和终端。本文深入解析其技术架构、功能特性及部署方法。MCP服务器解决了传统AI模型缺乏实时上下文感知的问题,支持代码执行、变量状态获取、文件管理等功能,提升编程效率。同时,严格的权限控制确保了安全性。作为智能化交互工具,Jupyter MCP为动态计算环境与AI模型之间搭建了高效桥梁。
871 2
Jupyter MCP服务器部署实战:AI模型与Python环境无缝集成教程
|
11月前
|
人工智能 自然语言处理 分布式计算
AI 驱动传统 Java 应用集成的关键技术与实战应用指南
本文探讨了如何将AI技术与传统Java应用集成,助力企业实现数字化转型。内容涵盖DJL、Deeplearning4j等主流AI框架选择,技术融合方案,模型部署策略,以及智能客服、财务审核、设备诊断等实战应用案例,全面解析Java系统如何通过AI实现智能化升级与效率提升。
877 0
|
监控 Java 调度
SpringBoot中@Scheduled和Quartz的区别是什么?分布式定时任务框架选型实战
本文对比分析了SpringBoot中的`@Scheduled`与Quartz定时任务框架。`@Scheduled`轻量易用,适合单机简单场景,但存在多实例重复执行、无持久化等缺陷;Quartz功能强大,支持分布式调度、任务持久化、动态调整和失败重试,适用于复杂企业级需求。文章通过特性对比、代码示例及常见问题解答,帮助开发者理解两者差异,合理选择方案。记住口诀:单机简单用注解,多节点上Quartz;若是任务要可靠,持久化配置不能少。
1146 4