1.使用场景

我们在与前端即时推送一些消息的时候,基本都是靠WebSocket实现的,而当前端的项目中需要建立多个WebSocket或者给特定的用户发送不同的消息时,传统的实现方式,我们在WebSocket中会传一些特定的参数当做标识,而今天我们所说的这种基于stomp协议的WebSocket,可以支持客户端订阅不同的主题,我们只要向不同的主题发送信息就行了。

2.SpringBoot中的实现

2.1 Maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version>2.1.1.RELEASE</version>
</dependency>

2.2 配置基于stomp协议的WebSocket

@Configuration
//注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer
{

    //某些特定业务中,可能要对WebSocket传入的值做一些操作,或者在刚刚建立连接的时候做一些操作,
    //这里支持我们配置自定义的拦截器,去做拦截的操作。
    @Autowired
    private ConnWebSocketInterceptor connWebSocketInterceptor;

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //注册一个STOMP的endpoint,并指定使用SockJS协议
        registry.addEndpoint("/endpoint").setAllowedOrigins("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //客户端发送消息的请求前缀
        registry.setApplicationDestinationPrefixes("/app");
        //客户端订阅消息的请求前缀,topic一般用于广播推送,queue用于点对点推送
        registry.enableSimpleBroker("/topic", "/queue");
        //服务端通知客户端的前缀,可以不设置,默认为user
        registry.setUserDestinationPrefix("/user");
    }

    // 注册拦截器
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(connWebSocketInterceptor);
    }
}

2.3(视业务而定)配置WebSocket拦截器

@Component
public class ConnWebSocketInterceptor implements ChannelInterceptor {

    // 这里使用的是消息模板,注意,这里的消息模板需要等上面的配置类配置结束之后才能使用
    // 但是上面的配置类同样需要我们拦截器的Bean,如何这里不加@Lazy注解,会导致上面的配置类配置失败。
    @Lazy
    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    // 这里实现的是在客户端与服务端建立连接之后,直接发送一条信息给客户端
    @Override
    public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
         messagingTemplate.convertAndSend("/topic/mytopic","Hello Client!");
    }
}

2.4 通过握手拦截器校验安全

public class HandleShakeInterceptors implements HandshakeInterceptor {

    /**
     * 在握手之前执行该方法, 继续握手返回true, 中断握手返回false.
     * 通过attributes参数设置WebSocketSession的属性
     */
    
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                   WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        //这里写业务代码,判断连接的用户名密码,或者有无Token等,判断让不让连接,不让连接返回false
        return true;
    }

    /**
     * 在握手之后执行该方法. 无论是否握手成功都指明了响应状态码和相应头.
     */
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                               WebSocketHandler wsHandler, Exception exception) {

    }
    
}

2.5 服务端配置接收消息的方法

@Controller
public class TestController
{

    // 消息发送模板
    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    // 接收后缀为/test的消息,可以是/app/test
    //@SendTo  发送给/topic/test的主题
    @MessageMapping("/test")
    @SendTo("/topic/test")
    public void test(String str){
        messagingTemplate.convertAndSend("receive:" + str);
    }

    //点对点式的发送消息
    public void P2P(){
        User user=new User();
	    user.setUserId(1);
        messagingTemplate.convertAndSendToUser(user.getUserId,"/queue/p2p","Hello" + user.getUserId);
    } 
}

3.前端实现

这里只写JS中的方法实现

<script src="https://cdn.bootcss.com/sockjs-client/1.4.0/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.2/stomp.min.js"></script>

订阅主题 

var stompClient = null;


// 订阅主题
function connect() {
    var socket = new SockJS('http://192.168.1.42:8080/endpoint');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/test', function (greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
    });
}

// 订阅点对点
function connect() {
    var userId = 1;
    var socket = new SockJS('http://192.168.1.42:8080/endpoint');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/user/' + userId + '/queue/getResponse', function (greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
    });
}


//断开连接
function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
}

function showGreeting(message) {
    $("#greetings").append("<tr><td>" + message + "</td></tr>");
}