Vue WebSocket实现聊天
SpringBoot后端代码
1.引入Websocket依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.配置websocket
@ServerEndpoint(value = "/chat/{fromUser}")
@Component
public class MyWebsocket {
private static Logger log = LoggerFactory.getLogger(MyWebsocket.class);
private Session session;
/**
* 在线用户列表
*/
public static Map<String, Session> sessionPool = new HashMap();
@OnOpen
public void onOpen(Session session, @PathParam(value = "fromUser") String fromUser) {
this.session = session;
sessionPool.put(fromUser, session);
System.out.println(fromUser + "【websocket消息】有新的连接,总数为:" + sessionPool.size());
}
@OnClose
public void onClose() {
sessionPool.remove(this);
System.out.println("【websocket消息】连接断开,总数为:" + sessionPool.size());
}
@OnMessage
public void onMessage(String msg) {
log.info("【websocket消息】收到客户端消息:" + msg);
String resoult = msg;
Map<String, Object> map = JsonUtils.toMap(msg);
String toUser = (String) map.get("toUser");
String ms = (String) map.get("msg");
String account = (String) map.get("fromUser");
sendOneMessage(account, toUser, ms);
}
/**
* 此为单点消息
*/
public void sendOneMessage(String account, String userName, String message) {
try {
log.info("【websocket消息】单点消息:" + message);
Session session = sessionPool.get(userName);
sessionPool.get(userName);
try {
//将信息保存到message表中
Record record=new Record();
record.set("date",TimeUtils.getCurrentTime());
record.set("touser",userName);
record.set("fromuser",account);
record.set("type","test");
record.set("message",message);
Db.save("message","id",record);
} catch (Exception e) {
log.info(e.getMessage());
}
ChatMessage chatMessage = new ChatMessage();
chatMessage.setToUser(userName);
chatMessage.setMsg(message);
chatMessage.setFromUser(account);
chatMessage.setType("test");
String time = TimeUtils.getCurrentTime();
chatMessage.setTime(time);
String s = JsonUtils.toJSONString(chatMessage);
if (session != null) {
session.getAsyncRemote().sendText(s);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Vue前端展示
1.完整代码chat.vue
<template>
<div>
<!--弹出层查看用户历史发送的消息 -->
<el-dialog
title="历史消息"
:visible.sync="dialogFormVisible"
width="900px"
top="200px"
>
<el-table :data="tableData" height="250" border style="width: 100%">
<el-table-column prop="date" label="日期" width="180">
</el-table-column>
<el-table-column prop="fromuser" label="发送者" width="180">
</el-table-column>
<el-table-column prop="touser" label="接收者"> </el-table-column>
<el-table-column prop="message" label="信息"> </el-table-column>
</el-table>
</el-dialog>
<el-row type="flex" justify="center">
<el-col :md="14">
<el-card
:body-style="{ padding: '10px' }"
style="background-color: #E8E8E8; border: 1px solid #DDDDDD; border-bottom: 0;"
>
<div style="height: 20px;">
<div
style="width: 20px; height: 20px; background-color: #DF7065; border-radius: 50%; float: left;"
@click="webSokcetClose()"
></div>
<div
style="width: 20px; height: 20px; background-color: #E6BB46; border-radius: 50%; float: left; margin-left: 10px;"
></div>
<div
style="width: 20px; height: 20px; background-color: #5BCC8B; border-radius: 50%; float: left; margin-left: 10px;"
></div>
<div>
<el-button
style="margin-left: 10px;"
size="mini"
type="success"
icon="el-icon-check"
circle
@click="getMessage()"
></el-button>
</div>
</div>
</el-card>
<el-card
:body-style="{ padding: '0' }"
style="border: 1px solid #DDDDDD; border-top: 0;"
>
<el-row>
<el-col
:md="6"
style=" height: 600px; border-right: 2px solid #DDDDDD; overflow: auto;"
>
<el-card
:body-style="{
'padding-top': '11px',
'padding-bottom': '12px',
'padding-left': '10px',
'padding-right': '12px'
}"
shadow="never"
>
<div>
<el-input
size="mini"
placeholder="请输入内容"
v-model="input1"
>
</el-input>
</div>
</el-card>
<div v-for="item in listUser" :key="item.id">
<a href="javascript:void(0);" @click="toggleChat(item.account)">
<el-card :body-style="{ padding: '5px 10px' }" shadow="never">
<el-row>
<el-col :span="7">
<el-image
style="width: 40px; height: 40px; border-radius: 50%;"
src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
fit="cover"
></el-image>
</el-col>
<el-col :span="17">
<h4
class="m-0"
style="color: #666666; font-size: 15px;"
>
{{ item.account }}
</h4>
<p
style="color: #999999; font-size: 12px; margin: 0; margin-top: 3px;"
class="limitTitleDirectory"
>
这个家伙很懒,什么也没有留下。
</p>
</el-col>
</el-row>
</el-card>
</a>
</div>
</el-col>
<el-col :md="18">
<div v-show="toUser !== '游客'">
<el-card :body-style="{ padding: '15px' }" shadow="never">
<div class="text-center">
<p class="m-0">{{ toUser }}</p>
</div>
</el-card>
<el-card :body-style="{ padding: '5px' }" shadow="never">
<div
v-for="item in this.listUser"
:key="item.id"
v-show="toUser === item.account"
:id="item.account"
style="height: 327px; overflow: auto;"
></div>
</el-card>
<el-input
type="textarea"
:rows="5"
v-model="input"
@keyup.enter.native="webSokcetSend()"
style="margin-top:60px"
></el-input>
</div>
<div v-show="toUser === '游客'">
<div
style="text-align: center; line-height: 600px; background-color: #f3f3f3;"
>
<p style="margin: 0; font-size: 22px; color: #909399;">
没有会话消息
</p>
</div>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { getOnlineuser } from "@/api/chat";
import { formatTime } from "@/utils/TimeUtils";
import { getMessage } from "@/api/chat";
export default {
name: "mac",
data() {
return {
input: "",
input1: "",
//在线人数
listUser: [],
webSocket: "",
fromUser: "游客",
toUser: "游客",
tableData: "",
dialogFormVisible: false //弹出层默认为关闭
};
},
created() {
//得到在线人数
getOnlineuser("/ws/getOnlineuser").then(res => {
if (res.code === 200) {
this.listUser = res.data;
} else if (res.code === 500) {
this.$message(res.data);
}
});
this.fromUser = localStorage.getItem("username");
},
mounted() {
if (document.cookie === "") {
this.webSokcetClose();
} else {
}
//配置websocket服务器地址
this.webSocket = new WebSocket("ws://localhost:8001/chat/" + this.fromUser);
this.initWebSocket();
},
methods: {
initWebSocket() {
this.webSocket.onerror = this.onError; // 通讯异常
this.webSocket.onopen = this.onOpen; // 连接成功
this.webSocket.onmessage = this.onMessage; // 收到消息时回调
this.webSocket.onclose = this.onClose; // 连接关闭时回调
},
onError() {
console.log("通讯异常");
},
onOpen() {
console.log("通讯开始");
},
onMessage(event) {
console.log(event.data);
let data = JSON.parse(event.data);
// console.log(data);
if (data.list !== undefined) {
let list = data.list;
for (let i = 0; i < list.length; i++) {
if (list[i] === this.fromUser) {
list.splice(i, 1);
}
}
this.listUser = list;
}
this.messageDiv(event.data);
console.log(event.data);
},
onClose() {
console.log("通讯关闭");
},
webSokcetSend() {
var now = new Date();
var strnow = formatTime(now);
/*
* 发送消息
* */
let message = JSON.stringify({
fromUser: this.fromUser,
toUser: this.toUser,
msg: this.input,
time: this.strnow
});
this.webSocket.send(message);
this.input = "";
this.messageDiv(message);
},
webSokcetClose() {
},
toggleChat(toUser) {
this.toUser = toUser;
},
messageDiv(data1) {
let data = JSON.parse(data1);
let div = document.createElement("div");
let p1 = document.createElement("p");
let p = document.createElement("p");
if (data.time == null) {
data.time = formatTime(new Date());
}
p1.innerHTML = data.fromUser + " " + data.time;
debugger;
if (data.fromUser !== "系统通知") {
if (data.type !== undefined) {
/*
* data.type !== undefined
* 说明这是接收到消息
* 把值插到发送者的div
* */
// let fromUser = document.getElementById("test");
let fromUser = document.getElementById(data.fromUser);
p.innerHTML = "收到:" + data.msg;
div.style =
"width: 500px; float: left; margin-left: 15px; margin-top:10px; ";
p1.style =
"font-size: 15px; margin-bottom: 0; margin-top: 0; float: left; font-weight: 500; ";
p.style =
"padding: 10px; background-color: #F1F1F1; margin-top: 25px; margin-bottom: 10px; word-wrap : break-word; ";
div.appendChild(p1);
div.appendChild(p);
fromUser.appendChild(div);
fromUser.scrollTop = fromUser.scrollHeight;
if (fromUser.style.display === "none") {
// console.log("有新的消息未查看!");
this.$message("收到新的消息");
}
} else {
/*
* 说明这是发送消息
* 把值插到接收者的div
* */
// let toUser = document.getElementById("test");
let toUser = document.getElementById(data.toUser);
p.innerHTML = "发出:" + data.msg;
div.style =
"width: 500px; float: right; margin-right: 15px; margin-top: 10px;";
p1.style =
"font-size: 15px; margin-bottom: 0; margin-top: 0; float: right; font-weight: 500;";
p.style =
"padding: 10px; background-color: #9FE86C; margin-top: 25px; margin-bottom: 10px; word-wrap : break-word;";
div.appendChild(p1);
div.appendChild(p);
toUser.appendChild(div);
toUser.scrollTop = toUser.scrollHeight;
}
}
},
//获得历史消息
getMessage() {
getMessage("/ws/getMessage").then(res => {
if (res.code === 200) {
this.dialogFormVisible = true;
this.tableData = res.data;
} else if (res.code === 500) {
this.$message(res.data);
}
});
}
},
beforeRouteEnter(to, from, next) {
document
.querySelector("body")
.setAttribute("style", "background-color: #F9F9F9");
next();
},
beforeRouteLeave(to, from, next) {
// 去除背景色
document.querySelector("body").setAttribute("style", "");
next();
}
};
</script>
<style scoped>
.limitTitleDirectory {
width: 120px; /* 限制文本宽度 */
overflow: hidden; /* 超出的文本隐藏 */
text-overflow: ellipsis; /* 溢出的文本内容用 ... 代替 */
white-space: nowrap; /* 溢出不换行*/
}
element.style {
padding-left: 10px;
}
.el-menu-item {
font-size: 14px;
color: #303133;
padding: 0 10px;
cursor: pointer;
-webkit-transition: border-color 0.3s, background-color 0.3s, color 0.3s;
transition: border-color 0.3s, background-color 0.3s, color 0.3s;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
::-webkit-scrollbar {
width: 5px; /*滚动条宽度*/
height: 5px; /*滚动条高度*/
}
/*定义滚动条轨道 内阴影+圆角*/
::-webkit-scrollbar-track {
/*-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);*/
border-radius: 10px; /*滚动条的背景区域的圆角*/
/*background-color: red;!*滚动条的背景颜色*!*/
}
/*定义滑块 内阴影+圆角*/
::-webkit-scrollbar-thumb {
border-radius: 10px; /*滚动条的圆角*/
/*-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);*/
background-color: #b8b8bc; /*滚动条的背景颜色*/
}
.el-col-md-14 {
width: 80%;
}
.el-scrollbar {
height: 100%;
}
</style>
实现效果
源码地址:
https://gitee.com/Marlon_Brando