基于vue、websocket、springboot、mybatis的聊天室

StuartZzzzzzzzzz 2022-01-17 13:12:53

vue的初了解

vue是当前比较流行的一个框架技术,与传统的技术相比,vue的优势在于数据绑定是双向绑定,这就很大程度上解决了数据交互的问题。同时vue的开发非常灵活,有许多vue的构建工具来帮助我们构建项目,但是这些工具并不会阻碍我们如何去编写我们的应用代码。在这里我发现一篇博客对vue的介绍比较好:https://blog.csdn.net/huangjianfeng21/article/details/92043800

websocket初了解

与http协议不同,websocket协议是双通道协议,这就意味着可以同时双向通信。http协议是客户端发起请求,然后服务端响应请求。所以聊天室使用websocket协议更加合理并且方便。两种协议的比较具体介绍:https://zhuanlan.zhihu.com/p/113286469

数据库的设计

数据库采用的是mysql数据库,设计了三张表,user、msg_info(消息)、session_list(会话)。

mes_info表:

session_list表:

user表就是name和id来表示一个用户。

服务端

服务端采用java语言来开发,使用springboot框架来搭建。整体的结构如下:

具体介绍一下发送消息的websocket的controller层。

通过注解来打开或者关闭会话。

    @OnOpen
    public void onOpen(Session session,@PathParam(value="userId")Integer userId, @PathParam(value="sessionId")String sessionId) {
        this.session = session;
        //存放user和其对应的websocket
        CurPool.webSockets.put(userId,this);
        List<Object> list = new ArrayList<Object>();
        list.add(sessionId);
        list.add(session);
        //存放user和对应的session
        CurPool.sessionPool.put(userId , list);
    }

    @OnClose
    public void onClose() {
        // 在断开连接的情况下
        // 删除user、删除session
        Integer userId = Integer.parseInt(this.session.getRequestParameterMap().get("userId").get(0));
        CurPool.sessionPool.remove(userId);
        CurPool.webSockets.remove(userId);
        if (userMapper == null){
            this.userMapper = (UserMapper)SpringContextUtil.getBean("userMapper");
        }
        User user = userMapper.selectByPrimaryKey(userId);
        CurPool.curUserPool.remove(user.getName());
    }

发送消息的控制层。

    @OnMessage
    public void onMessage(String message) {
        //获取sessionid
        String sessionId = this.session.getRequestParameterMap().get("sessionId").get(0);
        if (sessionId == null){
            System.out.println("sessionId 错误");
            return;
        }
        // 注入Mapper
        if (seesionListMapper == null){
            this.seesionListMapper = (SeesionListMapper)SpringContextUtil.getBean("seesionListMapper");
        }
        if (userMapper == null){
            this.userMapper = (UserMapper)SpringContextUtil.getBean("userMapper");
        }
        if (msgInfoMapper == null){
            this.msgInfoMapper = (MsgInfoMapper)SpringContextUtil.getBean("msgInfoMapper");
        }
        //创建实体
        SessionList sessionList = seesionListMapper.selectByPrimaryKey(Integer.parseInt(sessionId));
        User user = userMapper.selectByPrimaryKey(sessionList.getUserId());
        MsgInfo msgInfo = new MsgInfo();
        msgInfo.setContent(message);
        msgInfo.setCreateTime(new Date());
        msgInfo.setFromUserId(sessionList.getUserId());
        msgInfo.setFromUserName(user.getName());
        msgInfo.setToUserId(sessionList.getToUserId());
        msgInfo.setToUserName(sessionList.getListName());
        msgInfo.setUnReadFlag(0);
        // 存入消息
        msgInfoMapper.insert(msgInfo);

        // 判断用户是否存在,不存在就结束
        List<Object> list = CurPool.sessionPool.get(sessionList.getToUserId());
        if (list == null || list.isEmpty()){
            // 如果当前用户不在线的话,就更新未读消息
            seesionListMapper.addUnReadCount(sessionList.getToUserId(),sessionList.getUserId());
        }else{
            // 如果用户在线的话,看看是否已经建立了会话
            String id = seesionListMapper.selectIdByUser(sessionList.getToUserId(), sessionList.getUserId())+"";
            String o = list.get(0) + "";
            if (id.equals(o)){
                // 如果会话存在
                // 那么直接发送消息
                sendTextMessage(sessionList.getToUserId(),JsonUtils.objectToJson(msgInfo));
            }else {
                // 判断会话列表是否存在
                if ("".equals(id) || "null".equals(id)){
                    // 新增会话列表
                    SessionList tmpSessionList = new SessionList();
                    tmpSessionList.setUserId(sessionList.getToUserId());
                    tmpSessionList.setToUserId(sessionList.getUserId());
                    tmpSessionList.setListName(user.getName());
                    tmpSessionList.setUnReadCount(1);
                    seesionListMapper.insert(tmpSessionList);
                }else {
                    // 更新未读消息数量
                    seesionListMapper.addUnReadCount(sessionList.getToUserId(),sessionList.getUserId());
                }
                // 会话不存在发送列表消息
                List<SessionList> sessionLists = seesionListMapper.selectByUserId(sessionList.getToUserId());
                sendTextMessage(sessionList.getToUserId() ,JsonUtils.objectToJson(sessionLists));
            }
        }
        System.out.println("客户端消息:"+message);
    }

 会话建立的控制层。

    //查询已经建立的全部会话
    @GetMapping("/sessionList/already")
    public AjaxResult<?> sessionListAlready(@RequestParam Integer id){
        List<SessionList> sessionLists = seesionListMapper.selectByUserId(id);
        return  AjaxResult.success(sessionLists);
    }

    // 查询可建立会话的全部用户
    @GetMapping("/sessionList/not")
    public AjaxResult<?> sessionListNot(@RequestParam Integer id){
        List<Integer> list = seesionListMapper.selectUserIdByUserId(id);
        list.add(id);
        List<User> cloudList = userMapper.getCloudList(list);
        return AjaxResult.success(cloudList);
    }

    // 根据userid和username创建一个新的会话
    @GetMapping("/createSession")
    public AjaxResult<?> createSession(@RequestParam Integer id,@RequestParam Integer toUserId,@RequestParam String toUserName){
        SessionList sessionList = new SessionList();
        sessionList.setUserId(id);
        sessionList.setUnReadCount(0);
        sessionList.setListName(toUserName);
        sessionList.setToUserId(toUserId);
        seesionListMapper.insert(sessionList);
        // 如果对方和我没有建立会话,那么我要主动与对方建立会话
        Integer integer = seesionListMapper.selectIdByUser(toUserId, id);
        if (integer == null || integer <= 0){
            User user = userMapper.selectByPrimaryKey(id);
            sessionList.setUserId(toUserId);
            sessionList.setToUserId(id);
            sessionList.setListName(user.getName());
            seesionListMapper.insert(sessionList);
        }
        return AjaxResult.success();
    }

    // 删除当前的会话
    @GetMapping("/delSession")
    public AjaxResult<?> delSession(@RequestParam Integer sessionId){
        seesionListMapper.deleteByPrimaryKey(sessionId);
        return AjaxResult.success();
    }

 数据库操作

数据库持久化采用mybatis。设计好接口然后编写mapper文件。MessageInfo操作的xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xdx97.socket.mapper.SeesionListMapper">
  <resultMap id="BaseResultMap" type="com.xdx97.socket.bean.SessionList">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="user_id" jdbcType="INTEGER" property="userId" />
    <result column="to_user_id" jdbcType="INTEGER" property="toUserId" />
    <result column="list_name" jdbcType="VARCHAR" property="listName" />
    <result column="un_read_count" jdbcType="INTEGER" property="unReadCount" />
  </resultMap>
  <sql id="Base_Column_List">
    id, user_id,to_user_id, list_name, un_read_count
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from session_list
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from session_list
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" parameterType="com.xdx97.socket.bean.SessionList">
    insert into session_list (id, user_id, to_user_id,list_name,
      un_read_count)
    values (#{id,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}, #{toUserId,jdbcType=INTEGER}, #{listName,jdbcType=VARCHAR},
      #{unReadCount,jdbcType=INTEGER})
  </insert>
  <insert id="insertSelective" parameterType="com.xdx97.socket.bean.SessionList">
    insert into session_list
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="id != null">
        id,
      </if>
      <if test="userId != null">
        user_id,
      </if>
      <if test="toUserId != null">
        to_user_id,
      </if>
      <if test="listName != null">
        list_name,
      </if>
      <if test="unReadCount != null">
        un_read_count,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="id != null">
        #{id,jdbcType=INTEGER},
      </if>
      <if test="userId != null">
        #{userId,jdbcType=INTEGER},
      </if>
      <if test="toUserId != null">
        #{toUserId,jdbcType=INTEGER},
      </if>
      <if test="listName != null">
        #{listName,jdbcType=VARCHAR},
      </if>
      <if test="unReadCount != null">
        #{unReadCount,jdbcType=INTEGER},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.xdx97.socket.bean.SessionList">
    update session_list
    <set>
      <if test="userId != null">
        user_id = #{userId,jdbcType=INTEGER},
      </if>
      <if test="toUserId != null">
        to_user_id = #{toUserId,jdbcType=INTEGER},
      </if>
      <if test="listName != null">
        list_name = #{listName,jdbcType=VARCHAR},
      </if>
      <if test="unReadCount != null">
        un_read_count = #{unReadCount,jdbcType=INTEGER},
      </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.xdx97.socket.bean.SessionList">
    update session_list
    set user_id = #{userId,jdbcType=INTEGER},
      to_user_id = #{toUserId,jdbcType=INTEGER},
      list_name = #{listName,jdbcType=VARCHAR},
      un_read_count = #{unReadCount,jdbcType=INTEGER}
    where id = #{id,jdbcType=INTEGER}
  </update>

  <select id="selectUserIdByUserId" resultType="java.lang.Integer">
    SELECT to_user_id FROM session_list WHERE user_id = #{id}
  </select>

  <select id="selectByUserId" resultMap="BaseResultMap">
    SELECT * FROM session_list WHERE user_id = #{id}
  </select>

  <select id="selectIdByUser" resultType="java.lang.Integer">
    SELECT id FROM session_list WHERE user_id = #{fromId} AND to_user_id = #{toId}
  </select>

  <select id="select" resultMap="BaseResultMap">
    SELECT * FROM session_list
    <trim prefix="WHERE" suffixOverrides="AND">
      <if test="id != null">
        id = #{id,jdbcType=INTEGER},
      </if>
      <if test="userId != null">
        user_id = #{userId,jdbcType=INTEGER},
      </if>
      <if test="toUserId != null">
        to_user_id = #{toUserId,jdbcType=INTEGER},
      </if>
      <if test="listName != null">
        list_name = #{listName,jdbcType=VARCHAR},
      </if>
      <if test="unReadCount != null">
        un_read_count = #{unReadCount,jdbcType=INTEGER},
      </if>
    </trim>
  </select>

  <update id="addUnReadCount">
    UPDATE session_list SET un_read_count = un_read_count + 1 WHERE user_id = #{userId} AND to_user_id = #{toUserId}
  </update>

  <update id="delUnReadCount">
    UPDATE session_list SET un_read_count = 0 WHERE user_id = #{fromUserId} AND to_user_id = #{toUserId}
  </update>
</mapper>

客户端

页面的设计。

<template>
	<div class="main">	
		<div class="main_up">
			<div v-if="curUserName == ''">
				<el-button @click="openDialog('登录')">登录</el-button>
				<el-button @click="openDialog('注册')">注册</el-button>
			</div>
			<div v-else>
				{{this.curUserName}}
				<el-button type="info" @click="loginOut">退出登录</el-button>
			</div>
			
		</div>
		<div class="main_down">
			<div class="left">
				<div class="left_up">
					<div class="label_title">
						已建立会话
					</div>
					<div :class="curSessionId == item.id ? 'box_select' : 'box'" v-for="item in sessionList_already" :key="item.id">
						<div class="box_left"  @click="startSession(item.id)">
							{{item.listName}}
						</div>
						<div class="right_left">
							<div class="right_left_count">
								{{item.unReadCount}}
							</div>
							<div class="right_left_del">
								<i class="el-icon-close" @click="delSession(item.id)"></i>
							</div>
						</div>
					</div>
				</div>
				<div class="left_down">
					<div class="label_title">
						可建立会话
					</div>
					<div v-for="item in sessionList_not" :key="item.id" class="box" @click="createSession(item.id, item.name)">
						<div class="box_left">
							{{item.name}}
						</div>
					</div> 
				</div>

			</div>
			<div class="right">
				<div class="up" ref="element" id="msg_end">
					<div v-for="(item,i) in list" :key="i" :class="item.fromUserId === curUserId ? 'msg_right' : 'msg_left'">
						<div class="msg_right_up">
							{{item.fromUserName}}
						</div>
						<div :class="item.fromUserId === curUserId ? 'msg_right_down' : 'msg_left_down'">
							{{item.content}}
						</div>
					</div>
					
				</div>
				<div class="down">
					<el-input
					type="textarea"
					:rows="9"
					placeholder="请输入内容,回车发送!"
					@keyup.enter.native = "sendMsg"
					v-model="textarea">
					</el-input>
				</div>
			</div>
		</div>

		<el-dialog
			:title="dialogTitle"
			:visible.sync="dialogVisible"
			width="30%"
			>
			<el-input v-model="loginName" placeholder="请输入用户名..."></el-input>
			<span slot="footer" class="dialog-footer">
				<el-button @click="dialogVisible = false">取 消</el-button>
				<el-button type="primary" @click="loginOrRegister">确 定</el-button>
			</span>
		</el-dialog>
  </div>
</template>

websocket的代码。

        initWebSocket: function (userId,sessionId) {                               
			this.websock = new WebSocket("ws://127.0.0.1:1997/websocket/"+userId+"/"+sessionId);                
			this.websock.onopen = this.websocketonopen;                
			this.websock.onerror = this.websocketonerror;                
			this.websock.onmessage = this.websocketonmessage;                
			this.websock.onclose = this.websocketclose;
		},              
		websocketonopen: function () {                
			console.log("WebSocket连接成功");    
		},              
		websocketonerror: function (e) { 
			console.log("WebSocket连接发生错误",e);              
		},              
		websocketonmessage: function (e) {  
			let data = JSON.parse(e.data);
			if(data instanceof Array){
				// 列表数据
				this.sessionList_already = data
			}else{
				// 消息数据
				this.list.push(data)
			}
		},              
		websocketclose: function (e) {
			if(this.curUserId != null){
				if(this.curSessionId != null){
					this.initWebSocket(this.curUserId, this.curSessionId)
				}else{
					this.initWebSocket(this.curUserId, 99999999)
				}
			}
			console.log("connection closed",e);     
			console.log(e);              
		},

消息发送的代码

        sendMsg(){
			if(this.curSessionId == ''){
				return this.$message.error("请选择左边的对话框开始聊天!")
			}
			let data = {
				"fromUserId": this.curUserId,
				"fromUserName": this.curUserName,
				"content": this.textarea
			}
			this.list.push(data)
			this.websock.send(this.textarea)
			this.textarea = ''
		},

结果

 

 

NP614

 

...全文
658 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
基于SpringBoot+Vue开发的网页版聊天室系统源码+项目说明+sql数据库(前端+后端).zip 【优质项目推荐】 1.项目代码功能经验证ok,确保稳定可靠运行。欢迎下载使用!在使用过程中,如有问题或建议,请及时私信沟通,帮助解答。 2.项目主要针对各个计算机相关专业,包括计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师或企业员工使用。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、项目初期立项演示等用途。 4.如果基础还行,或热爱钻研,可基于此项目进行二次开发,DIY其他不同功能。 基于SpringBoot+Vue开发的网页版聊天室系统源码+项目说明+sql数据库(前端+后端).zip 微言聊天室是基于前后端分离,采用SpringBoot+Vue框架开发的网页版聊天室。 使用了Spring Security安全框架进行密码的加密存储和登录登出等逻辑的处理,以WebSocket+Socket.js+Stomp.js实现消息的发送与接收,监听。搭建FastDFS文件服务器用于保存图片,使用EasyExcel导出数据,使用Vue.js结合Element UI进行显示弹窗和数据表格分页等功能,以及整个系统的界面进行UI设计,并且使用MyBatis结合数据库MySQL进行开发。最后使用了Nginx进行部署前后端分离项目。 功能实现:群聊,单聊,邮件发送,emoji表情发送,图片发送,用户管理,群聊记录管理,Excel的导出。 项目预览地址:http://www.javahai.top/index.html ## 项目技术栈 ### 后端技术栈 1. Spring Boot 2. Spring Security 3. MyBatis 4. MySQL 5. WebSocket 6. RabbitMQ 7. Redis ### 前端技术栈 1. Vue 2. ElementUI 3. axios 4. vue-router 5. Vuex 6. WebSocket 7. vue-cli4 ... ## 项目预览图 客户端界面-群聊主界面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201108163850583.png) 客户端界面-私聊界面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020110816390059.png) 管理端界面-用户管理 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201108163906854.png) 管理端界面-群聊消息管理 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201108163912953.png) ## 部署流程 1. clone 项目到本地 2. 在本地 MySQL 中创建一个空的数据库 subtlechat,在该数据库中运行提供的数据库脚本subtlechat.sql,完成表的创建和数据的导入。 3. 提前准备好Redis,在项目中的mail模块的 application.yml 文件中,将 Redis 配置改为自己的。 4. 提前准备好RabbitMQ,在项目中的mail模块的 application.yml 文件中和web模块中的 application-dev.properties,将 RabbitMQ 的配置改为自己的。 5. 注册邮箱的授权码,在项目中的mail模块的 application.yml 文件中填入 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201108165225396.png) 6. 搭建fastdfs服务器,fastdfs-client.properties文件改成自己的。 7. 在 IntelliJ IDEA 中打开subtlechat项目,先启动 mail模块,再启动web模块。 8. 启动vue项目。
毕设新项目基于SpringBoot+Vue的web网页版聊天室系统完整源码+项目说明+数据库(含前端+后端).zip 【1】项目代码完整且功能都验证ok,确保稳定可靠运行后才上传。欢迎下载使用!在使用过程中,如有问题或建议,请及时私信沟通,帮助解答。 【2】项目主要针对各个计算机相关专业,包括计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师或企业员工使用。 【3】项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 【4】如果基础还行,或热爱钻研,可基于此项目进行二次开发,DIY其他不同功能,欢迎交流学习。 【备注】 项目下载解压后,项目名字和项目路径不要用中文,否则可能会出现解析不了的错误,建议解压重命名为英文名字后再运行!有问题私信沟通,祝顺利! 微言聊天室是基于前后端分离,采用SpringBoot+Vue框架开发的网页版聊天室。 使用了Spring Security安全框架进行密码的加密存储和登录登出等逻辑的处理,以WebSocket+Socket.js+Stomp.js实现消息的发送与接收,监听。搭建FastDFS文件服务器用于保存图片,使用EasyExcel导出数据,使用Vue.js结合Element UI进行显示弹窗和数据表格分页等功能,以及整个系统的界面进行UI设计,并且使用MyBatis结合数据库MySQL进行开发。最后使用了Nginx进行部署前后端分离项目。 功能实现:群聊,单聊,邮件发送,emoji表情发送,图片发送,用户管理,群聊记录管理,Excel的导出。 项目技术栈 #后端技术栈 1. Spring Boot 2. Spring Security 3. MyBatis 4. MySQL 5. WebSocket 6. RabbitMQ 7. Redis #前端技术栈 1. Vue 2. ElementUI 3. axios 4. vue-router 5. Vuex 6. WebSocket 7. vue-cli4 ... 项目预览图 客户端界面-群聊主界面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201108163850583.png) 客户端界面-私聊界面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020110816390059.png) 管理端界面-用户管理 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201108163906854.png) 管理端界面-群聊消息管理 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201108163912953.png) 部署流程 1. csdn下载项目到本地 2. 在本地 MySQL 中创建一个空的数据库 subtlechat,在该数据库中运行提供的数据库脚本subtlechat.sql,完成表的创建和数据的导入。 3. 提前准备好Redis,在项目中的mail模块的 application.yml 文件中,将 Redis 配置改为自己的。 4. 提前准备好RabbitMQ,在项目中的mail模块的 application.yml 文件中和web模块中的 application-dev.properties,将 RabbitMQ 的配置改为自己的。 5. 注册邮箱的授权码,在项目中的mail模块的 application.yml 文件中填入 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201108165225396.png) 6. 搭建fastdfs服务器,fastdfs-client.properties文件改成自己的。 7. 在 IntelliJ IDEA 中打开subtlechat项目,先启动 mail模块,再启动web模块。 8. 启动vue项目。

571

社区成员

发帖
与我相关
我的任务
社区描述
软件工程教学新范式,强化专项技能训练+基于项目的学习PBL。Git仓库:https://gitee.com/mengning997/se
软件工程 高校
社区管理员
  • 码农孟宁
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧