websocket的聊天室案例(springBoot+vue)
后端
maven的坐标
<!--websocket依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
配置文件
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
- @author chen
- @description WebSocketConfig配置类,注入对象ServerEndpointExporter,
- 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
实体类封装
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
//用户间传送的消息
public class ResultMessage {
private boolean isSystem;
private String fromName;
//private String toName;
private Object message;
}
=====================================================
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
//呃…
public class Message {
private String toName;
private String message;
private String fromName;
}
工具类
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.websocker.pojo.ResultMessage;
//封装发送的消息内容
public class MessageUtils {
public static String getMessage(boolean isSystemMessage,String fromName,Object message){
try{
ResultMessage resultMessage = new ResultMessage();
resultMessage.setSystem(isSystemMessage);
resultMessage.setMessage(message);
if(fromName != null){
resultMessage.setFromName(fromName);
}
// if(toName !=null ){
// resultMessage.setToName(toName);
// }
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(resultMessage);
}catch (JsonProcessingException e){
e.printStackTrace();
}
return null;
}
}
配置注入httpsession
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
public class GetHttpSesstionConfig extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
HttpSession httpSession= (HttpSession) request.getHttpSession();
//将httpsession对象存储到配置 ServerEndpointConfig对象中
if(httpSession!=null){
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}
}
webscoket层的编写
import com.fasterxml.jackson.databind.ObjectMapper;
import com.websocker.pojo.Message;
import com.websocker.utile.MessageUtils;
import org.springframework.stereotype.Component;
import javax.websocket.;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
//设置我们当前是webscoket的类
@Component
@ServerEndpoint(value = “/chat”, configurator = GetHttpSesstionConfig.class)
public class ChatWebSocket {
// 用来 存储每一个客户端对应的当前类对象
private static Map<String, ChatWebSocket> onlineUsers = new ConcurrentHashMap<>();
//声明session 对象 通过该对象可以发给指定的用户
private Session session;
@OnOpen
//连接立时被调用
public void Onpen(Session session, EndpointConfig config) {
//给全局变量赋值
this.session = session;
// this.httpSession= (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
//从httpsession获取用户名
String username = session.getRequestParameterMap().get(“username”).get(0);
//将当前的这个对象存储到容器当中
onlineUsers.put(username, this);
//将当前在线用户的用户名推送到所有的客户端
String message = MessageUtils.getMessage(true, null, getNames());
//调用方法进行系统消息的推送
broadcastAllUsers(message);
}
private void broadcastAllUsers(String message) {
//要将改消息给全部的客户端
Set<String> strings = onlineUsers.keySet();
for (String name : strings) {
// 获取当前集合里面的全部客户端
ChatWebSocket chatWebSocket = onlineUsers.get(name);
try {
//把消息推送给每一个客客户端
chatWebSocket.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//获取全部的客户端名
private Set<String> getNames() {
return onlineUsers.keySet();
}
@OnMessage
/*
* 接收到的数据
*/
public void onMessage(String message, Session session) throws IOException {
//将数据转成message对象
ObjectMapper mapper = new ObjectMapper();
Message mess = mapper.readValue(message, Message.class);
//获取要将发送给的用户
String toName = mess.getToName();
//获取推送给指定用户的信息格式的数据
String data = mess.getMessage();
//获取当前登录的用户
String username = session.getRequestParameterMap().get(“username”).get(0);
String resultMessage = MessageUtils.getMessage(false, username, data);
//发送消息
onlineUsers.get(toName).session.getBasicRemote().sendText(resultMessage);
}
@OnClose
//关闭连接
public void onClose(Session session) {
//从容器删除指定的用户
//获取当前登录的用户
String username = session.getRequestParameterMap().get(“username”).get(0);
onlineUsers.remove(username);
//获取推送的信息
String message = MessageUtils.getMessage(true, null, getNames());
broadcastAllUsers(message);
}
}
前端
<template>
<div>
<el-row :gutter=“20”>
<el-col :span=“12” :offset=“6”>
<div class=“main”>
<el-row>
<el-input
placeholder=“请输入自己的昵称”
prefix-icon=“el-icon-user-solid”
v-model=“name”
style=“width: 50%”
></el-input>
<el-button type=“primary” @click=“conectWebSocket()”
>建立连接</el-button
>
<el-button type=“danger” @click=“colos”>断开连接</el-button>
</el-row>
<el-row>
<el-input
placeholder=“请输入要发送的消息”
prefix-icon=“el-icon-s-promotion”
v-model=“messageValue”
style=“width: 50%”
></el-input>
<el-button type=“primary” @click=“sendMessage()”>发送</el-button>
</el-row>
<div class=“message”>
<center v-if=“names.length > 0”>和{{ toName }}的聊天</center>
<div v-for=“(value, index) in messageList” :key=“index”>
<el-tag
v-if=“value.fromName == name”
type=“success”
style=“float: right”
>我:{{ value.message }}</el-tag
>
<br />
<el-tag v-if=“value.fromName != name” style=“float: left”
>{{ value.fromName }}:{{ value.message }}</el-tag
>
<br />
</div>
</div>
<br />
<div>
<el-card class=“box-card”>
<center v-if=“names.length > 0”>
<el-tag type=“success” key=“选择好友” effect=“dark”
>选择好友</el-tag
>
</center>
<div v-for=“o in names” :key=“o” class=“text item”>
<el-tag @click=“updateToname” type=“” effect=“dark”>{{
o
}}</el-tag>
</div>
</el-card>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
data() {
return {
name: “”, // 昵称
websocket: null, // WebSocket对象
aisle: “”, // 对方频道号
messageList: [], // 消息列表
messageValue: “”, // 消息内容
names: [],
broadcastList: “”,
toName: “”,
charData: null,
};
},
methods: {
updateToname(v) {
if (this.toName != v.srcElement.innerText) {
this.messageList = [];
}
console.log(v.srcElement.innerText);
this.toName = v.srcElement.innerText;
var data = JSON.parse(window.sessionStorage.getItem(this.toName));
console.log(data);
if (data != null) {
//将聊天记录渲染到聊天记录里面去
this.messageList.push(data);
}
},
colos() {
// this.websocket.close();
this.name = “”;
this.websocket = null;
this.messageList = [];
(this.messageValue = “”), (names = []);
},
//弹窗提示
ElementAlert(meassge) {
this.KaTeX parse error: Expected '}', got '&' at position 18: …tify.success({ &̲nbsp; &nbs…alert(“请输入自己的昵称”, “提示”, {
confirmButtonText: “确定”,
callback: (action) => {},
});
} else {
//判断当前浏览器是否支持WebSocket
if (“WebSocket” in window) {
this.websocket = new WebSocket(
“ws://localhost:8089/chat?username=” + this.name
);
} else {
alert(“不支持建立socket连接”);
}
//连接发生错误的回调方法
this.websocket.onerror = function (er) {
console.log(er);
};
//连接成功建立的回调方法
this.websocket.onopen = function (event) {};
//接收到消息的回调方法
var that = this;
this.websocket.onmessage = function (event) {
var dataStr = event.data;
//将dataStr 转换为json对象
var res = JSON.parse(dataStr);
// console.log(res)
if (res.system) {
//系统消息
var names = res.message;
//好丫列表
var userListStr = [];
var broadcastListStr = “”;
// console.log(that.name);
for (var i = 0; i < names.length; i++) {
if (that.name != names[i]) {
userListStr[userListStr.length] = names[i];
}
}
// console.log(userListStr)
that.names = userListStr;
that.broadcastList = broadcastListStr;
//s console.log(that.names)
//系统展示
} else {
//不是系统消息
//将服务的信息进行展示s
// that.messageList.push(res);
// console.log(““, res);
if (that.toName == res.fromName) {
that.messageList.push(res);
}
console.log(that.toName, res.fromName);
that.charData = window.sessionStorage.getItem(res.fromName);
var ss;
console.log(that.charData);
if (that.charData != null) {
that.charData[that.length] = res;
ss = that.charData;
} else {
ss = res;
}
console.log(”", ss, "*”, res.fromName);
window.sessionStorage.setItem(res.fromName, JSON.stringify(ss));
}
// that.messageList.push(object);
};
//连接关闭的回调方法
this.websocket.onclose = function () {};
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
// this.websocket.close();
};
}
},
// 发送消息
sendMessage: function () {
//获取发送给数据
var messageValue = this.messageValue;
this.messageValue = “”;
var json = { toName: this.toName, message: messageValue };
//发送数据给服务器
this.websocket.send(JSON.stringify(json));
var json1 = { fromName: this.name, message: messageValue };
this.messageList.push(json1);
},
showInfo: function (people, aisle) {
this.$notify({
title: “当前在线人数:” + people,
message: “您的频道号:” + aisle,
duration: 0,
});
},
},
};
</script>
<style scoped>
.main {
position: relative;
top: 20px;
}
.message {
position: relative;
overflow: auto;
top: 20px;
width: 100%;
height: 40%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
padding: 5px;
}
.text {
font-size: 14px;
}
.item {
padding: 18px 0;
}
.box-card {
width: 480px;
}
</style>
优化后和的前端代码
<template>
<div>
<el-row :gutter=“20”>
<el-col :span=“12” :offset=“6”>
<div class=“main”>
<!-- <el-row>
<el-input
placeholder=“请输入自己的昵称”
prefix-icon=“el-icon-user-solid”
v-model=“name”
style=“width: 50%”
></el-input>
<el-button type=“primary” @click=“conectWebSocket()”
>建立连接</el-button
>
</el-row> -->
<el-row>
<center>
<el-tag type=“danger” key=“选择好友” effect=“dark”>
当前用户:{{ user.name }}</el-tag
>
</center>
</el-row>
<br/>
<el-row>
<el-input
placeholder=“请输入要发送的消息”
prefix-icon=“el-icon-s-promotion”
v-model=“messageValue”
style=“width: 50%”
></el-input>
<el-button type=“primary” @click=“sendMessage()”>发送</el-button>
</el-row>
<div class=“message” v-if=“toName”>
<center>
<el-tag type=“success” key=“选择好友” effect=“dark”
>和{{ toName }}的聊天</el-tag
>
</center>
<div v-for=“(value, index) in messageList” :key=“index”>
<el-tag
v-if=“value.fromName == name”
type=“success”
style=“float: right”
>我:{{ value.message }}</el-tag
>
<br />
<el-tag v-if=“value.fromName != name” style=“float: left”
>{{ value.fromName }}:{{ value.message }}</el-tag
>
<br />
</div>
</div>
<br />
<div>
<el-card class=“box-card”>
<center v-if=“names.length > 0”>
<el-tag type=“success” key=“选择好友” effect=“dark”
>选择好友</el-tag
>
</center>
<div v-for=“o in names” :key=“o” class=“text item”>
<el-tag @click=“updateToname” type=“” effect=“dark”>{{
o
}}</el-tag>
</div>
</el-card>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
var that;
import socket from “@/api/websocket”;
import user from “@/api/user”;
export default {
data() {
return {
name: “”, // 昵称
websocket: null, // WebSocket对象
aisle: “”, // 对方频道号
messageList: [], // 消息列表
messageValue: “”, // 消息内容
names: [],
broadcastList: “”,
toName: “”,
charData: null,
user: {},
};
},
methods: {
updateToname(v) {
this.messageList = [];
console.log(v.srcElement.innerText);
this.toName = v.srcElement.innerText;
socket.getfinByNames(this.name, this.toName).then((res) => {
this.messageList = res.data.data.data;
console.log(res.data.data.data);
});
},
colos() {
// this.websocket.close();
this.name = “”;
this.websocket = null;
this.messageList = [];
(this.messageValue = “”), (names = []);
},
//弹窗提示
ElementAlert(meassge) {
this.KaTeX parse error: Expected '}', got '&' at position 18: …tify.success({ &̲nbsp; &nbs…alert(“请输入自己的昵称”, “提示”, {
confirmButtonText: “确定”,
callback: (action) => {},
});
} else {
//判断当前浏览器是否支持WebSocket
if (“WebSocket” in window) {
this.websocket = new WebSocket(
“ws://localhost:8989/chat?username=” + this.name
);
} else {
alert(“不支持建立socket连接”);
}
//连接发生错误的回调方法
this.websocket.onerror = function (er) {
console.log(er);
};
//连接成功建立的回调方法
this.websocket.onopen = function (event) {};
//接收到消息的回调方法
this.websocket.onmessage = function (event) {
var dataStr = event.data;
//将dataStr 转换为json对象
var res = JSON.parse(dataStr);
// console.log(res)
if (res.system) {
//系统消息
var names = res.message;
//好丫列表
var userListStr = [];
var broadcastListStr = “”;
// console.log(that.name);
for (var i = 0; i < names.length; i++) {
if (that.name != names[i]) {
userListStr[userListStr.length] = names[i];
}
}
// console.log(userListStr)
that.names = userListStr;
that.broadcastList = broadcastListStr;
//s console.log(that.names)
//系统展示
} else {
//不是系统消息
//将服务的信息进行展示s
if (that.toName == res.fromName) {
if (that.messageList != null) {
that.messageList.push(res);
} else {
that.messageList = res;
}
}
/* console.log( that.messageList, “==========”); /
socket.saveList(that.messageList);
}
};
//连接关闭的回调方法
this.websocket.onclose = function () {};
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
// this.websocket.close();
};
}
},
// 发送消息
sendMessage: function () {
//获取发送给数据
var messageValue = this.messageValue;
this.messageValue = “”;
var json = { toName: this.toName, message: messageValue };
//发送数据给服务器
this.websocket.send(JSON.stringify(json));
var json1 = {
fromName: this.name,
message: messageValue,
toName: this.toName,
};
/ console.log(that.messageList,“***前”) /
if (that.messageList != null) {
that.messageList.push(json1);
} else {
that.messageList = [json1];
}
/ console.log(that.messageList,“*后”) */
socket.saveList(that.messageList);
},
showInfo: function (people, aisle) {
this.$notify({
title: “当前在线人数:” + people,
message: “您的频道号:” + aisle,
duration: 0,
});
},
//获取我们用户信息的方法
getUser() {
user.getUserToken(window.sessionStorage.getItem(“token”)).then((res) => {
console.log(res.data);
this.user = res.data.data;
this.name = this.user.name;
this.conectWebSocket()
});
},
},
//页面加载完毕
created() {
this.getUser();
},
};
</script>
<style scoped>
.main {
position: relative;
top: 20px;
}
.message {
position: relative;
overflow: auto;
top: 20px;
width: 100%;
height: 40%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
padding: 5px;
}
.text {
font-size: 14px;
}
.item {
padding: 18px 0;
}
.box-card {
width: 480px;
}
</style>
后端把聊天记录保存到redis中
import com.shop.shop.entity.Message;
import com.shop.shop.utile.Q;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping(“/shop/websocket”)
/**
对我们的聊天记录进行保存到我们的redis当中去
/
public class WebSocketRedisConreoller {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@GetMapping(“/findName/{name}/{toName}”)
/
根据name查询聊天记录
*/
public Q findName(@PathVariable(“toName”) String toName,
@PathVariable(“name”) String name
) throws IOException {
List<Message> list = (List<Message>) redisTemplate.opsForValue().get(name + “" + toName);
List<Message> list1 = (List<Message>) redisTemplate.opsForValue().get(toName + "” + name);
if (list == null && list1 != null) {
list = list1;
}
return Q.ok().data(“data”, list);
}
@PostMapping(“/save”)
//直接保存
public Q saveList(@RequestBody List<Message> list) {
if (list.size() <= 0) {
return Q.error();
}
redisTemplate.opsForValue().set(list.get(0).getFromName() + “" + list.get(0).getToName(), list);
redisTemplate.opsForValue().set(list.get(0).getToName() + "” + list.get(0).getFromName(), list);
return Q.ok();
}
}