WebSocket-STOMP应用于路径广播实现配置

WebSocket-STOMP方式用户,大屏通信

一、pom.xml引入web-socket包和jedis包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>websocket</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>urlWebsocket</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- socket依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- Fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <!-- redis依赖,也可用到自己的starter封装 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

二.application.yml启动配置文件
server:
  port: 7001
  servlet:
    session.timeout: 300

logging:
  level:
    #日志优先级(只会输出指定优先级及以上的日志信息):trace<debug<info<warn<error
    org.springframework.web: debug
    cn.zifangsky: debug
#  file: logs/stomp-websocket.log


  #Thymeleaf
  thymeleaf:
    mode: LEGACYHTML5
    prefix: classpath:/templates/
    suffix: .html
    template-resolver-order: 0
    cache: false

auth:
  aes:
    key: **************

#redis连接
spring.redis.host= 192.168.0.9
三、java代码实现

1.WebSocketConfig.java配置

@Slf4j
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Autowired
    private MyChannelInterceptor myChannelInterceptor;

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/stomp-websocket")
                .setAllowedOrigins("*")
                .withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //客户端需要把消息发送到/message/xxx地址
        registry.setApplicationDestinationPrefixes("/message");
        //服务端广播消息的路径前缀,客户端需要相应订阅/topic/xxx这个地址的消息
        registry.enableSimpleBroker(Constant.BROKER_DESTINATION_USER_PREFIX,Constant.BROKER_DESTINATION_PREFIX);
        //给指定用户发送消息的路径前缀,默认值是/user/
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        //配置连接用户关系保存
        registration.interceptors(myChannelInterceptor);
    }
}

2、MyChannelInterceptor.java拦截保存获取token中的userId会员id设置到socket中

@Component
@Slf4j
public class MyChannelInterceptor implements ChannelInterceptor {

    @Value("${auth.aes.key}")
    private String aesKey;

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
            if (raw instanceof Map) {
                Object token = ((Map) raw).get("token");
                if(token != null){
                    // 设置当前访问的认证用户
                    String tokenString = ((LinkedList)token).get(0).toString();
                    String tokenValue = AESUtils.decrypt(tokenString, aesKey);
                    String[] tokenValues = tokenValue.split("##");
                    String userId = tokenValues[1];//todo 小程序登录时取0
                    CustomPrincipal customPrincipal = new CustomPrincipal(userId);
                    accessor.setUser(customPrincipal);
                }

            }
        }
        return message;
    }

    @Override
    public boolean preReceive(MessageChannel channel){
        return true;
    }

    @Override
    public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
        StompCommand command = accessor.getCommand();

        //用户已经断开连接
        if(StompCommand.DISCONNECT.equals(command)){
            String user = "";
            Principal principal = accessor.getUser();
            if(principal != null && !StringUtils.isEmpty(principal.getName())){
                user = principal.getName();
            }else{
                user = accessor.getSessionId();
            }
            log.info(MessageFormat.format("用户{0}的WebSocket连接已经断开", user));
        }
    }
}

3.CustomPrincipal.java,socket用户凭证

public class CustomPrincipal implements Principal {
    private String userId;

    public CustomPrincipal(String userId) {
        this.userId = userId;
    }

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

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
}

4.RedisConfig.java 配置redis监听

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

@Configuration
public class RedisConfig {

    @Autowired
    private LettuceConnectionFactory lettuceConnectionFactory;

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer() {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(lettuceConnectionFactory);
        return container;

    }
}

5.RedisChannelListener.java实现redis订阅自定义的channel,用户接受代码中向通道发送的信息,接受到后根据消息体的不同发送到广播,或者指定用户

@Slf4j
@Component
public class RedisChannelListener implements MessageListener {

    @Autowired
    SimpMessagingTemplate simpMessagingTemplate;

    @Override
    public void onMessage(Messachange message, byte[] pattern) {
        String channel = new String(pattern);
        log.info("channel:" + channel + "receives message :" + message.getBody());
        if (!StringUtils.isEmpty(message) && Constant.STOMP_MESSAGE_CHANNEL.equals(channel)) {
            try {
                StompMessage msg = JSON.parseObject(message.getBody(), StompMessage.class);
                //如果消息包含会员id表示单独发送给小程序会员,小程序会员响应广播,否则发送到指定用户
                if(msg.getMemberId() == null){
                    String destination = Constant.BROKER_DESTINATION_PREFIX + "/" + msg.getMarketId();
                    simpMessagingTemplate.convertAndSend(destination, JSON.toJSON(msg));
                }else{
                    String destination = Constant.BROKER_DESTINATION_USER_PREFIX + "/" + msg.getMarketId();
                    simpMessagingTemplate.convertAndSendToUser(msg.getMemberId().toString(),destination,JSON.toJSON(msg));
                }

            } catch (Exception e) {
                log.info("onMessage error:{}", e.getMessage());
            }
        }
    }

}

6.MyApplicationRunner.java项目启动则订阅redis通道

@Component
public class MyApplicationRunner implements ApplicationRunner {

    @Autowired
    private SubscribeListener subscribeListener;

    @Autowired
    RedisMessageListenerContainer redisMessageListenerContainer;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        this.subWebsocketChannel();
    }

    /**
     * 订阅redis频道
     */
    private void subWebsocketChannel(){
        redisMessageListenerContainer.addMessageListener(subscribeListener,new ChannelTopic(webSocketChannel));
    }
}

7.PublishService 将消息发布到通道

/**
 * 通道发布
 */
@Component
public class PublishService {

    @Autowired
    StringRedisTemplate redisTemplate;

    /**
     * @param channel 消息发布订阅 主题
     * @param message 消息信息
     */
    public void publish(String channel, Object message) {
        redisTemplate.convertAndSend(channel, message);
    }
}

8.根据实际业务处理后需要向socket推送消息,则直接推送到redis通道,由redis订阅后再根据消息向订阅路径推送

@Autowired
private PublishService publishServerice;

//推送到广播
@GetMapping("/publish/topic")
    public void toTopic(long marketId){
        StompMessage stompMessage = new StompMessage();
        stompMessage.setMarketId(marketId);
        stompMessage.setMessageType(2);
        stompMessage.setData("from publish topic");
        publishServerice.publish(Constant.STOMP_MESSAGE_CHANNEL, JSON.toJSONString(stompMessage));
    }

//推送到指定用户
    @GetMapping("/publish/user")
    public void toUser(long marketId,long memberId){
        StompMessage stompMessage = new StompMessage();
        stompMessage.setMarketId(marketId);
        stompMessage.setMemberId(memberId);
        stompMessage.setMessageType(1);
        stompMessage.setData("from publish user *");
        publishServerice.publish(Constant.STOMP_MESSAGE_CHANNEL, JSON.toJSONString(stompMessage));
    }

    @MessageMapping("/send/to/{marketId}")
    public void sendToUser(Principal principal,@DestinationVariable Long marketId){
	//从principal.getName()可以获取连接时设置到socket中的userId,参数中获取marketId则可以处理对应业务,比如摇一摇计数       simpMessagingTemplate.convertAndSendToUser(principal.getName(),"/queue","from to user");
    }

9.StompMessage.javapublish到redis再推送到socket订阅路径的消息体,订阅者根据messageType处理相应业务

@Data
public class StompMessage {

    /**
     * 活动id
     */
    private Long marketId;

    /**
     * 推送消息类型
     */
    private Integer messageType;

    /**
     * 用户会员id
     */
    private Long memberId;

    /**
     * 消息内容
     */
    private Object data;
}

10.前端socket连接、订阅和发送部分代码

	var stompClient = null;
//连接
        function connect() {
            var target = $("#target").val();
            //target = http://localhost:7001/stomp-websocket
            var ws = new SockJS(target);
            stompClient = Stomp.over(ws);

//建立连接是将token作为header中参数,用于后端获取userId保存关系指定用户发送消息            stompClient.connect({"token":"40A070633494FFEF9050390AEF5C51761E067B8F4C9C446954785E25ED687B8D882C5F33A63DCBAA5605FA6431E60CBB"}, function () {
                setConnected(true);
                log('Info: STOMP connection opened.');

                //订阅广播 /topic/marketId ,根据接收消息类型相应处理
                stompClient.subscribe("/topic/1", function (greeting) {
                    log('Received topic: ' + greeting.body);
                });
				
				//订阅指定用户发送消息 /user/queue/marketId ,接收后端对这个用户的指定发送,这里默认/user为前缀,会接收到后端根据保存在socket中的Principal中的userName的消息
                stompClient.subscribe("/user/queue/1", function (greeting) {
                    log('Received to user: ' + greeting.body);
                });
            },function () {
                //断开处理
                setConnected(false);
                log('Info: STOMP connection closed.');
            });
        }

        //断开连接
        function disconnect() {
            if (stompClient != null) {
                stompClient.disconnect();
                stompClient = null;
            }
            setConnected(false);
            log('Info: STOMP connection closed.');
        }

        //向服务端发送姓名
        function sendName() {
            if (stompClient != null) {
                var username = $("#username").val();
                var mapping = $("#mapping").val();
                log('Sent: ' + username);
                stompClient.send("/message/"+mapping, {}, JSON.stringify({'name': username}));
            } else {
                alert('STOMP connection not established, please connect.');
            }
        }

        //日志输出
        function log(message) {
            console.log(message);
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以按照以下步骤来配置 SSL 访问 WebSocket: 1. 生成证书和私钥文件,可以使用 OpenSSL 工具。 2. 将证书和私钥文件放在服务器上,并记录其存储路径。 3. 在 Spring Boot 项目的 application.properties 或 application.yml 文件中配置 SSL 相关属性,如下所示: ``` server: port: 443 ssl: enabled: true key-store: /path/to/your/keystore.p12 key-store-password: your_password key-store-type: PKCS12 ``` 其中,`server.port` 指定端口号,这里设置为 443,即 HTTPS 默认端口号;`server.ssl.enabled` 表示启用 SSL;`server.ssl.key-store` 指定证书和私钥文件的存储路径;`server.ssl.key-store-password` 指定证书和私钥文件的密码;`server.ssl.key-store-type` 指定证书和私钥文件的类型,这里设置为 PKCS12。 4. 在 WebSocket 配置类中添加 SSL 相关配置,如下所示: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new MyWebSocketHandler(), "/websocket") .setAllowedOrigins("*") .withSockJS(); } @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); container.setMaxTextMessageBufferSize(8192); container.setMaxBinaryMessageBufferSize(8192); container.setMaxSessionIdleTimeout(30000); container.setAsyncSendTimeout(5000); container.setTaskExecutor(new ConcurrentTaskExecutor()); container.setWebSocketTransport(new StandardWebSocketTransport()); container.setSsl(getSslConfiguration()); return container; } private SslConfiguration getSslConfiguration() { SslConfiguration ssl = new SslConfiguration(); ssl.setKeyStore("/path/to/your/keystore.p12"); ssl.setKeyStorePassword("your_password"); ssl.setKeyStoreType("PKCS12"); return ssl; } } ``` 其中,`createWebSocketContainer` 方法用于创建 WebSocket 容器,可以通过其设置最大文本消息缓冲大小、最大二进制消息缓冲大小、最大会话空闲时间、异步发送超时时间、任务执行器、WebSocket 传输协议以及 SSL 配置。 `getSslConfiguration` 方法用于获取 SSL 配置,与 application.properties 或 application.yml 中的配置相同。 至此,你已经完成了 SSL 访问 WebSocket配置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值