springboot整合websocket做消息推送的功能

最近在了解springboot整合websocket的许多文章,然后根据自己的想法及理解下搭建的了下列的框架。
本人第一次写文章 , 转载 请注明。

1.引入Pom文件

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2.配置WebSocketAutoConfig 建立请求的通道

@Slf4j
@Configuration
@EnableWebSocket
public class WebSocketAutoConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // webSocket通道
        registry.addHandler(new MessageHandler(), "/xx/xxx")
                // 指定自定义拦截器
                .addInterceptors(new WebSocketInterceptor())
                // 允许跨域
                .setAllowedOrigins("*");
        // sockJs通道
        registry.addHandler(new MessageHandler(), "/xxx/xxx")
                .addInterceptors(new WebSocketInterceptor())
                .setAllowedOrigins("*")
                // 开启sockJs支持
                .withSockJS();
    }
}
第一种:
	var webscoket = new WebScoket('ws://ip:端口/通道');
第二种:
	var webscoket = new SockJs('http://ip:端口/通道');

注意:
1.以上两种不同的通道,他所请求的方式也有所不同,比较建议大家选择第二种;
2.在有些情况下则需要不同的模块推送不同的消息,且需要分开进行推送,所以可以在这里注册多个不同路径的通道;
3.若项目使用token的操作,可以把这个路径让他不走鉴权,目前还没发现websocket的鉴权的操作。

值得注意的是:若使用两个不同的通道的是,handler是需要分开的,HandshakeInterceptor 可以用同一个

3.建立处理handler

@Slf4j
public class MessageHandler implements WebSocketHandler {

    /**
     *  存储sessionId和webSocketSession
     *  需要注意的是,webSocketSession没有提供无参构造,不能进行序列化,也就不能通过redis存储
     *  在分布式系统中,要想别的办法实现webSocketSession共享
     */
    private static Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();
    private static Map<String, Set<String>> userMap = new ConcurrentHashMap<>();

    /**
     * webSocket连接创建后调用
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        // 获取参数
        String user = String.valueOf(session.getAttributes().get("user"));
        Set<String> us = (Objects.isNull(userMap.get(user))||userMap.get(user).size()==0)?new HashSet<>():userMap.get(user);
        us.add(session.getId());
        userMap.put(user, us);
        sessionMap.put(session.getId(), session);
    }

    /**
     * 接收到消息会调用
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        String user = String.valueOf(session.getAttributes().get("user"));
        if (message instanceof TextMessage) {
            sendMessage(user,((TextMessage) message).getPayload());
        } else if (message instanceof BinaryMessage) {

        } else if (message instanceof PongMessage) {

        } else {
            System.out.println("Unexpected WebSocket message type: " + message);
        }
    }

    /**
     * 连接出错会调用
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) {
        removeSessionUser(session);
    }

    /**
     * 连接关闭会调用
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        removeSessionUser(session);
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * 移除用户所存的信息
     * @param session
     */
    public void removeSessionUser(WebSocketSession session){
        String user = String.valueOf(session.getAttributes().get("user"));
        //获取sessionId
        Set<String> sessionIds = userMap.get(user);
        if(Objects.nonNull(sessionIds)&&sessionIds.size()>0){
            for(String sessionId : sessionIds){
                if(Objects.equals(sessionId,session.getId())){
                    //移除该端的id
                    sessionIds.remove(sessionId);
                    //将sessionMap中的数据也移除
                    sessionMap.remove(sessionId);
                }
            }
        }
        userMap.put(user,sessionIds);
    }

    /**
     * 后端发送消息-推送至某一个用户
     * return 推送失败的用户
     */
    public static boolean sendMessage(String user, Object sendMap){
        log.info("进入8081....");
        Set<String> sessionIds = userMap.get(user);
        boolean flag = true;
        if(Objects.nonNull(sessionIds)&&sessionIds.size()>0){
            for(String sessionId : sessionIds){
                if(Objects.nonNull(sessionId)){
                    WebSocketSession session = sessionMap.get(sessionId);
                    try {
                        TextMessage textMessage = new TextMessage(JSONUtil.toJsonStr(sendMap));
                        session.sendMessage(textMessage);
                    } catch (IOException e) {
                        e.printStackTrace();
                        flag = false;
                    }
                }else{
                    flag = false;
                }
            }
        }else{
            log.info("不存在用户!");
            flag = false;
        }
        return flag;
    }

    /**
     * 后端发送消息-推送至某一组用户
     * @param userIds
     * @param sendMap
     * @return 返回推送未成功的用户信息
     */
    public static List<String> sendMessage(List<String> userIds,Object sendMap){
        List<String> resultUserIds = new ArrayList<>();
        if(Objects.nonNull(userIds)&&userIds.size()>0){
            for(String userId : userIds){
                boolean s = sendMessage(userId, sendMap);
                if(!s){
                    resultUserIds.add(userId);
                }
            }
        }
        return resultUserIds;
    }
}

4.创建HandshakeInterceptor处理的机制

public class WebSocketInterceptor implements HandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;
            // 获取请求路径携带的参数
            String user = serverHttpRequest.getServletRequest().getParameter("user");
            attributes.put("user", user);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, @Nullable Exception e) {

    }
}

至此 webscoket推送消息的模块的算是基本完成

测试

下面为SockJs的测试用例

var sock = new SockJS('http://localhost:8081/xxx/xxx?user=jack');
    sock.onopen = function(){
        console.log('open');
        showText('open');
    };
    sock.onmessage = function(e){
        console.log(e);
        console.log('message',e.data);
        showText(e.data);
    }
    sock.close = function(data){
        console.log("colse");
        showText(data.data);
    
    }

    //监听窗口关闭事件,当窗口关闭,主动关闭连接
    window.onbeforeunload = function(data){
        showText(data.data);
        sock.close();
    }
    
    function showText(data){
        document.getElementById("message").innerHTML += data+"<br/>";
    }
    function send(){
        var text = document.getElementById("content").value;
        console.log(text);
        sock.send(text);
    }

若有两个不同的处理handler,可以采用下列方式

1.创建一个枚举

public enum RedisTypes {
    /**
     * 保存websocket数据
     */
    MESSAGE_HANDLER(1,"消息处理",处理类1.class,"sendMessage"),
    MESSAGE_NOTICE(2,"工作流消息处理", 处理类2.class,"sendMessage");

    private Integer val;
    private String message;
    private Class handlerClass;
    private String method;

    public Class getHandlerClass() {
        return handlerClass;
    }

    public Integer getVal() {
        return val;
    }

    public String getMessage() {
        return message;
    }

    public String getMethod(){
        return method;
    }

    RedisTypes(Integer val, String message,Class handlerClass,String method){
        this.val = val;
        this.message = message;
        this.handlerClass = handlerClass;
        this.method = method;
    }


    public static Class getSendClass(Integer val){
        for(RedisTypes types : values()){
            if(Objects.equals(types.getVal(),val)){
                return types.getHandlerClass();
            }
        }
        return null;
    }

    public static String getSendMethod(Integer val){
        for(RedisTypes types : values()){
            if(Objects.equals(types.getVal(),val)){
                return types.getMethod();
            }
        }
        return null;
    }
}

使用java的反射机制处理

/**
     * 发送指定的用户
     * @param val
     * @param userIds
     * @param sendMap
     */
    public Object sendMessage(Integer val, String userIds, Object sendMap)  {
        boolean flag = true;
        try {
            Class sendClass = RedisTypes.getSendClass(val);
            Method sendMessage = sendClass.getMethod(RedisTypes.getSendMethod(val),String.class,Object.class);
            Object invoke = sendMessage.invoke(sendClass, userIds, sendMap);
            return invoke;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            flag = false;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            flag = false;
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            flag = false;
        }
        return flag;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值