<!-- websocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
新建配置类
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*/@Beanpublic 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<String, WebSocket> 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*/@OnOpenpublic 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*/@OnClosepublic 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*/@OnMessagepublic 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*/@OnErrorpublic 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)
指定使用自定义配置文件