从零实现分布式WebSocket组件:设计模式深度实践指南

一、为什么需要WebSocket组件?

  1. 实时通信需求
    • 传统HTTP轮询效率低,WebSocket提供全双工通信
    • 适用于即时聊天、实时数据监控、协同编辑等场景
  2. 分布式系统挑战
    • 多节点部署时需解决会话同步问题
    • 跨节点消息广播需借助中间件(Redis/RocketMQ等)
  3. 统一管理诉求
    • 会话管理、安全认证、消息路由等共性功能需抽象

二、组件核心功能全景

功能模块 实现要点
会话管理 按用户维度存储WebSocketSession,支持按ID/用户类型/用户ID查询
消息路由 根据消息类型分发到对应处理器(WebSocketMessageListener)
消息广播 支持本地/Redis/RocketMQ等多种发送策略,适配不同集群规模
安全认证 握手阶段Token验证,用户信息绑定到Session
并发控制 使用ConcurrentWebSocketSessionDecorator防止并发发送异常

三、组件工作全流程图解

1. 连接建立流程

前端 LoginUserHandshakeInterceptor WebSocketSession WebSocketSessionHandlerDecorator WebSocketSessionManagerImpl 发起连接?token=xxx 解析Token获取LoginUser setAttribute(LOGIN_USER) addSession(session) 存储到idSessions/userSessions 前端 LoginUserHandshakeInterceptor WebSocketSession WebSocketSessionHandlerDecorator WebSocketSessionManagerImpl

2. 消息接收处理流程

前端 JsonWebSocketMessageHandler WebSocketMessageListener 发送消息 解析消息类型(type) 忽略/返回错误 根据type查找监听器 记录错误日志 触发onMessage() 执行业务逻辑 alt 未找到该消费类型的监听器 找到该消费类型的监听器 alt 无效消息 有效消息 前端 JsonWebSocketMessageHandler WebSocketMessageListener

3.消息发送流程(以RocketMQ为例)

业务代码 WebSocketMessageSender RocketMQWebSocketMessageSender RocketMQTemplate RocketMQ RocketMQWebSocketMessageConsumer AbstractWebSocketMessageSender WebSocketSession sendObject() send() 发送RocketMQ消息 发布到指定topic 订阅消费消息 doSend() 遍历会话发送 业务代码 WebSocketMessageSender RocketMQWebSocketMessageSender RocketMQTemplate RocketMQ RocketMQWebSocketMessageConsumer AbstractWebSocketMessageSender WebSocketSession

四、四步构建WebSocket核心链路

步骤一:连接建立(认证与会话管理)

相关类

WebSocketFrameworkUtils

java 复制代码
import org.springframework.web.socket.WebSocketSession;

import java.util.Map;

/**
 * 专属于 web 包的工具类
 *
 * @author dyh
 */
public class WebSocketFrameworkUtils {

    public static final String ATTRIBUTE_LOGIN_USER = "LOGIN_USER";

    /**
     * 设置当前用户
     *
     * @param loginUser 登录用户
     * @param attributes Session
     */
    public static void setLoginUser(LoginUser loginUser, Map<String, Object> attributes) {
        attributes.put(ATTRIBUTE_LOGIN_USER, loginUser);
    }

    /**
     * 获取当前用户
     *
     * @return 当前用户
     */
    public static LoginUser getLoginUser(WebSocketSession session) {
        return (LoginUser) session.getAttributes().get(ATTRIBUTE_LOGIN_USER);
    }

    /**
     * 获得当前用户的编号
     *
     * @return 用户编号
     */
    public static Long getLoginUserId(WebSocketSession session) {
        LoginUser loginUser = getLoginUser(session);
        return loginUser != null ? loginUser.getId() : null;
    }

    /**
     * 获得当前用户的类型
     *
     * @return 用户编号
     */
    public static Integer getLoginUserType(WebSocketSession session) {
        LoginUser loginUser = getLoginUser(session);
        return loginUser != null ? loginUser.getUserType() : null;
    }

    /**
     * 获得当前用户的租户编号
     *
     * @param session Session
     * @return 租户编号
     */
    public static Long getTenantId(WebSocketSession session) {
        LoginUser loginUser = getLoginUser(session);
        return loginUser != null ? loginUser.getTenantId() : null;
    }

}

JsonWebSocketMessage

java 复制代码
/**
 * JSON 格式的 WebSocket 消息帧
 *
 * @author dyh
 */
@Data
public class JsonWebSocketMessage implements Serializable {

    /**
     * 消息类型
     *
     * 目的:用于分发到对应的 {@link WebSocketMessageListener} 实现类
     */
    private String type;
    /**
     * 消息内容
     *
     * 要求 JSON 对象
     */
    private String content;

}

涉及核心类

设置用户属性 注册会话 LoginUserHandshakeInterceptor WebSocketSessionHandlerDecorator WebSocketSessionManagerImpl WebSocketSession

实现步骤

1. 握手拦截器(认证用户)

LoginUserHandshakeInterceptor

java 复制代码
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;

/**
 * 登录用户的 {@link HandshakeInterceptor} 实现类
 *
 * 流程如下:
 * 1. 前端连接 websocket 时,会通过拼接 ?token={token} 到 ws:// 连接后,这样它可以被 {@link TokenAuthenticationFilter} 所认证通过
 * 2. {@link LoginUserHandshakeInterceptor} 负责把 {@link LoginUser} 添加到 {@link WebSocketSession} 中
 *
 * @author dyh
 */
public class LoginUserHandshakeInterceptor implements HandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                   WebSocketHandler wsHandler, Map<String, Object> attributes) {
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        if (loginUser != null) {
            WebSocketFrameworkUtils.setLoginUser(loginUser, attributes);
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                               WebSocketHandler wsHandler, Exception exception) {
        // do nothing
    }

}
2. 会话装饰器(线程安全增强)

WebSocketSessionHandlerDecorator

java 复制代码
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;
import org.springframework.web.socket.handler.WebSocketHandlerDecorator;

/**
 * {@link WebSocketHandler} 的装饰类,实现了以下功能:
 *
 * 1. {@link WebSocketSession} 连接或关闭时,使用 {@link #sessionManager} 进行管理
 * 2. 封装 {@link WebSocketSession} 支持并发操作
 *
 * @author dyh
 */
public class WebSocketSessionHandlerDecorator extends WebSocketHandlerDecorator {

    /**
     * 发送时间的限制,单位:毫秒
     */
    private static final Integer SEND_TIME_LIMIT = 1000 * 5;
    /**
     * 发送消息缓冲上线,单位:bytes
     */
    private static final Integer BUFFER_SIZE_LIMIT = 1024 * 100;

    private final WebSocketSessionManager sessionManager;

    public WebSocketSessionHandlerDecorator(WebSocketHandler delegate,
                                            WebSocketSessionManager sessionManager) {
        super(delegate);
        this.sessionManager = sessionManager;
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        // 实现 session 支持并发
        session = new ConcurrentWebSocketSessionDecorator(session, SEND_TIME_LIMIT, BUFFER_SIZE_LIMIT);
        // 添加到 WebSocketSessionManager 中
        sessionManager.addSession(session);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
        sessionManager.removeSession(session);
    }

}
3. 会话管理器(存储结构)

WebSocketSessionManager

java 复制代码
import org.springframework.web.socket.WebSocketSession;

import java.util.Collection;

/**
 * {@link WebSocketSession} 管理器的接口
 *
 * @author dyh
 */
public interface WebSocketSessionManager {

    /**
     * 添加 Session
     *
     * @param session Session
     */
    void addSession(WebSocketSession session);

    /**
     * 移除 Session
     *
     * @param session Session
     */
    void removeSession(WebSocketSession session);

    /**
     * 获得指定编号的 Session
     *
     * @param id Session 编号
     * @return Session
     */
    WebSocketSession getSession(String id);

    /**
     * 获得指定用户类型的 Session 列表
     *
     * @param userType 用户类型
     * @return Session 列表
     */
    Collection<WebSocketSession> getSessionList(Integer userType);

    /**
     * 获得指定用户编号的 Session 列表
     *
     * @param userType 用户类型
     * @param userId 用户编号
     * @return Session 列表
     */
    Collection<WebSocketSession> getSessionList(Integer userType, Long userId);

}

WebSocketSessionManagerImpl

java 复制代码
import org.springframework.web.socket.WebSocketSession;
import cn.hutool.core.collection.CollUtil;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;


/**
 * 默认的 {@link WebSocketSessionManager} 实现类
 * 负责管理WebSocket会话,包含双重存储结构:
 * 1. 基于会话ID的快速查找
 * 2. 基于用户类型+用户ID的分层存储
 * @author dyh
 */
public class WebSocketSessionManagerImpl implements WebSocketSessionManager {

    /**
     * 会话ID与WebSocketSession的映射关系(线程安全)
     * Key格式:Session ID(WebSocket自动生成的唯一标识)
     * Value:对应的WebSocketSession对象
     */
    private final ConcurrentMap<String, WebSocketSession> idSessions = new ConcurrentHashMap<>();

    /**
     * 用户维度存储WebSocketSession(三级嵌套线程安全结构)
     * Key1:用户类型(Integer,如1-管理员 2-普通用户)
     * Key2:用户ID(Long类型)
     * Value:该用户的所有WebSocket会话列表(线程安全的CopyOnWriteArrayList)
     */
    private final ConcurrentMap<Integer, ConcurrentMap<Long, CopyOnWriteArrayList<WebSocketSession>>> userSessions = new ConcurrentHashMap<>();

    @Override
    public void addSession(WebSocketSession session) {
        // 1. 加入ID索引
        idSessions.put(session.getId(), session);

        // 2. 加入用户维度索引
        LoginUser user = WebSocketFrameworkUtils.getLoginUser(session);
        if (user == null) return; // 未认证会话不记录用户维度

        // 2.1 获取或创建用户类型层(双重检查锁模式)
        userSessions.computeIfAbsent(user.getUserType(), k -> new ConcurrentHashMap<>())
                // 2.2 获取或创建用户ID层
                .computeIfAbsent(user.getId(), k -> new CopyOnWriteArrayList<>())
                // 2.3 添加会话到列表(线程安全操作)
                .add(session);
    }

    @Override
    public void removeSession(WebSocketSession session) {
        // 1. 从ID索引移除
        idSessions.remove(session.getId());

        // 2. 从用户维度索引移除
        LoginUser user = WebSocketFrameworkUtils.getLoginUser(session);
        if (user == null) return;

        userSessions.computeIfPresent(user.getUserType(), (userType, userMap) -> {
            userMap.computeIfPresent(user.getId(), (userId, sessions) -> {
                // 2.1 移除指定会话(根据ID精准匹配)
                sessions.removeIf(s -> s.getId().equals(session.getId()));
                // 2.2 列表为空时自动清理用户ID层
                return sessions.isEmpty() ? null : sessions;
            });
            // 2.3 用户ID层空时自动清理用户类型层
            return userMap.isEmpty() ? null : userMap;
        });
    }

    @Override
    public WebSocketSession getSession(String id) {
        return idSessions.get(id); // O(1)时间复杂度直接查找
    }

    @Override
    public Collection<WebSocketSession> getSessionList(Integer userType) {
        // 1. 获取指定用户类型的所有会话映射
        ConcurrentMap<Long, CopyOnWriteArrayList<WebSocketSession>> userMap = userSessions.get(userType);
        if (CollUtil.isEmpty(userMap)) return Collections.emptyList();

        // 2. 多租户过滤处理
        Long currentTenantId = TenantContextHolder.getTenantId();
        LinkedList<WebSocketSession> result = new LinkedList<>(); // 避免ArrayList扩容开销

        userMap.values().forEach(sessions -> {
            if (CollUtil.isEmpty(sessions)) return;

            // 2.1 租户隔离检查(第一个会话代表用户所属租户)
            Long userTenantId = WebSocketFrameworkUtils.getTenantId(sessions.get(0));
            if (currentTenantId != null && !currentTenantId.equals(userTenantId)) return;

            result.addAll(sessions);
        });
        return result;
    }

    @Override
    public Collection<WebSocketSession> getSessionList(Integer userType, Long userId) {
        // 直接获取指定用户的所有会话(带租户上下文过滤)
        return Optional.ofNullable(userSessions.get(userType))
                .map(userMap -> userMap.get(userId))
                .filter(CollUtil::isNotEmpty)
                .map(ArrayList::new) // 返回拷贝避免直接操作内部集合
                .orElseGet(ArrayList::new);
    }
}

设计模式应用

  • 装饰器模式:ConcurrentWebSocketSessionDecorator增强原生Session的并发能力
  • 工厂方法模式:通过拦截器创建带有用户属性的Session对象

步骤二:接收消息(路由与处理)

涉及核心类

路由调用 实现 JsonWebSocketMessageHandler -Map listeners +handleTextMessage() <<interface>> WebSocketMessageListener +onMessage() +getType() WebSocketMessageListener具体实现类 +onMessage() +getType()

实现步骤

1. 消息处理器(路由分发)

JsonWebSocketMessageHandler

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * JSON 格式 {@link WebSocketHandler} 实现类
 *
 * 基于 {@link JsonWebSocketMessage#getType()} 消息类型,调度到对应的 {@link WebSocketMessageListener} 监听器。
 *
 * @author dyh
 */
@Slf4j
public class JsonWebSocketMessageHandler extends TextWebSocketHandler {

    /**
     * type 与 WebSocketMessageListener 的映射
     */
    private final Map<String, WebSocketMessageListener<Object>> listeners = new HashMap<>();

    @SuppressWarnings({"rawtypes", "unchecked"})
    public JsonWebSocketMessageHandler(List<? extends WebSocketMessageListener> listenersList) {
        listenersList.forEach((Consumer<WebSocketMessageListener>)
                listener -> listeners.put(listener.getType(), listener));
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 1.1 空消息,跳过
        if (message.getPayloadLength() == 0) {
            return;
        }
        // 1.2 ping 心跳消息,直接返回 pong 消息。
        if (message.getPayloadLength() == 4 && Objects.equals(message.getPayload(), "ping")) {
            session.sendMessage(new TextMessage("pong"));
            return;
        }

        // 2.1 解析消息
        try {
            JsonWebSocketMessage jsonMessage = JsonUtils.parseObject(message.getPayload(), JsonWebSocketMessage.class);
            if (jsonMessage == null) {
                log.error("[handleTextMessage][session({}) message({}) 解析为空]", session.getId(), message.getPayload());
                return;
            }
            if (StrUtil.isEmpty(jsonMessage.getType())) {
                log.error("[handleTextMessage][session({}) message({}) 类型为空]", session.getId(), message.getPayload());
                return;
            }
            // 2.2 获得对应的 WebSocketMessageListener
            WebSocketMessageListener<Object> messageListener = listeners.get(jsonMessage.getType());
            if (messageListener == null) {
                log.error("[handleTextMessage][session({}) message({}) 监听器为空]", session.getId(), message.getPayload());
                return;
            }
            // 2.3 处理消息
            Type type = TypeUtil.getTypeArgument(messageListener.getClass(), 0);
            Object messageObj = JsonUtils.parseObject(jsonMessage.getContent(), type);
            Long tenantId = WebSocketFrameworkUtils.getTenantId(session);
            TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj));
        } catch (Throwable ex) {
            log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload());
        }
    }

}
2. 定义监听器接口

WebSocketMessageListener

java 复制代码
import org.springframework.web.socket.WebSocketSession;

/**
 * WebSocket 消息监听器接口
 *
 * 目的:前端发送消息给后端后,处理对应 {@link #getType()} 类型的消息
 *
 * @param <T> 泛型,消息类型 
 * @author dyh          
 */
public interface WebSocketMessageListener<T> {

    /**
     * 处理消息
     *
     * @param session Session
     * @param message 消息
     */
    void onMessage(WebSocketSession session, T message);

    /**
     * 获得消息类型
     *
     * @see JsonWebSocketMessage#getType()
     * @return 消息类型
     */
    String getType();

}
3. 业务监听器(使用示例)

注意:这里提供的是使用示例,具体接收到前端消息处理逻辑请根据业务实现
OrderMessageListener

java 复制代码
   @Component
   public class OrderMessageListener implements WebSocketMessageListener<OrderMessage> {
       @Override
       public String getType() { return "order-msg"; }
       
       @Override
       public void onMessage(WebSocketSession session, OrderMessage message) {
           orderService.process(message);
       }
   }
   

设计模式应用

  • 策略模式:每个业务监听器对应一种消息处理策略
  • 观察者模式:将消息类型与处理逻辑解耦,方便扩展新消息类型。

设计原则

  • 依赖注入:Spring自动注入所有监听器实现
  • 开闭原则:新增消息类型只需添加Listener实现类
  • 单一职责:Handler只负责路由,Listener专注业务处理

步骤三:发送消息(策略化广播)

注意:此处只给了rocketmq消息策略,还可以自行实现rediskafkarabbitmq策略

相关类

RocketMQWebSocketMessage

java 复制代码
import lombok.Data;

/**
 * RocketMQ 广播 WebSocket 的消息
 *
 * @author dyh
 */
@Data
public class RocketMQWebSocketMessage {

    /**
     * Session 编号
     */
    private String sessionId;
    /**
     * 用户类型
     */
    private Integer userType;
    /**
     * 用户编号
     */
    private Long userId;

    /**
     * 消息类型
     */
    private String messageType;
    /**
     * 消息内容
     */
    private String messageContent;

}

涉及核心类

触发实际发送 <<interface>> WebSocketMessageSender +send(Integer userType, Long userId, String type, String content) : void AbstractWebSocketMessageSender -WebSocketSessionManager sessionManager +send(...) : void #doSend(...) : void RocketMQWebSocketMessageSender -RocketMQTemplate rocketMQTemplate +doSend(...) : void RedisWebSocketMessageSender -RedisMQTemplate redisMQTemplate +doSend(...) : void RocketMQWebSocketMessageConsumer +onMessage(RocketMQWebSocketMessage message) : void

实现步骤

1. 定义发送器接口与抽象类

WebSocketMessageSender

java 复制代码
/**
 * WebSocket 消息的发送器接口
 *
 * @author dyh
 */
public interface WebSocketMessageSender {

    /**
     * 发送消息给指定用户
     *
     * @param userType 用户类型
     * @param userId 用户编号
     * @param messageType 消息类型
     * @param messageContent 消息内容,JSON 格式
     */
    void send(Integer userType, Long userId, String messageType, String messageContent);

    /**
     * 发送消息给指定用户类型
     *
     * @param userType 用户类型
     * @param messageType 消息类型
     * @param messageContent 消息内容,JSON 格式
     */
    void send(Integer userType, String messageType, String messageContent);

    /**
     * 发送消息给指定 Session
     *
     * @param sessionId Session 编号
     * @param messageType 消息类型
     * @param messageContent 消息内容,JSON 格式
     */
    void send(String sessionId, String messageType, String messageContent);

    default void sendObject(Integer userType, Long userId, String messageType, Object messageContent) {
        send(userType, userId, messageType, JsonUtils.toJsonString(messageContent));
    }

    default void sendObject(Integer userType, String messageType, Object messageContent) {
        send(userType, messageType, JsonUtils.toJsonString(messageContent));
    }

    default void sendObject(String sessionId, String messageType, Object messageContent) {
        send(sessionId, messageType, JsonUtils.toJsonString(messageContent));
    }

}

AbstractWebSocketMessageSender

java 复制代码
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * WebSocketMessageSender 实现类
 *
 * @author dyh
 */
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractWebSocketMessageSender implements WebSocketMessageSender {

    private final WebSocketSessionManager sessionManager;

    @Override
    public void send(Integer userType, Long userId, String messageType, String messageContent) {
        send(null, userType, userId, messageType, messageContent);
    }

    @Override
    public void send(Integer userType, String messageType, String messageContent) {
        send(null, userType, null, messageType, messageContent);
    }

    @Override
    public void send(String sessionId, String messageType, String messageContent) {
        send(sessionId, null, null, messageType, messageContent);
    }

    /**
     * 发送消息
     *
     * @param sessionId Session 编号
     * @param userType 用户类型
     * @param userId 用户编号
     * @param messageType 消息类型
     * @param messageContent 消息内容
     */
    public void send(String sessionId, Integer userType, Long userId, String messageType, String messageContent) {
        // 1. 获得 Session 列表
        List<WebSocketSession> sessions = Collections.emptyList();
        if (StrUtil.isNotEmpty(sessionId)) {
            WebSocketSession session = sessionManager.getSession(sessionId);
            if (session != null) {
                sessions = Collections.singletonList(session);
            }
        } else if (userType != null && userId != null) {
            sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType, userId);
        } else if (userType != null) {
            sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType);
        }
        if (CollUtil.isEmpty(sessions)) {
            if (log.isDebugEnabled()) {
                log.debug("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]",
                        sessionId, userType, userId, messageType, messageContent);
            }
        }
        // 2. 执行发送
        doSend(sessions, messageType, messageContent);
    }

    /**
     * 发送消息的具体实现
     *
     * @param sessions Session 列表
     * @param messageType 消息类型
     * @param messageContent 消息内容
     */
    public void doSend(Collection<WebSocketSession> sessions, String messageType, String messageContent) {
        JsonWebSocketMessage message = new JsonWebSocketMessage().setType(messageType).setContent(messageContent);
        String payload = JsonUtils.toJsonString(message); // 关键,使用 JSON 序列化
        sessions.forEach(session -> {
            // 1. 各种校验,保证 Session 可以被发送
            if (session == null) {
                log.error("[doSend][session 为空, message({})]", message);
                return;
            }
            if (!session.isOpen()) {
                log.error("[doSend][session({}) 已关闭, message({})]", session.getId(), message);
                return;
            }
            // 2. 执行发送
            try {
                session.sendMessage(new TextMessage(payload));
                log.info("[doSend][session({}) 发送消息成功,message({})]", session.getId(), message);
            } catch (IOException ex) {
                log.error("[doSend][session({}) 发送消息失败,message({})]", session.getId(), message, ex);
            }
        });
    }

}
2. 实现具体策略(以RocketMQ为例)

RocketMQWebSocketMessageSender

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;

/**
 * 基于 RocketMQ 的 {@link WebSocketMessageSender} 实现类
 *
 * @author dyh
 */
@Slf4j
public class RocketMQWebSocketMessageSender extends AbstractWebSocketMessageSender {

    private final RocketMQTemplate rocketMQTemplate;

    private final String topic;

    public RocketMQWebSocketMessageSender(WebSocketSessionManager sessionManager,
                                          RocketMQTemplate rocketMQTemplate,
                                          String topic) {
        super(sessionManager);
        this.rocketMQTemplate = rocketMQTemplate;
        this.topic = topic;
    }

    @Override
    public void send(Integer userType, Long userId, String messageType, String messageContent) {
        sendRocketMQMessage(null, userId, userType, messageType, messageContent);
    }

    @Override
    public void send(Integer userType, String messageType, String messageContent) {
        sendRocketMQMessage(null, null, userType, messageType, messageContent);
    }

    @Override
    public void send(String sessionId, String messageType, String messageContent) {
        sendRocketMQMessage(sessionId, null, null, messageType, messageContent);
    }

    /**
     * 通过 RocketMQ 广播消息
     *
     * @param sessionId Session 编号
     * @param userId 用户编号
     * @param userType 用户类型
     * @param messageType 消息类型
     * @param messageContent 消息内容
     */
    private void sendRocketMQMessage(String sessionId, Long userId, Integer userType,
                                     String messageType, String messageContent) {
        RocketMQWebSocketMessage mqMessage = new RocketMQWebSocketMessage()
                .setSessionId(sessionId).setUserId(userId).setUserType(userType)
                .setMessageType(messageType).setMessageContent(messageContent);
        rocketMQTemplate.syncSend(topic, mqMessage);
    }

}
3. 消息消费者处理广播

RocketMQWebSocketMessageConsumer

java 复制代码
import lombok.RequiredArgsConstructor;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;

/**
 * {@link RocketMQWebSocketMessage} 广播消息的消费者,真正把消息发送出去
 *
 * @author dyh
 */
@RocketMQMessageListener( // 重点:添加 @RocketMQMessageListener 注解,声明消费的 topic
        topic = "${dyh.websocket.sender-rocketmq.topic}",
        consumerGroup = "${dyh.websocket.sender-rocketmq.consumer-group}",
        messageModel = MessageModel.BROADCASTING // 设置为广播模式,保证每个实例都能收到消息
)
@RequiredArgsConstructor
public class RocketMQWebSocketMessageConsumer implements RocketMQListener<RocketMQWebSocketMessage> {

    private final RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender;

    @Override
    public void onMessage(RocketMQWebSocketMessage message) {
        rocketMQWebSocketMessageSender.send(message.getSessionId(),
                message.getUserType(), message.getUserId(),
                message.getMessageType(), message.getMessageContent());
    }

}

设计模式应用

  • 策略模式:WebSocketMessageSender 接口统一发送策略,RedisWebSocketMessageSender 和 RocketMQWebSocketMessageSender 实现不同策略。
  • 模板方法模式:AbstractWebSocketMessageSender

设计原则

  • 开闭原则:新增发送策略只需继承AbstractWebSocketMessageSender抽象类,无需修改核心逻辑
  • 依赖倒置:高层模块依赖WebSocketMessageSender抽象,不关心具体实现

步骤四:Spring 自动装配

相关类

WebSocketProperties

java 复制代码
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

/**
 * WebSocket 配置项
 *
 * @author dyh
 */
@ConfigurationProperties("dyh.websocket")
@Data
@Validated
public class WebSocketProperties {

    /**
     * WebSocket 的连接路径
     */
    @NotEmpty(message = "WebSocket 的连接路径不能为空")
    private String path = "/ws";

    /**
     * 消息发送器的类型
     *
     * 可选值:local、redis、rocketmq、kafka、rabbitmq
     */
    @NotNull(message = "WebSocket 的消息发送者不能为空")
    private String senderType = "local";

}

WebSocket 自动配置

DyhWebSocketAutoConfiguration

java 复制代码
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.List;

/**
 * WebSocket 自动配置
 *
 * @author dyh
 */
@AutoConfiguration(before = DyhRedisMQConsumerAutoConfiguration.class) // before DyhRedisMQConsumerAutoConfiguration 的原因是,需要保证 RedisWebSocketMessageConsumer 先创建,才能创建 RedisMessageListenerContainer
@EnableWebSocket // 开启 websocket
@ConditionalOnProperty(prefix = "dyh.websocket", value = "enable", matchIfMissing = true) // 允许使用 dyh.websocket.enable=false 禁用 websocket
@EnableConfigurationProperties(WebSocketProperties.class)
public class DyhWebSocketAutoConfiguration {

    @Bean
    public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor[] handshakeInterceptors,
                                                   WebSocketHandler webSocketHandler,
                                                   WebSocketProperties webSocketProperties) {
        return registry -> registry
                // 添加 WebSocketHandler
                .addHandler(webSocketHandler, webSocketProperties.getPath())
                .addInterceptors(handshakeInterceptors)
                // 允许跨域,否则前端连接会直接断开
                .setAllowedOriginPatterns("*");
    }

    @Bean
    public HandshakeInterceptor handshakeInterceptor() {
        return new LoginUserHandshakeInterceptor();
    }

    @Bean
    public WebSocketHandler webSocketHandler(WebSocketSessionManager sessionManager,
                                             List<? extends WebSocketMessageListener<?>> messageListeners) {
        // 1. 创建 JsonWebSocketMessageHandler 对象,处理消息
        JsonWebSocketMessageHandler messageHandler = new JsonWebSocketMessageHandler(messageListeners);
        // 2. 创建 WebSocketSessionHandlerDecorator 对象,处理连接
        return new WebSocketSessionHandlerDecorator(messageHandler, sessionManager);
    }

    @Bean
    public WebSocketSessionManager webSocketSessionManager() {
        return new WebSocketSessionManagerImpl();
    }

    @Bean
    public WebSocketAuthorizeRequestsCustomizer webSocketAuthorizeRequestsCustomizer(WebSocketProperties webSocketProperties) {
        return new WebSocketAuthorizeRequestsCustomizer(webSocketProperties);
    }

    // ==================== Sender 相关 ====================

    @Configuration
    @ConditionalOnProperty(prefix = "dyh.websocket", name = "sender-type", havingValue = "local")
    public class LocalWebSocketMessageSenderConfiguration {

        @Bean
        public LocalWebSocketMessageSender localWebSocketMessageSender(WebSocketSessionManager sessionManager) {
            return new LocalWebSocketMessageSender(sessionManager);
        }

    }

    @Configuration
    @ConditionalOnProperty(prefix = "dyh.websocket", name = "sender-type", havingValue = "redis")
    public class RedisWebSocketMessageSenderConfiguration {

        @Bean
        public RedisWebSocketMessageSender redisWebSocketMessageSender(WebSocketSessionManager sessionManager,
                                                                       RedisMQTemplate redisMQTemplate) {
            return new RedisWebSocketMessageSender(sessionManager, redisMQTemplate);
        }

        @Bean
        public RedisWebSocketMessageConsumer redisWebSocketMessageConsumer(
                RedisWebSocketMessageSender redisWebSocketMessageSender) {
            return new RedisWebSocketMessageConsumer(redisWebSocketMessageSender);
        }

    }

    @Configuration
    @ConditionalOnProperty(prefix = "dyh.websocket", name = "sender-type", havingValue = "rocketmq")
    public class RocketMQWebSocketMessageSenderConfiguration {

        @Bean
        public RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender(
                WebSocketSessionManager sessionManager, RocketMQTemplate rocketMQTemplate,
                @Value("${dyh.websocket.sender-rocketmq.topic}") String topic) {
            return new RocketMQWebSocketMessageSender(sessionManager, rocketMQTemplate, topic);
        }

        @Bean
        public RocketMQWebSocketMessageConsumer rocketMQWebSocketMessageConsumer(
                RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender) {
            return new RocketMQWebSocketMessageConsumer(rocketMQWebSocketMessageSender);
        }

    }

    @Configuration
    @ConditionalOnProperty(prefix = "dyh.websocket", name = "sender-type", havingValue = "rabbitmq")
    public class RabbitMQWebSocketMessageSenderConfiguration {

        @Bean
        public RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender(
                WebSocketSessionManager sessionManager, RabbitTemplate rabbitTemplate,
                TopicExchange websocketTopicExchange) {
            return new RabbitMQWebSocketMessageSender(sessionManager, rabbitTemplate, websocketTopicExchange);
        }

        @Bean
        public RabbitMQWebSocketMessageConsumer rabbitMQWebSocketMessageConsumer(
                RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender) {
            return new RabbitMQWebSocketMessageConsumer(rabbitMQWebSocketMessageSender);
        }

        /**
         * 创建 Topic Exchange
         */
        @Bean
        public TopicExchange websocketTopicExchange(@Value("${dyh.websocket.sender-rabbitmq.exchange}") String exchange) {
            return new TopicExchange(exchange,
                    true,  // durable: 是否持久化
                    false);  // exclusive: 是否排它
        }

    }

    @Configuration
    @ConditionalOnProperty(prefix = "dyh.websocket", name = "sender-type", havingValue = "kafka")
    public class KafkaWebSocketMessageSenderConfiguration {

        @Bean
        public KafkaWebSocketMessageSender kafkaWebSocketMessageSender(
                WebSocketSessionManager sessionManager, KafkaTemplate<Object, Object> kafkaTemplate,
                @Value("${dyh.websocket.sender-kafka.topic}") String topic) {
            return new KafkaWebSocketMessageSender(sessionManager, kafkaTemplate, topic);
        }

        @Bean
        public KafkaWebSocketMessageConsumer kafkaWebSocketMessageConsumer(
                KafkaWebSocketMessageSender kafkaWebSocketMessageSender) {
            return new KafkaWebSocketMessageConsumer(kafkaWebSocketMessageSender);
        }

    }

}

五、使用指南(含代码示例)

1. 发送消息示例

NotifyController

java 复制代码
@RestController
public class NotifyController {
    @Autowired
    private WebSocketMessageSender sender;

    // 发送给指定用户
    @PostMapping("/send")
    public void sendToUser(@RequestParam Long userId) {
        sender.sendObject(
            UserTypeEnum.ADMIN.getValue(), 
            userId,
            "demo-message", 
            new DemoMessage("Hello from server!")
        );
    }
}

2. 接收消息示例

DemoMessageListener

java 复制代码
@Component
public class DemoMessageListener implements WebSocketMessageListener<DemoMessage> {
    
    @Override
    public String getType() {
        return "demo-message"; // 声明处理的消息类型
    }

    @Override
    public void onMessage(WebSocketSession session, DemoMessage message) {
        // 1. 获取当前用户
        Long userId = WebSocketFrameworkUtils.getLoginUserId(session);
        
        // 2. 处理业务逻辑
        System.out.println("收到来自用户:" + userId 
            + " 的消息:" + message.getContent());
    }
}

六、设计模式与开发原则实践

1. 经典设计模式

模式名称 应用场景 代码示例
装饰器模式 增强WebSocketHandler的会话管理能力 WebSocketSessionHandlerDecorator
策略模式 可切换的消息发送策略(Local/Redis/RocketMQ) WebSocketMessageSender多实现
观察者模式 消息类型与监听器的解耦 WebSocketMessageListener+消息路由
模板方法 定义消息发送流程骨架 AbstractWebSocketMessageSender#send()

2. 开发原则体现

  • 单一职责原则:会话管理、消息路由、安全认证模块分离
  • 开闭原则:新增消息类型只需实现新Listener,无需修改核心逻辑
  • 依赖倒置:高层模块依赖WebSocketMessageSender抽象,不关心具体实现

七、从组件设计中学到的经验

  1. 分层架构的价值
    • 将会话管理、消息路由、网络传输分层处理,提高可维护性
    • 通过抽象接口隔离变化点(如消息发送策略)
  2. 分布式设计要点
    • 会话信息存储需考虑线程安全(ConcurrentHashMap)
    • 广播消息需借助中间件实现最终一致性
  3. 扩展性设计技巧
    • 使用Spring条件装配实现策略动态切换
    • 监听器机制方便业务功能扩展
  4. 生产环境考量
    • 心跳机制:内置Ping/Pong处理
    • 流量控制:ConcurrentWebSocketSessionDecorator限制缓冲区大小
相关推荐
闪电悠米9 小时前
黑马点评-Redis 消息队列-03_stream_consumer_group
开发语言·数据库·redis·分布式·缓存·junit·lua
艾利克斯冰10 小时前
Java 设计模式-行为型模式(更新中)
java·开发语言·设计模式
z落落12 小时前
C# 事件(Event)+自定义带参数事件例子
开发语言·分布式·c#
我是一颗柠檬13 小时前
【Java项目技术亮点】分库分表+数据路由策略:单表5000万后的架构升级方案
java·开发语言·分布式·架构
半夜修仙14 小时前
RabbitMQ中如何保证消息的可靠性传输
java·分布式·中间件·rabbitmq·github·java-rabbitmq
星心源七境15 小时前
七境体系全解析:从六韬兵法到AI锁颜,一套贯穿古典智慧与现代应用的成长操作系统
人工智能·设计模式·设计
qq_2975746716 小时前
设计模式系列文章(基础篇第21篇):迭代器模式——遍历聚合解耦,实现统一迭代访问
设计模式·迭代器模式
小二·17 小时前
Redis 7 分布式缓存架构实战
redis·分布式·缓存
zhuhai_xigedian17 小时前
源网荷储一体化 vs 传统供用电模式:差异、优势与转型路径
大数据·人工智能·分布式·系统架构·能源
TickDB18 小时前
Python 调用实时行情 API:ticker 返回成功后,如何校验字段再入库或展示
python·websocket·行情数据 api