<!-- websocket依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
import com.amazonaws.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.validation.constraints.NotNull;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 业务处理类
*
* @author ljg
* @ ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
* @date 2023.10.16
*/
@Slf4j
@Component
@ServerEndpoint("/websocket/{userId}")
public class WebSocketServer {
/**
* 当前在线人数
* 字符串比较的是什么
*/
private static AtomicInteger onlineCount = new AtomicInteger(0);
/**
* 存放每个客户端对应的 WebSocketServer 对象
*/
private static CopyOnWriteArrayList<WebSocketServer> webSocketServers = new CopyOnWriteArrayList<>();
private Session session;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "userId") String userId) {
this.session = session;
// 加入集合中
webSocketServers.add(this);
// 在线人数+1
int count = onlineCount.incrementAndGet();
log.info("有新的窗口开始监听:, 当前在线人数为:{}, userId: {}", count, userId);
}
/**
* 收到客服端消息后调用的方法
*
* @param message 客服端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session, @PathParam(value = "userId") String userId) {
log.info("收到来自窗口:{}, 消息内容:{},userId: {}", session.getMessageHandlers(), message, userId);
if (!StringUtils.isNullOrEmpty(message)) {
// 进行业务逻辑处理
}
// 群发消息
// for (WebSocketServer webSocketServer : getWebSocketServers()) {
// webSocketServer.sendMessage(message);
// }
}
/**
* `
* 发生错误时调用的方法
*/
@OnError
public void onError(Session session, @PathParam(value = "userId") String userId, @NotNull Throwable throwable) {
log.error("发生错误");
throwable.printStackTrace();
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(@PathParam(value = "userId") String userId) {
// 从集合中移除
webSocketServers.remove(this);
// 在线人数-1
int count = onlineCount.decrementAndGet();
log.info("释放的sid为:{}");
log.info("有一连接关闭!当前在线人数为:{},用户:{}已退出", count, userId);
}
/**
* 实现服务器主动推送消息
*
* @param message 消息内容
*/
public void sendMessage(String message) {
try {
this.session.getBasicRemote().sendText(message);
log.info("message:{}", message);
} catch (IOException e) {
log.error("websocket IO Exception");
e.printStackTrace();
}
}
/**
* 发送消息到指定窗口 null则群发
*/
public void sendInfo(String message) {
for (WebSocketServer webSocketServer : getWebSocketServers()) {
webSocketServer.sendMessage(message);
}
return;
}
public static CopyOnWriteArrayList<WebSocketServer> getWebSocketServers() {
return webSocketServers;
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
/**
* @Author ljg
* @Create 2023.10.16
* @Description 连接Websocket前首先会进入到WebsocketHandler进行业务逻辑处理,服务端的参数在拦截器中获取之后通过 attributes传递给WebSocketHandler
*/
@Slf4j
@Component
public class MyWebSocketHandler implements WebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) {
//连接成功之后,应该在这个方法中将session保存起来,通常都用
//一个Map来保存,key是用户ID(或其他唯一标识),这样的话想
//给哪个用户发送消息,那么就直接拿到那个用户的session,然后
//调用session.sendMessage方法发送消息就可以了
log.info("连接成功");
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
log.info("客户端过来一条消息,内容是:" + message.getPayload().toString());
// TextMessage textMessage = new TextMessage(JSONObject.toJSONString("客户端你好,我已经收到你的消息"));
// session.sendMessage(textMessage);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
log.info("错误处理");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
log.info("连接关闭");
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import java.util.Map;
/**
* @Author ljg
* @Create 2023.10.16
* @Description WebSocket 配置类实现(注册WebSocket实现类,绑定接口,同时将实现类和拦截器绑定)
*/
@Configuration
@Slf4j
public class WebSocketConfig implements WebSocketConfigurer {
private final MyWebSocketHandler wsHandler;
/**
* WebSecoket握手拦截器(执行相关业务逻辑)
*/
private HandshakeInterceptor handshakeInterceptor = new HandshakeInterceptor() {
/**
* 在获取请求或响应之前进行拦截,获取一些请求或响应的数据
* @param request
* @param response
* @param wsHandler
* @param attributes 如果该方法通过,可以在监听器或controller层拿到这里设置的数据
* @return 返回false则拦截,返回true则通过
* @throws Exception
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// 进行业务逻辑处理
return true; // 返回true
}
/**
* 在通过请求或响应之后被调用
* @param request
* @param response
* @param wsHandler
* @param exception
*/
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
};
public WebSocketConfig(MyWebSocketHandler wsHandler) {
this.wsHandler = wsHandler;
}
/**
* websocket拦截器(springsecurity需要配置,因为springsecurity拦截不到websocket)
*
* @param registry
* @ServerEndpoint("/websocket") 拦截的是websocket连接的服务
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(wsHandler, "/websocket") // 拦截路径
.setAllowedOrigins("*")
.addInterceptors(handshakeInterceptor); // 拦截之后进去的拦截器
}
/**
* 配置ServerEndpointExporter,配置后会自动注册所有“@ServerEndpoint”注解声明的Websocket Endpoint
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
接口测试地址:http://www.jsons.cn/websocket/