Java+Vue实现聊天室(WebSocket进阶-聊天记录)

这篇具有很好参考价值的文章主要介绍了Java+Vue实现聊天室(WebSocket进阶-聊天记录)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

废话不多说:上才艺 ^_^

要实现聊天记录的保存就要创建聊天记录表

Java+Vue实现聊天室(WebSocket进阶-聊天记录)

 建表语句

DROP TABLE IF EXISTS `user_message`;
CREATE TABLE `user_message`  (
  `from_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发送人',
  `message` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '消息',
  `to_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '接收人',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '日期'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

java引入依赖

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

配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @authoer:majinzhong
 * @Date: 2022/11/7
 * @description:
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

controller类

import com.shangfei.pojo.socket.SocketMsg;
import com.shangfei.response.WebResponse;
import com.shangfei.service.socket.WebSocketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

/**
 * @authoer:majinzhong
 * @Date: 2022/11/7

 * @Description: websocket的具体实现类
 * 使用springboot的唯一区别是要@Component声明下,而使用独立容器是由容器自己管理websocket的,
 * 但在springboot中连容器都是spring管理的。
 * 虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,
 * 所以可以用一个静态set保存起来。
 */

@RestController
public class WebSocketController {

    @Autowired
    WebSocketService webSocketService;

    /**
     * 打开发送消息页面
     * @return
     */
    @RequestMapping("/webSocketPage")
    public ModelAndView page(){
        return new ModelAndView("index");
    }

    /**
     * 获得在线人信息
    * @return
     */
    @RequestMapping("/members")
    public WebResponse members(){
        return webSocketService.members();
    }

    /**
     * 查询聊天记录
     * @return
     */
    @RequestMapping("/message")
    public WebResponse message(@RequestBody SocketMsg socketMsg){
        return webSocketService.message(socketMsg);
    }

}

service类

import cn.hutool.core.collection.CollectionUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.shangfei.mapper.plan.UserMapper;
import com.shangfei.mapper.socket.UserMessageMapper;
import com.shangfei.pojo.socket.SocketMsg;
import com.shangfei.pojo.socket.UserMessage;
import com.shangfei.response.WebResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.CrossOrigin;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @authoer:majinzhong
 * @Date: 2022/11/16
 * @description:
 */
@Component
@ServerEndpoint(value = "/websocket/{nickname}")
@CrossOrigin
@Service
public class WebSocketService {

    @Autowired
    UserMapper userMapper;

    private static UserMessageMapper userMessageMapper;

    @Autowired
    public void setLocationMapper(UserMessageMapper userMessageMapper) {
        WebSocketService.userMessageMapper = userMessageMapper;
    }

    /**
     * 新建list集合存储数据
     */
    private static List<UserMessage> messageList = new ArrayList<>();
    /**
     * 设置一次性存储数据的list的长度为固定值,每当list的长度达到固定值时,向数据库存储一次
     */
    private static final Integer LIST_SIZE = 5;
    /**
     * 用来存放每个客户端对应的MyWebSocket对象。
     **/
    private static CopyOnWriteArraySet<WebSocketService> webSocketSet = new CopyOnWriteArraySet<>();
    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     **/
    private Session session;
    /**
     * 用户名称
     **/
    private String nickname;
    /**
     * 用来记录sessionId和该session进行绑定
     **/
    private static Map<String,Session> map = new HashMap<String, Session>();

    /**
     * 查询当前在线人
     * @return
     */
    public WebResponse members(){
        Set<String> members = map.keySet();

        if(!CollectionUtil.isEmpty(members)) {
            List<Map<String,String>> userList=new ArrayList<>();
            for(String member:members){
                //通过工号查询名字
                Map<String, String> user = userMapper.selectName(member);
                if(!CollectionUtil.isEmpty(user)){
                    userList.add(user);
                }else{
                    Map<String, String> userMap = new HashMap<>();
                    userMap.put("username",member);
                    userMap.put("name","");
                    userList.add(userMap);
                }
            }
            return WebResponse.success(userList);
        }else{
            return WebResponse.success(members);
        }
    }

    /**
     * 查询聊天记录
     * @param socketMsg
     * @return
     */
    public WebResponse message(SocketMsg socketMsg) {
        List<UserMessage> userMessageList=userMessageMapper.selectByType(socketMsg);
        return WebResponse.success(userMessageList);
    }

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session,@PathParam("nickname") String nickname) {
        this.session = session;
        this.nickname=nickname;

        map.put(nickname, session);

        webSocketSet.add(this);
        System.out.println("有新连接加入:"+nickname+",当前在线人数为" + webSocketSet.size());
        broadcast("人员更新");
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        List<String> nickname = this.session.getRequestParameterMap().get("nickname");
        for(String nick:nickname) {
            map.remove(nick);
        }
        //当有人退出连接时,将集合里的信息保存到数据库
        if(messageList.size()>0){
            userMessageMapper.insertMessageList(messageList);
            //清除集合
            messageList.clear();
        }
        broadcast("人员更新");
        System.out.println("有一连接关闭!当前在线人数为" + webSocketSet.size());
    }

    /**
     * 收到客户端消息后调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session,@PathParam("nickname") String nickname) {
        System.out.println("来自客户端的消息-->"+nickname+": " + message);

        //从客户端传过来的数据是json数据,所以这里使用jackson进行转换为SocketMsg对象,
        // 然后通过socketMsg的type进行判断是单聊还是群聊,进行相应的处理:
        ObjectMapper objectMapper = new ObjectMapper();
        SocketMsg socketMsg;

        try {
            socketMsg = objectMapper.readValue(message, SocketMsg.class);
            //将聊天记录保存到数据库
            UserMessage userMessage = new UserMessage();
            userMessage.setFromName(nickname);
            if(socketMsg.getType() == 1){
                userMessage.setToName(socketMsg.getToUser());
            }else{
                userMessage.setToName("all");
            }
            userMessage.setMessage(socketMsg.getMsg());
            userMessage.setCreateTime(new Date());
            messageList.add(userMessage);
            //当集合里的数据已经达到最大值,就将信息进行保存
            if(messageList.size()==LIST_SIZE){
                userMessageMapper.insertMessageList(messageList);
                //清除集合
                messageList.clear();
            }
            //将账号换成名字
            String name=userMessageMapper.getName(nickname);
            if(socketMsg.getType() == 1){
                //单聊.需要找到发送者和接受者.
                socketMsg.setFromUser(nickname);
                Session fromSession = map.get(socketMsg.getFromUser());
                Session toSession = map.get(socketMsg.getToUser());
                //将账号换成名字
                String toName=userMessageMapper.getName(toSession.getPathParameters().get("nickname"));
                //发送给接受者.
                if(toSession != null){
                    //发送给发送者.
                    fromSession.getAsyncRemote().sendText(name+"->"+toName+":"+socketMsg.getMsg());
                    toSession.getAsyncRemote().sendText(name+"->"+toName+":"+socketMsg.getMsg());
                }else{
                    //发送给发送者.
                    fromSession.getAsyncRemote().sendText("系统消息:对方不在线或者您输入的频道号不对");
                }
            }else{
                //群发消息
                broadcast(name+": "+socketMsg.getMsg());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }

    /**
     * 群发自定义消息
     */
    public void broadcast(String message) {
        for (WebSocketService item : webSocketSet) {
            /**
             * 同步异步说明参考:http://blog.csdn.net/who_is_xiaoming/article/details/53287691
             *
             * this.session.getBasicRemote().sendText(message);
             **/
            item.session.getAsyncRemote().sendText(message);
        }
    }
}

实体类

import lombok.Data;

/**
 * @authoer:majinzhong
 * @Date: 2022/11/7
 * @description:
 */
@Data
public class SocketMsg {
    /**
     * 聊天类型0:群聊,1:单聊
     **/
    private int type;
    /**
     * 发送者
     **/
    private String fromUser;
    /**
     * 接受者
     **/
    private String toUser;
    /**
     * 消息
     **/
    private String msg;
}
import com.fasterxml.jackson.annotation.JsonFormat;

import java.util.Date;

public class UserMessage {
    private String fromName;

    private String message;

    private String toName;
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    public String getFromName() {
        return fromName;
    }

    public void setFromName(String fromName) {
        this.fromName = fromName == null ? null : fromName.trim();
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message == null ? null : message.trim();
    }

    public String getToName() {
        return toName;
    }

    public void setToName(String toName) {
        this.toName = toName == null ? null : toName.trim();
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

Vue代码

<template>
  <el-dialog
    v-model="dialogVisible"
    title="聊天"
    width="70%"
    @close="closeWebSocket"
  >
    <div class="chat-big-box">
      <div class="chat-left">
        <el-collapse v-model="activeNames">
          <el-collapse-item  name="1" >
            <template #title>
              <div class="online-personnel user-select">
                <div>在线人员</div>
                <div class="sum">{{userArr.length>0?userArr.length-1:0}}人</div>
              </div>
            </template>
            <div class="chat-user-name user-select"
              v-for="(item,index) in userArr" :key="item.username"
              v-show="index>0"
              @click="checkUser(item)"
              :class="{'user-name-check':item.isCheck}">
              <div>员工名称:</div>
              <div>{{item.name}}</div>
            </div>
          </el-collapse-item>
        </el-collapse>
      </div>
      <div class="chat-right">
        <div class="content" ref="smgContent">
          <div  v-for="item in msgArr" :key="item">{{item}}</div>
        </div>
        <div class="input-box">
          <el-input
            v-model="textarea"
            @keydown.enter="keydown"
            :autosize="{ minRows: 7, maxRows: 7 }"
            type="textarea"/>
          <el-button type="success"  size="large" plain @click="sendBtn" class="input-btn">发送</el-button>
        </div>
      </div>
    </div>
  </el-dialog>
</template>

<script setup>
import { ref, defineExpose, nextTick } from 'vue'
import axios from 'axios'

const dialogVisible = ref(false)
const smgContent = ref()
const textarea = ref('')
let chatWS = null
const userName= localStorage.getItem('userName')
let toUser = {id:null,name:null} // 要发送人的ID
const userArr = ref([]) // 人员数组
const activeNames = ref(['0'])
const msgArr = ref([])
// connectWebSocket()
function connectWebSocket () {
  // 判断当前浏览器是否支持WebSocket
  dialogVisible.value = true
  if ('WebSocket' in window) {
    // chatWS = new WebSocket('ws://172.16.26.37:8081/websocket/' + userName)
    chatWS = new WebSocket('ws://192.168.1.125:8080/websocket/' + userName)

  } else {
    alert('当前浏览器不支持Websocket')
  }
  chatWS.onmessage = (evt) => {
    console.log(evt.data,'人员更新');
    if (evt.data==='人员更新') {
      obtainName() // 调取人员接口
    }else{
      msgArr.value.push(evt.data)
    }
    nextTick(()=>{
      smgContent.value.scrollTop = smgContent.value.scrollHeight
    })
  }
}

// 获取聊天人员接口
function obtainName () {
  // axios.get('http://172.16.26.37:8002/java_backend/members').then(res => {
  axios.get('http://192.168.1.125:8080/members').then(res => {
    userArr.value = res.data.data.map(item => {
      item.isCheck = false
      if (toUser.id === item.username) {
        item.isCheck = true
      }
      return item
    })
  })
}

// 选择群聊或者单聊  获取聊天记录
function checkUser (item) {
  msgArr.value = [] // 清空聊天框内容
  userArr.value.forEach(i => {
    if (i.username !== item.username) {
      i.isCheck = false
    }
  })
  // 保存 选择人员的ID和name 并通过id 来区分是群聊还是单聊
  item.isCheck = !item.isCheck
  toUser.name = item.name
  if (item.isCheck) {
    toUser.id = item.username
  } else {
    toUser.id = ''
  }
  let data = {
      type: toUser.id === '' ? 0 : 1,
      toUser:toUser.id,
      fromUser:userName
    }
  // 获取聊天记录
  axios.post('http://192.168.1.125:8080/message',data).then(res => {
    let type =toUser.id === '' ? true : false
    if (type) {
      res.data.data.forEach(item=>{
        msgArr.value.push(`${item.fromName}:${item.message}`) // 群聊
      })
    }else{
      res.data.data.forEach(item=>{
        msgArr.value.push(`${item.fromName}->${item.toName}:${item.message}`) //单聊
      })
    }
    nextTick(()=>{
      smgContent.value.scrollTop = smgContent.value.scrollHeight
    })
  })
}
// 发送消息
function keydown(e) {
  if (e.ctrlKey && e.keyCode === 13) {
    // 换行
    textarea.value = textarea.value + '\n'
  }
  if (e.ctrlKey === false && e.keyCode === 13) {
    // 阻止浏览器默认行为  禁止发送时输入框换行
    e.preventDefault ? e.preventDefault() : (e.returnValue = false);
    // 发送
    sendBtn()
  }
}

function sendBtn () {
  if (textarea.value !== '') {
    const socketMsg = {msg: textarea.value,toUser:toUser.id,type: toUser.id === '' ? 0 : 1}
    chatWS.send(JSON.stringify(socketMsg))
    textarea.value = ''
  }
}
// 关闭连接
function closeWebSocket () {
  chatWS.close()
  userArr.value = []
  textarea.value = ''
  msgArr.value = []
  dialogVisible.value = false
}
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
  chatWS.close()
}
defineExpose({
  connectWebSocket
})
</script>
<style lang='css' scoped>

</style>
<style lang='less'>
.el-dialog__body{
  padding-top: 0px;
}

.chat-big-box{
  .user-select{
    user-select:none;
  }
  width: 100%;
  height: 60vh;
  display: flex;
  .chat-left{
    flex: 1;
    padding: 10px;
    border: solid 1px #ccc;
    overflow:auto;
    margin-right: 10px;
    .user-name-check{
      background: #ccc !important;
    }
    .chat-user-name{
      display: flex;
      padding: 10px;
      background: #dddadab4;
      line-height: 20px;
      margin-bottom: 5px;
      div:nth-child(1){
        margin-right: 10px;
      }
      div:nth-child(2){
        width: 150px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        text-align: left;
      }
    }
  }
  .chat-right{
    flex: 4;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
    position: relative;
    .content{
      border: solid 1px #ccc;
      width: 100%;
      height: 75%;
      text-align: left;
      overflow: auto;
      margin-bottom: 10px;
      div{
        margin: 10px;
      }
    }
    .input-box{
      width: 100%;
    }
    .input-btn{
      position: absolute;
      right: 1px;
      bottom: 1px;
    }
  }
  ::-webkit-scrollbar {
      width: 4px;
    }
  ::-webkit-scrollbar-thumb {
    /*滚动条里面小方块*/
    border-radius: 8px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
    background: #4da1ff;
  }
  ::-webkit-scrollbar-track {
    /*滚动条里面轨道*/
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
    background: #ededed;
    border-radius: 8px;
  }
}
.online-personnel{
  width: 100%;
  display: flex;
  justify-content: space-between;
  padding: 10px;

  .sum{
    margin-right: 10px;
  }
}

</style>

对比:相较于之前的那篇博客添加了以下几个点:

Java+Vue实现聊天室(WebSocket进阶-聊天记录)

 Java+Vue实现聊天室(WebSocket进阶-聊天记录)

 Java+Vue实现聊天室(WebSocket进阶-聊天记录)

 遇到问题:相对于接口调用@Autowired会将对象加载进容器,但是webSocket不是接口调用,所以在保存聊天记录的时候会报空指针,(mapp对象的空指针)

所以要对mappr对象进行提前加载,类似于饿汉模式

Java+Vue实现聊天室(WebSocket进阶-聊天记录)

之前:java实现聊天室(websocket)_奔驰的小野码的博客-CSDN博客_java websocket聊天室文章来源地址https://www.toymoban.com/news/detail-510298.html

到了这里,关于Java+Vue实现聊天室(WebSocket进阶-聊天记录)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • WebSocket+Vue实现简易多人聊天室 以及 对异步调用的理解

    代码仓库:github   HTTP是不支持长连接的,WebSocket是一种通信协议,提供了在单一、长连接上进行全双工通信的方式。它被设计用于在Web浏览器和Web服务器之间实现,但也可以用于任何需要实时通信的应用程序。使用ws作为协议标识符,如果需要加密则使用wss作为协议标识符

    2024年01月17日
    浏览(33)
  • Vue + Element-Plus + SpringBoot + WebSocket实现简易网络聊天室

    项目流程图 1. 前端搭建:         前端用Vue+Element-Plus 来搭建,由登录页面和聊天页面组成 1.1 登录页面         由一个昵称输入框组成,用户输入自己的昵称若昵称和别的用户不重复,则可进入聊天室,否则提示错误并请重新输入。         这段代码是一个Vue.js组件的

    2024年02月03日
    浏览(35)
  • 【你的第一个socket应用】Vue3+Node实现一个WebSocket即时通讯聊天室

    这篇文章主要是用WebSocket技术实现一个 即时通讯聊天室 ,首先先要了解为什么使用WebSocket而不是普通的HTTP协议,如果使用HTTP协议它是下面这种情况: 我发送一条消息,发送一个发送消息的请求;* 一直轮询接收别人发送的消息,不管有没有发送都要定时去调用接口。这里明

    2023年04月20日
    浏览(26)
  • 在线聊天室(Vue+Springboot+WebSocket)

    实现了一个简单的在线聊天室的前后端。前端用Vue实现,后端用Springboot实现。         在线聊天室的功能包括创建用户和显示在线用户列表、发送消息和显示消息列表、用户和消息列表实时更新这几点。以下是整体功能的活动图: 用户身份         进入聊天室的用户需

    2024年01月15日
    浏览(17)
  • 基于SpringBoot+Vue+WebSocket的在线聊天室

    WebSocket 是一种在 Web 应用程序中实现双向通信的协议。它提供了一种持久连接,允许客户端和服务器之间进行实时数据传输,而无需进行频繁的请求和响应。 相对于传统的 HTTP 请求-响应模式,WebSocket 在客户端和服务器之间建立起一条长久的双向通信通道。这意味着服务器可

    2024年01月16日
    浏览(33)
  • Vue+Nodejs 使用WebSocket创建一个简易聊天室

    使用vue编写前端页面,nodejs处理服务端消息,WebSocket进行实时通信 1.客户端 2. 服务端 使用的是nodejs

    2024年02月16日
    浏览(19)
  • Django实现websocket聊天室

    WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器双向通信,即允许服务器主动发送信息给客户端。因此,在WebSocket中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输,客户端和服务器之间的数据交换变

    2023年04月23日
    浏览(20)
  • django websocket实现聊天室功能

    注意事项channel版本 django2.x 需要匹配安装 channels 2 django3.x 需要匹配安装 channels 3 Django 3.2.4 channels 3.0.3 Django 3.2.* channels 3.0.2 Django4.2 channles==3.0.5 是因为最新版channels默认不带daphne服务器 直接用命令 python manage.py runsever 默认运行的是wsgi ,修改,删除settings中的wsgi,都不能正确运

    2024年01月22日
    浏览(23)
  • 【WebSocket】SpringBoot整合WebSocket实现聊天室(一)

    目录 一、准备 1、引入依赖 2、创建配置类 二、相关注解 首先我们需要在项目中引入依赖,有两种方式。第一种我们可以在创建Spring Boot项目时搜索WebSocket然后勾选依赖 第二种是我们可以直接在项目的pom.xml文件中插入以下依赖 我们需要进行如下配置 ServerEndpointExporter 是一个

    2024年02月13日
    浏览(21)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包