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

相关文章

Spring 缓存在项目中的使用详解

《Spring缓存在项目中的使用详解》Spring缓存机制,Cache接口为缓存的组件规范定义,包扩缓存的各种操作(添加缓存、删除缓存、修改缓存等),本文给大家介绍Spring缓存在项目中的使用... 目录1.Spring 缓存机制介绍2.Spring 缓存用到的概念Ⅰ.两个接口Ⅱ.三个注解(方法层次)Ⅲ.

Spring Boot 整合 Redis 实现数据缓存案例详解

《SpringBoot整合Redis实现数据缓存案例详解》Springboot缓存,默认使用的是ConcurrentMap的方式来实现的,然而我们在项目中并不会这么使用,本文介绍SpringB... 目录1.添加 Maven 依赖2.配置Redis属性3.创建 redisCacheManager4.使用Sp

Spring Cache注解@Cacheable的九个属性详解

《SpringCache注解@Cacheable的九个属性详解》在@Cacheable注解的使用中,共有9个属性供我们来使用,这9个属性分别是:value、cacheNames、key、key... 目录1.value/cacheNames 属性2.key属性3.keyGeneratjavascriptor

redis在spring boot中异常退出的问题解决方案

《redis在springboot中异常退出的问题解决方案》:本文主要介绍redis在springboot中异常退出的问题解决方案,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴... 目录问题:解决 问题根源️ 解决方案1. 异步处理 + 提前ACK(关键步骤)2. 调整Redis消费者组

一文教你Java如何快速构建项目骨架

《一文教你Java如何快速构建项目骨架》在Java项目开发过程中,构建项目骨架是一项繁琐但又基础重要的工作,Java领域有许多代码生成工具可以帮助我们快速完成这一任务,下面就跟随小编一起来了解下... 目录一、代码生成工具概述常用 Java 代码生成工具简介代码生成工具的优势二、使用 MyBATis Gen

springboot项目redis缓存异常实战案例详解(提供解决方案)

《springboot项目redis缓存异常实战案例详解(提供解决方案)》redis基本上是高并发场景上会用到的一个高性能的key-value数据库,属于nosql类型,一般用作于缓存,一般是结合数据... 目录缓存异常实践案例缓存穿透问题缓存击穿问题(其中也解决了穿透问题)完整代码缓存异常实践案例Red

CSS 样式表的四种应用方式及css注释的应用小结

《CSS样式表的四种应用方式及css注释的应用小结》:本文主要介绍了CSS样式表的四种应用方式及css注释的应用小结,本文通过实例代码给大家介绍的非常详细,详细内容请阅读本文,希望能对你有所帮助... 一、外部 css(推荐方式)定义:将 CSS 代码保存为独立的 .css 文件,通过 <link> 标签

SpringCloud整合MQ实现消息总线服务方式

《SpringCloud整合MQ实现消息总线服务方式》:本文主要介绍SpringCloud整合MQ实现消息总线服务方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、背景介绍二、方案实践三、升级版总结一、背景介绍每当修改配置文件内容,如果需要客户端也同步更新,

java中XML的使用全过程

《java中XML的使用全过程》:本文主要介绍java中XML的使用全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录什么是XML特点XML作用XML的编写语法基本语法特殊字符编写约束XML的书写格式DTD文档schema文档解析XML的方法​​DOM解析XM

Java 的 Condition 接口与等待通知机制详解

《Java的Condition接口与等待通知机制详解》在Java并发编程里,实现线程间的协作与同步是极为关键的任务,本文将深入探究Condition接口及其背后的等待通知机制,感兴趣的朋友一起看... 目录一、引言二、Condition 接口概述2.1 基本概念2.2 与 Object 类等待通知方法的区别