SpringBoot+Vue 整合websocket实现简单聊天窗口

效果图

1 输入临时名字充当账号使用
image-1694448636449

2 进入聊天窗口
image-1694448674599

3 发送消息 (复制一个页面,输入其他名字,方便展示效果)
image-1694448766333

4 其他窗口效果
image-1694448783698

代码实现

后端SpringBoot项目,自行创建

pom依赖

		<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.7.12</version></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.23</version></dependency>

WebSocketConfig.java

package com.dark.wsdemo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** WebSocket配置类。开启WebSocket的支持*/
@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}

WebSocketServer.java

package com.dark.wsdemo.service;import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.dark.wsdemo.vo.MessageVo;
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.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;/*** WebSocket的操作类*/
@Component
@Slf4j
@ServerEndpoint("/websocket/{name}")
public class WebSocketServer {/*** 静态变量,用来记录当前在线连接数,线程安全的类。*/private static final AtomicInteger onlineSessionClientCount = new AtomicInteger(0);/*** 存放所有在线的客户端*/private static final Map<String, Session> onlineSessionClientMap = new ConcurrentHashMap<>();/*** 连接 name 和连接会话*/private String name;@OnOpenpublic void onOpen(@PathParam("name") String name, Session session) {/*** session.getId():当前session会话会自动生成一个id,从0开始累加的。*/Session beforeSession = onlineSessionClientMap.get(name);if (beforeSession != null) {//在线数减1onlineSessionClientCount.decrementAndGet();log.info("连接已存在,关闭之前的连接 ==> session_id = {}, name = {}。", beforeSession.getId(), name);//通知之前其他地方连接被挤掉sendToOne(name, "您的账号在其他地方登录,您被迫下线。");// 从 Map中移除onlineSessionClientMap.remove(name);//关闭之前的连接try {beforeSession.close();} catch (Exception e) {log.error("关闭之前的连接异常,异常信息为:{}", e.getMessage());}}log.info("连接建立中 ==> session_id = {}, name = {}", session.getId(), name);onlineSessionClientMap.put(name, session);//在线数加1onlineSessionClientCount.incrementAndGet();this.name = name;sendToOne(name, "连接成功");log.info("连接建立成功,当前在线数为:{} ==> 开始监听新连接:session_id = {}, name = {}。", onlineSessionClientCount, session.getId(), name);}@OnClosepublic void onClose(@PathParam("name") String name, Session session) {if (name == null || name.equals("")) {name = this.name;}// 从 Map中移除onlineSessionClientMap.remove(name);//在线数减1onlineSessionClientCount.decrementAndGet();log.info("连接关闭成功,当前在线数为:{} ==> 关闭该连接信息:session_id = {}, name = {}。", onlineSessionClientCount, session.getId(), name);}@OnMessagepublic void onMessage(String message, Session session) {JSONObject jsonObject = JSON.parseObject(message);String toname = jsonObject.getString("name");String msg = jsonObject.getString("message");log.info("服务端收到客户端消息 ==> fromname = {}, toname = {}, message = {}", name, toname, message);/*** 模拟约定:如果未指定name信息,则群发,否则就单独发送*/if (toname == null || toname == "" || "".equalsIgnoreCase(toname)) {sendToAll(msg);} else {sendToOne(toname, msg);}}/*** 发生错误调用的方法** @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("WebSocket发生错误,错误信息为:" + error.getMessage());error.printStackTrace();}/*** 群发消息** @param message 消息*/private void sendToAll(String message) {// 遍历在线map集合onlineSessionClientMap.forEach((onlineName, toSession) -> {// 排除掉自己if (!name.equalsIgnoreCase(onlineName)) {log.info("服务端给客户端群发消息 ==> name = {}, toname = {}, message = {}", name, onlineName, message);MessageVo messageVo = new MessageVo();messageVo.setFrom(name);messageVo.setDate(new Date());messageVo.setMessage(message);toSession.getAsyncRemote().sendText(JSON.toJSONString(messageVo));}});}/*** 指定发送消息** @param toName* @param message*/private void sendToOne(String toName, String message) {// 通过name查询map中是否存在Session toSession = onlineSessionClientMap.get(toName);if (toSession == null) {log.error("服务端给客户端发送消息 ==> toname = {} 不存在, message = {}", toName, message);return;}// 异步发送log.info("服务端给客户端发送消息 ==> toname = {}, message = {}", toName, message);MessageVo messageVo = new MessageVo();messageVo.setFrom(name);messageVo.setDate(new Date());messageVo.setMessage(message);toSession.getAsyncRemote().sendText(JSON.toJSONString(messageVo));}}

MessageVo.java

package com.dark.wsdemo.vo;import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;import java.util.Date;@Data
public class MessageVo {private String from;//json时候格式化为时间格式@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date date;private String message;
}

Vue代码实现

App.vue

<template><div id="app"><!-- Modal Dialog --><div class="modal" v-if="!username"><div class="modal-content"><h2>请输入你的名字</h2><input type="text" v-model="inputUsername" /><button @click="setUsername">确定</button></div></div><!-- Chat Box --><div class="chat-box" v-if="username"><div class="chat-history"><div v-for="msg in messages" :key="msg.id" :class="[msg.type, 'message']"><div class="info"><span class="from">{{ msg.from }}</span><span class="date">{{ msg.date }}</span></div><div class="bubble">{{ msg.message }}</div></div></div><div class="chat-input"><input type="text" v-model="inputMessage" @keyup.enter="sendMessage" placeholder="请输入消息..."/><button @click="sendMessage">发送</button></div></div></div>
</template><script>
export default {data() {return {inputMessage: '',inputUsername: '',messages: [],username: '',ws: null,};},methods: {setUsername() {if (this.inputUsername.trim() === '') return;this.username = this.inputUsername.trim();this.ws = new WebSocket(`ws://localhost:8081/websocket/${this.username}`);this.ws.addEventListener('message', (event) => {const data = JSON.parse(event.data);this.messages.push({ ...data, type: 'left', id: this.messages.length });});},sendMessage() {if (this.inputMessage.trim() === '') return;const message = {from: this.username,date: new Date().toLocaleString(),message: this.inputMessage.trim(),};this.ws.send(JSON.stringify(message));this.messages.push({ ...message, type: 'right', id: this.messages.length });this.inputMessage = '';},},
};
</script><style>
/* Modal Styles */
.modal {display: flex;justify-content: center;align-items: center;position: fixed;left: 0;top: 0;width: 100%;height: 100%;background-color: rgba(0, 0, 0, 0.5);z-index: 9999;
}.modal-content {background-color: #fff;padding: 20px;width: 300px;text-align: center;border-radius: 10px;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}/* Chat Box Styles */
#app {background-color: #f2f2f2;display: flex;justify-content: center;align-items: center;height: 100vh;margin: 0;font-family: Arial, sans-serif;
}.chat-box {box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);width: 300px;height: 400px;border-radius: 8px;overflow: hidden;display: flex;flex-direction: column;
}.chat-history {flex: 1;overflow-y: auto;padding: 10px;background-color: #fff;
}.message {padding: 5px 0;
}.info {font-size: 12px;color: gray;margin-bottom: 4px;
}.left .bubble {background-color: #e6e6e6;border-radius: 15px;padding: 12px;display: inline-block;
}.right .bubble {background-color: #007bff;color: white;border-radius: 15px;padding: 12px;display: inline-block;margin-left: auto;
}.chat-input {display: flex;padding: 10px;background-color: #f7f7f7;border-top: 1px solid #ccc;
}input {flex: 1;padding: 8px;border: 1px solid #ccc;border-radius: 4px;margin-right: 10px;
}button {padding: 10px 20px;background-color: #007bff;color: white;border: none;border-radius: 4px;cursor: pointer;
}button:hover {background-color: #0056b3;
}
</style>

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

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

相关文章

有效回文字符串(Valid palindrome)

题目描述 思路分析 代码实践 java: public class Solutation1 {//定义一个方法&#xff0c;判断是否是有效数字或者字母private static boolean isValid(char c) {//如果不是字母或者数字&#xff0c;那就返回一个flase//这里调用了Character类里面的方法return Character.i…

Python - PyQt6、QDesigner、pyuic5-tool 安装使用

Python 开发可视化界面可以使用原生的 tkinter&#xff0c;但是原生框架使用起来颇为不方便&#xff0c;所以最流行的还是QT UI框架&#xff0c;QT是使用C语言开发&#xff0c;Python 想使用需要对其进行封装&#xff0c;所以就出现了PyQt框架&#xff0c;这个框架使用极其方便…

element的el-select给下拉框添加背景

第一步 :popper-append-to-body"false" <el-selectv-model"value"placeholder"请选择":popper-append-to-body"false"><el-optionv-for"item in options":key"item.value":label"item.label&quo…

【算法专题突破】双指针 - 最大连续1的个数 III(11)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;1004. 最大连续1的个数 III - 力扣&#xff08;Leetcode&#xff09; 这道题不难理解&#xff0c;其实就是求出最长的连续是1的子数组&#xff0c; 但是&#xff0c;他支…

Spring Reactive:响应式编程与WebFlux的深度探索

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

2023-简单点-什么是protobuf?

protobuf mother: 谷歌 作用 序列化 人话&#xff1a; 存储数据的一种结构 优势在&#xff1f; 类型安全 易用性好 序列化/反序列性能好 兼容性好 不仅可以定义结构体&#xff0c;还可以定义rpc服务接口 劣势在&#xff1f; 可读性较差&#xff1a;没有schema的情况下&a…

Linux--进程--进程-父进程退出

1.进程退出函数 进程退出分为正常退出&异常退出 正常退出&#xff1a; 1、main函数调用return 2、进程调用exit(),标准c库 3、进程调用_exit()或者_Exit(),属于系统调用 补充 1、进程最后一个线程返回 2、最后一个线程调用pthread_exit 异常退出&#xff1a; 1、调用abo…

【开箱即用】开发了一个基于环信IM聊天室的Vue3插件,从而快速实现仿直播间聊天窗功能

前言 由于看到有部分的需求为在页面层&#xff0c;快速的引入一个包&#xff0c;并且以简单的配置&#xff0c;就可以快速实现一个聊天窗口&#xff0c;因此尝试以 Vue3 插件的形式开发一个轻量的聊天窗口。 这次简单分享一下此插件的实现思路&#xff0c;以及实现过程&#xf…

OpenCV(二十二):均值滤波、方框滤波和高斯滤波

目录 1.均值滤波 2.方框滤波 3.高斯滤波 1.均值滤波 OpenCV中的均值滤波&#xff08;Mean Filter&#xff09;是一种简单的滤波技术&#xff0c;用于平滑图像并减少噪声。它的原理非常简单&#xff1a;对于每个像素&#xff0c;将其与其周围邻域内像素的平均值作为新的像素值…

5.9.Webrtc线程事件处理

在前面的课程中呢&#xff0c;我已经向你介绍了事件处理的一些基础知识&#xff0c;那今天呢&#xff0c;我们再来看一下外边儿rtc下事件处理的基本逻辑是什么&#xff1f; 那首先呢&#xff0c;我们来看一下事件是如何协调线程工作的&#xff0c;那就如果这张图所展示的有两个…

简单5步骤搞定windows server2019 配置IIS支持PHP

测试成功&#xff0c;记录一笔&#xff0c;感谢网上各位大佬的技术支持。 一、安装vcredist_x64.exe 否则可能会出现 FastCGI进程意外退出 二、IIS开启CGI&#xff08;可能需要重启&#xff09; 控制面板&#xff0c;启用或关闭windows程序&#xff0c;IIS--应用程序开发--CGI…

java封装国密SM4为 jar包,PHP调用

java封装国密SM4为 jar包,PHP调用 创建java工程引入SM4 jar包封装CMD可调用jar包PHP 传参调用刚用java弄了个class给php调用,本以为项目上用到java封装功能的事情就结束了,没想到又来了java的加密需求,这玩意上头,毕竟不是强项,没办法,只好再次封装。 但是这次的有点不…

CSS笔记(黑马程序员pink老师前端)盒子阴影,文字阴影

盒子阴影 属性值为box-shadow,盒子阴影不占空间,不影响盒子之间的距离. 值说明h-shadow必需,水平阴影位置,允许为负值v-shadow必需,水平阴影位置,允许为负值blur可选,模糊距离,数值越大影子越模糊spread可选,影子的尺寸color可选,影子的颜色inset可选, 将外阴影改为内阴影(省…

GO语言网络编程(并发编程)Goroutine池

GO语言网络编程&#xff08;并发编程&#xff09;Goroutine池 1. Goroutine池 1.1.1. worker pool&#xff08;goroutine池&#xff09; 本质上是生产者消费者模型可以有效控制goroutine数量&#xff0c;防止暴涨需求&#xff1a; 计算一个数字的各个位数之和&#xff0c;例…

router-link 和 router-view的区别

router-link 实现路由之间的跳转 router-view&#xff08;路由出口组件 -> 渲染路径匹配到的视图组件&#xff09; 当你访问的地址与路由path相符时&#xff0c;会将指定的组件替换该router-view router-link router-link 点击实现路由跳转&#xff0c;to属性指向目标地址&…

VM+Ubuntu+Xshell+Xftp安装教程

目录 VM17安装教程 检查网络连接 Ubuntu环境搭建 UBUNTU 系统配置 1、 SSH 服务器配置 服务端&#xff08;必须&#xff09; 1.安装 ssh 服务端 2.确认 sshserver 是否启动了&#xff08;看见 sshd 说明已启动&#xff09; 3.启动 sshserver 4.SSH 配置&#xff08;如果…

【科普向】Jmeter 如何测试接口保姆式教程

现在对测试人员的要求越来越高&#xff0c;不仅仅要做好功能测试&#xff0c;对接口测试的需求也越来越多&#xff01;所以也越来越多的同学问&#xff0c;怎样才能做好接口测试&#xff1f; 要真正的做好接口测试&#xff0c;并且弄懂如何测试接口&#xff0c;需要从如下几个…

【数据结构面试题】栈与队列的相互实现

目录 1.队列实现栈 1.1创建栈 1.2判断是否为空 1.3入栈 1.4出栈 1.5获取栈顶元素 1.6完整代码 2. 用栈实现队列 2.1创建队列 2.2判断是否为空 2.3入队列 2.4出队列 2.5获取队头元素 2.6完整代码 1.队列实现栈 用队列实现栈https://leetcode.cn/problems/impleme…

软件设计模式(三):责任链模式

前言 前面荔枝梳理了有关单例模式、策略模式的相关知识&#xff0c;这篇文章荔枝将沿用之前的写法根据示例demo来体会这种责任链设计模式&#xff0c;希望对有需要的小伙伴有帮助吧哈哈哈哈哈哈~~~ 文章目录 前言 责任链模式 1 简单场景 2 责任链模式理解 3 Java下servl…

【MFC】实现简单UDP通信

创建项目&#xff0c;初始化套接字 创建一个基于对话框的MFC项目&#xff08;名称为UDP&#xff09;&#xff0c;高级功能选中Windows套接字 这个时候在CUDP类的InitInstance()方法中就会出现这样的代码用来初始化套接字 if (!AfxSocketInit()) {AfxMessageBox(IDP_SOCKETS_…