基于Springboot+vue的前后端分离的实时多人在线聊天管理系统(简易版)

项目简介

本项目为基于Springboo+vue+websocket的前后端分离的实时多人在线聊天管理系统
本项目实现功能:

  • 登录注册
  • 添加搜索好友
  • 好友上线提醒
  • 好友上线下线标识
  • 好友发送消息通知(红点数字)
  • 好友申请通知
  • 我的申请记录
  • 单人聊天
  • 同时多人聊天
  • 不在线信息保存及上线信息提示等

注意:本项目为简易版项目,可能会存在部分bug,且功能并没有完善好,很适合学习使用,禁止商用!!后续会把项目继续完善好!

后面会把添加更多功能,包括:

  • 好友列表搜索
  • 好友分类
  • 个人信息编辑
  • 设置好友备注
  • 群聊功能

暂时就想到这些 这些也是下一步的开发计划 !

本项目源代码分为前后端以及上传到资源中了
目前项目是收费的,但是价格很低,前后端一共不到20块钱,算大家犒劳一下作者了!已开启不允许自动调整价格!(不喜勿喷)

软件架构

  • 数据库Mysql8.0
  • JDK1.8
  • Springboot2.1.9
  • vue2.6.14
  • axios1.4
  • webSocket
  • node16.14.2

运行截图

数据库模型
在这里插入图片描述

前端项目结构:
在这里插入图片描述
后端项目结构:
在这里插入图片描述

1.首页
在这里插入图片描述2.登录页面
在这里插入图片描述
3.聊天页面
在这里插入图片描述4.搜索添加联系人
在这里插入图片描述5.我的消息通知
在这里插入图片描述6.注册页面
在这里插入图片描述7.好友在线与不在线标识
在这里插入图片描述
8.我的申请列表
在这里插入图片描述9.多人同时聊天
在这里插入图片描述10.好友上线通知
在这里插入图片描述11.我的消息内容
在这里插入图片描述12.未读消息提示
在这里插入图片描述

部分代码

前端主要websocket链接代码

initConnect() {
        this.ws = new WebSocket(`ws://localhost:8080/talk/imserver/` + this.user.no);//websocket连接地址
        this.ws.onopen = () => {
          console.log('WebSocket连接已建立');
          this.isConnected = true;
          // 建立心跳定时器
          this.heartbeatInterval = setInterval(() => {
            if (this.ws.readyState === 1) {
              this.ws.send(JSON.stringify({ type: 'heartbeat' }));
              console.log('心跳开启');
            }
          }, this.heartbeatTimeout);
        };
        this.ws.onmessage = (event) => {
          const dataMessage = JSON.parse(event.data);
          if (dataMessage.type && dataMessage.type == 'heartbeat') {
            //心跳
            return;
          } else if (dataMessage.type && dataMessage.type == 'connect') {
            //连接成功的信息
            return;
          } else if (dataMessage.type && dataMessage.type == 'up') {
            //好友上线的信息
            this.pList.map(item=>{
              if(item.no == dataMessage.no){
                item.isRun = 1;
                this.$notify({
                  title: '上线通知',
                  message: '您的好友"'+item.name+'"已上线',
                  position: 'bottom-right',
                  type:'success'
                });
              }
            });
            return;
          } else if (dataMessage.type && dataMessage.type == 'down') {
            //好友下线的信息
            this.pList.map(item=>{
              if(item.no == dataMessage.no){
                item.isRun = 0;
              }
            });
            return;
          }
          //如果当前聊天的人不是发信息的人,则需要给下标且好友中的noReadNum需要加1
          //如果是当前聊天的人  则发请求将聊天记录未读信息设置为已读
          //当前聊天的id由后台给前端dataMessage.id
          console.log("this.activePeo",this.activePeo);
          if(dataMessage.fromno != this.activePeo.no){
            this.pList.map(item=>{
              if(item.no == dataMessage.fromno){
                item.noReadNum += 1;
              }
            })
          }else{
            //接收的是内容 from  text
            this.activePeo.talkList.push(dataMessage);
            updateRecord({id:dataMessage.id,isread:1}).then(res=>{
                console.log(res);
            })
          }

          this.$forceUpdate();
          this.$nextTick(() => {
            let scrollEl = this.$refs.mianscroll;
            scrollEl.scrollTo({ top: scrollEl.scrollHeight, behavior: 'smooth' });
          });
          console.log(dataMessage, '接收到的信息');
        };
        this.ws.onerror = () => {
          console.error('WebSocket连接发生错误');
        };
        this.ws.onclose = () => {
          console.log('WebSocket连接已关闭');
          this.ws.send('close');
          this.isConnected = false;
          // 清除心跳定时器
          clearInterval(this.heartbeatInterval);
        };
      },

后端websocket代码:

package com.ww.talk.component;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ww.talk.dao.FriendsDao;
import com.ww.talk.entity.TalkRecord;
import com.ww.talk.entity.User;
import com.ww.talk.service.FriendsService;
import com.ww.talk.service.TalkRecordService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author websocket服务
 */
@ServerEndpoint(value = "/imserver/{username}")
@Component
public class WebSocketServer {
    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
    /**
     * 记录当前在线连接数
     */
    public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();


    @Resource
    private FriendsService friendsService;
    @Resource
    private TalkRecordService talkRecordService;

    private static WebSocketServer  webSocketServer ;
    @PostConstruct //通过@PostConstruct实现初始化bean之前进行的操作
    public void init() {
        webSocketServer = this;
        webSocketServer.friendsService = this.friendsService;
        webSocketServer.talkRecordService = this.talkRecordService;
        // 初使化时将已静态化的testService实例化
    }
    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        sessionMap.put(username, session);
        log.info("有新用户加入,username={}, 当前在线人数为:{}", username, sessionMap.size());
        JSONObject result = new JSONObject();
        JSONArray array = new JSONArray();
        result.put("users", array);
        result.put("type", "connect");
        for (Object key : sessionMap.keySet()) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("username", key);
            // {"username", "zhang", "username": "admin"}
            array.add(jsonObject);
        }
        //好友上线发送上线消息
        sendMyFriend(username,"up");
//        {"users": [{"username": "zhang"},{ "username": "admin"}]}
//        sendAllMessage(JSONUtil.toJsonStr(result));  // 后台发送消息给所有的客户端
        sendMessage(JSONUtil.toJsonStr(result),session);
    }
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session, @PathParam("username") String username) {
        //好友下线发送下线消息
        sendMyFriend(username,"down");
        sessionMap.remove(username);
        log.info("有一连接关闭,移除username={}的用户session, 当前在线人数为:{}", username, sessionMap.size());
    }
    /**
     * 收到客户端消息后调用的方法
     * 后台收到客户端发送过来的消息
     * onMessage 是一个消息的中转站
     * 接受 浏览器端 socket.send 发送过来的 json数据
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("username") String username) {
        log.info("服务端收到用户username={}的消息:{}", username, message);
        if(message.equals("close")){
            onClose(session,username);
            return;
        }
        JSONObject obj = JSONUtil.parseObj(message);
        String toUsername = obj.getStr("to"); // to表示发送给哪个用户,比如 admin
        String text = obj.getStr("text"); // 发送的消息文本  hello
        String type = obj.getStr("type"); // 发送的消息类型 heartbeat 心跳
        if(type!=null && type.equals("heartbeat")){
            //心跳直接返回
            this.sendMessage(obj.toString(),session);
        }else {
            // {"to": "admin", "text": "聊天文本"}
            Session toSession = sessionMap.get(toUsername); // 根据 to用户名来获取 session,再通过session发送消息文本
            TalkRecord talkRecord = new TalkRecord();
            talkRecord.setFromno(Integer.parseInt(username));
            talkRecord.setTono(Integer.parseInt(toUsername));
            talkRecord.setSendtext(text);
            talkRecord.setSendtime(DateUtil.now());
            talkRecord.setIsread(0);//默认未读  在点击的时候才会将当前对象设置为已读
            webSocketServer.talkRecordService.insert(talkRecord);
            if (toSession != null) {
                // 服务器端 再把消息组装一下,组装后的消息包含发送人和发送的文本内容
                // {"from": "zhang", "text": "hello"}
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("from", username);  // from 是 zhang
                jsonObject.put("to", toUsername);  // from 是 zhang
                jsonObject.put("text", text);  // text 同上面的text
                jsonObject.put("talkId", talkRecord.getId());  // text 同上面的text
                this.sendMessage(JSONUtil.parse(talkRecord).toString(), toSession);
                log.info("发送给用户username={},消息:{}", toUsername, jsonObject.toString());
            } else {
                //不在线存入数据库中
                log.info("发送失败,未找到用户username={}的session", toUsername);
            }

        }
    }
    @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("服务端发送消息给客户端失败", e);
        }
    }
    /**
     * 服务端发送消息给所有客户端
     */
    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("服务端发送消息给客户端失败", e);
        }
    }


    /**
     * 发送给在线好友消息
     * @param username 当前人的no
     * @param type 类型  up 上线  down 下线
     */
    public void sendMyFriend(String username,String type){
        Integer cNo = Integer.parseInt(username);
        List<Map> all = webSocketServer.friendsService.getMyFriend(cNo);
        for(Map map : all){
            //拿出好友的信息
            Integer no = Integer.parseInt(map.get("userno").toString()) == cNo?Integer.parseInt(map.get("fno").toString()):Integer.parseInt(map.get("userno").toString());

            //发送给在线的好友
            if(sessionMap.get(no.toString())!=null){
                //通过websocket查询一下当前好友在不在线
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("no", cNo);  // 当前的人
                jsonObject.put("type", type);  // 类型
                this.sendMessage(jsonObject.toString(), sessionMap.get(no.toString()));
            }
        }
    }
}

源码获取

数据库代码在后端代码中!!
后端代码
前端代码

如果项目对大家有帮助?麻烦大家帮忙点点赞,点点收藏啦!谢谢大家!!