WebSocket in Spring Boot

Description

我在搭建Online-Food-Ordering-System这个项目时,当用户在微信小程序上下单的时候,商家的后台系统要能够第一时间收到新订单的提醒。既然是发送即时通知,使用Web Socket是一个比较好的解决方案

  1. 首先引入Maven Dependency

     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-websocket</artifactId>
     </dependency>
    
  2. 配置一个ServerEndpointExporter 这一步很关键。通常情况下Tomcat会主动使用ServletContainerInitializer扫描带有@ServerEndpoint注解的类,但是Spring-Boot这种内嵌web container的框架并不会执行这一步。如果忽略掉这一步配置就会是无尽的404…

         @Configuration
         public class WebSocketConfig {
             @Bean
             public ServerEndpointExporter serverEndpointExporter() {
                 return new ServerEndpointExporter();
             }
         }
    
  3. 后台订单页面引入Web Socket js部分。首先判断当前浏览器是否支持Web Socket,之后对onopen(web socekt建立链接), onclose(断开web socket), onerror(web socket链接错误), onmessage(web socket接收到新信息) 以及onbeforunload(页面重新加载前对操作)进行设置。

         var webSocket = null;
         if('WebSocket' in window){
             webSocket = new WebSocket('ws://markzhang.natapp1.cc/sell/webSocket');
         }else{
             alert("current browser does not support web socket");
         }
    
         webSocket.onopen = function(event){
             console.log("connection set up");
         }
    
         webSocket.onclose = function(event){
             console.log("web socket disconnected");
         };
    
         webSocket.onerror = function(event){
             alert("web socket connection error");
         };
    
         webSocket.onmessage = function(event){
             console.log("new message received: " + event.data);
             $('#orderAlert').modal('show');
             document.getElementById('altertAudio').play();
             $('#orderDetail').on("click", function(){
                 location.href="/sell/vendor/order/detail?orderId="+event.data;
             });
         };
    
         window.onbeforeunload = function(event){
             webSocket.close();
         };
    
  4. 后台中@ServerEndpoint用来设置web socket 链接的地址。同样对onOpen, onClose, onMessage事件设置响应的程序。这里后端的@onMessage部分不要与前端页面的webSocket.onmessage搞混。@onMessage是前端页面向后端发送信息后端的响应。而webSocket.onmessage是后台有新下单事件发生,后台通过broadcast方法向前端页面发送信息后前端作出的反应。

    @Component
    @Slf4j
    @ServerEndpoint("/webSocket")
    public class WebSocket {
    private Session session;
    /**
    * thread safe set
    */
    private static Set<WebSocket> listeners = new CopyOnWriteArraySet<>();

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        listeners.add(this);
        log.info("[web socket message] set up a connection, number of connections: {}", listeners.size());
    }

    @OnClose
    public void onClose(Session session) {
        listeners.remove(this);
        log.info("[web socket message] close a connection, number of connections: {}", listeners.size());
    }

    /**
    * server side receive message from the client side
    * @param message
    */
    @OnMessage
    public void onMessage(String message) {
        log.info("[web socket message] receive message from the client side={}", message);
    }

    /**
    * server broadcast message to front end sockets
    * @param message
    */
    public void broadcast(String message) {
        for (WebSocket listener : listeners) {
        log.info("[web socket message] broadcast message={}", message);
        try {
            listener.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error("[web socket message] broadcast message error: {}", e.getMessage());
        }
        }
    }

    }
  1. 这样接单的前台html就和后端打通了,只需要在service层中,下单之后调用boradcast方法就可以发送即时消息了。
         //4. revise the number of stock
         List<CartDTO> cartDTOS = orderDTO.getOrderDetailList().stream().
                 map(e -> new CartDTO(e.getProductId(), e.getProductQuantity())).collect(Collectors.toList());
         productInfoService.decreaseStock(cartDTOS);
    
         //5. send message to web socket
         webSocket.broadcast(orderDTO.getOrderId());
    

Presentation

pre-w60