vue+websocket实现即时聊天平台

目录

1 什么是websocket

2 实现步骤

2.1 导入依赖

2.2 编写代码


1 什么是websocket

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它主要用于在客户端和服务器之间建立持久的连接,允许实时数据交换。WebSocket 的设计目的是为了提高 Web 应用程序的交互性,减少延迟和带宽的使用。

  • 全双工通信:客户端和服务器可以同时发送和接收数据,而不需要等待对方完成发送。

  • 持久连接:建立一次连接后,可以保持该连接,直到主动关闭。这比传统的 HTTP 请求/响应模型更加高效。

  • 低延迟:由于不需要为每个请求建立新的连接,WebSocket 可以显著减少延迟。

  • 节省带宽:在 WebSocket 中,只有数据被发送而不需要携带大量的头部信息,这减少了带宽的消耗。

2 实现步骤

实施前提:默认在springBoot环境下实施

2.1 导入依赖

<!--WebSocket依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version> 3.3.4</version>
</dependency>

2.2 编写代码

WebSocketConfig:主要实现websocket的一些配置
package com.hyh.admin.config.websocket;

import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

/**
 * WebSocket配置
 * @author hyh
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig implements ServletContextInitializer {
    /*
     *  ServerEndpointExporter 作用
     *  这个Bean会自动注册使用@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    /*
     * 解除websocket对数据大小的限制
     * @param servletContext Servlet上下文
     *
     */
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 解除websocket对数据大小的限制
        servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize","10240000");
        servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize","10240000");
    }
}
WebSocketSingleServe:具体的实现聊天的实时代码需求
package com.hyh.admin.config.websocket;

import com.hyh.ad.common.core.domain.model.SysUser;
import com.hyh.admin.config.websocket.context.SpringBeanContext;
import com.hyh.admin.domain.Messages;
import com.hyh.admin.service.MessageService;
import com.hyh.admin.sys.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
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.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * WebSocket 单聊服务端
 */
@ServerEndpoint("/singleChat/{username}")
@Component
public class WebSocketSingleServe implements InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(WebSocketSingleServe.class);

    // 记录当前在线的连接
    public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(@PathParam("username") String username, Session session) {
        // 将用户的session放入map中
        session.getUserProperties().put("username", username);
        sessionMap.put(username, session);
        log.info("用户:{}",session.getUserProperties().get("username"));
        log.info("用户:{} 连接成功,session:{},总数:{}", username, session.getId(), sessionMap.size());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        try {
            sessionMap.values().remove(session);
            log.info("连接关闭,session:{},总数:{}", session.getId(), sessionMap.size());
        } catch (Exception e) {
            log.error("连接关闭异常:{}", e.getMessage());
        }
    }

    /**
     * 收到客户端消息后调用的方法
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session fromSession) {
        // 假设消息格式为 "username:file:data"
        String[] parts = message.split(":", 3);
        if (parts.length == 3) {
            String targetUsername = parts[0].trim(); // 目标用户
            String type = parts[1].trim(); // 消息类型(text/file)
            String content = parts[2].trim(); // 消息内容

            log.info("收到消息:{},类型:{},内容:{}", targetUsername, type, content);
            // 根据类型处理消息
            if ("text".equals(type)) {
                // 发送文本消息
                sendMessageToUser(targetUsername, content, type);
            } else if ("image".equals(type)) {
                // 发送文件消息
                sendFileToUser(targetUsername, content, type);
            }else if ("file".equals(type)) {
                // 发送文件消息
                sendFileToUser(targetUsername, content, "file");
            }

            // 消息持久化
            String username = (String) fromSession.getUserProperties().get("username");
            saveMessage(username, targetUsername, content, type);
        }
    }



    /*
     * 消息持久化
     */
    private void saveMessage(String sendUsername, String targetUsername, String msg, String type) {
        // 保存消息
        try {
            MessageService messageService = SpringBeanContext.getContext().getBean(MessageService.class);
            ISysUserService sysUserService = SpringBeanContext.getContext().getBean(ISysUserService.class);
            SysUser targetUser = sysUserService.selectUserByUserName(targetUsername);
            Long targetUserId = targetUser.getId();
            SysUser sendUser = sysUserService.selectUserByUserName(sendUsername);
            Long userId = sendUser.getId();

            Messages messages = new Messages();
            messages.setSenderId(userId);
            messages.setReceiverId(targetUserId);
            messages.setContent(msg);
            messages.setMessageType(type); // 保存消息类型

            messageService.addMessage(messages);
            log.info("消息持久化成功");
        } catch (Exception e) {
            log.error("消息持久化失败:{}", e.getMessage());
        }
    }

    /*
     *  发送文件给用户
     */
    private void sendFileToUser(String targetUsername, String fileContent, String type) {
        Session targetSession = sessionMap.get(targetUsername);
        if (targetSession != null) {
            try {
                targetSession.getBasicRemote().sendText(type + "|" + fileContent); // 文件发送格式
                log.info("发送文件给用户:{},发送成功", targetUsername);
            } catch (IOException e) {
                log.error("发送文件失败:{}", e.getMessage());
            }
        }
    }


    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误,session:{} ,错误信息:{}", session.getId(), error);
    }

    /**
     * 服务端发送消息给指定用户
     * @param username 目标用户
     * @param message 消息内容
     */
    public void sendMessageToUser(String username, String message, String type) {
        Session session = sessionMap.get(username);
        if (session != null && session.isOpen()) {
            try {
                session.getBasicRemote().sendText(type + "|" + message);
                log.info("发送给用户:{},内容:{}", username, message);
            } catch (IOException e) {
                log.error("发送消息失败:{}", e.getMessage());
            }
        } else {
            log.warn("用户:{} 不在线,无法发送消息", username);
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("WebSocket服务端启动");
    }
}

  onopen方法主要用于连接的的方法,所有和websocket发起连接的客户端都会经过这个方法。

  onmessage方法主要用于发送消息的方法,其中定义了发送消息的格式,可以自行定义。

   前端代码:

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>聊天界面</title>
    <style>
        body { font-family: Arial, sans-serif; }
        #messages { border: 1px solid #ccc; height: 300px; overflow-y: scroll; margin-bottom: 10px; }
        input, button { margin: 5px; }
    </style>
</head>
<body>
<h2>聊天界面</h2>
<input type="text" id="targetUser" placeholder="输入目标用户名...">
<input type="text" id="message" placeholder="输入消息...">
<button id="sendBtn">发送</button>
<div id="messages"></div>
<img src="https://c-ssl.duitang.com/uploads/item/202003/27/20200327141738_ulbvu.jpg" alt="">
<script>
    const username = prompt("请输入您的用户名:"); // 获取当前用户的用户名
    const socket = new WebSocket(`ws://127.0.0.1:8088/singleChat/${username}`);

    socket.onopen = function() {
        console.log(`${username} 已连接`);
    };

    socket.onmessage = function(event) {
        const messagesDiv = document.getElementById("messages");
        messagesDiv.innerHTML += `<p>${event.data}</p>`;
        messagesDiv.scrollTop = messagesDiv.scrollHeight; // 滚动到底部
    };

    document.getElementById("sendBtn").onclick = function() {
        const targetUser = document.getElementById("targetUser").value;
        const messageInput = document.getElementById("message").value;
        const message = `${targetUser}:text:${messageInput}`; // 格式化消息
        socket.send(message);

        // 显示自己发送的消息
        const messagesDiv = document.getElementById("messages");
        messagesDiv.innerHTML += `<p>我: ${messageInput}</p>`;
        messagesDiv.scrollTop = messagesDiv.scrollHeight; // 滚动到底部

        document.getElementById("message").value = "";  // 清空输入框
    };
</script>
</body>
</html>

  

vue的部分代码和项目完整的截图为:

 this.socket = new WebSocket(
        `ws://127.0.0.1:8088/singleChat/${localStorage.getItem("username")}`
      );

      this.socket.onopen = () => {
        console.log(localStorage.getItem("username") + " 连接成功");
      };

      // 只设置一次 onmessage 处理逻辑
      this.socket.onmessage = (event) => {
        const message = event.data; // 假设格式为 "type:content"
        const parts = message.split("|"); // 按冒号分割

        if (parts.length === 2) {
          const type = parts[0].trim(); // 消息类型
          const content = parts[1].trim(); // 消息内容

          this.contactRecord.push({
            id: Date.now(), // 使用时间戳作为消息 ID
            senderId: this.user.id, // 或者其他用户的 ID
            content: content,
            messageType: type, // 添加类型
          });

          // 进度条滚动到底部
          this.scrollToBottom();
        }
      };
    },

 

需要源码的请私信我:

谢谢各位的支持!!! 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

本地跟单软件,精准跟随、实时同步 轻松实现自动跟单 MT4免费EA

指标名称&#xff1a;本地跟单软件 版本&#xff1a;MT4 ver. 2.01&#xff08;EA&#xff09; 我们经常在一些论坛或网站上看到一些朋友提供的观摩账户吧&#xff0c;可以看到别人的账户情况&#xff0c;遇到有实力的交易者&#xff0c;很是羡慕啊。 如果我们自己的账户可以…

基于stm32的智能电子称(开源)

功能演示 基于stm32的智能电子称 简介 这是最近别人让我帮他做的一个毕业设计&#xff0c;总体来说非常简单&#xff0c;半天都不需要就可以实现&#xff0c;我做完之后&#xff0c;打算开源在这里让大家进行学习&#xff0c;我们先看一下他的任务书吧: 主要内容与基本要求&am…

写过的试卷怎么打印出新的?

当您有一份已经完成的试卷&#xff0c;但又需要重新打印一份全新的试卷时&#xff0c;这似乎是一个令人头疼的问题。不用担心&#xff0c;这里为您介绍一种简便的方法——利用“扫描”或“试卷还原”的软件&#xff0c;通过拍照的方式&#xff0c;将试卷上的答案清除&#xff0…

【51单片机】串口通信原理 + 使用

学习使用的开发板&#xff1a;STC89C52RC/LE52RC 编程软件&#xff1a;Keil5 烧录软件&#xff1a;stc-isp 开发板实图&#xff1a; 文章目录 串口硬件电路UART串口相关寄存器 编码单片机通过串口发送数据电脑通过串口发送数据控制LED灯 串口 串口是一种应用十分广泛的通讯接…

动态规划 —— dp 问题-打家劫舍II

1.打家劫舍II 题目链接&#xff1a; 213. 打家劫舍 II - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/house-robber-ii/ 2. 题目解析 通过分类讨论&#xff0c;将环形问题转换为两个线性的“打家劫舍|” 当偷第一个位置的时候&#xff0c;rob1在&#…

开车去内蒙古旅游要做什么准备?

一、车辆选择与准备 车辆类型&#xff1a; 尽量选择越野车或SUV&#xff0c;这类车辆底盘高、通过性好&#xff0c;适合草原、沙漠等复杂地形。车辆检查&#xff1a; 出发前全面检查车辆&#xff0c;包括轮胎、刹车系统、发动机等&#xff0c;确保车辆状态良好。冬季出行需特别…

【题解】—— LeetCode一周小结44

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结43 28.冗余连接 II 题目链接&#xff1a;685. 冗余连接 II 在…

视频Qoe测量学习笔记(一)

目录 流媒体协议详解 RTSP&#xff1a;实时流式协议 RTCP&#xff1a;实时运输控制协议 RTP&#xff1a;实时运输协议 H.264 流媒体协议详解 RTSP&#xff1a;实时流式协议 由IETF MMusic小组开发&#xff0c;已成为互联网建议标准[RFC 2326]。RTSP本身并不传送数据&…

使用Spring Validation实现数据校验详解

目录 前言1. Spring Validation概述2. 配置Spring Validation2.1 引入依赖2.2 启用全局校验 3. 使用注解进行参数校验3.1 基本校验注解3.2 使用Pattern进行正则校验3.3 综合示例 4. 在控制器层应用校验4.1 方法参数校验4.2 自定义错误处理 5. 高级应用&#xff1a;自定义校验注…

解决阿里云三个月证书过期 免费SSL证书部署教程

相信有上线过自己的网站、小程序经验的同学深有体会&#xff0c;给服务加上 SSL 证书还挺麻烦的&#xff0c;尤其是没有运维经验的同学。本来最省事的方法是买个证书&#xff0c;但是一看价格&#xff0c;还是算了吧&#xff0c;动辄就是几万块一年。作为个人来说&#xff0c;这…

简单走近ChatGPT

目录 一、ChatGPT整体背景认知 &#xff08;一&#xff09;ChatGPT引起关注的原因 &#xff08;二&#xff09;与其他公司的竞争情况 二、NLP学习范式的发展 &#xff08;一&#xff09;规则和机器学习时期 &#xff08;二&#xff09;基于神经网络的监督学习时期 &…

恢复Ubuntu+Windows10双系统安装前状态及分区还原详细步骤

1、恢复到安装 Ubuntu 之前的状态&#xff0c;先看看系统属性 2、选择 运行 3、 输入 msinfo32 回车 4、注意查看 BIOS 模式这一栏&#xff0c;UEFI&#xff0c;这里我们以UEFI系统为例 5、下来就可以开始进行 Ubuntu 的移除操作了 6、从Windows打开网页搜索磁盘精灵&#xff0…

一文搞定 InternStudio 开发机的使用 | 书生大模型

文章目录 创建开发机使用 SSH 远程连接开发机使用密码进行 SSH 远程连接使用 VScode 进行 SSH 远程连接 端口映射核心目标开发机端口映射的工作方式使用 VScode 进行端口映射运行 hello_world.py 代码进行测试测试成功页面 参考文献 创建开发机 InternStudio控制台 这里先做测…

WindowsDocker安装到D盘,C盘太占用空间了。

Windows安装 Docker Desktop的时候,默认位置是安装在C盘,使用Docker下载的镜像文件也是保存在C盘,如果对Docker使用评率比较高的小伙伴,可能C盘空间,会被耗尽,有没有一种办法可以将Docker安装到其它磁盘,同时Docker的数据文件也保存在其他磁盘呢? 答案是有的,我们可以…

关于我、重生到500年前凭借C语言改变世界科技vlog.15——深入理解指针(4)

文章目录 1.回调函数的介绍2. qsort使用实例2.1 qsort函数介绍2.2使用 qsort 函数排序整型数据2.3使用 qsort 排序结构数据 3. qsort的模拟实现希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xff01; 1.回调函数的介绍 回调函数就是一个通过函数指针调用…

内网项目,maven本地仓库离线打包,解决Cannot access central in offline mode?

背景&#xff1a; 内网项目打包&#xff0c;解决Cannot access central in offline mode? 1、修改maven配置文件&#xff1a; localRepository改为本地仓库位置 <localRepository>D:\WorkSpace\WorkSoft\maven-repository\iwhalecloud-repository\business</loca…

笔记整理—linux驱动开发部分(8)framebuffer类设备

framebuffer显示设备。 在应用层直接抽象位向DDR中存放图片。 在操作系统中&#xff0c;将上图分为两个部分&#xff1a;驱动应用。 使用复制的方法效率十分的低&#xff0c;所以有了内存映射方法实现图片的显示。 framebuffer帧&#xff08;铺满一个屏幕&#xff09;&#xff…

第三十章 章节练习商品列表组件封装

目录 一、需求说明 二、技术要点 三、完整代码 3.1. main.js 3.2. App.vue 3.3. MyTable.vue 3.4. MyTag.vue 一、需求说明 1. my-tag 标签组件封装 (1) 双击显示输入框&#xff0c;输入框获取焦点 (2) 失去焦点&#xff0c;隐藏输入框 (3) 回显标签信息 (4) 内…

EHOME视频平台EasyCVR萤石设备视频接入平台视频诊断技术可以识别哪些视频质量问题?

EasyCVR视频监控汇聚管理平台是一款针对大中型项目设计的跨区域网络化视频监控集中管理平台。萤石设备视频接入平台EasyCVR不仅具备视频资源管理、设备管理、用户管理、运维管理和安全管理等功能&#xff0c;还支持多种主流标准协议&#xff0c;如GB28181、GB35114、RTSP/Onvif…

如何在 PyQt 中启动“绘图循环”?

在 PyQt 中实现一个“绘图循环”可以使用 定时器&#xff08;QTimer&#xff09;&#xff0c;让应用程序在指定的时间间隔内反复触发一个绘图函数。这种方法对于需要持续更新绘图&#xff08;例如动画效果&#xff09;的情况特别有用。 1、问题背景 在GUI编程中&#xff0c;我…