Java WebSocket 基于spring + stomp + SimpMessagingTemplate支持IE9

簡(jiǎn)述:支持發(fā)送公告如绸、發(fā)送指定用戶(hù)及異地登錄。

spring mvc需要的jar包:


image.png

使用spring boot 可以集成websocket庫(kù),如果少了其他庫(kù)选脊,可以在啟動(dòng)spring boot項(xiàng)目看控制臺(tái)報(bào)什么錯(cuò)澎怒,再添加相對(duì)應(yīng)的庫(kù)即可褒搔。

需要的JavaScript庫(kù):
sockjs.js
stomp.js

定義收發(fā)消息實(shí)體類(lèi):

package com.test.springWebsocket;

public class WebMessage {

    /**
     * 用戶(hù)id
     */
    private Long userId;

    /**
     * 用戶(hù)名
     */
    private String username;

    /**
     * 客戶(hù)端標(biāo)記
     */
    private String clientMark;

    /**
     * 內(nèi)容
     */
    private String contents;

    /**
     * 消息類(lèi)型抄瑟,1.公告局雄,2.點(diǎn)對(duì)點(diǎn)發(fā)消息徙垫,3.檢查異地登錄
     */
    private String type;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getClientMark() {
        return clientMark;
    }

    public void setClientMark(String clientMark) {
        this.clientMark = clientMark;
    }

    public String getContents() {
        return contents;
    }

    public void setContents(String contents) {
        this.contents = contents;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

定義WebSocket配置:

package com.test.springWebsocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
        //客戶(hù)端與服務(wù)器端建立連接的點(diǎn)衩侥,允許使用sockJs方式訪問(wèn)猴誊,允許跨域
        stompEndpointRegistry.addEndpoint("/any-socket").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry) {
        //訂閱Broker名稱(chēng)
        messageBrokerRegistry.enableSimpleBroker("/topic", "/user");
        //全局使用的訂閱前綴(客戶(hù)端訂閱路徑上會(huì)體現(xiàn)出來(lái))
        messageBrokerRegistry.setApplicationDestinationPrefixes("/app");
        //點(diǎn)對(duì)點(diǎn)使用的訂閱前綴(客戶(hù)端訂閱路徑上會(huì)體現(xiàn)出來(lái))强霎,不設(shè)置的話撕攒,默認(rèn)也是/user/
        //注意:enableSimpleBroker方法里的某個(gè)參數(shù)路徑必須和該方法的路徑要一樣扁藕,不然指定用戶(hù)發(fā)送消息將會(huì)失敗
        messageBrokerRegistry.setUserDestinationPrefix("/user/");
    }
}

定義WebSocketController:

package com.test.springWebsocket;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;

@Controller
public class WebSocketController {

    @Autowired
    private SimpMessagingTemplate template;

    /**
     * 發(fā)送公告
     *
     * @param msg WebMessage
     */
    @MessageMapping("/to-all")
    public void toAll(WebMessage msg) {
        template.convertAndSend("/topic/to-all", msg);
    }

    /**
     * 發(fā)送指定用戶(hù)
     *
     * @param msg WebMessage
     */
    @MessageMapping("/to-one")
    public void toOne(WebMessage msg) {
        Long userId = msg.getUserId();
        if (userId != null) {
            template.convertAndSendToUser(userId.toString(), "/to-one", msg);
        }
    }
}

定義jsp頁(yè)面RequestMapping:

   /**
     * spring WebSocket 頁(yè)面
     *
     * @param request HttpServletRequest
     * @return String
     */
    @RequestMapping("/spring-websocket.xhtm")
    public String springWebsocket(HttpServletRequest request) {
        String clientMark = (String) request.getSession().getAttribute("clientMark");
        if (clientMark == null) {
            clientMark = GenerateUtil.getUUID();
            request.getSession().setAttribute("clientMark", clientMark);
        }
        Admin admin = (Admin) request.getSession().getAttribute("admin");
        request.setAttribute("userId", admin.getId());
        request.setAttribute("username", admin.getAdmin());
        request.setAttribute("clientMark", clientMark);
        return "springWebsocket/springWebsocket";
    }

定義jsp頁(yè)面:

<!DOCTYPE html>

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

<%
    String rootPath = request.getContextPath();
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" +
            request.getServerPort() + rootPath + "/";
%>

<html>
<head>
    <title>Spring WebSocket</title>
    <meta http-equiv="Expires" content="0"/>
    <meta http-equiv="Cache" content="no-cache"/>
    <meta http-equiv="Pragma" content="no-cache"/>
    <meta http-equiv="Cache-Control" content="no-cache"/>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <style type="text/css">
        .msgBox {
            height: 300px;
            width: 800px;
            border: 2px solid #2196F3;
            overflow: auto;
        }
    </style>
    <script type="text/javascript">
        var rootPath = "<%=rootPath%>";
        var basePath = "<%=basePath%>";
        var userId = '${userId}';
        var username = '${username}';
        var clientMark = '${clientMark}';
    </script>
    <script type="text/javascript" src="<%=basePath%>static/js/plugins/jquery/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="<%=basePath%>static/js/common/sockjs.v1.0.0.js"></script>
    <script type="text/javascript" src="<%=basePath%>static/js/common/stomp.v2.3.3.js"></script>
</head>
<body>
    <div>用戶(hù)id:${userId}</div>
    <div>用戶(hù)名:${username}</div>
    <div>
        <span>公告:</span><input type="text" name="notice"/>
        <button type="button" id="notice">發(fā)送</button>
    </div>
    <div>
        <span>用戶(hù)id:</span><input type="text" name="userId"/>
        <span>內(nèi)容:</span><input type="text" name="contents"/>
        <button type="button" id="sendToUser">發(fā)送</button>
    </div>
    <div>公告列表:</div>
    <div id="noticeList" class="msgBox"></div>
    <div>消息列表:</div>
    <div id="msgList" class="msgBox"></div>
    <script type="text/javascript" src="<%=basePath%>static/js/springWebsocket/springWebsocket.js"></script>
</body>
</html>

定義JavaScript腳本:

var socket = null;
var stompClient = null;

function closeSocket() {
    if (socket == null || socket.readyState == 2 || socket.readyState == 3) {
        return true;
    }
    socket.close();
}

//監(jiān)聽(tīng)窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動(dòng)去關(guān)閉websocket連接,防止連接還沒(méi)斷開(kāi)就關(guān)閉窗口,server端會(huì)拋異常.
window.onbeforeunload = function () {
    closeSocket();
};

function showMsg(webMsg) {
    switch (webMsg['type']) {
        //公告
        case '1': {
            var noticeHtm = '<div>' + webMsg['contents'] + '</div>';
            $('#noticeList').append(noticeHtm);
            $("#noticeList").scrollTop($("#noticeList")[0].scrollHeight);
            break;
        }
        //點(diǎn)對(duì)點(diǎn)發(fā)消息
        case '2': {
            var msgHtm = '<div>' + webMsg['contents'] + '</div>';
            $('#msgList').append(msgHtm);
            $("#msgList").scrollTop($("#msgList")[0].scrollHeight);
            break;
        }
        //檢查異地登錄
        case '3': {
            if (webMsg['clientMark'] != clientMark) {
                closeSocket();
                alert('您的賬號(hào)在另一處登錄');
            }
            break;
        }
        default: {
            alert("WebSocket接收到未知消息...");
            break;
        }
    }
}

function connect() {
    socket = new SockJS(rootPath + '/any-socket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        // 訂閱 /topic/to-all 實(shí)現(xiàn)公告
        stompClient.subscribe(rootPath + '/topic/to-all', function (dd) {
            showMsg(JSON.parse(dd.body));
        });
        // 訂閱 /user/userId/to-one 實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)
        stompClient.subscribe(rootPath + '/user/' + userId + '/to-one', function (dd) {
            showMsg(JSON.parse(dd.body));
        });
        var webMsg = {
            'userId': userId,
            'username': username,
            'clientMark': clientMark,
            'type': '3'
        };
        stompClient.send(rootPath + "/app/to-one", {}, JSON.stringify(webMsg));
    });
}

connect();

$(function () {
    $('#notice').on('click', function () {
        if (socket == null) {
            alert('WebSocket連接未打開(kāi)');
            return true;
        }
        if (socket.readyState == 0) {
            alert('WebSocket正在連接中咬像,請(qǐng)稍后再發(fā)送消息');
            return true;
        }
        if (socket.readyState == 2) {
            alert('WebSocket連接正在關(guān)閉中算撮,無(wú)法發(fā)送消息');
            return true;
        }
        if (socket.readyState == 3) {
            alert('WebSocket連接已關(guān)閉生宛,無(wú)法發(fā)送消息');
            return true;
        }
        var webMsg = {
            'contents': $('input[name="notice"]').val(),
            'type': '1'
        };
        stompClient.send(rootPath + "/app/to-all", {}, JSON.stringify(webMsg));
    });
    $('#sendToUser').on('click', function () {
        if (socket == null) {
            alert('WebSocket連接未打開(kāi)');
            return true;
        }
        if (socket.readyState == 0) {
            alert('WebSocket正在連接中,請(qǐng)稍后再發(fā)送消息');
            return true;
        }
        if (socket.readyState == 2) {
            alert('WebSocket連接正在關(guān)閉中肮柜,無(wú)法發(fā)送消息');
            return true;
        }
        if (socket.readyState == 3) {
            alert('WebSocket連接已關(guān)閉陷舅,無(wú)法發(fā)送消息');
            return true;
        }
        var webMsg = {
            'userId': $('input[name="userId"]').val(),
            'username': username,
            'contents': $('input[name="contents"]').val(),
            'type': '2'
        };
        stompClient.send(rootPath + "/app/to-one", {}, JSON.stringify(webMsg));
    });
});
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市审洞,隨后出現(xiàn)的幾起案子莱睁,更是在濱河造成了極大的恐慌,老刑警劉巖芒澜,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仰剿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡痴晦,警方通過(guò)查閱死者的電腦和手機(jī)南吮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)誊酌,“玉大人部凑,你說(shuō)我怎么就攤上這事”套牵” “怎么了涂邀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)箱锐。 經(jīng)常有香客問(wèn)我比勉,道長(zhǎng),這世上最難降的妖魔是什么驹止? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任浩聋,我火速辦了婚禮,結(jié)果婚禮上幢哨,老公的妹妹穿的比我還像新娘赡勘。我一直安慰自己,他們只是感情好捞镰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布闸与。 她就那樣靜靜地躺著,像睡著了一般岸售。 火紅的嫁衣襯著肌膚如雪践樱。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天凸丸,我揣著相機(jī)與錄音拷邢,去河邊找鬼。 笑死屎慢,一個(gè)胖子當(dāng)著我的面吹牛瞭稼,可吹牛的內(nèi)容都是我干的忽洛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼环肘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼欲虚!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起悔雹,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤复哆,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后腌零,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體梯找,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年益涧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锈锤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡饰躲,死狀恐怖牙咏,靈堂內(nèi)的尸體忽然破棺而出臼隔,到底是詐尸還是另有隱情嘹裂,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布摔握,位于F島的核電站寄狼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏氨淌。R本人自食惡果不足惜泊愧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盛正。 院中可真熱鬧删咱,春花似錦、人聲如沸豪筝。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)续崖。三九已至敲街,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間严望,已是汗流浹背多艇。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留像吻,地道東北人峻黍。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓复隆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親姆涩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昏名,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,773評(píng)論 6 342
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)阵面,斷路器轻局,智...
    卡卡羅2017閱讀 134,628評(píng)論 18 139
  • spring官方文檔:http://docs.spring.io/spring/docs/current/spri...
    牛馬風(fēng)情閱讀 1,650評(píng)論 0 3
  • 有的時(shí)候你想讓一個(gè)函數(shù)同時(shí)是兩個(gè)類(lèi)的友元函數(shù)。你可以選擇把一個(gè)類(lèi)的成員函數(shù)作為另一個(gè)類(lèi)的友元函數(shù)样刷,但是一般的情況是...
    Stroman閱讀 225評(píng)論 0 0
  • 今天看《最美的教育最簡(jiǎn)單》第二章的前三節(jié),作者舉了很多高教育水平父母沒(méi)能教育出他們理想中和他們一樣“高水平”的孩子...
    林江影月閱讀 589評(píng)論 0 2