添加依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
开启WebSocket配置类
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
定时任务类:
@Configuration
@EnableScheduling
public class WebSocketScheduleTask {
@Resource
private SessionPool sessionPool;
//清理超时Session
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void cleanupTimeoutSessions() {
sessionPool.cleanupTimeoutSessions(300000); // 超时时间 5 分钟
}
}
服务端:
@Slf4j
@Component
@ServerEndpoint("/webSocket/{userId}")
public class WebSocketServer {
//静态函数注入
private final SessionPool sessionPool;
public WebSocketServer(SessionPool sessionPool) {
this.sessionPool = sessionPool;
}
public WebSocketServer() {
// 通过 Spring 工具获取 Bean
this.sessionPool = SpringUtil.getApplicationContext().getBean(SessionPool.class);;
}
//连接成功调用
@OnOpen
public void onOpen(Session session, @PathParam("userId") Long userId) {
sessionPool.addSession(userId, session);
}
//关闭连接
@OnClose
public void onClose(Session session, @PathParam("userId") Long userId) {
sessionPool.removeSession(userId);
log.info("用户: {} 下线了", userId);
}
//收到客户端消息后使用的方法
@OnMessage
public void OnMessage(String message, Session session, @PathParam("userId") Long userId) {
try {
sessionPool.onMessage(message, userId, session); // 委托给 SessionPool 处理
log.info("服务端收到用户userId:{} 的消息:{}", userId, message);
} catch (Exception e) {
log.error("消息处理异常, userId: {}, message: {}", userId, message, e);
}
}
@OnError
public void onError(Session session, Throwable error) {
log.error("WebSocket 错误", error);
}
}
执行管理:
@Slf4j
@Component
public class SessionPool {
public static final String PING = "ping";//心跳检测
// 存储所有活跃的 Session 及其对应的锁对象
private final ConcurrentHashMap<Long, Session> sessionMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, Object> sessionLocks = new ConcurrentHashMap<>();
//用户的最后活跃时间
private final ConcurrentHashMap<Long, Long> lastActiveTimeMap = new ConcurrentHashMap<>();
//连接成功调用
public void addSession(Long userId, Session session) {
sessionMap.put(userId, session);
sessionLocks.put(userId, new Object());
log.info("新用户加入,连接成功!userId: {} 在线人数: {}", userId, sessionMap.size());
}
//关闭连接
public void removeSession(Long userId) {
Session session = sessionMap.remove(userId);
if (StringUtils.isNotNull(session) && session.isOpen()) {
try {
session.close();
} catch (IOException e) {
log.error("关闭Session异常, userId: {},异常:{}", userId, e.getMessage());
} finally {
sessionLocks.remove(userId);
lastActiveTimeMap.remove(userId);
}
}
}
//清理超时Session
public void cleanupTimeoutSessions(long timeoutMillis) {
long currentTime = System.currentTimeMillis();
sessionMap.keySet().removeIf(userId -> {
Long lastActiveTime = lastActiveTimeMap.get(userId);
if (StringUtils.isNull(lastActiveTime) || (currentTime - lastActiveTime) > timeoutMillis) {
Session session = sessionMap.get(userId);
if ( StringUtils.isNotNull(session) && session.isOpen()) {
try {
session.close();
} catch (IOException e) {
log.error("清理超时Session异常, userId: {},异常:{}", userId, e.getMessage());
}
}
sessionLocks.remove(userId);
lastActiveTimeMap.remove(userId);
log.info("清理超时用户:{}", userId);
return true;
}
return false;
});
}
//处理消息
public void onMessage(String message, Long userId, Session session) throws IOException {
lastActiveTimeMap.put(userId, System.currentTimeMillis()); // 更新活跃时间
Object sessionLock = sessionLocks.get(userId);
if (StringUtils.isNull(sessionLock)) {
log.warn("未找到userId: {} 的锁对象", userId);
return;
}
synchronized (sessionLock) {
if (message.equalsIgnoreCase(PING) && session.isOpen()) {
// 心跳检测应答
session.getBasicRemote().sendText(new JSONObject() {{
put("type", "pong");
}}.toString());
log.info("心跳检测,应答客户端:{}", userId);
} else {
JSONObject msgJson = JSON.parseObject(message);
String type = msgJson.getString("type");
// 调用业务逻辑
}
}
}
}