通过WebSocket实现实时系统通知,以后再也不能装作没看到老板的通知了~~

news2024/10/6 20:27:46

📞 文章简介:WebSocket实时通知Demo
💡 创作目的:因为公司正在从零搭建CRM,其中有一个需求是系统通知管理,老板发布通知给员工。简单的用数据库实现感觉缺少一些实时性,不是那么生动。于是想到了使用WebSocket建立通讯,让系统中在线的员工可以实时接收到系统通知。借此学习一下WebSocket,

📝 每日一言:学习如一粒种子,只有努力播种才会有收获。

☀️ 今日天气:2022-11-19 多云 满是灰色的🤫

文章目录

  • WebSocket核心代码
    • WebSocket核心业务类
    • 消息实体
    • webSocket 配置bean
    • 关于session 、socket池的管理
    • 多例注入单例获取bean的方法 (感谢大佬 😍)
    • 总结

效果演示🌈😁
效果演示

注意:因为是个实现效果的小demo,所以用了若依开源框架快速集成。也借此机会使用一下若依其他生态项目。gif有些掉帧😮

WebSocket核心代码

话不多说直接上代码

WebSocket核心业务类

WebSocket.java


//springboot 组建注解
@Component
//核心socket路径注解
@ServerEndpoint("/websocket/{userId}")
@CrossOrigin
@Lazy
//此注解相当于设置访问URL
public class WebSocket {
	//注入session对象用来读取发送消息
    private Session session;


	//因为涉及到发布短信接收短信还有一些短信的状态在数据库所以引入了messageService
	//这里遇到一个问题,就是socket每次连接会创建多个实例 但是引入了注入到Spring中的bean
	//普通的 @Autowired 注解注入不进来,所以额外写了一个读取Bean的类下面会有
    ICclMessageService cclMessageService = (ICclMessageService) SpringContext.getBean("cclMessageServiceImpl");



	//初始化socket实例,建立连接
	//userId 这个参数自定义,根据路径上面的值而定,可以根据业务修改或者多参传入
    @OnOpen
    public void onOpen(Session session,@PathParam("userId")Integer userId) {
        this.session = session;
        //将连接添加到sockets池
        //CurPool是自定义的一个socket池,将他们集中管理起来
        CurPool.webSockets.put(userId,this);
        System.out.println("【websocket消息】有新的连接,总数为:"+CurPool.webSockets.size());
        //下面是业务逻辑,每次连接会查询没有发送的通知,并接收通知
        LambdaQueryWrapper<CclMessage> lq =new LambdaQueryWrapper<>();
        lq.eq(CclMessage::getSysUserId,userId);
        lq.eq(CclMessage::getSendStatus,0);
        //查询离线的时候发送过来的通知
        List<CclMessage> list = cclMessageService.list(lq);
        if (list!=null){
        	//进行遍历发送
            List<String> msgList =new ArrayList<>();
            for (CclMessage cclMessage : list) {
                String mes = JSON.toJSONString(cclMessage);
                msgList.add(mes);
                cclMessage.setSendStatus(1);
            }
            //调用发送多条消息方法,进行通知
            sendAllMessageByUserId(msgList,userId);
            //发送状态改变
            cclMessageService.updateBatchById(list);
        }
    }

	//连接关闭走的方法
    @OnClose
    public void onClose() {
        // 断开连接删除用户删除session
        Integer userId = Integer.parseInt(this.session.getRequestParameterMap().get("userId").get(0));
        //从socket池中移除实例
        CurPool.webSockets.remove(userId);
        System.out.println("【websocket消息】连接断开,总数为:"+CurPool.webSockets.size());
    }
	//发送消息实例(处理发送消息类型,进行发送)
    @OnMessage
    public void onMessage(String message) {
        MessageVo messageVo = JSON.parseObject(message, MessageVo.class);
        System.out.println("现在存活个数"+CurPool.webSockets.size());
        //发送消息
        String message1 = JSON.toJSONString(messageVo);
        String[] userIds = messageVo.getUserIds().split(",");
        Integer[] userIdsInt = new Integer[userIds.length==0?1:userIds.length];
        int count =0;
        for (String userId : userIds) {
            userIdsInt[count] = Integer.valueOf(userId);
            count++;
        }
        sendByUserIdMessage(message1,userIdsInt);
    }


    // 指定多人通知 (具体发送消息方法)
    public void sendByUserIdMessage(String message,Integer[] userIds) {
    	//遍历发送的用户id,给他们发送消息
        for (Integer userId : userIds) {
        	//在socket池中获取实例
            WebSocket webSocket = CurPool.webSockets.get(userId);
            //如果为null则说明不在线
            if (webSocket == null){
                System.out.println(userId+"需要稍后通知!!!");
            }else {
                try {
                	//发送消息给指定用户
                    webSocket.session.getAsyncRemote().sendText(message);
                    System.out.println("用户名为"+userId+"已经发送完:"+message);
                    //修改数据库中发送状态
                    LambdaQueryWrapper<CclMessage> lq =new LambdaQueryWrapper<CclMessage>();
                    lq.eq(CclMessage::getSysUserId,userId);
                    cclMessageService.update(new CclMessage().setSendStatus(1),lq);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }



    //发送多条消息给个人
    public  void  sendAllMessageByUserId(List<String> messageList,Integer userId ){
        WebSocket webSocket = CurPool.webSockets.get(userId);
        if (webSocket != null){
            for (String msg : messageList) {
                webSocket.session.getAsyncRemote().sendText(msg);
                System.out.println(msg+"消息已经发送完");
                try {
                    Thread.sleep(500);
                }catch (Exception e){
                    System.out.println("延迟失败");
                    e.printStackTrace();
                }
            }
        }

    }

}

上面的是主要的WebSocket业务处理代码,因为写的小demo,想着实现功能,逻辑可能过于不注重性能,勿喷!
重点

  • @ServerEndpoint(“/websocket/{userId}”) 这是定义ws连接路径,根据实际业务自定义参数传递及名称
  • @OnOpen 连接进来以后初始化session实例的方法,主要走一些实例存储等逻辑
  • @OnClose 连接关闭后需要进行的业务逻辑,比如说在池中删除实例、界面对应响应等等等
  • @OnMessage 做一些发送消息,接收消息的业务逻辑

消息实体

MessageVo.java

public class MessageVo {
	//标题
    private String title;
    //内容
    private String content;
    //用户id
    private String userIds;
    }

get、set、toString 我就省略了太占地方了

webSocket 配置bean

WebSocketConfig.java

@Configuration
public class WebSocketConfig {

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

将WebSocket注入到bean

关于session 、socket池的管理

CurPool.java

/**
 * 统一管理session、websocket
 */
public class CurPool {

    public static Map<Integer, WebSocket> webSockets = new ConcurrentHashMap<>();
    // list 里面第一个存sessionId,第二个存session
    public static Map<Integer, List<Object>> sessionPool = new ConcurrentHashMap<>();
}

主要存储session跟socket实例管理,可以通过池的管理进行范围的区分,后期可以扩展到以组为维度,并非个人。实现群发,创建部门群等操作。

多例注入单例获取bean的方法 (感谢大佬 😍)


@Component
public class SpringContext implements ApplicationContextAware {



	private static final Logger logger = LoggerFactory.getLogger(SpringContext.class);

	private static ApplicationContext applicationContext;

	private static DefaultListableBeanFactory beanFactory;

	public static <T> T getBean(String name, Class<T> clazz) {

		return applicationContext.getBean(name, clazz);
	}

	public static Object getBean(String name) {

		return applicationContext.getBean(name);
	}


	public static DefaultListableBeanFactory getBeanFactory() {
		return beanFactory;
	}

	public static ApplicationContext getApplicationContext() {

		return applicationContext;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

		SpringContext.applicationContext = applicationContext;

		ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext ;
		beanFactory = (DefaultListableBeanFactory) configurableApplicationContext
				.getBeanFactory();
	}


	public static void printBeanDefinitionNames() {
		String[] beanDefinitionNames = applicationContext
				.getBeanDefinitionNames();
		for (int i = 0; i < beanDefinitionNames.length; i++) {
			logger.info(beanDefinitionNames[i]);
		}
	}


	public static XmlBeanDefinitionReader getXmlBeanDefinitionReader() {
		return new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory);
	}

	/**
	 * 动态加载bean
	 * @param fileName
	 * @throws BeanDefinitionStoreException
	 * @throws IOException
	 */
	public static void loadBean(String fileName)
			throws BeanDefinitionStoreException, IOException {

		XmlBeanDefinitionReader beanDefinitionReader = getXmlBeanDefinitionReader();
		beanDefinitionReader.setResourceLoader(applicationContext);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(
				applicationContext));
		beanDefinitionReader.loadBeanDefinitions(applicationContext
				.getResources(fileName));

	}

	public static void loadBean(String fileName, String propertyHolderBeanName)
			throws BeanDefinitionStoreException, IOException {

		XmlBeanDefinitionReader beanDefinitionReader = getXmlBeanDefinitionReader();
		beanDefinitionReader.setResourceLoader(applicationContext);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(
				applicationContext));
		beanDefinitionReader.loadBeanDefinitions(applicationContext
				.getResources(fileName));

		if (propertyHolderBeanName != null) {
			postProcessBeanFactory(propertyHolderBeanName);
		}
	}

	public static void postProcessBeanFactory(String propertyHolderBeanName)
			throws BeanDefinitionStoreException, IOException {
		BeanFactoryPostProcessor bfpp = (BeanFactoryPostProcessor) SpringContext
				.getBean(propertyHolderBeanName);
		bfpp.postProcessBeanFactory(SpringContext.getBeanFactory());
	}
}

哈哈,这是遇到问题的时候去解决在网上大佬发布的,小弟属实佩服!!!

总结

工作以后不像原来系统化的学习,盲目去学,而是抓住工作中的需求。根据工作的需求找到合理的解决方案,在解决问题的同时升华自己,从中不断吸取知识。这强于系统化的学习很多倍。

🌈欢迎大佬们阅读,也希望可以有更好的想法弹出。学习如一粒种子,只有努力播种才会有收获。

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

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

相关文章

向毕业妥协系列之深度学习笔记(三)DL的实用层面(上)

目录 一.训练_开发_测试集 二.方差与偏差 三.正则化 四.Dropout正则化 五.其他正则化方法 本篇文章大部分又是在ML中学过的&#xff0c;除了Dropout正则化及之后的部分。 一.训练_开发_测试集 在配置训练、验证和测试数据集的过程中做出正确决策会在很大程度上帮助大家创…

[Spring MVC 8]高并发实战小Demo

本项目基于Spring MVC进行关于点赞项目的开发&#xff0c;从传统的点赞到高并发缓存开发最后到消息队列异步开发&#xff0c;可谓是令人大开眼界。 本篇博客全部代码已经放出&#xff0c;本博客重点是后端操作&#xff0c;所以对于前端就十分简单的页面。讲述了关于Redis,Quart…

软件安装教程1——Neo4j下载与安装

Neo4j的下载地址Neo4j Download Center - Neo4j Graph Data Platform 我下载的是Neo4j社区版&#xff08;免费&#xff09;【企业版收费】 解压后的目录如下&#xff1a; 接下来配置环境变量 进入bin目录&#xff0c;复制路径&#xff1a;E:\neo4j\neo4j-community-5.1.0-win…

决策树——预剪枝和后剪枝

一、 为什么要剪枝 1、未剪枝存在的问题 决策树生成算法递归地产生决策树&#xff0c;直到不能继续下去为止。这样产生的树往往对训练数据的分类很准确&#xff0c;但对未知的测试数据的分类却没有那么准确&#xff0c;即容易出现过拟合现象。解决这个问题的办法是考虑决策树…

【Lua基础 第2章】lua遍历table的方式、运算符、math库、字符串操作方法

文章目录&#x1f4a8;更多相关知识&#x1f447;一、lua遍历table的几种方式&#x1f342;pairs遍历&#x1f342;ipairs遍历&#x1f342;i1,#xxx遍历&#x1f31f;代码演示&#x1f342;pairs 和 ipairs区别二、如何打印出脚本自身的名称三、Lua运算符&#x1f538;算术运算…

微服务治理-含服务线上稳定性保障建设治理

微服务的概念 任何组织在设计一套系统&#xff08;广义概念上的系统&#xff09;时&#xff0c;所交付的设计方案在结构上都与该组织的沟通结构保持一致。 —— 康威定律 微服务是一种研发模式。换句话理解上面这句康威定律&#xff0c;就是说 一旦企业决定采用微服务架构&am…

Js逆向教程-12FuckJs

Js逆向教程-12FuckJs 它利用了js的语法特性&#xff1a; 一、特性1 任何一个js类型的变量结果 加上一个字符串 &#xff0c;只会变成字符串。 数组加上字符串&#xff1a; [0]"" 0true加上字符串 true "" true数字加上字符串 1"" 1二、特性…

14天学习训练营之 初识Pygame

目录 学习知识点 PyGame 之第一个 PyGame 程序 导入模块 初始化 ​​1.screen 2. 游戏业务 学习笔记 当 init () 的时候&#xff0c;它在干什么&#xff1f; init () 实际上检查了哪些东西呢&#xff1f; 它到底 init 了哪些子模块&#xff1f; 总结 14天学习训练营导…

2023年计算机毕设选题推荐

同学们好&#xff0c;这里是海浪学长的毕设系列文章&#xff01; 对毕设有任何疑问都可以问学长哦! 大四是整个大学期间最忙碌的时光,一边要忙着准备考研,考公,考教资或者实习为毕业后面临的就业升学做准备,一边要为毕业设计耗费大量精力。近几年各个学校要求的毕设项目越来越…

·工业 4.0 和第四次工业革命详细介绍

工业 4.0 是制造/生产及相关行业和价值创造过程的数字化转型。 目录 工业 4.0 指南 工业 4.0 与第四次工业革命互换使用&#xff0c;代表了工业价值链组织和控制的新阶段。 网络实体系统构成了工业 4.0 的基础&#xff08;例如&#xff0c;「智慧机器」&#xff09;。他们使用…

基于SpringBoot+Vue的疫苗接种管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端&#xff1a;SpringBoot 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7 数据库管理工具&#xff1a;Navicat 12 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / MyEclipse 是否Maven项…

实验二 帧中继协议配置

计算机网络实验实验二 帧中继协议配置一、实验目的二、实验内容三、实验条件四、实验步骤4.1 连接帧中继交换网4.2 创建DLCI4.3 创建串行接口间的虚电路映射关系4.4 配置路由器的串行接口七、思考题实验二 帧中继协议配置 一、实验目的 掌握路由器上配置帧中继协议的方法 掌握…

SSM整合(一)

SSM整合之简单使用通用mapper 1.准备工作 1.1 在java文件夹下面创建所需要的目录 1.2 导入SSM整合时所需要的所有依赖 <properties><!--这个是统一一些spring插件的包名,避免因为版本不一样而报错--><spring.version>5.3.22</spring.version></p…

SAP S4 FI 后台详细配置教程文档 PART2 (财务会计的基本设置篇)

本篇是系列文章的第二部分&#xff0c;目标是家在配置“字段状态变式”和“年度与期间的配置” 目录 1、 字段状态变式 1.1定义字段状态变式 1.2 向字段状态变式分配公司代码 2、会计年度与记账期间 2.1维护会计年度变式 2.2 向一个会计年度变式分配公司代码 2.3定义未结…

服务器虚拟化有什么好处

服务器虚拟化是一种逻辑角度出发的资源配置技术&#xff0c;是物理实际的逻辑抽象。对于用户&#xff0c;虚拟化技术实现了软件跟硬件分离&#xff0c;用户不需要考虑后台的具体硬件实现&#xff0c;而只需在虚拟层环境上运行自己的系统和软件。 说起服务器虚拟化这个技术&…

你的新进程是如何被内核调度执行到的?(下)

接上文你的新进程是如何被内核调度执行到的&#xff1f;&#xff08;上&#xff09; 四、新进程加入调度 进程在 copy_process 创建完毕后&#xff0c;通过调用 wake_up_new_task 将新进程加入到就绪队列中&#xff0c;等待调度器调度。 //file:kernel/fork.c long do_fork(.…

表白墙服务器版【交互接口、服务器端代码、前端代码、数据存入文件/数据库】

文章目录 一、准备工作二、约定前后端交互接口三、实现服务器端代码 四、调整前端页面代码五、数据存入文件六、数据存入数据库一、准备工作 1) 创建 maven 项目2) 创建必要的目录 webapp, WEB-INF, web.xml&#xff1b;web.xml如下&#xff1a;<!DOCTYPE web-app PUBLIC&qu…

家居行业如何实现智能化?快解析来助力

什么是智能家居&#xff1f;主要是指利用先进的电子通信技术&#xff0c;将居家生活有关的各个子系统有机结合在一起&#xff0c;通过网络化便可以对这些系统进行智能控制与管理。智能家居概念之所以逐渐普及&#xff0c;得益于物联网、大数据、人工智能等新兴技术的进步。智能…

科学计算模型 Numpy 详解

本文主要介绍Numpy&#xff0c;并试图对其进行一个详尽的介绍。 通过阅读本文&#xff0c;你可以&#xff1a; 了解什么是 Numpy掌握如何使 Numpy 操作数组&#xff0c;如创建数组、改变数组的维度、拼接和分隔数组等掌握 Numpy 的常用函数&#xff0c;如数组存取函数、加权平均…

表关联查询

表关联查询 1.表别名 当表的名字很长或者执行一些特殊查询时&#xff0c;为了方便操作或者需要多次使用相同的表时&#xff0c;可以为表指定别名&#xff0c;以替代表原来的名称。 在为表取别名时&#xff0c;要保证不能与数据库中的其他表的名称冲突。 对单表做简单的别名查询…