聊天广场(Vue+WebSocket+SpringBoot)

由于心血来潮想要做个聊天室项目 ,但是仔细找了一下相关教程,却发现这么多的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.mfbz.cn/a/778283.html

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

相关文章

大数据中的常见数据问题:独断脏

想象你刚刚入职一家声称正在进行"数字化转型"的大型企业,担任大数据开发工程师。在入职的第一周,你满怀热情,迫不及待地想要大展拳脚,用你的技能来推动公司的数据驱动决策。 目录 大数据中的常见数据问题1. 独 - 数据孤岛2. 断 - 数据价值链断层3. 缺 - 标准、治理…

并口、串口和GPIO口区别

并口 并行接口,简称并口。并口采用的是25针D形接头。所谓“并行”,是指8位数据同时通过并行线进行传送,这样数据传送速度大大提高,但并行传送的线路长度受到限制,因为长度增加,干扰就会增加,数据也就容易出错,目前,并行接口主要作为打印机端口等。 并口的工作模式 …

ctfshow web 36d 练手赛

不知所措.jpg 没啥用然后测试了网站可以使用php伪达到目的 ?filephp://filter/convert.base64-encode/resourcetest/../index.<?php error_reporting(0); $file$_GET[file]; $file$file.php; echo $file."<br />"; if(preg_match(/test/is,$file)){inclu…

安全测试之使用Docker搭建SQL注入安全测试平台sqli-labs

1 搜索镜像 docker search sqli-labs 2 拉取镜像 docker pull acgpiano/sqli-labs 3 创建docker容器 docker run -d --name sqli-labs -p 10012:80 acgpiano/sqli-labs 4 访问测试平台网站 若直接使用虚拟机&#xff0c;则直接通过ip端口号访问若通过配置域名&#xff0…

【论文笔记】UniST:通用预训练城市时空预测模型

目录 写在前面1. 通用时空模型的挑战与能力特性2. 构建通用时空模型UniST2.1 大规模时空预训练2.2 时空知识规则引导提示学习 3. UniST的实验与分析3.1 模型预测效果3.2其他实验分析 写在前面 文章标题&#xff1a;UniST: A Prompt-Empowered Universal Model for Urban Spati…

基于SpringBoot+Vue的招生管理系统(带1w+文档)

基于SpringBootVue的招生管理系统(带1w文档&#xff09; 通过招生管理系统的研究可以更好地理解系统开发的意义&#xff0c;而且也有利于发展更多的智能系统&#xff0c;解决了人才的供给和需求的平衡问题&#xff0c;招生管理系统的开发建设&#xff0c;由于其开发周期短&…

linux centos 安装niginx并且添加ssl(https)模块

文章目录 前言一、nginx安装教程1.流程步骤 总结 前言 一、nginx安装教程 1.流程步骤 代码如下&#xff08;示例&#xff09;&#xff1a; 1.先下载linux安装包 2.解压安装命令 sudo tar -zxvf nginx-1.20.1.tar.gz3.进入解压后的目录 sudo cd nginx-1.20.14.安装 sudo y…

AntDesign上传组件upload二次封装+全局上传hook使用

文章目录 前言a-upload组件二次封装1. 功能分析2. 代码详细注释3. 使用到的全局上传hook代码4. 使用方式5. 效果展示 总结 前言 在项目中&#xff0c;ant-design是我们常用的UI库之一&#xff0c;今天就来二次封装常用的组件a-upload批量上传组件,让它用起来更方便。 a-uploa…

C++ | Leetcode C++题解之第213题打家劫舍II

题目&#xff1a; 题解&#xff1a; class Solution { public:int robRange(vector<int>& nums, int start, int end) {int first nums[start], second max(nums[start], nums[start 1]);for (int i start 2; i < end; i) {int temp second;second max(fi…

Nacos服务注册总流程(源码分析)

文章目录 服务注册NacosClient找看源码入口NacosClient服务注册源码NacosServer处理服务注册 服务注册 服务注册 在线流程图 NacosClient找看源码入口 我们启动一个微服务&#xff0c;引入nacos客户端的依赖 <dependency><groupId>com.alibaba.cloud</groupI…

工作两年后,我如何看待设计模式

在软件工程中&#xff0c;设计模式是经过反复验证的最佳实践&#xff0c;用于解决在软件设计中经常遇到的一类问题。它们为开发者提供了一种通用的解决方案和语言&#xff0c;使得复杂的编程问题得以简化&#xff0c;代码结构更加清晰&#xff0c;可维护性大大提高。简而言之&a…

PostgreSQL 如何优化存储过程的执行效率?

文章目录 一、查询优化1. 正确使用索引2. 避免不必要的全表扫描3. 使用合适的连接方式4. 优化子查询 二、参数传递1. 避免传递大对象2. 参数类型匹配 三、减少数据量处理1. 限制返回结果集2. 提前筛选数据 四、优化逻辑结构1. 分解复杂的存储过程2. 避免过度使用游标 五、事务处…

隐私计算实训营第二期第七课:XGB算法与SGB算法开发实践

隐私计算实训营第二期-第七课 第七课&#xff1a;XGB算法与SGB算法开发实践1 决策树模型1.1 决策树的训练和预测过程1.2 决策树的发展过程 2 GBDT模型2.1 Boosting核心思想2.2 GBDT原理 3 XGB模型3.1 XGB核心思想3.2 XGB优点 3 隐语纵向树模型3.1 数据纵向分割3.2 隐私保护的树…

本地部署到服务器上的资源路径问题

本地部署到服务器上的资源路径问题 服务器端的源代码的静态资源目录层级 当使用Thymeleaf时&#xff0c;在templates的目录下为返回的html页面&#xff0c;下面以两个例子解释当将代码部署到tomcat时访问资源的路径配置问题 例子一 index.html&#xff08;在templates的根目录…

EtherCAT转Profinet网关配置说明第二讲:上位机软件配置

EtherCAT协议转Profinet协议网关模块&#xff08;XD-ECPNS20&#xff09;&#xff0c;不仅可以实现数据之间的通信&#xff0c;还可以实现不同系统之间的数据共享。EtherCAT协议转Profinet协议网关模块&#xff08;XD-ECPNS20&#xff09;具有高速传输的特点&#xff0c;因此通…

安卓安全概述

安卓安全概述 1.Android系统概述2.Android系统安全概述3.Android系统的安全机制应用程序框架安全机制内核安全机制运行环境安全机制 4.Android反编译工具 1.Android系统概述 Android采用层次化系统架构&#xff0c;Google官方公布的标准架构如图所示&#xff0c;自顶而下划分为…

vue事件处理v-on或@

事件处理v-on或 我们可以使用v-on指令&#xff08;简写&#xff09;来监听DOM事件&#xff0c;并在事件触发时执行对应的Javascript。用法&#xff1a;v-on:click"methodName"或click"hander" 事件处理器的值可以是&#xff1a; 内敛事件处理器&#xff1…

【MindSpore学习打卡】应用实践-自然语言处理-基于RNN的情感分类:使用MindSpore实现IMDB影评分类

情感分类是自然语言处理&#xff08;NLP&#xff09;中的一个经典任务&#xff0c;广泛应用于社交媒体分析、市场调研和客户反馈等领域。本篇博客将带领大家使用MindSpore框架&#xff0c;基于RNN&#xff08;循环神经网络&#xff09;实现一个情感分类模型。我们将详细介绍数据…

【数据结构(邓俊辉)学习笔记】高级搜索树01——伸展树

文章目录 1. 逐层伸展1. 1 宽松平衡1. 2 局部性1. 3 自适应调整1. 4 逐层伸展1. 5 实例1. 6 一步一步往上爬1. 7 最坏情况 2. 双层伸展2.1 双层伸展2.2 子孙异侧2.3 子孙同侧2.4 点睛之笔2.5 折叠效果2.6 分摊性能2.7 最后一步 3 算法实现3.1 功能接口3.2 伸展算法3.3 四种情况…

uniapp H5页面设置跨域请求

记录一下本地服务在uniapp H5页面访问请求报跨域的错误 这是我在本地起的服务端口号为8088 ip大家可打开cmd 输入ipconfig 查看 第一种方法 在源码视图中配置 "devServer": {"https": false, // 是否启用 https 协议&#xff0c;默认false"port&q…