聊天广场(Vue+WebSocket+SpringBoot)

news2025/2/27 9:08:09

由于心血来潮想要做个聊天室项目 ,但是仔细找了一下相关教程,却发现这么多的WebSocket教程里面,很多都没有介绍详细,代码都有所残缺,所以这次带来一个比较完整得使用WebSocket的项目。

目录

一、效果展示

二、准备工作

一、前端框架,Vue + elementUI组件 +JsCookie

二、后端 SpringBoot + websocket包

三、前端代码

四、后端代码


一、效果展示

1.用户交流


二、准备工作

一、前端框架,Vue + elementUI组件 +JsCookie

新建一个vue项目

引入以下组件与依赖

npm i element-ui -S
npm install js-cookie

二、后端 SpringBoot + websocket包

创建SpringBoot项目后

在pom.xml文件中引入以下依赖:

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

注意项目前端为8081端口,后端为8080端口,所以先运行后端再运行前端


三、前端代码

在App.vue中即可引入以下代码:

html:

<template>

  <div id="Layout">
    <el-container>
      <el-aside width="200px">Aside</el-aside>
      <el-container>
        <el-header style="background-color: rgb(245, 245, 245); border-bottom: 1px solid grey;">
          <h3>聊天广场</h3>
        </el-header>

        <el-main style="background-color: rgb(244, 245, 247);
        min-height: 700px; max-height: 700px; ">

          <div id="chatContent" style="padding-left: 10px; line-height: normal; ">
            <!-- 循环输出对话内容 -->
            <el-scrollbar v-for="(message, index) in messages" :key="index">
              <div ref="scrollbar" v-if="message.sender !== senderName" class="chat-message" id="ChatContentCard" style=" background-color: white;
              margin-top: 20px; 
              box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)  
              ">
                <el-avatar :size="40"
                  src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"></el-avatar>
                <div class="message-content" style="width: 100%;">
                  <div style="text-align: left; text-indent: 1em;"> {{ message.sender }}</div>
                  <div id="chatContentText">{{ message.text }}</div>
                </div>
              </div>

              <div ref="scrollbar" v-if="message.sender === senderName" id="myChatContentCard">
                <el-avatar :size="40"
                  src="https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg"></el-avatar>
                <div class="message-content" style="width: 100%;">
                  <div style="text-align: left; text-indent: 1em;"> {{ message.sender }}</div>
                  <div id="chatContentText">{{ message.text }}</div>
                </div>
              </div>
            </el-scrollbar>
          </div>
        </el-main>


        <!-- 底层交互框 -->
        <el-footer style="height: 190px; background-color: rgb(244, 245, 247); ">

          <div id="Gadget" style="background-color: rgb(244, 245, 247); height: 35px; margin-bottom: 10px;">

            <el-upload class="upload-demo" ref="upload" action="https://jsonplaceholder.typicode.com/posts/"
              :on-preview="handlePreview" :on-remove="handleRemove" :file-list="fileList" :auto-upload="false" style="float: left;">

              <el-button slot="trigger" size="small" type="primary"><i class="el-icon-picture-outline-round"></i></el-button>

              <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>
              
            </el-upload>


          </div>

          <el-form @submit.native.prevent="sendMessage"
            style="background-color: rgb(244, 245, 247); height: 80%; width: 100%; position: relative;">
            <el-input v-model="messageInput" rows="4" resize="none" type="textarea" placeholder="请输入内容......."
              @keyup.enter="sendMessage" style="height: 100%; max-height: 60px; ">
            </el-input>
            <div style="text-align: right; background-color: rgb(244, 245, 247); margin-top: 34px;">
              <el-button type="primary" @click="sendMessage">发送</el-button>
            </div>

          </el-form>
        </el-footer>


      </el-container>
    </el-container>

  </div>
</template>

script:

<script>
import Cookies from 'js-cookie';


export default {
  computed: {
    senderName() {
      return Cookies.get('account') || '游客';
    }
  },
  name: 'App',
  data() {
    return {
      messages: [],
      messageInput: '',
      ws: null,
      fileList: []
    };
  },
  mounted() {
    this.initWebSocket();
  },
  beforeDestroy() {
    this.closeWebSocket();
  },
  methods: {
    initWebSocket() {
      this.ws = new WebSocket('ws://localhost:8080/chat');
      this.ws.onopen = () => {
        console.log('Connected to server.');
      };
      this.ws.onmessage = (event) => {
        try {
          let messageData;
          if (isJson(event.data)) {
            messageData = JSON.parse(event.data);
          } else {
            messageData = { text: event.data };
          }
          this.messages.push({
            sender: messageData.sender || 'Anonymous',
            text: messageData.text,
          });

          // 使用Vue.nextTick确保DOM更新后再执行滚动操作
          this.$nextTick(() => {
            // 确保scrollbar存在且已渲染
            if (this.$refs.scrollbar) {
              // 直接滚动到底部,不需要使用contentSize
              // this.$refs.scrollbar.$el.scrollTop = this.$refs.scrollbar.$el.scrollHeight;
            }
          });
        } catch (error) {
          console.error('Error parsing message:', error);
        }

      };


      // 辅助函数,检查字符串是否可能是JSON格式
      function isJson(str) {
        try {
          JSON.parse(str);
        } catch (e) {
          return false;
        }
        return true;
      }


      this.ws.onerror = (error) => {
        console.error('WebSocket error:', error);
      };
      this.ws.onclose = () => {
        console.log('Disconnected from server.');
      };
    },
    sendMessage() {
      console.log('调用sendMessage');
      const senderName = Cookies.get('account');
      if (senderName === null) {
        this.senderName = "游客";
      }
      if (this.messageInput.trim() !== '') {
        this.ws.send(JSON.stringify({ sender: senderName, text: this.messageInput }));
        this.messageInput = ''; // 清空输入框
      }
    },
    closeWebSocket() {
      if (this.ws && this.ws.readyState === WebSocket.OPEN) {
        this.ws.close();
      }
    },


// 文件上传函数
submitUpload() {
        this.$refs.upload.submit();
      },
      handleRemove(file, fileList) {
        console.log(file, fileList);
      },
      handlePreview(file) {
        console.log(file);
      }


  },
};
</script>

css:

<style scoped>
.chat-message {
  display: flex;
  align-items: center;
  margin-bottom: 10px;
}

.message-content {
  margin-left: 10px;
}

#Layout {
  line-height: normal;
}

/* 添加动画关键帧 */
@keyframes slideInFromLeft {
  0% {
    transform: translateX(-100%);
    opacity: 0;
  }

  100% {
    transform: translateX(0);
    opacity: 1;
  }
}

#ChatContentCard {
  min-height: 80px;
  width: 50%;
  /* 应用动画 */
  animation: slideInFromLeft 0.3s ease-in-out forwards;
  border-radius: 30px
}

#chatContentText {
  width: 99%;
  overflow-wrap: break-word;

}


.el-header,
.el-footer {
  background-color: #B3C0D1;
  color: #333;
  text-align: center;
  line-height: 60px;
}

.el-aside {
  background-color: #D3DCE6;
  color: #333;
  text-align: center;
  line-height: 200px;
}

.el-main {
  background-color: #E9EEF3;
  color: #333;
  text-align: center;
  line-height: 160px;
}

body>.el-container {
  margin-bottom: 40px;
}

.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
  line-height: 260px;
}

.el-container:nth-child(7) .el-aside {
  line-height: 320px;
}


#myChatContentCard{
  display: flex;
  align-items: center;
  margin-bottom: 10px;
  margin-left: 50%;

  min-height: 80px;
  width: 50%;
  /* 应用动画 */
  animation: slideInFromRight 0.3s ease-in-out forwards;
  border-radius: 30px;

  background-color: rgb(149, 236, 105);
              margin-top: 20px; 
              box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04) 

}

@keyframes slideInFromRight {
  0% {
    transform: translateX(100%);
    opacity: 0;
  }

  100% {
    transform: translateX(0);
    opacity: 1;
  }
}


</style>

注意: 这个项目中如果script需要进行修改,由于我这里完成了一个登陆系统,所以采用了对Cookie的使用,而如果只是体验的话,只需要把Cookie去掉将其改为游客+随机字符串去替代即可。

前端启动

npm run serve


四、后端代码

WebSocket配置:

1.WebSocketConfig.java

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new ChatWebSocketHandler(), "/chat").setAllowedOrigins("*");
    }
}

 2.ChatWebSocketHandler.java

@Slf4j
public class ChatWebSocketHandler extends TextWebSocketHandler {

    private static final Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<>());

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
        broadcast("欢迎新的小伙伴加入");
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        broadcast(message.getPayload());
    }

    private void broadcast(String message) {
        log.info("服务器广播数据:"+message);
        sessions.forEach(session -> {
            try {
                session.sendMessage(new TextMessage(message));
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        sessions.remove(session);

    }
}

 后端启动:

 


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

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

相关文章

python自动化办公之cryptography加密解密

目录 用到的库 实现效果 代码部分 1、加密2024.txt文件 2、解密2024.txt文件 用到的库 cryptography 实现效果 加密文件和解密文件 代码部分 1、加密2024.txt文件 # 加密 from cryptography.fernet import Fernet # 生成加密密钥 keyFernet.generate_key() cipher_s…

robotframework-appiumLibrary 应用 - 实现 app 自动化

1、安装appiumLibrary第三方库 运行pip命令&#xff1a;pip install robotframework-appiumlibrary 若已安装&#xff0c;需要更新版本可以用命令&#xff1a;pip install -U robotframework-appiumlibrary 2、安装app自动化环境。 参考我的另外一篇专门app自动化环境安装的…

baomidou多数据源切换注解@DS没有效果

baomidou多数据源切换注解DS没有效果 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.1.1</version> </dependency> ##原因 方法上有Transaction…

Android Studio Run窗口中文乱码解决办法

Android Studio Run窗口中文乱码解决办法 问题描述&#xff1a; AndroidStudio 编译项目时Run窗口中文乱码&#xff0c;如图&#xff1a; 解决方法&#xff1a; 依次打开菜单&#xff1a;Help--Edit Custom VM Options&#xff0c;打开studio64.exe.vmoptions编辑框&#xf…

2.1 tmux和vim

文章目录 前言概述tmuxvim总结 前言 开始学习的时间是 2024.7.6 ,13&#xff1a;47 概述 最好多使用&#xff0c;练成条件反射式的 直接使用终端的工具&#xff0c;可以连接到服务器&#xff0c;不需要使用本地的软件 tmux 这个主要有两个功能&#xff0c;第一个功能是分…

macOS查看系统日志的方法

1、command空格键打开搜索框&#xff0c;输入‘控制台’并打开 2、选择日志报告&#xff0c;根据日期打开自己需要的文件就可以

【vue组件库搭建05】vitePress中使用vue/antd/demo预览组件

一、vitepress使用vue及antd组件 1.安装antd之后在docs\.vitepress\theme\index.ts引入文件 // https://vitepress.dev/guide/custom-theme import { h } from vue import type { Theme } from vitepress import DefaultTheme from vitepress/theme import ./style.css impor…

智慧矿山建设规划方案(121页Word)

智慧矿山建设项目方案摘要 一、项目背景及现状分析 项目背景 随着信息技术的迅猛发展&#xff0c;智慧化、数字化已成为矿山行业转型升级的必然趋势。智慧矿山建设项目旨在通过集成先进的信息技术手段&#xff0c;实现对矿山生产、管理、安全等全过程的智能化监控与管理&…

大厂面试官赞不绝口的后端技术亮点【后端项目亮点合集(2)】

本文将持续更新~~ hello hello~ &#xff0c;这里是绝命Coding——老白~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#xff1a;绝命C…

【MYSQL】InnoDB引擎为什么选可重复读作为默认隔离级别

InnoDB引擎为什么选可重复读作为默认隔离级别 一般的DBMS系统&#xff0c;默认都会使用读提交&#xff08;Read-Comitted&#xff0c;RC&#xff09;作为默认隔离级别&#xff0c;如Oracle、SQL Server等&#xff0c;而MySQL却使用可重复读&#xff08;Read-Repeatable&#x…

一级指针 二级指针

目录 一级指针 二级指针 通过二级指针打印原数据 一级指针 一级指针就是存放变量的指针 代码演示&#xff1a; #include<stdio.h> int main() {int a 10;int* pa &a;return 0; } pa就是一级指针变量&#xff0c;是变量就会有地址&#xff0c;因为变量都是在…

Spring Boot 中的监视器是什么?有什么作用?

前言&#xff1a; 监听器相信熟悉 Spring、Spring Boot 的都知道&#xff0c;但是监视器又是什么&#xff1f;估计很多人一脸懵的状态&#xff0c;本篇分享一下 Spring Boot 的监视器。 Spring Boot 系列文章传送门 Spring Boot 启动流程源码分析&#xff08;2&#xff09; …

四端口千兆以太网交换机与 SFP 扩展功能

在数字化时代&#xff0c;网络基础设施的重要性日益凸显&#xff0c;它是企业和个人取得成功的关键支撑。配备 SFP 插槽的 4 端口千兆以太网交换机提供了一种灵活且可扩展的网络解决方案&#xff0c;能够应对快速的数据传输、低延迟以及不断增长的带宽需求。本篇文章深入探讨了…

轻松设置:服务器域名配置全攻略

目录 前置条件 在阅读本篇内容之前&#xff0c;请先确保以下物料已准备好&#xff1a; 一台公网服务器&#xff0c;服务正常运行申请完成的域名&#xff0c;在对应域名服务商后台正常DNS解析域名备案完成可选条件&#xff1a;有https访问请求时&#xff0c;需要申请SSL证书 …

Spring源码十三:非懒加载单例Bean

上一篇Spring源码十二&#xff1a;事件发布源码跟踪中&#xff0c;我们介绍了Spring中是如何使用观察者设计模式的思想来实现事件驱动开发的&#xff1a;实际上就是将所有监听器注册到广播器中&#xff0c;并通过监听该事件的监听器来处理时间的。结合前面十二篇文章我们将Spri…

关于linux服务器更改镜像后连接不上vscode问题

问题样子解决办法直接看 问题样子 问题描述&#xff1a;从centos换到ubantu后&#xff0c;xshell能直接连接上&#xff08;没有更改ssh配置信息&#xff09;&#xff0c;但是vscode连不上&#xff08;配置文件因为端口号和ip是一样的&#xff0c;也没法改&#xff09; 猜测…

这款新的 AI 语音助手击败了 OpenAI,成为 ChatGPT 最受期待的功能之一

OpenAI 推迟了 ChatGPT 令人印象深刻的语音模式&#xff0c;这让许多 AI 聊天机器人的粉丝感到不安&#xff0c;但他们现在可能已经被挖走了。法国人工智能开发商 Kyutai 推出了一款名为 Moshi 的实时语音 AI 助手。 Moshi 旨在通过语音&#xff08;如 Alexa 或 Google Assista…

207 课程表

题目 你这个学期必须选修 numCourses 门课程&#xff0c;记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出&#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示如果要学习课程 ai 则 必须 先学习课程 bi 。 …

SpringBoot新手快速入门系列教程二:MySql5.7.44的免安装版本下载和配置,以及简单的Mysql生存指令指南。

我们要如何选择MySql 目前主流的Mysql有5.0、8.0、9.0 主要区别 MySQL 5.0 发布年份&#xff1a;2005年特性&#xff1a; 基础事务支持存储过程、触发器、视图基础存储引擎&#xff08;如MyISAM、InnoDB&#xff09;外键支持基本的全文搜索性能和扩展性&#xff1a; 相对较…

3.python

闯关 3作业 本节关卡&#xff1a; 学习 python 虚拟环境的安装 Python 的基本语法 学会 vscode 远程连接 internstudio 打断点调试 python 程序