Vue3+springboot通过websocket实现实时通信

本文章使用vue3+springboot通过websocket实现两个用户之间的实时通信,聊天信息使用mongodb非关系型数据库进行存储。

效果图如下:

用户发送信息

Vue3+springboot通过websocket实现实时通信_第1张图片

 农户收到信息并发送回去

Vue3+springboot通过websocket实现实时通信_第2张图片

后台消息打印

Vue3+springboot通过websocket实现实时通信_第3张图片

Springboot

引入依赖



    org.springframework.boot
    spring-boot-starter-websocket




    cn.hutool
    hutool-all
    5.8.7




    org.slf4j
    slf4j-api

 配置

在config目录下,创建WebSocketConfig类

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

@Configuration
public class WebSocketConfig{


    /*
    * 注入一个ServerEndpointExporter,该ean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
    * */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

创建一个WebSocketServer来处理连接

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.result.UpdateResult;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

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

@ServerEndpoint(value = "/chat/{userId}")    // 这里设置的是访问后端接口
@Component
public class WebSocketServer {

    // 这部分是日志打印
    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);

    // 在线连接数
    public static final Map sessionMap = new ConcurrentHashMap<>();

    // 这里我是采用mongodb去存储聊天数据
    private MongoCollection collection;

    public WebSocketServer() {
        initializeMongoCollection();
    }

    private void initializeMongoCollection() {
        MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017");
        MongoDatabase database = mongoClient.getDatabase("farmer");    // 这里设置mongodb数据库名
        collection = database.getCollection("chat");    // 设置集合名称
    }

    // 当有用户建立连接到服务器的时候触发
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId){
        // 建立连接的时候就将该用户的session存起来,方便后续的信息返回
        sessionMap.put(userId, session);
        log.info("新聊天用户={},当前在线人数:{}",userId, sessionMap.size());
    }

    // 关闭时触发
    @OnClose
    public void onClose(Session session, @PathParam("userId") String userId){
        sessionMap.remove(userId);
        log.info("有一连接关闭,移除聊天用户={},当前在线人数为:{}", userId, sessionMap.size());
    }

    // 当用户发送消息时触发
    @OnMessage
    public void onMessage(String parmas, Session session, @PathParam("userId") String userId){
        log.info("服务器收到聊天用户={}的消息:{}", userId, parmas);
        // 将收到前端的消息解析取出
        JSONObject object = JSONUtil.parseObj(parmas);
        String chatId = object.getStr("chatId");    // 聊天id
        String toUserId = object.getStr("to");      // 发送给谁
        String text = object.getStr("text");        // 文本信息
        String time = object.getStr("time");        // 发送时间

        Session toSession = sessionMap.get(toUserId);    // 查询要发送的人是否在线
        // 新建对象将要存储到数据库的内容封装到一起
        JSONObject jsonObject = new JSONObject();
        jsonObject.set("from", userId);
        jsonObject.set("to", toUserId);
        jsonObject.set("text", text);
        jsonObject.set("time", time);
        
        // 如果toSession为空,则说明对面没在线,直接存到数据库
        // 若不为空,则说明对面在线,将数据存到数据库并且通过sendMessage实时发送给目标客户
        if(toSession != null){
            sendMessage(jsonObject.toString(), toSession);
            log.info("发送给用户username={},消息:{}", toUserId, jsonObject.toString());
            insertChatData(jsonObject, chatId);
        }else{
            log.info("用户{}不在线", toUserId);
            insertChatData(jsonObject, chatId);
        }
    }

    @OnError
    public void onError(Session session, Throwable error){
        log.error("发生错误");
        error.printStackTrace();
    }

    // 服务器发送消息给客户端
    private void sendMessage(String message, Session toSession){
        try {
            log.info("服务器给用户【{}】发送消息:{}", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
        }catch (Exception e){
            log.error("服务器发送消息给客户端失败");
        }
    }

    // 服务器发送信息给所有客户端(这步可拓展到群聊)
    private void sendAllMessage(String message){
        try {
            for(Session session : sessionMap.values()){
                log.info("服务器给全用户【{}】发送消息:{}", session.getId(), message);
                session.getBasicRemote().sendText(message);
            }
        }catch (Exception e){
            log.error("服务器发送消息给客户端失败");
        }
    }


    // 这是mongodb存到数据库的操作
    private void insertChatData(JSONObject chatData, String chatId) {
        Document document = Document.parse(chatData.toString());

        Document query = new Document("chatId", chatId);
        Document update = new Document("$push", new Document("chatData", document));

        UpdateResult updateResult = collection.updateOne(query, update, new UpdateOptions().upsert(true));
        System.out.println(updateResult);
    }
}

Vue3

用户部分IndexMessage:

在script中先定义一个

let socket;

在methods中

init() {
  let _this = this
  let userId = this.user.userId;    // 当前本用户的id
  if (this.supportWebSocket()) {
    let socketUrl = "ws://localhost:8090/chat/" + userId;
    if (socket != null) {
        socket.close();
        socket = null;
    }
    // 开启一个websocket服务
    socket = new WebSocket(socketUrl);
    //打开事件
    socket.onopen = function () {
        console.log("websocket已打开");
    };
    // 监听数据
    // 浏览器端收消息,获得从服务端发送过来的文本消息
    socket.onmessage = function (msg) {
        console.log("收到数据====" + msg.data)
        let data = JSON.parse(msg.data)

        // 收到最新消息的时候,更新左侧聊天导航的文本内容
        _this.chatNav.forEach(item => {
            if(item.farmerId == data.from){
                item.text = data.text
            }
        })

        // 收到消息的时候去更新chatData中的数据
        _this.addToConetnt(data)

    };
    //关闭事件
    socket.onclose = function () {
        console.log("websocket已关闭");
    };
    //发生了错误事件
    socket.onerror = function (e) {
        console.log(e)
        console.log("websocket发生了错误");
    }
  }
},

send() {
  if (!this.text) {
    // 若为空,提示为空,此处使用elementUI的消息提示
    this.$message({type: 'warning', message: "请输入内容"})
  } else {
    // 封装了一个查看是否支撑websocket的方法
    if(this.supportWebSocket()){

      // 发送的消息
      let message = {
          from: this.user.userId,
          to: this.toUser.toUserId,
          text: this.text,
          time: moment().format('x')    // 使用moment包生成一个时间戳
      }
      this.addToConetnt(message)

      this.text = '';
      this.$refs.messageInput.innerHTML = ""    // 将输入的值设置为空

      // 整合发送消息
      message["chatId"] = this.chatId
      socket.send(JSON.stringify(message));  // 将组装好的json发送给服务端,由服务端进行转发

      // 发送消息的时候,更新左侧聊天导航的文本内容
      this.chatNav.forEach(item => {
          if(item.farmerId == message.to){
              item.text = message.text
          }
      })

      // 将展示内容的div设置滚动到底部
      this.$nextTick(()=>{
          this.$refs.messageContent.scrollTop = this.$refs.messageContent.scrollHeight
      })
    }
  }
},

// 是否支撑websocket
supportWebSocket(){
  if(typeof (WebSocket) == "undefined"){
      console.log("您的浏览器不支持WebSocket");
      return false
  }else{
      return true
  }
},

// 新增记录判断
addToConetnt(message){
  let arr = this.chatData[this.chatData.length - 1]

  const startTime = moment(parseInt(arr.chatItem[arr.chatItem.length - 1].time));    // 最近一条消息的时间
  const endTime = moment();                 // 当前时间

  const duration = moment.duration(endTime.diff(startTime));
  const minutes = duration.asMinutes();

  // 判断最近的消息和上一条消息是否超过3分钟,超过三分钟则重新创建一个对象存储
  if(minutes > 3){
      this.chatData.push({
          time: moment(endTime).format("HH:mm"),
          chatItem: [message]
      })
  }else{
      this.chatData[this.chatData.length - 1].chatItem.push(message)
  }

  this.$nextTick(()=>{
      this.$refs.messageContent.scrollTop = this.$refs.messageContent.scrollHeight
  })
},

重点部分在init中几个socket事件的写法和send发送中scoket的写法,其他的是自己项目的功能实现,按照自己的需求去写就好了

一些chatData的数据类型如下:

Vue3+springboot通过websocket实现实时通信_第4张图片

 Vue3+springboot通过websocket实现实时通信_第5张图片

mongdb的数据库类型如下:

Vue3+springboot通过websocket实现实时通信_第6张图片

mongodb只是作为参考,若你想使用关系型数据库mysql也是可以,修改具体逻辑就行

你可能感兴趣的:(杂谈,spring,boot,websocket,java,vue.js)