571
社区成员




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