springboot + websocket 整合之STOMP实现

最近在做一个springboot+websocket的服务,踩了很多坑,查阅了大量资料,在此把前辈的资料和自己的代码整理一份:

简单说明

1.实现websocket有两种方式,一种是基于h5(后台对应tomcat实现方式),另一种是stomp(socketjs)协议(后台对应spring框架实现方式)
2.其中,Tomcat实现方式,需要Tomcat7.x以上,JEE7的支持;而spring框架实现方式,需要spring版本4.X以上
3.(划重点)目前两种方式并不兼容,所以选择实现方式时,要和前端工程师沟通好
4.对于两种方式优劣,仁者见仁智者见智了,还是要看业务
5.关于websocket介绍和实现方式的原理,本文暂不介绍了
6.附参考资料:(1).https://blog.csdn.net/qq_28988969/article/details/78113463
(2)https://www.cnblogs.com/interdrp/p/7903736.html

先说方式一:spring实现

1.用springboot搭建的话,只需引入websocket包就ok了


org.springframework.boot
spring-boot-starter-websocket

2.websocket配置类

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
/**
 通过EnableWebSocketMessageBroker 开启使用STOMP协议来传输基于代理(message broker)的消息,
 此时浏览器支持使用@MessageMapping 就像支持@RequestMapping一样。
 */
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) { 
    
        // 允许使用socketJs方式访问,访问点为/websocket/server,允许跨域
        registry.addEndpoint("/websocket/server").setAllowedOrigins("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
    
    //用户订阅主题的前缀  /topic 代表发布广播,即群发,若要实现点对点发送,后面会有介绍
        registry.enableSimpleBroker("/topic");
    }
}

备注:网上关于配置的方法还有

extends AbstractWebSocketMessageBrokerConfigurer

等过时方法,本文给出的是最新方法,功能是一样的。

3.消息类实体

客户端发送服务端实体类,这个就很随意了,可以自定义

public class Message {

    private String name;

    private String retailmId;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getRetailmId() {
        return retailmId;
    }

    public void setRetailmId(String retailmId) {
        this.retailmId = retailmId;
    }

    @Override
    public String toString() {
        return "Message{" +
                "name='" + name + '\'' +
                ", retailmId='" + retailmId + '\'' +
                '}';
    }
}

4.websocket控制层

import com.koolearn.bean.Message;
import com.koolearn.bean.Response;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 〈一句话功能简述〉
* 〈这是一个例子〉 * * @author LiYuAn * @create 2018/11/5 * @since 1.0.0 */ @Api(value = "websocket推送接口Demo", tags = "websocket推送接口Demo") @Controller public class DemoController { @Autowired private SimpMessagingTemplate messagingTemplate; @MessageMapping(value = "/welcome") public void userChat(Message message) throws Exception { System.out.println(message.toString()); String url = "/topic/getResponse/"+message.getRetailmId(); messagingTemplate.convertAndSend(url, new Response("welcome!","1")); } @MessageMapping("/sendTest") @SendTo("/topic/subscribeTest") public ServerMessage sendDemo(ClientMessage message) { logger.info("接收到了信息" + message.getName()); return new ServerMessage("你发送的消息为:" + message.getName()); } @SubscribeMapping("/topic/getResponse") public Response sub() { return new Response("","感谢您订阅了我"); } }

代码详解:
1.@Api
注解是本项目集成了swagger功能,若不集成,可以删掉;集成方法很简单,百度即可。。。。
2.@MessageMapping("/sendTest")
(1)订阅/sendTest地址,即客户端发送地址为:服务器ip+/websocket/server/sendTest时,通过该注解接受客户端发送的消息,在方法里处理业务, @MessageMapping功能和@requestMapping类似;
(2)当和@SendTo("/topic/subscribeTest")一起使用时,可以实现处理业务后,向订阅了/topic/subscribeTest地址的客户端发送消息;
(3)SimpMessagingTemplate messagingTemplate;这是spring框架的发送模板,和@sendTo功能一样,可实现点对点发送,即在和前端约定好在订阅地址后加动态参数即可,在本文的例子中,前端传入了唯一标识,可以是id、token,loginName等等,后台通过消息类取到:String url = “/topic/getResponse/”+message.getRetailmId();此时,url是唯一的,通过convertAndSend(url,XXX)方法就实现了点对点推送
(4)关于点对点发送,还可以通过@sendToUser和messagingTemplate.convertAndSendToUser(user,url,xxx)实现,具体代码下回见分晓 !
springboot + websocket 整合之STOMP实现_第1张图片
3.@SubscribeMapping("/topic/getResponse")
当客户端订阅了此地址时,交给该注解所在方法处理业务

5.书写客户端




    
    Spring Boot+WebSocket+广播式(点对点)




说明:
前端用Stomp实现很简单,只要引入两个stomp的js文件即可



如果你的业务更复杂一些,比如A服务想通过http请求调用websoket服务,并向前端推送消息,可以通过@RequestMapping+SimpMessagingTemplate组合实现,返回类型就自定义了

@Api(value = "websocket推送接口", tags = "websocket推送接口")
@Controller
@RequestMapping(value = "/websocket")
public class WebSocketController {
    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @ApiOperation(value = "消息推送接口")
    @RequestMapping(value = "/push", method = RequestMethod.GET)
    @ResponseBody
    public xxx pushMsg(@RequestParam Integer count,
                              @RequestParam String 唯一标识 {
        String url = "/topic/getResponse/" + 唯一标识;
        messagingTemplate.convertAndSend(url, new Response(xxx));
        return xxx;
    }
}

到此,springboot+websocket的代码就写完了,本文也解决了跨域、广播、点对点发送的坑,当然实现方法还有很多,需要自己去探索。。。。然而,你以为到此就结束了吗?To young to simple…

在项目中,LZ又踩到了一个坑,就是在开发微信公众号时用到了websocket,前端页面是https的,如果你前台发起websocket连接,会有惊喜
springboot + websocket 整合之STOMP实现_第2张图片
如果前台的链接写成htpps,还会报错,那该怎么解决呢?LZ的解决方案是Nginx升级至websocket协议,只需添加配置:

location ^~ /websocket/server {
				proxy_pass  http://127.0.0.1:项目端口;
                proxy_http_version 1.1;
                proxy_set_header X-Client-IP $remote_addr;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                
        }

需要注意的是,在h5实现的方法里,ws对应http,即ip写成ws://xxx.x.x.x…;wss对应https,即wss:xxx.x.x.x…,stomp协议的实现方式正常写就可以了
详情参考http://www.cnblogs.com/zhjh256/p/6262620.html,其他的报错也可参考https://my.oschina.net/robinjiang/blog/898557,貌似前提是你的服务器要配置成支持https的,关于Nginx的使用和配置,这里就暂不介绍了,以后单写一篇
END

你可能感兴趣的:(技术总结)