后端
相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-api</artifactId>
</dependency>
相关配置
后来由于无法注入spring boot依赖,修改过多次,但是最终还是没有成功,最后还是保持了原本状态
@Configuration
public class WebSocketConfig extends ServerEndpointConfig.Configurator {
/*private final ApplicationContext applicationContext;
public WebSocketConfig(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
*/
/**
* 注入ServerEndpointExporter,
* 这个bean会自动注册使用了@ServerEndpoint注解声明的WebSocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
WebSocket
@onOpen, @onclose,@onerror,@message等都是直接按照教程写的,改变成本项目适配的,对于接收信息写了一个函数sendmessage(),主要是这些
package com.mental.mental.utils;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.mental.mental.config.WebSocketConfig;
import com.mental.mental.service.ChatService;
import jakarta.annotation.PostConstruct;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* WebSocket实现类
* @author 周恒
* @date 24/5/18
*/
@ServerEndpoint(value = "/websocket/{username}")
@Component
public class WebSocket {
//private static ApplicationContext applicationContext;
//@Autowired
//private ChatService chatService;
//@Autowired
//public void setChatService(ChatService chatService){
// this.chatService = chatService;
// }
// @Autowired
// public void setApplicationContext(ApplicationContext applicationContext) {
// WebSocket.applicationContext = applicationContext;
//}
//@PostConstruct
// public void init() {
// chatService = applicationContext.getBean(ChatService.class);
// }
private static final Logger log = LoggerFactory.getLogger(WebSocket.class);
/**
* 记录当前在线连接数
*/
public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();//适用于多线程
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("username") String username){
sessionMap.put(username,session);
log.info("有新用户加入,username={}, 当前在线人数为:{}",username,sessionMap.size());
JSONObject result = new JSONObject();
JSONArray array = new JSONArray();
result.set("users",array);
for(Object key : sessionMap.keySet()){
JSONObject jsonObject = new JSONObject();
jsonObject.set("username",key);
array.add(jsonObject);
}
sendAllMessage(JSONUtil.toJsonStr(result));//后台发送消息给所有的客户端
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session,@PathParam("username") String username){
sessionMap.remove(username);
log.info("有一连接关闭,移除username={}的用户session, 当前在线人数为:{}",username,sessionMap.size());
}
/**
* 收到客户端消息后调用的方法
* 后台收到客户端发送过来的消息
* onMessage是一个消息的中转站
* 接受 浏览器端 socket.send 发送过来的 json数据
* @param message
* @param session
* @param username
*/
@OnMessage
public void onMessage(String message,Session session,@PathParam("username") String username){
log.info("服务器收到用户username={}的消息:{}",username,message);
JSONObject obj = JSONUtil.parseObj(message);
String toUsername = obj.getStr("to");
String text = obj.getStr("content");//发送的消息文本
//chatService.saveMessage(username,toUsername,text);
Session toSession = sessionMap.get(toUsername);//根据to用户名获取session,再通过session发送消息文本
//{"to":"user2","text":" "}
if(toSession!=null){
//服务器端,再把消息组装一下,组装后的消息包含发送人和发送的文本内容
//{"from":"user1","text":""}
JSONObject jsonObject = new JSONObject();
jsonObject.set("from",username);
jsonObject.set("to", toUsername);
jsonObject.set("text",text);
this.sendMessage(jsonObject.toString(),toSession);
log.info("发送给用户username={},消息:{}", toUsername, jsonObject.toString());
} else {
log.info("发送失败,未找到用户username={}的session", toUsername);
}
}
@OnError
public void onError(Session session, Throwable error){
log.error("发生错误");
error.printStackTrace();
}
/**
* 服务端发送消息给客户端
* @param message
* @param toSession
*/
private void sendMessage(String message,Session toSession){
try{
log.info("服务端给客户端[{}]发送消息{}",toSession.getId(),message);
toSession.getBasicRemote().sendText(message);
}catch (Exception e){
log.error("服务端发送消息给客户端失败",e);
}
}
/**
* 服务器端发送消息给所有客户端
* @param message
*/
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("服务器发送消息给客户端失败",e);
}
}
}
前端
EventBus
监听全局事件,这里我主要是用来获取webSocket返回值
在store.js里面加上
// 监听 WebSocketService 的全局事件并更新 Vuex 状态
EventBus.$on('messageReceived', (message) => {
store.commit('addChatMessage', message);
});
新建一个文件EventBus.js
import Vue from 'vue'
export const EventBus = new Vue()
WebSocketService.js
// WebSocketService.js
import { EventBus } from './EventBus'; // 你可以使用 EventBus 或 Vue.prototype.$emit 来触发全局事件
class WebSocketService {
constructor() {
this.socket = null;
this.connect();
}
connect() {
const user = JSON.parse(localStorage.getItem('user'));
this.socket = new WebSocket('ws://localhost:9966/websocket/' + user.uname);
this.socket.onopen = () => {
console.log('WebSocket Connection Opened');
// 这里可以发送认证信息或初始化消息
};
this.socket.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Received Message:', message);
EventBus.$emit('messageReceived', message); // 触发全局事件
};
this.socket.onerror = (error) => {
console.error('WebSocket Error:', error);
};
this.socket.onclose = (event) => {
if (event.wasClean) {
console.log('WebSocket Connection Closed Cleanly');
} else {
console.error('WebSocket Connection Died');
// 可以在这里尝试重新连接
}
};
}
sendMessage(message) {
if (this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(message));
} else {
console.error('WebSocket is not open. ReadyState:', this.socket.readyState);
}
}
}
export default new WebSocketService(); // 导出单例
仍然是固定套路,连接和发送信息,主要是将实例改成自己需要的形式
总结
本周和之前的vuex那一周一样,主要还是学习为主,一个以前没有接触过的东西,学习的时候有点难,但一旦学会了用的时候就很简单