使用Spring + websocket 的方式向前臺推送數據

最近遇到一個需求,首頁的待辦任務要求實時刷新。剛開始的時候再前端寫了一個定時器輪訓查詢。但是過了一段時間之後覺得太LOW逼了,正好想到了websocket,準備試驗一下,廢話不說,上代碼:

(注:這裏使用maven方式添加 手動添加的同學請自行下載相應jar包放到lib目錄,本文使用的版本爲4.3.5.RELEASE)



    <!-- spring websocket-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-messaging</artifactId>
        <version>${spring.version}</version>
    </dependency>
 
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-websocket</artifactId>
        <version>${spring.version}</version>
    </dependency>

在spring配置文件中添加如下代碼

<!-- websocket相關掃描,主要掃描:WebSocketConfig  如果前面配置能掃描到此類則可以不加 -->
<context:component-scan base-package="org.zwc.ssm" />

 

package com.mlkj.common.websocket;

import com.mlkj.common.config.Global;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * Component註解告訴SpringMVC該類是一個SpringIOC容器下管理的類
 * 其實@Controller, @Service, @Repository是@Component的細化
 */
@Component
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
    @Autowired
    WebSocketHandler handler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        //添加websocket處理器,添加握手攔截器
        webSocketHandlerRegistry.addHandler(handler, Global.getAdminPath()+"/ws").addInterceptors(new HandShakeInterceptor());

        //添加websocket處理器,添加握手攔截器
        webSocketHandlerRegistry.addHandler(handler, Global.getAdminPath()+"/ws/sockjs").addInterceptors(new HandShakeInterceptor()).withSockJS();
    }
}

首先定義一個WebSocketConfig    繼承 WebMvcConfigurerAdapter 以及實現   WebSocketConfigurer接口  ,使用註解@EnableWebSocket 表示這是一個webSocket

2 定義 WebSocketHandler

package com.mlkj.common.websocket;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mlkj.common.result.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;

import java.io.IOException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


@Component
public class WebSocketHandler implements org.springframework.web.socket.WebSocketHandler {

    //當MyWebSocketHandler類被加載時就會創建該Map,隨類而生
    public static final Map<String, WebSocketSession> userSocketSessionMap;

    static {
        userSocketSessionMap = new HashMap<String, WebSocketSession>();
    }

    //握手實現連接後
    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
        String uid = (String) webSocketSession.getAttributes().get("uid");
        userSocketSessionMap.putIfAbsent(uid, webSocketSession);
    }

    //發送信息前的處理
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {

        if(webSocketMessage.getPayloadLength()==0)return;

        //TODO 得到Socket通道中的數據並轉化爲Message對象

        //TODO 將信息保存至數據庫


        //發送Socket信息
        sendMessageToUser("1", new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(new Result())));
    }
    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {

    }

    /**
     * 在此刷新頁面就相當於斷開WebSocket連接,原本在靜態變量userSocketSessionMap中的
     * WebSocketSession會變成關閉狀態(close),但是刷新後的第二次連接服務器創建的
     * 新WebSocketSession(open狀態)又不會加入到userSocketSessionMap中,所以這樣就無法發送消息
     * 因此應當在關閉連接這個切面增加去除userSocketSessionMap中當前處於close狀態的WebSocketSession,
     * 讓新創建的WebSocketSession(open狀態)可以加入到userSocketSessionMap中
     * @param webSocketSession
     * @param closeStatus
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        System.out.println("WebSocket:"+webSocketSession.getAttributes().get("uid")+"close connection");
        Iterator<Map.Entry<String,WebSocketSession>> iterator = userSocketSessionMap.entrySet().iterator();
        while(iterator.hasNext()){
            Map.Entry<String,WebSocketSession> entry = iterator.next();
            if(entry.getValue().getAttributes().get("uid")==webSocketSession.getAttributes().get("uid")){
                userSocketSessionMap.remove(webSocketSession.getAttributes().get("uid"));
                System.out.println("WebSocket in staticMap:" + webSocketSession.getAttributes().get("uid") + "removed");
            }
        }
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
    //發送信息的實現
    public void sendMessageToUser(String uid, TextMessage message) throws IOException {
        WebSocketSession session = userSocketSessionMap.get(uid);
        if (session != null && session.isOpen()) {
            session.sendMessage(message);
        }
    }
}

定義  HandShakeInterceptor 握手攔截器

package com.mlkj.common.websocket;


import com.mlkj.common.utils.StringUtils;
import com.mlkj.modules.sys.utils.UserUtils;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * websocket握手攔截器
 * 攔截握手前,握手後的兩個切面
 */
public class HandShakeInterceptor implements HandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        System.out.println("Websocket:用戶[ID:" + ((ServletServerHttpRequest) serverHttpRequest).getServletRequest().getSession(false).getAttribute("id") + "]已經建立連接");
        if (serverHttpRequest instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) serverHttpRequest;
            HttpSession session = servletRequest.getServletRequest().getSession(false);
            // 標記用戶
            String id = UserUtils.getUser().getLoginName();
            if (StringUtils.isNotBlank(id)) {
                map.put("uid", id);//爲服務器創建WebSocketSession做準備
                System.out.println("用戶id:" + id + " 被加入");
            } else {
                System.out.println("user爲空");
                return false;
            }
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {

    }
}

每次用戶請求的時候通過後臺獲取到用戶的id,將id放入到map中,帶到WebSocketHandler.userSocketSessionMap 中存儲起來

 

前端代碼: 

<script type="text/javascript">
    var webSocket;
    $(function() {
        websocketRefresh()
    });

    function websocketRefresh(){
        if('WebSocket' in window) {
            try{
                var  ip = '${pageContext.request.localAddr}';
                var reg =  /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
                if( reg.test(ip)){
                    console.log("此瀏覽器支持websocket");
                    webSocket = new WebSocket("ws://"+ip+":${pageContext.request.localPort}${ctx}/ws");
                }else{
                    /*alert("ip地址"+ip+"不合法,請勿使用127.0.0.1或者http://localhost");*/
                }

            }catch (e) {
                alert("websocket鏈接異常");
            }

        } else if('MozWebSocket' in window) {
            alert("此瀏覽器只支持MozWebSocket");
        } else {
            alert("此瀏覽器只支持SockJS");
        }

        webSocket.onopen = function(event){
            console.log("連接成功");
            console.log(event);
        };
        webSocket.onerror = function(event){
            console.log("連接失敗");
            console.log(event);
        };
        webSocket.onclose = function(event){
            console.log("Socket連接斷開");
            console.log(event);
        };
        webSocket.onmessage = function(event){
            //接受來自服務器的消息
            //...
            console.log(webSocket.onmessage);
            console.log(event);
            var actLsit = JSON.parse(event.data);
            console.log(actLsit);
            apendTask(actLsit.body.data.list);
        }


        /*websocket.onopen = function(evnt) {
            console.log("鏈接服務器成功")
        };
        websocket.onmessage = function(evnt) {
            var actLsit = JSON.parse(evnt.data);
            console.log(actLsit.body.data);
            apendTask(actLsit.body.data);
        };
        websocket.onerror = function(evnt) {};
        websocket.onclose = function(evnt) {
            if(i<10){
                console.log("與服務器斷開了鏈接,嘗試連接"+i);
                i++;
                websocketRefresh();
            }else{
                alert("與服務器斷開連接,請刷新活重新登錄");
                i=0;
            }
        }*/
    }

    function apendTask(acts){
        $("#act").empty();
        console.log(acts);
       var  actsLength = acts.length;
        if(actsLength>10){
            actsLength =10;
        }
        var st = "";
        for (var i = 0; i <actsLength ; i++) {
            var vars = acts[i].vars;
            st += '<div class="search-result search-hover" style="padding: 0 20px;">';
            st += '    <div class="row">';
            st += '             <div class="col-sm-5">';
            st += '                 <h4 style="margin: 10px 0;line-height: normal;">';
            st += '                     <a href="${ctx}/act/task/form?taskId=' + acts[i].taskId + '&taskName=' + acts[i].taskName + '&taskDefKey=' + acts[i].taskDefKey + '&procInsId=' + acts[i].procInsId + '&procDefId=' + acts[i].procDefId + '&status=' + acts[i].status + '">';
            st +=                           vars.map.title == null ? acts[i].taskId : vars.map.title;
            st += '                     </a>';
            st += '                 </h4>';
            st += '             </div>';
            st += '             <div class="col-sm-2 text-right" style="text-align: center;">';
            st += '                 <p class="forum-sub-title" style="font-size: 14px;margin: 10px 0;color: #181818;">';
            st +=                       acts[i].taskName;
            st += '                 </p>';
            st += '             </div>';
            st += '             <div class="col-sm-5" style="text-align: right;margin: 10px 0; color: #8b9298;">';
            st += '                 <div class="forum-sub-title">接收時間:' + acts[i].taskCreateDate + '</div>';
            st += '             </div>';
            st += '     </div>';
            st += '</div>';
        }
        $("#act").append(st);
    }
</script>

我的業務邏輯爲每隔20秒從後臺查詢數據並推送到前端,具體的推送環節我使用了定時器 如下代碼

package com.mlkj.common.schedule;

import com.google.gson.GsonBuilder;
import com.mlkj.common.json.AjaxJson;
import com.mlkj.common.persistence.Page;
import com.mlkj.modules.act.entity.Act;
import com.mlkj.modules.act.service.ActTaskService;
import com.mlkj.common.websocket.WebSocketHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.Map;

/**
 * 待辦任務定時器
 */
@Service
@Lazy(false)
public class actTaskTodoSchedule {

    @Autowired
    private WebSocketHandler handler;

    @Autowired
    private ActTaskService actTaskService;

    /**
     * 每隔20秒獲取所有連接websocket的用戶,推送當前最新的代辦列表
     */
    @Scheduled(cron = "*/20 * * * * ? ")
    public void act() {
        Map<String, WebSocketSession> stringWebSocketSessionMap = WebSocketHandler.userSocketSessionMap;
        for (String st : stringWebSocketSessionMap.keySet()) {
            try {
                Page<Act> list = actTaskService.todoList(new Act(), st, new Page<Act>(0, 8));
                AjaxJson ajaxJson = new AjaxJson();
                ajaxJson.put("data",list);
                handler.sendMessageToUser(st, new TextMessage(ajaxJson.getJsonStr()));
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

本人小白,剛剛接觸,如有不對敬請指正 

參考代碼 https://blog.csdn.net/zmx729618/article/details/78584633

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章