org.springframework.boot
spring-boot-starter-websocket
新建配置类
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @Description 开启springboot对websocket的支持
* @Author WangKun
* @Date 2023/8/14 17:21
* @Version
*/
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
@Configuration
public class WebSocketConfig{
/**
* @Description 注入一个ServerEndpointExporter, 会自动注册使用@ServerEndpoint注解
* @param
* @Throws
* @Return org.springframework.web.socket.server.standard.ServerEndpointExporter
* @Date 2023-08-14 17:26:31
* @Author WangKun
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
这个注解需要打上声明是开发环境,否则在tomcat部署中会报错
新建服务类
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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.concurrent.ConcurrentHashMap;
/**
* @Description websocket服务,不考虑分组
* @Author WangKun
* @Date 2023/8/14 17:29
* @Version
*/
@ConditionalOnClass(value = WebSocketConfig.class)
@ServerEndpoint("/websocket/{userId}")
@Component
@Slf4j
public class WebSocket {
//在线计数器
private static int onlineCount = 0;
//存放每个客户端对应的WebSocket对象。
private static final ConcurrentHashMap WEB_SOCKET_MAP = new ConcurrentHashMap<>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
private String userId;
/**
* @param
* @Description 在线数
* @Throws
* @Return int
* @Date 2023-08-14 17:47:19
* @Author WangKun
*/
public static synchronized int getOnlineCount() {
return onlineCount;
}
/**
* @param
* @Description 在线数加1
* @Throws
* @Return void
* @Date 2023-08-14 17:47:32
* @Author WangKun
*/
public static synchronized void addOnlineCount() {
WebSocket.onlineCount++;
}
/**
* @param
* @Description 在线数减1
* @Throws
* @Return void
* @Date 2023-08-14 17:47:47
* @Author WangKun
*/
public static synchronized void subOnlineCount() {
WebSocket.onlineCount--;
}
/**
* @param session
* @param userId
* @Description 建立连接
* @Throws
* @Return void
* @Date 2023-08-14 17:52:08
* @Author WangKun
*/
@OnOpen
public void onOpen(final Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
// 防止前端刷新重连用户重复,存在计数器不累加
if (WEB_SOCKET_MAP.containsKey(userId)) {
WEB_SOCKET_MAP.remove(userId);
WEB_SOCKET_MAP.put(userId, this);
} else {
WEB_SOCKET_MAP.put(userId, this);
addOnlineCount();
}
sendMessage(String.valueOf(ResponseCode.CONNECT_SUCCESS.getCode())); //自定义成功返回码
log.info("用户--->{} 连接成功,当前在线人数为--->{}", userId, getOnlineCount());
}
/**
* @param message
* @Description 向客户端发送消息 session.getBasicRemote()与session.getAsyncRemote()的区别
* @Throws
* @Return void
* @Date 2023-08-14 17:51:07
* @Author WangKun
*/
public synchronized void sendMessage(String message) {
try {
// 加锁避免阻塞
this.session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("向客户端发送消息--->{}", e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* @param
* @Description 关闭连接
* @Throws
* @Return void
* @Date 2023-08-14 17:52:30
* @Author WangKun
*/
@OnClose
public void onClose(Session session) {
if (!WEB_SOCKET_MAP.isEmpty() && WEB_SOCKET_MAP.containsKey(userId)) {
this.session = session;
WEB_SOCKET_MAP.remove(userId);
subOnlineCount();
log.info("用户--->{} 关闭连接!当前在线人数为--->{}", userId, getOnlineCount());
}
}
/**
* @param message
* @param session
* @Description 收到客户端消息
* @Throws
* @Return void
* @Date 2023-08-15 10:54:55
* @Author WangKun
*/
@OnMessage
public void onMessage(String message, Session session) {
//这一块可以操作数据,比如存到数据
//判断心跳是否存活, 防止心跳自动断开,再重连
if ("ping".equalsIgnoreCase(message) && !WEB_SOCKET_MAP.isEmpty() && WEB_SOCKET_MAP.containsKey(userId)) {
WEB_SOCKET_MAP.get(userId).sendMessage("pong");
}
log.info("收到来自客户端用户:{} 消息:--->{}", userId, message);
}
/**
* @param session
* @param error
* @Description 发生错误时
* @Throws
* @Return void
* @Date 2023-08-15 10:55:27
* @Author WangKun
*/
@OnError
public void onError(Session session, Throwable error) {
if (!WEB_SOCKET_MAP.isEmpty() && WEB_SOCKET_MAP.containsKey(userId)) {
WEB_SOCKET_MAP.remove(userId);
subOnlineCount();
log.error("用户--->{} 错误!" + userId, "原因--->{}" + error.getMessage());
error.printStackTrace();
}
}
/**
* @param userId
* @param message
* @Description 通过userId向客户端发送消息(指定用户发送)
* @Throws
* @Return void
* @Date 2023-08-14 18:01:35
* @Author WangKun
*/
public static void sendTextMessageByUserId(String userId, String message) {
log.info("服务端发送消息到用户{},消息:{}", userId, message);
if (!WEB_SOCKET_MAP.isEmpty() && StringUtils.isNotBlank(userId) && WEB_SOCKET_MAP.containsKey(userId)) {
WEB_SOCKET_MAP.get(userId).sendMessage(message);
} else {
log.error("用户{}不在线", userId);
}
}
/**
* @param message
* @Description 群发自定义消息
* @Throws
* @Return void
* @Date 2023-08-14 18:03:38
* @Author WangKun
*/
public static void sendTextMessage(String message) {
// 如果在线一个就广播
log.info("广播数据到当前在线人,人数:{}", getOnlineCount());
if (getOnlineCount() > 0 && !WEB_SOCKET_MAP.isEmpty()) {
for (String item : WEB_SOCKET_MAP.keySet()) {
WEB_SOCKET_MAP.get(item).sendMessage(message);
log.info("服务端发送消息到用户{},消息:{}", item, message);
}
}
}
}
@ConditionalOnClass(value = WebSocketConfig.class)
指定使用自定义配置文件