本文章使用vue3+springboot通过websocket实现两个用户之间的实时通信,聊天信息使用mongodb非关系型数据库进行存储。
效果图如下:
用户发送信息
农户收到信息并发送回去
后台消息打印
org.springframework.boot
spring-boot-starter-websocket
cn.hutool
hutool-all
5.8.7
org.slf4j
slf4j-api
在config目录下,创建WebSocketConfig类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig{
/*
* 注入一个ServerEndpointExporter,该ean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
* */
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
创建一个WebSocketServer来处理连接
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.result.UpdateResult;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint(value = "/chat/{userId}") // 这里设置的是访问后端接口
@Component
public class WebSocketServer {
// 这部分是日志打印
private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
// 在线连接数
public static final Map sessionMap = new ConcurrentHashMap<>();
// 这里我是采用mongodb去存储聊天数据
private MongoCollection collection;
public WebSocketServer() {
initializeMongoCollection();
}
private void initializeMongoCollection() {
MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017");
MongoDatabase database = mongoClient.getDatabase("farmer"); // 这里设置mongodb数据库名
collection = database.getCollection("chat"); // 设置集合名称
}
// 当有用户建立连接到服务器的时候触发
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId){
// 建立连接的时候就将该用户的session存起来,方便后续的信息返回
sessionMap.put(userId, session);
log.info("新聊天用户={},当前在线人数:{}",userId, sessionMap.size());
}
// 关闭时触发
@OnClose
public void onClose(Session session, @PathParam("userId") String userId){
sessionMap.remove(userId);
log.info("有一连接关闭,移除聊天用户={},当前在线人数为:{}", userId, sessionMap.size());
}
// 当用户发送消息时触发
@OnMessage
public void onMessage(String parmas, Session session, @PathParam("userId") String userId){
log.info("服务器收到聊天用户={}的消息:{}", userId, parmas);
// 将收到前端的消息解析取出
JSONObject object = JSONUtil.parseObj(parmas);
String chatId = object.getStr("chatId"); // 聊天id
String toUserId = object.getStr("to"); // 发送给谁
String text = object.getStr("text"); // 文本信息
String time = object.getStr("time"); // 发送时间
Session toSession = sessionMap.get(toUserId); // 查询要发送的人是否在线
// 新建对象将要存储到数据库的内容封装到一起
JSONObject jsonObject = new JSONObject();
jsonObject.set("from", userId);
jsonObject.set("to", toUserId);
jsonObject.set("text", text);
jsonObject.set("time", time);
// 如果toSession为空,则说明对面没在线,直接存到数据库
// 若不为空,则说明对面在线,将数据存到数据库并且通过sendMessage实时发送给目标客户
if(toSession != null){
sendMessage(jsonObject.toString(), toSession);
log.info("发送给用户username={},消息:{}", toUserId, jsonObject.toString());
insertChatData(jsonObject, chatId);
}else{
log.info("用户{}不在线", toUserId);
insertChatData(jsonObject, chatId);
}
}
@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("服务器发送消息给客户端失败");
}
}
// 服务器发送信息给所有客户端(这步可拓展到群聊)
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("服务器发送消息给客户端失败");
}
}
// 这是mongodb存到数据库的操作
private void insertChatData(JSONObject chatData, String chatId) {
Document document = Document.parse(chatData.toString());
Document query = new Document("chatId", chatId);
Document update = new Document("$push", new Document("chatData", document));
UpdateResult updateResult = collection.updateOne(query, update, new UpdateOptions().upsert(true));
System.out.println(updateResult);
}
}
用户部分IndexMessage:
在script中先定义一个
let socket;
在methods中
init() {
let _this = this
let userId = this.user.userId; // 当前本用户的id
if (this.supportWebSocket()) {
let socketUrl = "ws://localhost:8090/chat/" + userId;
if (socket != null) {
socket.close();
socket = null;
}
// 开启一个websocket服务
socket = new WebSocket(socketUrl);
//打开事件
socket.onopen = function () {
console.log("websocket已打开");
};
// 监听数据
// 浏览器端收消息,获得从服务端发送过来的文本消息
socket.onmessage = function (msg) {
console.log("收到数据====" + msg.data)
let data = JSON.parse(msg.data)
// 收到最新消息的时候,更新左侧聊天导航的文本内容
_this.chatNav.forEach(item => {
if(item.farmerId == data.from){
item.text = data.text
}
})
// 收到消息的时候去更新chatData中的数据
_this.addToConetnt(data)
};
//关闭事件
socket.onclose = function () {
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function (e) {
console.log(e)
console.log("websocket发生了错误");
}
}
},
send() {
if (!this.text) {
// 若为空,提示为空,此处使用elementUI的消息提示
this.$message({type: 'warning', message: "请输入内容"})
} else {
// 封装了一个查看是否支撑websocket的方法
if(this.supportWebSocket()){
// 发送的消息
let message = {
from: this.user.userId,
to: this.toUser.toUserId,
text: this.text,
time: moment().format('x') // 使用moment包生成一个时间戳
}
this.addToConetnt(message)
this.text = '';
this.$refs.messageInput.innerHTML = "" // 将输入的值设置为空
// 整合发送消息
message["chatId"] = this.chatId
socket.send(JSON.stringify(message)); // 将组装好的json发送给服务端,由服务端进行转发
// 发送消息的时候,更新左侧聊天导航的文本内容
this.chatNav.forEach(item => {
if(item.farmerId == message.to){
item.text = message.text
}
})
// 将展示内容的div设置滚动到底部
this.$nextTick(()=>{
this.$refs.messageContent.scrollTop = this.$refs.messageContent.scrollHeight
})
}
}
},
// 是否支撑websocket
supportWebSocket(){
if(typeof (WebSocket) == "undefined"){
console.log("您的浏览器不支持WebSocket");
return false
}else{
return true
}
},
// 新增记录判断
addToConetnt(message){
let arr = this.chatData[this.chatData.length - 1]
const startTime = moment(parseInt(arr.chatItem[arr.chatItem.length - 1].time)); // 最近一条消息的时间
const endTime = moment(); // 当前时间
const duration = moment.duration(endTime.diff(startTime));
const minutes = duration.asMinutes();
// 判断最近的消息和上一条消息是否超过3分钟,超过三分钟则重新创建一个对象存储
if(minutes > 3){
this.chatData.push({
time: moment(endTime).format("HH:mm"),
chatItem: [message]
})
}else{
this.chatData[this.chatData.length - 1].chatItem.push(message)
}
this.$nextTick(()=>{
this.$refs.messageContent.scrollTop = this.$refs.messageContent.scrollHeight
})
},
重点部分在init中几个socket事件的写法和send发送中scoket的写法,其他的是自己项目的功能实现,按照自己的需求去写就好了
一些chatData的数据类型如下:
mongdb的数据库类型如下:
mongodb只是作为参考,若你想使用关系型数据库mysql也是可以,修改具体逻辑就行