SpringBoot+WebSocket实现即时通讯(一)

前言

本博客姊妹篇

  • SpringBoot+WebSocket实现即时通讯(一)
  • SpringBoot+WebSocket实现即时通讯(二)
  • SpringBoot+WebSocket实现即时通讯(三)
  • SpringBoot+WebSocket实现即时通讯(四)

传统方式

背景:即时通讯过程中,解决传统网站使用HTTP轮询方式请求获取最新的数据(如每3秒请求一次)。

缺点

  • Web客户端反复发出请求消耗服务器资源
  • 请求包含较长的头部,浪费很多的带宽资源
  • 只能由Web客户端发送请求到服务端获取数据
  • 实时性不高

WebSocket

WebSocket:WebSocket是一种在单个TCP连接上进行全双工通信的协议。

优势

  • 一个Web客户端和服务端只建立一个TCP连接
  • 请求包含轻量级的头部,减少了数据传输量
  • 服务端可以主动推送数据到Web客户端
  • 实时性高

一、功能描述

  • 即时通讯:发送、接收消息
  • 用户管理:业务自己实现,暂从数据库添加
  • 好友管理:添加好友、删除好友、修改备注、好友列表等
  • 群组管理:新建群、解散群、编辑群、变更群主、拉人进群、踢出群等
  • 聊天模式:私聊、群聊
  • 消息类型:系统、文本、语音、图片、视频
  • 聊天管理:删除聊天、置顶聊天、查看聊天记录等

二、WebSocket服务

2.1 引入依赖

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

2.2 配置WebSocket扫描

package com.qiangesoft.im.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** WebSocket配置类*/
@Configuration
public class WebSocketConfig {/*** bean注册:会自动扫描带有@ServerEndpoint注解声明的Websocket Endpoint(端点),注册成为Websocket bean。* 注意:如果项目使用外置的servlet容器,而不是直接使用springboot内置容器的话,就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理。*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}

2.3 WebSocket服务类

package com.qiangesoft.im.core;import com.alibaba.fastjson2.JSONObject;
import com.qiangesoft.im.core.constant.ChatTypeEnum;
import com.qiangesoft.im.core.constant.ImBodyEnum;
import com.qiangesoft.im.pojo.bo.ImMessageBO;
import com.qiangesoft.im.pojo.dto.PingDTO;
import com.qiangesoft.im.pojo.vo.ImMessageVO;
import com.qiangesoft.im.pojo.vo.PongVO;
import com.qiangesoft.im.pojo.vo.SysUserVo;
import com.qiangesoft.im.service.IImGroupUserService;
import com.qiangesoft.im.util.SpringUtil;
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.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;/*** 聊天会话** @author qiangesoft* @date 2023-08-30*/
@Slf4j
@ServerEndpoint("/ws/im/{userId}")
@Component
public class ImWebSocketServer {/*** concurrent包的线程安全Set,用来存放每个客户端对应的session*/private static final ConcurrentHashMap<Long, Session> WEBSOCKET_MAP = new ConcurrentHashMap<>();/*** 连接成功:用map存客户端对应的session*/@OnOpenpublic void onOpen(Session session, @PathParam("userId") Long userId) {log.info("User [{}] connection opened=====>", userId);// 关闭之前的if (WEBSOCKET_MAP.containsKey(userId)) {Session oldSession = WEBSOCKET_MAP.get(userId);close(oldSession, userId);}// 存储sessionWEBSOCKET_MAP.put(userId, session);// 在线人数log.info("User connection add 1, online num is [{}]", WEBSOCKET_MAP.size());// 响应PongVO pongVO = new PongVO();pongVO.setType(ImBodyEnum.PONG.getCode());pongVO.setContent("连接成功");pongVO.setTimestamp(System.currentTimeMillis());doSendMessage(session, pongVO);}/*** 收到客户端消息*/@OnMessagepublic void onMessage(Session session, @PathParam("userId") Long userId, String message) {log.info("User [{}] send a message, content is [{}]", userId, message);PingDTO pingDTO = null;try {pingDTO = JSONObject.parseObject(message, PingDTO.class);} catch (Exception e) {log.error("消息解析失败");e.printStackTrace();}if (pingDTO == null || !ImBodyEnum.PING.getCode().equals(pingDTO.getType())) {sendInValidMessage(session);return;}// 响应PongVO pongVO = new PongVO();pongVO.setType(ImBodyEnum.PONG.getCode());pongVO.setContent("已收到消息~");pongVO.setTimestamp(System.currentTimeMillis());doSendMessage(session, pongVO);}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(Session session, @PathParam("userId") Long userId) {close(session, userId);// 在线人数减1if (!WEBSOCKET_MAP.containsKey(userId)) {log.info("User connection reduce 1, online num is [{}]", WEBSOCKET_MAP.size());}log.info("User [{}] connection is closed<=====", userId);}/*** 报错** @param session* @param error*/@OnErrorpublic void onError(Session session, @PathParam("userId") Long userId, Throwable error) {log.info("User [{}] connection is error!", userId);error.printStackTrace();}/*** 指定的userId服务端向客户端发送消息*/public static void sendMessage(ImMessageBO message) {String chatType = message.getChatType();if (ChatTypeEnum.GROUP.getCode().equals(chatType)) {sendGroupMessage(message);}if (ChatTypeEnum.PERSON.getCode().equals(chatType)) {sendPersonMessage(message);}}/*** 被挤下线*/public static void offline(Long userId) {Session session = WEBSOCKET_MAP.get(userId);if (session != null) {// 设备下线PongVO pongVO = new PongVO();pongVO.setType(ImBodyEnum.OFFLINE.getCode());pongVO.setContent("设备被挤下线");pongVO.setTimestamp(System.currentTimeMillis());doSendMessage(session, pongVO);// 关闭close(session, userId);}}/*** 自定义关闭** @param session* @param userId*/public static void close(Session session, Long userId) {try {session.close();} catch (IOException e) {e.printStackTrace();}WEBSOCKET_MAP.remove(userId);}/*** 发送无效消息*/private static void sendInValidMessage(Session session) {PongVO pongVO = new PongVO();pongVO.setType(ImBodyEnum.PONG.getCode());pongVO.setContent("无效消息");pongVO.setTimestamp(System.currentTimeMillis());doSendMessage(session, pongVO);}/*** 发送群组消息** @param message*/private static void sendGroupMessage(ImMessageBO message) {MessageHandlerService messageHandlerService = SpringUtil.getBean(MessageHandlerService.class);ImMessageVO messageVO = messageHandlerService.buildVo(message);PongVO pongVO = new PongVO();pongVO.setType(ImBodyEnum.MESSAGE.getCode());pongVO.setContent(messageVO);pongVO.setTimestamp(System.currentTimeMillis());// 发送给群成员IImGroupUserService groupUserService = SpringUtil.getBean(IImGroupUserService.class);List<Long> userIdList = groupUserService.listGroupUser(message.getTargetId()).stream().map(SysUserVo::getId).collect(Collectors.toList());for (Long userId : userIdList) {Session session = WEBSOCKET_MAP.get(userId);doSendMessage(session, pongVO);}}/*** 发送私聊消息** @param message*/private static void sendPersonMessage(ImMessageBO message) {MessageHandlerService messageHandlerService = SpringUtil.getBean(MessageHandlerService.class);ImMessageVO messageVO = messageHandlerService.buildVo(message);PongVO pongVO = new PongVO();pongVO.setType(ImBodyEnum.MESSAGE.getCode());pongVO.setContent(messageVO);pongVO.setTimestamp(System.currentTimeMillis());// 发送给好友Session session = WEBSOCKET_MAP.get(message.getTargetId());doSendMessage(session, pongVO);}/*** 发送消息** @param session* @param message*/private static void doSendMessage(Session session, PongVO message) {try {if (session != null) {session.getBasicRemote().sendText(JSONObject.toJSONString(message));}} catch (IOException e) {e.printStackTrace();}}
}

三、源码地址

源码地址:https://gitee.com/qiangesoft/boot-business/tree/master/boot-business-im

后续内容见下章

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

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

相关文章

OpenWRT部署web站点并结合内网穿透实现无公网ip远程访问

文章目录 前言1. 检查uhttpd安装2. 部署web站点3. 安装cpolar内网穿透4. 配置远程访问地址5. 配置固定远程地址 前言 uhttpd 是 OpenWrt/LuCI 开发者从零开始编写的 Web 服务器&#xff0c;目的是成为优秀稳定的、适合嵌入式设备的轻量级任务的 HTTP 服务器&#xff0c;并且和…

day38打卡

day38打卡 509. 斐波那契数 状态表示&#xff1a; ​ 第i个数的斐波那契数是dp[i] 状态转移方程 ​ 见题目&#xff1a;dp[i] dp[i-1] dp[i-2] 初始化 ​ 见题目&#xff0c;dp[0] 0&#xff0c;dp[1] 1&#xff0c;本题用两个变量代替即可。 填表顺序 ​ 从左到右…

接口超时-千篇一律的重试和优化

我以为我当甲方爸爸了&#xff0c;结果作为一个程序员在哪里工作能拿出甲方爸爸的硬气来呢&#xff1f;别个是真的不重视啊。调用别人接口&#xff0c;有问题别人是真不回啊&#xff0c;一个问题用了一天解决&#xff1f; 接口超时了&#xff0c;半分钟的超时时间都没够用 半…

uniapp微信小程序-项目实战修改密码

图标是使用uview里面的图标&#xff0c;icfont也可以 以下是所有代码 <template><view><!-- 密码三个 --><view class"password" v-for"(item,index) in userList"><view class"contentuser"><view class&qu…

几个常见的C/C++语言冷知识

当涉及到C/C语言时&#xff0c;有一些冷知识可能并不为人所熟知&#xff0c;但却可以让你更深入地理解这门古老而强大的编程语言。以下是一些有趣的C/C语言冷知识。 1. 数组的下标可以是负数 在我们日常的C语言编程中&#xff0c;数组是一个非常常见的数据结构。我们习惯性地使…

DIY赴美生子必看~

现在选择赴美生子的宝妈越来越多&#xff0c;很多开始选择半DIY的形式&#xff0c;那么有哪些细节需要注意呢? 说中文的医生更方便? 这条对于DIY赴美生子者来说很关键&#xff0c;不要认为你的日常英文沟通没问题&#xff0c;涉及一些医学术语时&#xff0c;能否顺畅地与医生…

ai写作软件手机版哪个好用?

ai写作软件手机版哪个好用&#xff1f;随着技术的发展&#xff0c;越来越多的ai写作软件出现在互联网上&#xff0c;当然&#xff0c;这取决于人们对它的需求很大&#xff0c;ai写作软件可以帮助大家完成文章自动写作&#xff0c;也可以帮助大家生成优质的文案&#xff0c;同时…

提升网络质量:UDPspeeder 实现网络优化与提速

提升网络质量&#xff1a;UDPspeeder 实现网络优化与提速 背景与意义原理与功能使用方法未来展望相关链接服务 在当今高度互联的网络环境下&#xff0c;网络质量的优化和提速对于用户体验至关重要。针对高延迟和丢包率较高的网络链路&#xff0c;UDPspeeder 提供了一种前向纠错…

淘宝代购系统;海外代购系统;代购程序,代购系统源码PHP前端源码

代购业务近年兴起的一种购物模式&#xff0c;是帮国外客户购买中国商品。主要通过外贸代购模式&#xff0c;把淘宝、 天猫等电商平台的全站商品通过API接入到你的网站上&#xff0c;瞬间就可以架设一个有数亿产品的大型网上商城&#xff0c;而且可以 把这些中文的商品全部自动翻…

Shopee平台文具选品策略大揭秘:打造畅销产品,提升市场竞争力

在Shopee平台上销售文具类商品&#xff0c;是许多卖家追求的目标。然而&#xff0c;要在激烈的市场竞争中脱颖而出&#xff0c;并取得可观的销售业绩&#xff0c;需要制定一系列有效的选品策略。以下是一些在Shopee平台上进行文具选品时的关键策略&#xff0c;帮助卖家提高产品…

【杭州游戏业:创业热土,政策先行】

在前面的文章中&#xff0c;我们探讨了上海、北京、广州、深圳等城市的游戏产业现状。现在&#xff0c;我们切换视角&#xff0c;来看看另一个游戏创业热土——杭州的发展情况 最近第19届亚运会在杭州举办&#xff0c;本次亚运会上&#xff0c;电子竞技首次获准列为正式比赛项…

人脸美型SDK解决方案,包括瘦脸、大眼、瘦鼻等功能

为了满足市场不断升级的美颜需求&#xff0c;美摄科技凭借其在人脸识别与图像处理领域的深厚积累&#xff0c;推出了一款高效且易集成的人脸美型SDK解决方案。该方案旨在通过先进的算法和丰富的调节功能&#xff0c;帮助企业客户快速实现用户脸部形状的精准美化&#xff0c;进而…

一文看懂FAN73893MX 三相半桥门极驱动集成电路的选择

FAN73893MX产品概述&#xff1a; 是一款单片三相半桥栅极驱动 IC&#xff0c;设计用于高压、高速驱动 MOSFET 和 IGBT&#xff0c;工作电压高达 600 V。Fairchild 的高压工艺和共模噪声消除技术可以保证高端驱动器在高 dv/dt 噪声环境下稳定工作。先进的电平转换电路使高端栅极…

iOS面试:1.计算机网络

一、HTTP 1.1 HTTP介绍 HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超文本传输协议&#xff09;是一种用于传输超文本数据&#xff08;如 HTML 文档、图片、视频等&#xff09;的应用层协议&#xff0c;是互联网上应用最为广泛的协议之一。HTTP协议建立在TCP协…

深度学习????????

深度学习是人工智能领域的一个重要分支&#xff0c;它利用神经网络模拟人类大脑的学习过程&#xff0c;通过大量数据训练模型&#xff0c;使其能够自动提取特征、识别模式、进行分类和预测等任务。近年来&#xff0c;深度学习在多个领域取得了显著的进展&#xff0c;尤其在自然…

国产嵌入式教学实验箱操作教程:2-13 定时器控制实验

一、实验目的 熟悉定时器的基本结构&#xff0c;学习定时器的功能和控制方法&#xff0c;并实现基于定时器中断方式控制程序。 二、实验原理 定时器 TMS320CC6748有4个定时器/计数器&#xff0c;均可配置为64位计数器、两个独立32位计数器及自动重装32位计数器&#xff0c;…

git_新建仓库提交旧项目

将旧项目提交到新Git仓库中的步骤 1、首先&#xff0c;在本地将旧项目文件夹初始化为一个Git仓库&#xff1a; cd /path/to/old_project git init2、将旧项目的文件添加到Git仓库中&#xff0c;并提交更改&#xff1a; git add . git commit -m "Initial commit of old…

C++笔记:OOP三大特性之继承

文章目录 一、继承的概念和定义1.1 概念1.2 定义格式1.3 继承关系和访问限定符 二、基类和派生类对象赋值兼容转换2.1 类型转换存在临时对象的意义2.2 赋值兼容转换不会产生临时变量 三、继承中的作用域四、派生类中的默认成员函数4.1 构造4.2 拷贝构造4.3 赋值重载4.4 析构 五…

行政窗口为什么要开展神秘顾客调查

在竞争日益激烈的服务市场中&#xff0c;行政窗口作为公共服务的直接提供者&#xff0c;其服务质量的好坏直接关系到政府的形象和公众对政府的信任度。为了更好地满足市民的需求&#xff0c;提升服务质量&#xff0c;开展神秘顾客调查显得尤为重要。神秘顾客调查的必要性包括以…

C++如何避免float误差?

C如何避免float误差&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「c的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; …