NipGeihou's blog NipGeihou's blog
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档

NipGeihou

我见青山多妩媚,料青山见我应如是
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档
  • 设计模式

  • 开发规范

  • 经验分享

  • 记录

  • 快速开始

    • Spring Boot整合RabbitMQ
    • Spring Boot整合Email邮件发送
    • Spring Boot整合jasypt加密配置文件
    • Spring Boot整合单元测试
    • Spring Boot整合优雅关机
    • Spring Boot整合Redis分布式锁
    • Spring Boot整合MyBatis-plus
    • XXL-JOB快速上手
    • Spring Boot整合WebSocket(stomp协议)
      • 前言
      • 依赖
      • 配置
      • 拦截器
      • 控制器
      • 调试
      • 问题与进阶
      • 参考资料
    • SpringBoot整合i18n(多语言)
    • 第三方登录 - Google
    • 第三方登录 - Facebook
    • Spring Boot 整合Elasticsearch
  • 笔记

  • 面试题

  • 微服务

  • 踩过的坑

  • Java
  • 快速开始
NipGeihou
2023-05-09
目录

Spring Boot整合WebSocket(stomp协议)

# 前言

  • 场景:通过 websocket 实现实时返回用户的通知消息
  • 难点:
    • 如果发送给指定用户?
    • 集群下如何发送?

# 依赖

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

# 配置

@Configuration(proxyBeanMethods = false)
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // ws://xxxxx/ws 连接
        registry
                .addEndpoint("/ws")
                .setAllowedOriginPatterns("*");
        
        // 启用WebSocket端点 前端即可通过 http://xxxx/ws与服务器建立通信
        registry
                .addEndpoint("/ws") 
                // .setAllowedOrigins("*") // 很多教程都说使用这个解决跨域问题,实际上要用下面的setAllowedOriginPatterns
                .setAllowedOriginPatterns("*") // 解决跨域问题
                .withSockJS(); // 启用SockJS支持
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 请求路径(destination)前缀
        registry.setApplicationDestinationPrefixes("/app");
        // 订阅路径前缀,即所有订阅路径都要以/topic作为前缀
        registry.enableSimpleBroker("/topic");
        // 发送给指定用户的前缀
        registry.setUserDestinationPrefix("/user");
        
    }
    
    /**
     * 配置客户端入站通道拦截器
     *
     * @param registration
     */
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new MyChannelInterceptor());
    }

}

# 拦截器

public class MyChannelInterceptor implements ChannelInterceptor {

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

        // 判断是否是连接,如果是,需要获取token,并且设置用户对象
        if (Objects.nonNull(accessor) && StompCommand.CONNECT.equals(accessor.getCommand())) {
            List<String> nativeHeader = accessor.getNativeHeader("Blade-Auth");
            String auth = nativeHeader.get(0);
            if (StrUtil.isNotBlank(auth)) {
                String token = JwtUtil.getToken(auth);
                Claims claims = AuthUtil.parseJWT(token);
                if (Objects.nonNull(claims)) {
                    String userId = (String) claims.get("user_id");
                    if (StringUtils.hasText(userId)) {
                        // 设置当前访问器的认证用户
                        accessor.setUser(() -> userId);
                    }
                    return message;
                }
            }
            return null;
        }
        return message;
    }
}

# 控制器

@Controller
public class TestController {

    @MessageMapping("/chat.sendMessage")
    @SendTo("/topic/public")
    public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
        return chatMessage;
    }

    @MessageMapping("/chat.addUser")
    @SendTo("/topic/public")
    public ChatMessage addUser(@Payload ChatMessage chatMessage,
                               SimpMessageHeaderAccessor headerAccessor) {
        // Add username in web socket session
        headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
        return chatMessage;
    }
    
    /**
     * 点对点发送消息,将消息发送到指定用户
     */
    @MessageMapping("/test")
    public String sendUserMessage(Principal principal, @Payload ChatMessage chatMessage) {
        // 订阅路径:/user/topic/public
        simpMessageSendingOperations.convertAndSendToUser(chatMessage.getSender(), "/topic/public",
                String.format("%s 发送给 %s", principal.getName(), chatMessage.getSender()));
        // 订阅路径:/topic/public
        simpMessageSendingOperations.convertAndSend("/topic/public",
                String.format("%s 发送给222 %s", principal.getName(), chatMessage.getSender()));
        return "success";
    }


}
@Data
public class ChatMessage {

	public enum MessageType {
		/**
		 * 聊天
		 */
		CHAT,
		/**
		 * 加入
		 */
		JOIN,
		/**
		 * 离开
		 */
		LEAVE
	}

	private MessageType type;
	private String content;
	private String sender;

}

# 调试

前端调试工具:https://gitee.com/Samler/sockjs-client-tool

在线地址:http://ws-tool.samler.cn/

注意

不建议使用 apifox 的 WebSocket 接口调试功能,至少截止软件版本 2.2.33 版的 beta 版功能会提示 无法连接到 http://localhost:8207/ws

实际上是没有开启 ws 连接

# 问题与进阶

  1. 前后端分离项目提示跨域

配置 setAllowedOriginPatterns("*")

  1. 经过网关调用报 401 未授权

将 /ws/** 设置白名单

  1. 如何鉴权

通过请求头的 token,参考拦截器代码

  1. 经过 Gateway 转发,返回两个 Access-Control-Allow-Origin

在网关配置

spring:
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_LAST

参考:Spring Cloud Gateway 2.X 跨域时出现重复 Origin 的 BUG - 明天,你好啊 - 博客园 (opens new window)

# 参考资料

  • SpringBoot 实现 Websocket 通信详解 | 小豆丁技术栈 (opens new window)
  • 解决 springboot2.5.6 版本 websocket 跨域的问题_springboot websocket 跨域_兜里都是口香糖的博客 - CSDN 博客 (opens new window)
  • Getting Started | Using WebSocket to build an interactive web application (opens new window)
  • springboot 中通过 stomp 方式来处理 websocket 及 token 权限鉴权相关 - 漫游云巅 - 博客园 (opens new window)
  • WebSocket 集群方案 (opens new window)
上次更新: 2023/05/15, 10:58:20
XXL-JOB快速上手
SpringBoot整合i18n(多语言)

← XXL-JOB快速上手 SpringBoot整合i18n(多语言)→

最近更新
01
iSCSI服务搭建
05-10
02
磁盘管理与文件系统
05-02
03
网络测试 - iperf3
05-02
更多文章>
Theme by Vdoing | Copyright © 2018-2025 NipGeihou | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式