首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Spring 整合 WebSocket 實戰(上):配置、握手攔截與會話管理

     編輯:Java 相關知識

WebSocket 協議優勢

  • 全雙工通信:單 TCP 連接雙向傳輸,無需輪詢
  • 低延遲:連接建立後無 HTTP 頭開銷
  • 服務端主動推送:適合即時通訊、股票行情、協作編輯、通知推送

Maven 依賴

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.3.x</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>5.3.x</version>
</dependency>
<!-- Jackson JSON 序列化 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

Spring XML 配置

<beans xmlns:websocket="https://www.springframework.org/schema/websocket" ...>
    <bean id="websocketEndpoint" class="cn.example.websocket.WebSocketEndpoint"/>
    <bean id="handshakeInterceptor" class="cn.example.websocket.HandshakeInterceptor"/>

    <websocket:handlers>
        <websocket:mapping path="/websocket" handler="websocketEndpoint"/>
        <websocket:handshake-interceptors>
            <ref bean="handshakeInterceptor"/>
        </websocket:handshake-interceptors>
    </websocket:handlers>
</beans>

握手攔截器:綁定用戶身份

public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {

    private static final Logger log = LoggerFactory.getLogger(HandshakeInterceptor.class);

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                   WebSocketHandler wsHandler, Map<String, Object> attributes) {

        // 從 HTTP Session 獲取當前登錄用戶
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        UserInfo currentUser = UserManager.getSessionUser(attrs.getRequest());

        UserSessionVo sessionVo = new UserSessionVo();
        String userKey = (currentUser != null) ? currentUser.getEmail() : "guest_" + System.currentTimeMillis();
        sessionVo.setUserKey(userKey);
        sessionVo.setConnectTime(new Date());

        // 放入 attributes,後續可在 WebSocketSession.getAttributes() 取出
        attributes.put("USER_SESSION", sessionVo);

        log.info("WebSocket 握手準備: userKey={}", userKey);
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                               WebSocketHandler wsHandler, Exception ex) {
        log.info("WebSocket 握手完成");
        super.afterHandshake(request, response, wsHandler, ex);
    }
}

用戶會話 VO 設計

public class UserSessionVo implements Serializable {
    private String userKey;              // 用戶唯一標識
    private Date connectTime;            // 連接建立時間
    private Date lastPingTime;           // 最後心跳時間
    private Date lastPushTime;           // 最後推送時間
    private WebSocketSession session;    // 關聯的 WebSocket 會話

    // getter/setter 省略
}

WebSocket 端點處理器

@Component
public class WebSocketEndpoint extends TextWebSocketHandler {

    private static final Logger log = LoggerFactory.getLogger(WebSocketEndpoint.class);
    @Autowired private MessagePushService pushService;

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        UserSessionVo vo = (UserSessionVo) session.getAttributes().get("USER_SESSION");
        if (vo != null) {
            vo.setSession(session);
            // 存入本地緩存或 Redis
            SessionRegistry.register(vo.getUserKey(), vo);
            // 觸發業務邏輯:推送離線消息、同步狀態等
            pushService.onUserConnected(vo.getUserKey());
        }
        log.info("連接建立: {}", session.getId());
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 處理客戶端發來的文本消息(如心跳、確認收到)
        String payload = message.getPayload();
        log.debug("收到消息: {}", payload);
        // 可選:回傳確認
        session.sendMessage(new TextMessage("ACK: " + payload));
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        UserSessionVo vo = (UserSessionVo) session.getAttributes().get("USER_SESSION");
        if (vo != null) {
            SessionRegistry.unregister(vo.getUserKey());
            pushService.onUserDisconnected(vo.getUserKey());
        }
        log.info("連接關閉: {}, 狀態: {}", session.getId(), status);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        log.error("傳輸錯誤: {}", session.getId(), exception);
        // 可觸發重連邏輯或告警
    }
}

本地會話註冊表

public class SessionRegistry {
    private static final ConcurrentHashMap<String, UserSessionVo> REGISTRY = new ConcurrentHashMap<>();

    public static void register(String userKey, UserSessionVo vo) {
        REGISTRY.put(userKey, vo);
    }
    public static UserSessionVo get(String userKey) {
        return REGISTRY.get(userKey);
    }
    public static void unregister(String userKey) {
        REGISTRY.remove(userKey);
    }
    public static Collection<UserSessionVo> allSessions() {
        return REGISTRY.values();
    }
}

集群環境需替換為 Redis + 消息總線(如 Spring Cloud Stream / RabbitMQ)實現跨節點消息路由。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved