WebSocket 提供全雙工長連接,解決 HTTP 輪詢高延遲、高開銷問題。握手階段升級 HTTP 協議(Upgrade: websocket),後續雙向幀傳輸,無需重複 Header。
| 方案 | 特點 | 適用場景 |
|---|---|---|
| Spring WebSocket (標準 JSR-356) | 註解驅動、STOMP 子協議、SockJS 降級 | 企業級應用、消息代理、廣播/點對點 |
| Spring WebFlux WebSocket | 響應式、非阻塞、函數式路由 | 高併發、Reactive 技術棧 |
| Netty 自定義 | 完全控制、極致性能 | IM、遊戲、IoT 网關 |
本文採用 Spring Boot + Spring WebSocket + STOMP + SockJS 方案。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.4</version>
</dependency>
</dependencies>
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 簡單內存代理:/topic 廣播、/queue 點對點
registry.enableSimpleBroker("/topic", "/queue");
// 應用目標前綴:客戶端發送 @MessageMapping 路徑
registry.setApplicationDestinationPrefixes("/app");
// 用戶專屬隊列前綴
registry.setUserDestinationPrefix("/user");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 原生 WebSocket 端點
registry.addEndpoint("/ws").setAllowedOriginPatterns("*");
// SockJS 降級端點(兼容舊瀏覽器、代理)
registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new AuthChannelInterceptor()); // 鑒權攔截器
}
}
public class ChatMessage {
private String content;
private String sender;
private MessageType type; // CHAT, JOIN, LEAVE
private Instant timestamp;
// getter/setter
}
public class PrivateMessage {
private String toUser;
private String content;
// getter/setter
}
public enum MessageType { CHAT, JOIN, LEAVE }
public class AuthChannelInterceptor implements ChannelInterceptor {
@Override
public Message> preSend(Message> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// 從 Header 獲取 Token
String token = accessor.getFirstNativeHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
// 驗證 Token 並設置 Principal
UserDetails user = tokenService.validate(token);
if (user != null) {
accessor.setUser(new StompPrincipal(user.getUsername()));
} else {
throw new InvalidTokenException("Token 無效");
}
} else {
throw new MissingTokenException("缺少 Authorization Header");
}
}
return message;
}
}
<script src="/webjars/sockjs-client/1.6.1/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/2.3.4/stomp.min.js"></script>
enableSimpleBroker 為 enableStompBrokerRelay("/topic", "/queue") 連接 RabbitMQ/ActiveMQ/Kafka,支持集群水平擴展。setHeartbeatValue(new long[]{10000, 10000}) 客戶端/服務端心跳間隔。| 現象 | 原因 | 解決 |
|---|---|---|
| 握手 403 | CSRF 保護攔截、Origin 不匹配 | 配置 setAllowedOriginPatterns("*")、關閉 CSRF 或配置 CsrfTokenRepository |
| SockJS 降級輪詢 | 代理/防火牆阻斷 WebSocket | 檢查 Nginx proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; |
| 用戶收不到私聊 | Principal 未正確設置、目標用戶未連接 | 檢查 AuthChannelInterceptor、用戶在線狀態 |
| 消息丟失 | SimpleBroker 內存隊列溢出 | 遷移外部 Broker、配置持久化 |
Spring WebSocket + STOMP 提供開箱即用的消息推送能力:@MessageMapping 處理業務、SimpMessagingTemplate 主動推送、@SendTo 廣播、convertAndSendToUser 私聊。配合 SockJS 兼容性、外部 Broker 集群化、鑒權攔截器,可構建企業級實時通訊系統。