pom.xml
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
WebSocketConfig.java
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,
* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
Application
解决spring bean无法注入问题,如:在websocket中使用redis
@Slf4j
@SpringBootApplication
@EnableScheduling
@EnableAspectJAutoProxy
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
ConfigurableApplicationContext ctx = app.run(args);
/**解决spring bean无法注入问题*/
WebSocket.setApplicationContext(ctx);
/**解决spring bean无法注入问题*/
}
}
WebSocket.java
结合 springboot集成redis,redis常用工具类及redis队列使用
如果不需要删除redis相关代码就行
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.linus.config.redis.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Description: websocker服务类
* @date: 2024/7/26
*/
@Slf4j
@Component
@ServerEndpoint("/websocket/{userId}") //此注解相当于设置访问URL
public class WebSocket {
private Session session;
private String userId;
//线程安全的map,用来保存每个客户端对应的WebSocket对象
private static ConcurrentHashMap<String, WebSocket> webSocketMap = new ConcurrentHashMap<>();
//缓存消息的redis key
final String WEBSOCKET_CACHE_MESSAGE_REDIS_KEY = "WEBSOCKET_CACHE_MESSAGE_REDIS_KEY";
/*************解决spring bean无法注入问题**************/
private static ApplicationContext applicationContext;
public static void setApplicationContext(ApplicationContext applicationContext) {
WebSocket.applicationContext = applicationContext;
}
/*************解决spring bean无法注入问题**************/
/**
* 连接成功
* @OnOpen注解:websocket 连接成功后,触发该注解修饰的方法
* @param session
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "userId") String userId) {
try {
this.session = session;
this.userId = userId;
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
webSocketMap.put(userId, this);
} else {
webSocketMap.put(userId, this);
}
log.info("【websocket消息】有新的连接,总数为:" + webSocketMap.size());
//取出消息列表
RedisUtil redisUtil = applicationContext.getBean(RedisUtil.class);
while (redisUtil.hasKey(WEBSOCKET_CACHE_MESSAGE_REDIS_KEY+":"+userId)) {
String message = (String) redisUtil.lLeftPop(WEBSOCKET_CACHE_MESSAGE_REDIS_KEY + ":" + userId);
if (StrUtil.isEmpty(message)) {
log.info("用户" + userId + "没有缓存的消息");
} else {
//todo 发送消息-发送后清除缓存的消息
sendInfoByUserId(userId, message);
log.info("用户" + userId + "缓存的消息发送成功");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 接收消息
* @OnMessage注解:客户端发送消息时,触发该注解声明的方法
* @param message
* @return
*/
@OnMessage
public void onMessage(String message) {
log.info("【websocket消息】收到客户端消息userId:" + userId + ",接收信息:" + message);
if (webSocketMap.containsKey(userId)) {
try {
JSONObject obj = new JSONObject();
obj.put("cmd","heart");
obj.put("msgTxt", "心跳");
sendInfoByUserId(this.userId,obj.toJSONString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 连接关闭
* @OnClose注解:websocket断开连接后,触发该注解修饰的方法
* @param session
*/
@OnClose
public void onClose(Session session) {
try {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
}
log.info("【websocket消息】连接断开,总数为:" + webSocketMap.size());
// userStatusMessage();
} catch (Exception e) {
}
}
/**
* 连接异常
* @OnError注解:当建立的连接出现异常后,触发该注解修饰的方法
* @param session
* @param throwable
*/
@OnError
public void onError(Session session,Throwable throwable) {
log.info("【websocket消息】连接异常:" + throwable.getMessage());
}
/**
* 服务器给指定WebSocket客户端发送信息
* @param userId
* @param message
*/
public void sendInfoByUserId(String userId, String message) {
System.out.println("后端发送前端web数据userId:" + userId + "发送消息:" + message);
try {
if (webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).session.getBasicRemote().sendText(message);
} else {
log.info("用户" + userId + "没有连接");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 服务器给指定WebSocket客户端发送信息,如果未连接,缓存到redis
* @param userId
* @param message
*/
public void sendInfoByUserId_cache(String userId, String message) {
System.out.println("后端发送前端web数据userId:" + userId + "发送消息:" + message);
try {
if (webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).session.getBasicRemote().sendText(message);
} else {
log.info("用户" + userId + "没有连接");
//把新消息缓存到redis列表
RedisUtil redisUtil = applicationContext.getBean(RedisUtil.class);
redisUtil.rSet(WEBSOCKET_CACHE_MESSAGE_REDIS_KEY+":"+userId,message);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 服务器给全部连接客户端发送信息
* @param message
*/
public void sendInfoAll(String message) {
try {
webSocketMap.values().forEach(ws -> ws.session.getAsyncRemote().sendText(message));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 此为单点消息(多人)
* @param userIds
* @param message
*/
public void sendInfoByUserIds(String[] userIds, String message) {
for (String userId : userIds) {
sendInfoByUserId(userId, message);
}
}
/**
* 此为单点消息(多人),如果未连接缓存消息到redis
* @param userIds
* @param message
*/
public void sendInfoByUserIds_cache(String[] userIds, String message) {
for (String userId : userIds) {
sendInfoByUserId_cache(userId, message);
}
}
/**
* 获取全部连接用户的id
* @return
*/
public Set<String> getAllUserIds() {
return webSocketMap.keySet();
}
}
socket.js
uniapp
class Socket {
constructor(options) {
this.socketUrl = 'ws://117.72.37.178:24908/websocket/';
this.socketStart = false;
this.heartbeatTimer = null;
this.user = null;
this.initSocketListeners();
}
initSocketListeners() {
uni.onSocketOpen(() => {
this.socketStart = true;
console.log('WebSocket连接已打开!');
this.startHeartbeat();
});
uni.onSocketMessage(res => {
const data = JSON.parse(res.data);
this.acceptMessage && this.acceptMessage(data);
});
uni.onSocketClose(() => {
console.log('WebSocket 已关闭!');
this.socketStart = false;
this.reconnect();
});
uni.onSocketError(() => {
console.log('WebSocket连接打开失败,请检查!');
this.socketStart = false;
this.reconnect();
});
}
init(callback) {
this.user = uni.getStorageSync('user')
if (this.socketUrl && this.user.id) {
if (!this.socketStart) {
const url = `${this.socketUrl}${this.user.id}_app`;
console.log("启动 socketUrl 连接地址:", url);
uni.connectSocket({ url, method: 'GET' });
callback && callback();
} else {
console.log('webSocket已经启动了');
}
} else {
console.log('config/baseUrl socketUrl为空');
}
}
send(data) {
return new Promise((resolve, reject) => {
uni.sendSocketMessage({
data: JSON.stringify(data),
success: resolve,
fail: reject
});
});
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.send({ type: "心跳", userUid: this.user.id })
.catch(() => {
console.log('心跳发送失败');
this.reconnect();
});
}, 10000);
}
stopHeartbeat() {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
reconnect() {
this.stopHeartbeat();
setTimeout(() => {
this.init();
}, 5000);
}
closeSocket() {
uni.closeSocket();
this.user = null;
this.socketStart = false;
this.stopHeartbeat();
}
}
const mySocket = new Socket();
export default mySocket;
vue
export const WebsocketMixin = {
mounted() {
this.initWebSocket();
},
destroyed: function () {
// 离开页面生命周期函数
this.websocketOnclose();
},
methods:{
initWebSocket: function () {
console.log("------------WebSocket连接成功");
// WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https
var userId = store.getters.userInfo.id;
if(!this.socketUrl.startsWith('/')){
this.socketUrl = '/' + this.socketUrl
}
if(!this.socketUrl.endsWith('/')){
this.socketUrl = this.socketUrl + '/'
}
let url = window._CONFIG['domianURL'].replace("https://","wss://").replace("http://","ws://") + this.socketUrl + userId;
this.websock = new WebSocket(url);
this.websock.onopen = this.websocketOnopen;
this.websock.onerror = this.websocketOnerror;
this.websock.onmessage = this.websocketOnmessage;
this.websock.onclose = this.websocketOnclose;
},
websocketOnopen: function () {
console.log("WebSocket连接成功");
},
websocketOnerror: function (e) {
console.log("WebSocket连接发生错误");
this.reconnect();
},
websocketOnclose: function (e) {
this.reconnect();
},
websocketSend(text) {
// 数据发送
try {
this.websock.send(text);
} catch (err) {
console.log("send failed (" + err.code + ")");
}
},
reconnect() {
var that = this;
if(that.lockReconnect) return;
that.lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
setTimeout(function () {
console.info("尝试重连...");
that.initWebSocket();
that.lockReconnect = false;
}, 5000);
},
}
}