springboot+websocket+vue聊天室

本文主要是介绍springboot+websocket+vue聊天室,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 一、项目实现内容
  • 二、websocket
  • 三、实现过程
    • java后端
    • vue前端
    • 源代码
  • WebSocketServer调用spring容器注意事项
  • 扩展

一、项目实现内容

  1. http://localhost:8080/websocket?uid=1

在这里插入图片描述

  1. http://localhost:8080/websocket?uid=2

在这里插入图片描述

  1. http://localhost:8080/websocket?uid=3

在这里插入图片描述

二、websocket

websocket api介绍
再看这里,这个是我看介绍比较好的websocket使用

  1. websocket方法定义
    WebSocket.onclose
    用于指定连接关闭后的回调函数。
    WebSocket.onerror
    用于指定连接失败后的回调函数。
    WebSocket.onmessage
    用于指定当从服务器接受到信息时的回调函数。
    WebSocket.onopen
    用于指定连接成功后的回调函数。

  2. 先是定义websocket的处理逻辑
    在这里插入图片描述

  3. 消息流转过程
    在这里插入图片描述

三、实现过程

前提:这只是一个小demo没用到数据库,只是简单的在后端直接返回准备好的用户,但是逻辑是没有问题的,只要你的用户信息换成查数据库和将发到服务器的消息数据保存一份到数据库就行了。(CURD比较简单,逻辑明白就行)

java后端

  1. @Component注册到spring容器,交由spring控制
  2. @ServerEndpoint("/path")是和@RequestMapping("/path")差不多类似的,若是有ws协议的路上path匹配则交由该对象处理(主要是将目前的类定义成一个websocket服务器端, 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
  3. WebSocketServer的加载spring容器之前,后面有客户端连接服务器,则将WebSocketServersession、uid替换成客户端对应的存储在Map中记录起来,发送消息还得用到对应的session
    在这里插入图片描述
  4. 之后接收到客户端的消息,onMessage内可以通过webSocketMap记录的
    WebSocketServer使用session.getBasicRemote().sendText(message);发送消息message
@Component
@ServerEndpoint("/wechat/{uid}")
public class WebSocketServer {/*** 记录在线的用户数*/private static AtomicInteger onlineUserNum=new AtomicInteger(0);/*** 存储连接该服务的用户(客户端)对应的WebSocketServer (uid,WebSocketServer)*/private static Map<Integer,WebSocketServer> webSocketMap=new ConcurrentHashMap<>();/*** 与某个客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;/*** 当前连接进行用户的uid*/private int uid;/*** 连接成功后的回调函数* @param uid* @param session*/@OnOpenpublic void onOpen(@PathParam("uid")int uid,Session session){//获取当前的session、uidthis.session=session;this.uid=uid;//存储客户端对应的websocketif (!webSocketMap.containsKey(uid)){//判断这里还应该查一下数据库,但是我这里比较潦草就没做//还未连接过webSocketMap.put(uid,this);//在线人数+1onlineUserNum.incrementAndGet();}else{//已经连接过,记录新的websocketwebSocketMap.replace(uid,this);}System.out.println("用户id:"+uid+"建立连接!");}/*** 连接失败后的回调函数* @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {System.out.println("用户:"+this.uid+"连接失败,原因:"+error.getMessage());error.printStackTrace();}/*** 前提:成功建立连接*      发送过来的消息按如下的机制推送到接收的客户端* @param message* @param session*/@OnMessagepublic void onMessage(String message,Session session){System.out.println(message);if(message.isEmpty()||message==null){//消息不正常,不处理return;}//初始化消息的格式 json->自己定义的消息体Message fromMessage = JSON.parseObject(message,Message.class);if(!webSocketMap.containsKey(fromMessage.getToUid())){System.out.println("要接收的用户不在线,暂存数据库,等该用户上线在获取!");return;}//在线则直接推送数据到接收端客户端WebSocketServer webSocketServer = webSocketMap.get(fromMessage.getToUid());webSocketServer.sendMessage(message);}/*** 推送消息到客户端* @param message*/public void sendMessage(String message) {try {this.session.getBasicRemote().sendText(message);} catch (IOException e) {e.printStackTrace();}}/*** 连接关闭后的回调函数*/@OnClosepublic void onClose(){if (webSocketMap.containsKey(uid)){webSocketMap.remove(uid);//在线人数-1onlineUserNum.decrementAndGet();}}}

vue前端

下面介绍进入页面的逻辑(可以和前面的图多结合理解)

  1. localhost:8080/?uid=i 获取用户i的信息进入页面(因为没登陆注册就这样用于测试)
  2. 获取在线用户,除去本身
  3. 创建WebSocket对象连接服务器(此时服务器记录了和客户端连接时的WebSocketServer
  4. 就可以通过全局的WebSocket对象发送消息了
<template><div class="bg"><el-container class="wechat"><el-aside width="35%" style="border-right: 1px solid #fff"><!-- 自己 --><div class="item"><el-avatar:size="46":src="user.avatarUrl"style="float: left; margin-left: 2px"></el-avatar><div class="name">{{ user.nickname}}<el-tag style="margin-left: 5px" type="success">本人</el-tag></div></div><!-- 在线用户 --><divclass="item"v-for="(item1, index) in userlist":key="item1.uid"@click="selectUser(index)"><!-- 新数消息 --><el-badge:value="new_message_num[index]":max="99":hidden="!new_message_num[index] > 0"style="float: left; margin-left: 2px"><el-avatar :size="46" :src="item1.avatarUrl"></el-avatar></el-badge><div class="name">{{ item1.nickname }}</div></div></el-aside><el-main><el-container class="wechat_right"><!-- 右边顶部 --><el-header class="header">{{anotherUser != null && anotherUser.uid > 0? anotherUser.nickname: "未选择聊天对象"}}</el-header><!-- 聊天内容 --><el-main class="showChat"><div v-for="item2 in messageList[index]" :key="item2.msg"><!-- 对方发的 --><div class="leftBox" v-if="item2.FromUid == anotherUser.uid"><span style="font-size: 4px">{{ item2.time }}</span>{{ item2.msg }}</div><div class="myBr" v-if="item2.FromUid == anotherUser.uid"></div><!-- 自己发的 --><div class="rightBox" v-if="item2.FromUid == user.uid"><span style="font-size: 4px">{{ item2.time }}</span>{{ item2.msg }}</div><div class="myBr" v-if="item2.FromUid == user.uid"></div></div></el-main><!-- 输入框 --><el-main class="inputValue"><textarea v-model="inputValue" id="chat" cols="26" rows="5"></textarea><!-- 发送按钮 --><el-buttonv-if="anotherUser != null && anotherUser.uid > 0 && inputValue != ''"type="success"size="mini"roundid="send"@click="senMessage">发送</el-button></el-main></el-container></el-main></el-container></div>
</template><script>
export default {data() {return {//自己user: {},//要私信的人anotherUser: {},//在线的用户userlist: [],//要私信的人在userlist的索引位置index: 0,//消息队列集合 [本人和第一个人之间的消息集合、本人和第二个人之间的消息集合、...]messageList: [],//新消息个数集合new_message_num: [],//将要发送的内容inputValue: "",//websocketwebsocket: null,};},methods: {//获取自己被分配的信息getYourInfo(uid) {let params = new URLSearchParams();this.$axios.post("/user/getYourInfo/" + uid, params).then((res) => {this.user = res.data.data;if (res.data.code == 200) {//获取在线用户this.getUserList();}}).catch((err) => {console.error(err);});},//获取在线用户getUserList() {let params = new URLSearchParams();this.$axios.post("/user/getUserList", params).then((res) => {this.userlist = res.data.data.filter(//去掉自己(user) => user.uid !== this.user.uid);//填充消息数据 messagelist:[[]、[]...]  并且将新消息队列置为0for (let i = 0; i < this.userlist.length; i++) {this.messageList.push([]);this.new_message_num.push(0);}//将当前的客户端和服务端进行连接,并定义接收到消息的处理逻辑this.init(this.user.uid);}).catch((err) => {console.error(err);});},//选择聊天对象selectUser(index) {this.anotherUser = this.userlist[index];this.index = index;//将新消息置为0this.new_message_num[index] = 0;},//将当前的客户端和服务端进行连接,并定义接收到消息的处理逻辑init(uid) {var self = this;if (typeof WebSocket == "undefined") {console.log("您的浏览器不支持WebSocket");return;}//清除之前的记录if (this.websocket != null) {this.websocket.close();this.websocket = null;}//-----------------------连接服务器-----------------------let socketUrl = "ws://localhost:8088/wechat/" + this.user.uid;//开启WebSocket 连接this.websocket = new WebSocket(socketUrl);//指定连接成功后的回调函数this.websocket.onopen = function () {console.log("websocket已打开");};//指定连接失败后的回调函数this.websocket.onerror = function () {console.log("websocket发生了错误");};//指定当从服务器接受到信息时的回调函数this.websocket.onmessage = function (msg) {//消息体例如{"FromUid":1,"ToUid":2,"msg":"你好","time":"00:07:03"} => message对象let data = JSON.parse(msg.data);//添加到对应的消息集合中let index = data.FromUid > uid ? data.FromUid - 2 : data.FromUid - 1;self.messageList[index].push(data);//新消息数+1self.new_message_num[index]++;};//指定连接关闭后的回调函数this.websocket.onclose = function () {console.log("websocket已关闭");};},//发送信息senMessage() {//消息体例如{"FromUid":1,"ToUid":2,"msg":"你好","time":"00:07:03"}let message = {FromUid: this.user.uid,ToUid: this.anotherUser.uid,msg: this.inputValue,time: new Date().toLocaleTimeString(),};//将消息插进消息队列,显示在前端this.messageList[this.index].push(message);//将消息发送至服务器端再转发到对应的用户this.websocket.send(JSON.stringify(message));//清空一下输入框内容this.inputValue = "";},},created() {let uid = this.$route.query.uid;if (uid != undefined) {//获取被分配的用户信息this.getYourInfo(uid);}},
};
</script><style>
/*改变滚动条 */
::-webkit-scrollbar {width: 3px;border-radius: 4px;
}::-webkit-scrollbar-track {background-color: inherit;-webkit-border-radius: 4px;-moz-border-radius: 4px;border-radius: 4px;
}::-webkit-scrollbar-thumb {background-color: #c3c9cd;-webkit-border-radius: 4px;-moz-border-radius: 4px;border-radius: 4px;
}
.bg {background: url("https://s1.ax1x.com/2022/06/12/Xgr9u6.jpg") no-repeat top;background-size: cover;background-attachment: fixed;width: 100%;height: 100%;position: fixed;top: 0;left: 0;right: 0;bottom: 0;
}
.wechat {width: 60%;height: 88%;margin: 3% auto;border-radius: 20px;background-color: rgba(245, 237, 237, 0.3);
}
/*聊天框左侧 */
.item {position: relative;width: 94%;height: 50px;margin-bottom: 3%;border-bottom: 1px solid #fff;
}
.item .name {line-height: 50px;float: left;margin-left: 10px;
}
/*聊天框右侧 */.wechat_right {position: relative;width: 100%;height: 100%;
}
.header {text-align: left;height: 50px !important;
}
.showChat {width: 100%;height: 65%;
}
.inputValue {position: relative;margin: 0;padding: 0;width: 100%;height: 50%;
}
.inputValue #chat {font-size: 18px;width: 96%;height: 94%;border-radius: 20px;resize: none;background-color: rgba(245, 237, 237, 0.3);
}
#send {position: absolute;bottom: 12%;right: 6%;
}
/*展示区 */
.leftBox {float: left;max-width: 60%;padding: 8px;position: relative;font-size: 18px;border-radius: 12px;background-color: rgba(40, 208, 250, 0.76);
}
.rightBox {float: right;max-width: 60%;padding: 8px;font-size: 18px;border-radius: 12px;position: relative;background-color: rgba(101, 240, 21, 0.945);
}
.myBr {float: left;width: 100%;height: 20px;
}
.leftBox > span {left: 3px;width: 120px;position: absolute;top: -16px;
}
.rightBox > span {width: 120px;position: absolute;right: 3px;top: -16px;
}
</style>

源代码

源代码

WebSocketServer调用spring容器注意事项

WebSocket启动的时候优先于spring容器,从而导致在WebSocketServer中调用业务Service会报空指针异常
解决方法,静态初始化并提前加载bean

  1. 静态初始化
//如需要 MessageService
private static MessageService messageService;
  1. 提前加载bean
@Configuration
public class WebSocketConfig {/*** 注入ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}//通过get方法注入bean@Autowiredprotected void getMessageService(MessageService ms){WebSocketServer.messageService=ms;}
}

扩展

  1. 群发实现
  2. 多人聊天实现
  3. 语音、视屏聊天实现

相信前两个大家如果看明白上面的demo应该能做

  • 给消息设置一个状态,后端服务器接收到消息是群发之后,就将消息发送给所有的在线用户,不在线的先存数据库(或者维护一个uid数组,这样更灵活)
  • 定义一个群ID,将消息发送至群内的所有人
  • 这个我建议自己查查看

使用netty实现了上面一模一样的功能
netty+springboot+vue聊天室(需要了解netty)

这篇关于springboot+websocket+vue聊天室的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1042671

相关文章

Java中StopWatch的使用示例详解

《Java中StopWatch的使用示例详解》stopWatch是org.springframework.util包下的一个工具类,使用它可直观的输出代码执行耗时,以及执行时间百分比,这篇文章主要介绍... 目录stopWatch 是org.springframework.util 包下的一个工具类,使用它

Java进行文件格式校验的方案详解

《Java进行文件格式校验的方案详解》这篇文章主要为大家详细介绍了Java中进行文件格式校验的相关方案,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、背景异常现象原因排查用户的无心之过二、解决方案Magandroidic Number判断主流检测库对比Tika的使用区分zip

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义

Java使用Curator进行ZooKeeper操作的详细教程

《Java使用Curator进行ZooKeeper操作的详细教程》ApacheCurator是一个基于ZooKeeper的Java客户端库,它极大地简化了使用ZooKeeper的开发工作,在分布式系统... 目录1、简述2、核心功能2.1 CuratorFramework2.2 Recipes3、示例实践3

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

springboot security使用jwt认证方式

《springbootsecurity使用jwt认证方式》:本文主要介绍springbootsecurity使用jwt认证方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录前言代码示例依赖定义mapper定义用户信息的实体beansecurity相关的类提供登录接口测试提供一

Spring Boot 3.4.3 基于 Spring WebFlux 实现 SSE 功能(代码示例)

《SpringBoot3.4.3基于SpringWebFlux实现SSE功能(代码示例)》SpringBoot3.4.3结合SpringWebFlux实现SSE功能,为实时数据推送提供... 目录1. SSE 简介1.1 什么是 SSE?1.2 SSE 的优点1.3 适用场景2. Spring WebFlu

基于SpringBoot实现文件秒传功能

《基于SpringBoot实现文件秒传功能》在开发Web应用时,文件上传是一个常见需求,然而,当用户需要上传大文件或相同文件多次时,会造成带宽浪费和服务器存储冗余,此时可以使用文件秒传技术通过识别重复... 目录前言文件秒传原理代码实现1. 创建项目基础结构2. 创建上传存储代码3. 创建Result类4.

Java利用JSONPath操作JSON数据的技术指南

《Java利用JSONPath操作JSON数据的技术指南》JSONPath是一种强大的工具,用于查询和操作JSON数据,类似于SQL的语法,它为处理复杂的JSON数据结构提供了简单且高效... 目录1、简述2、什么是 jsONPath?3、Java 示例3.1 基本查询3.2 过滤查询3.3 递归搜索3.4

Tomcat版本与Java版本的关系及说明

《Tomcat版本与Java版本的关系及说明》:本文主要介绍Tomcat版本与Java版本的关系及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Tomcat版本与Java版本的关系Tomcat历史版本对应的Java版本Tomcat支持哪些版本的pythonJ