springboot 接入websocket实现定时推送消息到客户端

news2024/11/26 11:38:22

在这里插入图片描述

目录

  • 说明
  • 代码实现

说明

如标题,举例需求场景:
前端与后端websocket连接上后,多用户登录,后端根据不同用户定时发消息给前端用于展示

代码实现

1、

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

2、

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Component
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }

}

3、

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;



// 交给IOC容器
@Component
// 如果去掉/{userId} 那就是不分用户 给连接上的用户统一发送消息
@ServerEndpoint("/websocket/{userId}")
@Slf4j
public class WebSocketService {


    // 这里用ConcurrentHashMap 因为他是一个线程安全的Map
    private static ConcurrentHashMap<String, CopyOnWriteArraySet<WebSocketService>> userwebSocketMap = new ConcurrentHashMap<>();

    private static ConcurrentHashMap<String, Integer> count = new ConcurrentHashMap<>();

    private String userId;


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

    /**
     * 连接建立成功调用的方法
     *
     * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") final String userId) {
        this.session = session;
        this.userId = userId;
        System.out.println("session:" + session);
        System.out.println("userId:" + userId);
        if (!exitUser(userId)) {
            initUserInfo(userId);
        } else {
            CopyOnWriteArraySet<WebSocketService> webSocketServiceSet = getUserSocketSet(userId);
            webSocketServiceSet.add(this);
            userCountIncrease(userId);
        }
        System.out.println("有" + userId + "新连接加入!当前在线人数为" + getCurrUserCount(userId));
    }


    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        CopyOnWriteArraySet<WebSocketService> webSocketServiceSet = userwebSocketMap.get(userId);
        //从set中删除
        webSocketServiceSet.remove(this);
        //在线数减1
        userCountDecrement(userId);
        System.out.println("有一连接关闭!当前在线人数为" + getCurrUserCount(userId));
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     * @param session 可选的参数
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        CopyOnWriteArraySet<WebSocketService> webSocketSet = userwebSocketMap.get(userId);
        System.out.println("来自客户端" + userId + "的消息:" + message);
        //群发消息
        for (WebSocketService item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }

    }


    /**
     * 发生错误时调用
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }


    /**
     * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
     *
     * @param message
     * @throws IOException
     */

    public void sendMessage(String message) throws IOException {
        System.out.println("服务端推送" + userId + "的消息:" + message);
        this.session.getAsyncRemote().sendText(message);
    }


    /**
     * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。   我是在有代办消息时 调用此接口 向指定用户发送消息
     *
     * @param message
     * @throws IOException
     */

    public void sendMessage(String userId, String message) throws IOException {
        System.out.println("服务端推送" + userId + "的消息:" + message);
        CopyOnWriteArraySet<WebSocketService> webSocketSet = userwebSocketMap.get(userId);
        //群发消息
        for (WebSocketService item : webSocketSet) {
            try {
                item.session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    public void sendOpenAllUserMessage(List<String> userIds, String message) {
        for (String userId : userIds) {
            CopyOnWriteArraySet<WebSocketService> webSocketSet = userwebSocketMap.get(userId);
            //群发消息
            for (WebSocketService item : webSocketSet) {
                try {
                    item.session.getBasicRemote().sendText(message);
                } catch (IOException e) {
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }


    public boolean exitUser(String userId) {
        return userwebSocketMap.containsKey(userId);
    }

    public CopyOnWriteArraySet<WebSocketService> getUserSocketSet(String userId) {
        return userwebSocketMap.get(userId);
    }

    public void userCountIncrease(String userId) {
        if (count.containsKey(userId)) {
            count.put(userId, count.get(userId) + 1);
        }
    }


    public void userCountDecrement(String userId) {
        if (count.containsKey(userId)) {
            count.put(userId, count.get(userId) - 1);
        }
    }

    public void removeUserConunt(String userId) {
        count.remove(userId);
    }

    public Integer getCurrUserCount(String userId) {
        return count.get(userId);
    }

    private void initUserInfo(String userId) {
        CopyOnWriteArraySet<WebSocketService> webSocketServiceSet = new CopyOnWriteArraySet<WebSocketService>();
        webSocketServiceSet.add(this);
        userwebSocketMap.put(userId, webSocketServiceSet);
        count.put(userId, 1);
    }

    public List<String> getAllUser() {
        List<String> allUser = new LinkedList<>();
        Enumeration<String> keys = userwebSocketMap.keys();
        while (keys.hasMoreElements()) {
            String key = keys.nextElement();
            allUser.add(key);
        }
        return allUser;
    }

}

4、

import com.lq.demo1.service.WebSocketService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.util.List;

@EnableScheduling
@Configuration
@Slf4j
public class TaskTimer {
    @Autowired
    private WebSocketService webSocketService;

    @Scheduled(cron = "0/10 * * * * ?")
    public void cleanToken() {
        //10s推送一次
        List<String> allUser = webSocketService.getAllUser();
        //自己可以定义不同用户发送的信息,这里不做演示了
        webSocketService.sendOpenAllUserMessage(allUser, "告警!");
    }

}

@EnableScheduling
在这里插入图片描述

然后把项目启动,打开在线调试websocket连接

路径格式为:ws://localhost:8081/websocket/1
在这里插入图片描述

在这里插入图片描述

成功展示,也可以搞多个窗口,发送内容一致

就先说到这 \color{#008B8B}{ 就先说到这} 就先说到这
在下 A p o l l o \color{#008B8B}{在下Apollo} 在下Apollo
一个爱分享 J a v a 、生活的小人物, \color{#008B8B}{一个爱分享Java、生活的小人物,} 一个爱分享Java、生活的小人物,
咱们来日方长,有缘江湖再见,告辞! \color{#008B8B}{咱们来日方长,有缘江湖再见,告辞!} 咱们来日方长,有缘江湖再见,告辞!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/333099.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vit-pytorch实现 MobileViT注意力可视化

项目链接 https://github.com/lucidrains/vit-pytorch 注意一下参数设置&#xff1a; Parameters image_size: int. Image size. If you have rectangular images, make sure your image size is the maximum of the width and heightpatch_size: int. Number of patches. im…

git:详解git rebase命令

背景 今天无意中打开 git 官网&#xff0c;发现 git 命令还是很多的&#xff0c;然而我们常用的就那几个&#xff0c;今天来学习一个也不怎么常用的命令 rebase 官网链接 都说学一个东西最好的方式就是读他的 官方文档&#xff0c;这里我读了一遍&#xff0c;把一些核心的地…

读书思考:步步惊心的《技术陷阱》

《技术陷阱》这本书450页&#xff0c;43万字之巨&#xff0c;信息量密密麻麻&#xff0c;采集的资料极其丰富&#xff0c;复习了一遍大停滞、大分流、大平衡、大逆转时代&#xff0c;并展望未来。看完了有很多想法&#xff0c;随手写了下来&#xff0c;希望不是蹭热点。&#x…

vue 最详细教学篇(一)

文章目录前言前景Vue 的长期技术支持 (LTS)、终止支持 (EOL) 及其延长版服务学习vue 要掌握那些技能-为什么学习 vue走进vueHello World 起手提示&#xff1a;示例&#xff1a;示例解析编辑器 VSCodevsCode 插件正式使用 vue.js要使用 vue 就绕不开生命周期 下面是生命周期图&a…

全国青少年编程等级考试scratch一级真题2022年9月(含题库答题软件账号)

青少年编程等级考试scratch真题答题考试系统请点击电子学会-全国青少年编程等级考试真题Scratch一级&#xff08;2019年3月&#xff09;在线答题_程序猿下山的博客-CSDN博客_小航答题助手1点击绿旗&#xff0c;下列哪个选项可以实现播放马叫声并在声音全部播放完后&#xff0c;…

Java常见数据结构的排序与遍历(包括数组,List,Map)

数组遍历与排序 数组定义 //定义 int a[] new int[5]int[] a new int[5];//带初始值定义 int b[] {1,2,3,4,5};赋值 //定义时赋值 int b[] {1,2,3,4,5};//引用赋值 a[6] 1 a[9] 9 //未赋值为空取值 //通过下表取值&#xff0c;从0开始 b[1] 1 b[2] 2遍历 Test p…

C语言操作符详解 一针见血!

目录算数操作符移位操作符位操作符赋值操作符单目操作符关系操作符逻辑操作符条件操作符逗号表达式下标引用、函数调用和结构成员表达式求值11.1 隐式类型转换算数操作符&#x1f4ad; 注意/ 除法 --得到的是商% 取模&#xff08;取余&#xff09;--得到的是余数如果除法操作符…

CentOS 根路径下各个目录的作用及介绍

前言 很多小伙伴刚刚开始接触Linux系统时肯定和我一样&#xff0c;都很懵&#xff0c;黑黢黢的界面&#xff0c;一个个目录&#xff0c;没有图形化界面&#xff0c;看着难受&#xff0c;多接触了一些后会好受一些&#xff0c;不过&#xff0c;对各个目录的了解肯定也很基础&am…

若依框架---PageHelper分页(十)

在前几天的文章中&#xff0c;我们介绍了PageHelper的分页方法&#xff0c;研读代码定位到了ExecutorUtil.pageQuery(...)方法&#xff0c;并阅读到了其中的部分代码。 今天我们将看到重要的SQL修改代码。 getPageSql 我们接着看代码&#xff1a; if (!dialect.beforePage(…

2023爬虫学习笔记 -- 批量爬取图片

一、目标网址http://img.itlun.cn/uploads/allimg/180703/1-1PF3160531-lp.jpg二、右击图片获取图片地址http://img.itlun.cn/uploads/allimg/180703/1-1PF3160531-lp.jpg三、以二进制形式返回响应数据响应requests.get(网页,headers头) 响应内容响应.content四、存储二进制数据…

SpringBoot整合Mybatis的核心原理

0. 前言&#xff1a;1. 自动配置类MybatisAutoConfiguration&#xff1a;1.1. SqlSessionFactory的生成&#xff1a;1.2. Mapper的扫描和代理生成&#xff1a;1.2.1. MapperScannerConfigurer1.2.2. MapperFactoryBean1.2.3. getMapper生成代理对象2. 小结&#xff1a;0. 前言&…

Vue2仿网易云风格音乐播放器(附源码)

Vue2仿网易云风格音乐播放器1、整体效果2、使用技术3、实现内容4、源码5、使用图片1、整体效果 2、使用技术 使用了HTML5 CSS3进行页面布局及美化使用Vue2进行数据渲染与页面交互使用Axios发送http请求获取数据 3、实现内容 实现了搜索歌曲功能&#xff0c;输入歌手或歌曲关…

如果企业遭受到攻击应该进行怎样的处理

声明 本文是学习2018勒索病毒白皮书政企篇. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 政企遭遇勒索攻击分析 由于感染政企客户更有可能获得赎金&#xff0c;再加上勒索病毒本身也以服务器定向攻击为主&#xff0c;所以&#xff0c;2018年政企客…

构建工具tsup入门第三部分

&#x1f384;Hi~ 大家好&#xff0c;我是小鑫同学&#xff0c;一位长期从事前端开发的编程爱好者&#xff0c;我将使用更为实用的案例输出更多的编程知识&#xff0c;同时我信奉分享是成长的唯一捷径&#xff0c;在这里也希望我的每一篇文章都能成为你技术落地的参考~ 目录&am…

“慌不择路”周鸿祎,昔日大炮忙跟风【短评】

文|智能相对论作者| 凯文2月7日下午360经历两次急速拉升后涨停了&#xff0c;作为一个被套牢的股民&#xff0c;我是羡慕的&#xff0c;但理智告诉我&#xff0c;360的后续难以为继。360涨停的原因很简单&#xff0c;只因其在投资者互动平台上对类ChatGPT技术的布局做出了回应&…

安装Sentinel控制台与初始化演示工程

目录 一、Sentinel 二、安装Sentinel控制台 &#xff08;一&#xff09;sentinel组件由2部分构成 &#xff08;二&#xff09;安装步骤 三、初始化演示工程 四、流控模式 &#xff08;一&#xff09;快速失败 &#xff08;二&#xff09;关联资源 &#xff08;三&…

第四章——随机变量的数字特征

文章目录1、数字特征的定义2、数学期望&#xff08;均值&#xff09;2.1、数学期望的定义及性质2.1.1、定义2.1.2、性质2.2、数学期望相关例题2.3、Yg(X)的数学期望2.4、Zg(X,Y)的数学期望2.5、随机变量函数的数学期望例题3、方差3.1、方差的定义与性质3.2、相关例题3.3、切比雪…

3.2 埃尔米特转置

定义 对于复矩阵&#xff0c;转置又不一样&#xff0c;常见的操作是共轭转置&#xff0c;也叫埃尔米特转置Hermitian transpose。埃尔米特转置就是对矩阵先共轭&#xff0c;再转置&#xff0c;一般来说用三种符号表示埃尔米特转置&#xff1a; 第一种符号是AHA^HAH&#xff0c…

热门盘点 | 10款评分最高的项目管理工具

项目管理软件可以让项目经理及时掌握项目进展可把复杂的任务分解简单帮助项目经理及时了解整个团队进展随着现代项目需求日趋复杂和个性选一个好的项目管理软件还是很有必要的① PingCode国内研发项目管理软件PingCode&#xff0c;它是国内软件研发项目榜单中评分最高的项目管理…

达梦实现高可用性的实现(failover功能/负载均衡/虚拟ip透明切换)

达梦实现高可用性的实现&#xff08;failover功能/负载均衡/虚拟ip透明切换&#xff09;一&#xff1a;failover功能&#xff1a;基于守护进程和监视器两个内在工具实现守护进程监视器&#xff1a;数据守护和读写分离集群共享存储集群二&#xff1a;负载均衡&#xff1a;基于jd…