Spring Boot整合STOMP实现实时通信

目录

引言

代码实现

配置类WebSocketMessageBrokerConfig

 DTO

工具类

Controller

common.html

stomp-broadcast.html

运行效果 

完整代码地址


引言

STOMP(Simple Text Oriented Messaging Protocol)作为一种简单文本导向的消息传递协议,提供了一种轻量级且易于使用的方式来实现实时通信。本篇博客将讲解如何使用Spring Boot创建一个基于STOMP的WebSocket应用程序,并展示相关的配置类。同时,还会介绍如何使用Thymeleaf模板引擎生成动态的HTML页面,以展示实时通信的效果。

代码实现

配置类WebSocketMessageBrokerConfig

package com.wsl.websocket.config;

import org.springframework.context.annotation.Configuration;
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;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketMessageBrokerConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // it is OK to leave it here
        registry.addEndpoint("/broadcast");
        //registry.addEndpoint("/broadcast").withSockJS();
        // custom heartbeat, every 60 sec
        //registry.addEndpoint("/broadcast").withSockJS().setHeartbeatTime(60_000);
    }
}

 DTO

package com.wsl.websocket.dto;

import com.wsl.websocket.util.StringUtils;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ChatMessage {

    private String from;
    private String text;
    private String recipient;
    private String time;

    public ChatMessage() {

    }

    public ChatMessage(String from, String text, String recipient) {
        this.from = from;
        this.text = text;
        this.recipient = recipient;
        this.time = StringUtils.getCurrentTimeStamp();
    }
}

工具类

package com.wsl.websocket.util;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class StringUtils {
    
    private static final String TIME_FORMATTER= "HH:mm:ss";
    
    public static String getCurrentTimeStamp() {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(TIME_FORMATTER);
        return LocalDateTime.now().format(formatter);
    }
}

Controller

package com.wsl.websocket.controller;

import com.wsl.websocket.dto.ChatMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class WebSocketBroadcastController {

    @GetMapping("/stomp-broadcast")
    public String getWebSocketBroadcast() {
        return "stomp-broadcast";
    }

    @GetMapping("/sockjs-broadcast")
    public String getWebSocketWithSockJsBroadcast() {
        return "sockjs-broadcast";
    }

    @MessageMapping("/broadcast")
    @SendTo("/topic/broadcast")
    public ChatMessage send(ChatMessage chatMessage) {
        return new ChatMessage(chatMessage.getFrom(), chatMessage.getText(), "ALL");
    }
}

common.html

src/main/resources/templates/common.html




    
    
    
    
    





stomp-broadcast.html

src/main/resources/templates/stomp-broadcast.html




    WebSocket With STOMP Broadcast Example
    


WebSocket

WebSocket Broadcast - with STOMP

运行效果 

http://localhost:8080/stomp-broadcast

Spring Boot整合STOMP实现实时通信_第1张图片

完整代码地址

 GitHub - wangsilingwsl/springboot-stomp: springboot integrates stomp

指定消息的目标用户

此功能基于Spring Boot,和上面代码分隔开,没有关联关系。请结合实际情况参考下列代码。

配置类

package com.twqc.config.websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
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消息代理配置类
 * 

* 注解@EnableWebSocketMessageBroker表示启用WebSocket消息代理功能。不开启不能使用@MessageMapping和@SendTo注解。 * 注意:@MessageMapping和@SendTo的使用方法: *

* 注解@MessageMapping的方法用于接收客户端发送的消息,方法的参数用于接收消息内容,方法的返回值用于发送消息内容。 * 注解@SendTo的方法用于发送消息内容,方法的返回值用于发送消息内容。 *

* 示例:@MessageMapping("/example")注解的方法用于接收客户端发送的消息,@SendTo("/topic/example")注解的方法用于发送消息内容。 * 3.1 对应的客户端连接websocket的路径为:ws://localhost:8080/example * 3.2 对应的客户端发送消息的路径为:/app/example * 3.3 对应的客户端接收消息的路径为:/topic/example * 3.4 app和topic在WebSocketMessageBrokerConfigurer.configureMessageBroker方法中配置 * 3.5 具体的路径需要自己定义,上文仅为示例,与本项目中使用的路径无关。 *

* * @author wsl * @date 2024/2/29 * @see WebSocketMessageBrokerConfigurer * @see MessageMapping * @see SendTo * @see Spring Boot WebSocket with STOMP Tutorial */ @Configuration @EnableWebSocketMessageBroker public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer { /** * 配置消息代理 * * @param config 消息代理配置注册器 */ @Override public void configureMessageBroker(MessageBrokerRegistry config) { // 设置消息代理的目的地前缀,所有以"/websocket/topic"开头的消息都会被代理发送给订阅了该目的地的客户端 config.enableSimpleBroker("/websocket/topic"); // 设置应用的目的地前缀,所有以"/websocket/app"开头的消息都会被路由到带有@MessageMapping注解的方法中进行处理 config.setApplicationDestinationPrefixes("/websocket/app"); // 设置用户的目的地前缀,所有以"/user"开头的消息都会被代理发送给订阅了该目的地的用户 config.setUserDestinationPrefix("/user"); } /** * 注册消息端点 * 可以注册多个消息端点,每个消息端点对应一个URL路径。 * * @param registry 消息端点注册器 */ @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 注册消息端点,参数为消息端点的URL路径(对应@MessageMapping注解的路径,也是客户端连接的路径) registry.addEndpoint("/websocket/bpm/runFlow") // 设置允许的跨域请求来源 .setAllowedOrigins("*"); } /** * 配置客户端入站通道 * * @param registration 客户端入站通道注册器 */ @Override public void configureClientInboundChannel(ChannelRegistration registration) { // 设置客户端入站通道的自定义拦截器 registration.interceptors(new MyWebSocketInterceptor()); } }

拦截器

package com.twqc.config.websocket;

import lombok.extern.slf4j.Slf4j;
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 java.util.List;

/**
 * WebSocket拦截器
 *
 * @author wsl
 * @date 2024/3/4
 */
@Slf4j
public class MyWebSocketInterceptor implements ChannelInterceptor {

    @Override
    public Message preSend(Message message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            List nativeHeaders = accessor.getNativeHeader("username");
            if (nativeHeaders != null) {
                String username = nativeHeaders.get(0);
                // 存入Principal
                accessor.setUser(() -> username);
                log.info("用户:{}发起了stomp连接", username);
            } else {
                log.info("未提供用户名的stomp连接");
                return message;
            }
        }

        return message;
    }
}

用户的消息推送有两种实现方式

@SendToUser

    @MessageMapping("/websocket/bpm/runFlow")
    @SendToUser("/websocket/topic/websocket/bpm/runFlow")
    public String runFlow2(Principal principal) {
        if (principal == null || principal.getName() == null) {
            log.error("未提供用户名的stomp连接,无法运行流程");
        }
        String username = principal.getName();
        return "success" + username;
    }

SimpMessagingTemplate

    @MessageMapping("/websocket/bpm/runFlow")
    public void runFlow(FlowRequest request, Principal principal) {
        if (principal == null || principal.getName() == null) {
            log.error("未提供用户名的stomp连接,无法运行流程");
        }
        String username = principal.getName();
        flowService.runFlow(request, username);
    }

 flowService

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    private void sendNodeResult(FlowResponse response, String username) {
        messagingTemplate.convertAndSendToUser(username, BpmConstant.Flow.TOPIC_FLOW_RESULTS, response);
    }

前端(客户端)






 请注意,客户端接收信号时,需要在订阅地址前加上/app,否则接收失败。

完整代码地址

GitHub - wangsilingwsl/vue-stomp

参考:Spring Boot + WebSocket With STOMP Tutorial | Dariawan 

你可能感兴趣的:(Java,websocket,spring,boot,java,后端)