集成基于Websocket的Stomp连接-进阶

本文介绍了如何在Spring中配置WebSocketSTOMP,包括配置文件、添加拦截器以验证用户身份,以及监听断开连接事件。此外,文章还展示了如何通过SimpMessagingTemplate给指定用户发送消息,并提供前端使用webstomp-client和stompjs集成的例子。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇文章讲了如何进行stomp集成的简易教程,这一篇主要讲两点

  • 解释如何给某个人发送消息
  • 前端如何通过node基于模块化的方式引入Stomp

开头把详情文档地址放一下:Web on Servlet Stack (spring.io)

第一步:config文件调整

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

/**
 * websocket的配置类
 * @author dfg
 * @date 2023-04-28 21:50
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Autowired
    private AuthChannelInterceptor authChannelInterceptor;

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 客户端接收服务器消息的地址前缀
        config.enableSimpleBroker("/topic");
        // 客户端给服务端发消息的地址前缀
        config.setApplicationDestinationPrefixes("/app");
    }

    /**
     * 注册端点
     * setAllowedOrigins 支持跨域
     * withSockJS 支持socketJs
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/gs-guide-websocket").setAllowedOriginPatterns("*").withSockJS();
    }

    /**
     * 连接之前注册登陆信息,识别身份
     * @param registration
     */
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(authChannelInterceptor);
    }
}

添加AuthChannelInterceptor类,通过spring的方式引入,代码如下

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Component;

/**
 * 用于控制登陆的通道拦截器
 * @author dfg
 * @date 2023-05-05 22:40
 */
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class AuthChannelInterceptor implements ChannelInterceptor {
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if (accessor == null) {
            return message;
        }

        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            // todo 做登陆拦截控制
            accessor.setUser(new LoginUser(accessor.getSessionId()));
        }
        return message;
    }
}

/**
 * 自定义的用户认证类
 * @author dfg
 * @date 2023-05-05 22:48
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements Principal {
    /**
     * 登陆的id
     */
    private String name;

    @Override
    public String getName() {
        return name;
    }
}

这个token可以设置成业务系统的uid,这样通过uid就能给某个人发送消息了,之后的文章中出现用户token指的就是此处的token

第二步:监听断开连接事件

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;

/**
 * 监听器
 * @author dfg
 * Created by on 2023-05-06 22:36
 */
@Service
public class MyListener implements ApplicationListener {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof SessionDisconnectEvent) {
            // 取消连接的消息 打印离开人的id
            System.out.println(((SessionDisconnectEvent) event).getSessionId());
        }
    }
}

监听断开事件通过spring的消息机制实现,所以通过实现ApplicationListener即可完成功能自定义,其他事件查看官方链接自行了解Web on Servlet Stack

第三步:给指定用户发送消息

1.先认识一下SimpMessagingTemplate

全路径:org.springframework.messaging.simp.SimpMessagingTemplate

上一篇说到的广播形式,底层就是通过这个类进行的消息发送,通过简单调试找到这个位置org.springframework.messaging.simp.annotation.support.SendToMethodReturnValueHandler#handleReturnValue,截图如下

这里再说一下,虽然是通过SimpMessagingTemplate发送的消息,但是真正底层给客户端发送消息的并不是这个类,而是上一篇原理图提到的SimpleBrokerMessageHandler,通过调试找到org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler#sendMessageToSubscribers这个位置,我给大家放一张调试的截图如下

总结一下广播消息的原理:后端维护了一个map结构,key是目的地,value是用户token的链表,广播消息就是找到这个map,拿出value,遍历value拿到用户token,根据用户token跟客户端通道的绑定关系找到通道,通过通道给用户发送消息

2.封装成一个service

为啥要封装呢,因为SimpMessagingTemplate不支持给多人发送消息,封装一下更好用

import java.util.List;

import cn.hutool.core.util.StrUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

/**
 * 发送消息
 * @author dfg
 * Created by on 2023-05-06 21:39
 */
@Service
public class SendMsgServiceImpl implements SendMsgService {
    @Autowired
    private SimpMessagingTemplate template;

    @Override
    public void sendMsg(String destination, Object payload) {
        if (StrUtil.isBlank(destination) || payload == null) {
            return;
        }

        // 发送消息
        template.convertAndSend(destination, payload);
    }

    @Override
    public void sendMsgToUser(String userToken, String destination, Object payload) {
        if (StrUtil.isBlank(userToken) || StrUtil.isBlank(destination) || payload == null) {
            return;
        }

        template.convertAndSendToUser(userToken, destination, payload);
    }

    @Override
    public void sendMsgToUsers(List<String> userTokens, String destination, Object payload) {
        if (CollectionUtils.isEmpty(userTokens) || StrUtil.isBlank(destination) || payload == null) {
            return;
        }

        // 循环发送消息
        userTokens.forEach(x -> template.convertAndSendToUser(x, destination, payload));
    }
}

到了这服务端部分已经结束了

第四步:前端通过node使用stomp

第一种:webstomp-client+sockjs-client

这是spring推荐的使用方式,文档地址:Web on Servlet Stack

第一步:下载npm包

npm i --save sockjs-client

npm i --save webstomp-client

第二步:测试连接

import webstomp from 'webstomp-client'
import SockJS from 'sockjs-client/dist/sockjs.min.js'

var socket = new SockJS('http://127.0.0.1:8080/gs-guide-websocket');
var stompClient = webstomp.over(socket)
stompClient.connect({}, frame => {
    console.log(frame)
})

这里使用了vite项目以typescript为脚本做了测试,其他webstomp-client用法详见GitHub - JSteunou/webstomp-client: Stomp client over websocket for browsers

第二种:stompjs+sockjs-client

因为webstomp-client挺古老了, 正好在Issus里面看到了stompjs,所以也集成了一下

第一步:下载npm包

npm i --save sockjs-client

npm i --save @stomp/stompjs ws

第二步:测试连接

import SockJS from 'sockjs-client/dist/sockjs.min.js'
import { Client } from '@stomp/stompjs'

var client = new Client()
// 通过sockJS连接
client.webSocketFactory= function () {
    return new SockJS("http://127.0.0.1:8080/gs-guide-websocket");
};

// 连接之后打印结果
client.onConnect = frame => {
    console.log(frame)
};

// 激活连接
client.activate();

其他stompjs用法,查看用法详见Using StompJs v5+ - StompJS Family

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值