简单的springboot通过stomp方式集成websocket与前端vue进行点对点通信

Java

1.pom坐标
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
2.websocket 配置
/**
 * websocket stomp方式
 * 四种方式分别见: https://juejin.cn/post/6844903976727494669#heading-27
 *
 * @author wh
 * @date 2021-11-25
 */
// TODO:如何解决服务器重启websocket前端连接 循环?
@Slf4j
@Configuration
//注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /**
     * 添加这个Endpoint,这样在网页中就可以通过websocket连接上服务,
     * 也就是我们配置websocket的服务地址,并且可以指定是否使用socketjs
     *
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {

        /*
         * 1. 将 /ws/ep路径注册为STOMP的端点,
         *    用户连接了这个端点后就可以进行websocket通讯,支持socketJs
         * 2. setAllowedOriginPatterns("*")表示可以跨域
         * 3. withSockJS()表示支持socktJS访问
         * 4. addInterceptors 添加自定义拦截器,这个拦截器是上一个demo自己定义的获取httpsession的拦截器
         * 5. addInterceptors 添加拦截处理,这里MyPrincipalHandshakeHandler 封装的认证用户信息

         */
        // https://blog.csdn.net/jxysgzs/article/details/110818712 这个问题的sendToUser 解决了
        registry.addEndpoint("/ws/ep").setAllowedOriginPatterns("*").withSockJS();
    }

    /**
     * 配置客户端入站通道拦截器
     * 设置输入消息通道的线程数,默认线程为1,可以自己自定义线程数,最大线程数,线程存活时间
     *
     * @param registration
     */
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new ChannelInterceptor() {
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
//                log.info("--websocket信息发送前--");
                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                if (accessor != null) {
                    // 判断是否是连接Command 如果是,需要获取token对象
                    if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                        final String token = accessor.getFirstNativeHeader(Constant.HEADER_TOKEN);
                        if (!TokenUtil.validateToken(token)) {
                            return null;
                        }
                        final LoginUser user = TokenUtil.getUserFromToken(token);
                        UserUtil.setUser(user);
                        // sendToUser 需要与这里的user获取的principal一样
                        accessor.setUser(new SocketUser(user));
                        log.info("websocket 连接成功");
                    }
                }
                return message;
            }
        });
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 设置广播节点
        registry.enableSimpleBroker("/queue");
    }
}

上述代码中
registerStompEndpoints()方法用来注册端点,vue项目通过此端点进行连接
configureClientInboundChannel()方法用来增加拦截器进行token验证,其中判断了command,仅在command为CONNECT时才进行判断
configureMessageBroker()方法用来配置广播节点,配置了此节点才可以实现点对点发送

package com.christo.christoboot.bo;

import com.christo.christoboot.LoginUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.security.Principal;

/**
 * websocket专用user 因为要实现Principal的getName 这个name必须具有唯一性。所以获取userId
 *
 * @author wh
 * @date 2021-11-25
 */
@Data
@NoArgsConstructor
public class SocketUser implements Principal {

    private LoginUser user;

    private String id;

    public SocketUser(LoginUser user) {
        this.user = user;
        this.id = user.getId().toString();
    }

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


websocket的sendToUser方法需要传递一个String类型的user,这个user就是我们在accessor中set的user,他是获取user中的name进行点对点发送的,我们平时的User类中一般存储的name字段都是名称,不能作为一个点对点发送的唯一识别码,所以这边增加一个SocketUser类,获取name时实际获取userId即可

	@GetMapping(value = "ws")
    public R<String> wsTest(String message, String to) {
        ChatMsgDTO chatMsgDTO = new ChatMsgDTO();
        chatMsgDTO.setTime(LocalDateTime.now());
        chatMsgDTO.setContent(message);
        chatMsgDTO.setTo(to);
        simpMessagingTemplate.convertAndSendToUser(to, "/queue/chat",chatMsgDTO);
        return R.ok();
    }

这边写一个发送到指定用户的测试方法,根据上方讲解,这边的to即为我们的User类中的id,chatMsgDTO是我随便定义的一个类,实际开发中需要传递什么类型的消息自定义即可

Vue

vue中实现连接与监听就比较容易了,首先我们需要install对应的js文件

npm install sockjs-client
npm install stompjs

然后在页面中进行引入

import SockJS from "sockjs-client";
import Stomp from "stompjs";

我选择在页面登录时进行socket的连接与订阅,所以讲下方代码放在login完成之后的代码里即可

	websocketRun() {
      const sock = new SockJS('http://127.0.0.1:2525/ws/ep');
      const client = Stomp.over(sock);
      const token = getToken();
      const that  =this
      client.connect({Authorization: token}, () => {
        console.log("连接websocket成功111")
        // //连接成功后订阅消息接口
        //订阅个人消息
        client.subscribe('/user/queue/chat', function(res){
          const data = JSON.parse(res.body);
          that.$notify({
            title: data.to,
            message: data.content
          });
        });
      });
    }

其中值得注意的点
1.SockJs中的协议是http而不是ws,这样做的原因我暂时不太了解,但是如果换成ws:// console中会进行报错
2.在connect中将我们登陆完成获得的token传入以通过我们后台设定的拦截器校验
3.在连接完成之后,进行subsribe时的url需要以/user开头,这个是因为后台sendToUser点对点发送的规范,必须以/user开头,是个写死的规范,监听方法里通过res.body获取到我们发送的消息进行解码进行对应的处理,我这里只是测试一下,所以仅仅是notify一下。
4.几个容易出错的点 1.端口不要写错啦 2.端点不要写错,是config中配置的/ws/ep 3.监听user时发送的to字段,一定要是Principal对象的getName

小结

1.后台代码中还有很多地方可以改进,实际项目使用中代码应该更为严谨,提供的代码仅记录我的学习过程
2.前端代码中也只是简单的测试了一下,实际使用中还涉及到stomp对象全局封装和断线重连等问题,也仅记录我的学习过程

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值