WebSocket_html/css_WEB-ITnose に基づくリアルタイム動的チャート

WBOY
リリース: 2016-06-21 08:57:37
オリジナル
2198 人が閲覧しました

この記事では、リアルタイム動的チャートの表示機能を実現する、WebSocket に基づくリアルタイム データ双方向通信の小規模なアプリケーションを紹介します。実際、これがチャートの動的な更新を実現する唯一の方法ではありません。ユーザーページのjsハートビートポーリングもバックグラウンドで最新データを取得できますが、擬似リアルタイムな気がします。

まず、WebSocket とは何かについてご紹介します。

WebSocket は、HTML5 が提供し始めた、単一の TCP 接続で全二重通信を行うためのプロトコルです。 WebSocket の通信プロトコルは 2011 年に IETF によって標準 RFC 6455 として制定される予定であり、WebSocket API は W3C によって標準規格として定められています。 WebSocket API では、ブラウザとサーバーはハンドシェイク アクションを実行するだけで、ブラウザとサーバーの間に高速チャネルが形成されます。 データは両者間で直接送信できます。または、国内の Zhihu からの説明をご覧ください: https://www.zhihu.com/question/20215561

プロジェクト要件:

実際には、タイトルこの問題を解決するには、アイコンのリアルタイムの動的更新を実現する必要があることが明らかになりました。また、私のデータ ソースは MQ (Message Queue) からのものです。つまり、メッセージが消費される場所でデータ プッシュがトリガーされます。

STOMP について:

ここで STOMP について言及する必要があります。これも Spring の調査中に発見した新しいプロトコルです。正式名: Simple Text-Orientated Messaging Protocol。プロトコルの公式 Web サイト: http://jmesnil.net/stomp-websocket/doc/。個人的には、これは WebSocket プロトコルのカプセル化された実装であると理解しています。もちろん、Spring は STOMP の実装をうまくカプセル化しており、公式ドキュメントの説明も非常に包括的です。

システム構成:

システム全体は JavaConfig モードで構成されます。したがって、指定された構成メソッドはすべてクラス ファイルです。XML 構成メソッドを使用したい場合は、それを自分で変換できます。

Java

@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {    @Override    public void configureMessageBroker(MessageBrokerRegistry registry) {        registry.setApplicationDestinationPrefixes("/app"); //接受请求前缀        registry.enableSimpleBroker("/topic");  //返回请求前缀    }    public void registerStompEndpoints(StompEndpointRegistry registry) {        registry.addEndpoint("/getLoanPoints").withSockJS();    }}
ログイン後にコピー
@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {     @Override    public void configureMessageBroker(MessageBrokerRegistryregistry) {        registry.setApplicationDestinationPrefixes("/app"); //接受请求前缀        registry.enableSimpleBroker("/topic");  //返回请求前缀    }     public void registerStompEndpoints(StompEndpointRegistryregistry) {        registry.addEndpoint("/getLoanPoints").withSockJS();    } }
ログイン後にコピー

WebSocket メッセージ送信インターフェース

WebSocket メッセージ処理インターフェイス

Java

public interface WebSocketCommonHandler<T> {    /**     * WebSocket发送消息方法     *     * @param t     */    void send(T t);}
ログイン後にコピー
public interface WebSocketCommonHandler<T> {     /**     * WebSocket发送消息方法     *     * @param t     */    void send(T t);}
ログイン後にコピー

WebSocket メッセージ処理インターフェイス抽象クラス

Java

public abstract class AbstractWebSocketCommonHandlerimplements WebSocketCommonHandler {

@Autowiredprivate SimpMessagingTemplate template;/** * 设置消息返回路由 * * @return */public abstract String setTopic();/** * WebSocket发送消息方法 * * @param o */public void send(T o) {    String topic = setTopic();    if (StringUtils.isEmpty(topic) || o == null) {        throw new RuntimeException("Topic is Empty or Object is null!");    }    this.template.convertAndSend(topic, o);}
ログイン後にコピー

}

public abstract class AbstractWebSocketCommonHandler<T> implements WebSocketCommonHandler<T> {     @Autowired    private SimpMessagingTemplatetemplate;     /**     * 设置消息返回路由     *     * @return     */    public abstract String setTopic();     /**     * WebSocket发送消息方法     *     * @param o     */    public void send(T o) {        String topic = setTopic();        if (StringUtils.isEmpty(topic) || o == null) {            throw new RuntimeException("Topic is Empty or Object is null!");        }        this.template.convertAndSend(topic, o);    }}
ログイン後にコピー

WebSocket メッセージ送信実装クラス

WebSocket メッセージ処理実装クラス

Java

@Componentpublic class DemoWebSocketHandler extends AbstractWebSocketCommonHandler<DataVo> {    /**     * 设置消息返回路由     *     * @return     */    @Override    public String setTopic() {        return "/topic/addLoanPoint";    }}
ログイン後にコピー
@Componentpublic class DemoWebSocketHandler extends AbstractWebSocketCommonHandler<DataVo> {     /**     * 设置消息返回路由     *     * @return     */    @Override    public String setTopic() {        return "/topic/addLoanPoint";    }}
ログイン後にコピー

メッセージ処理と WebSocket データプッシュ

メッセージ消費リスナーと WebSocket データ プッシュ

Java

public class DemoMessageListener implements MessageListener {    private static final Logger LOGGER = LoggerFactory.getLogger(RepayMessageListener.class);    @Autowired    private DemoWebSocketHandler demoWebSocketHandler;    public void onMessage(List<Message> list) throws Exception {        for (Message message : list) {            LOGGER.info("还款消息体是:" + message.getText());            Gson gson = new Gson();            Demo demo = gson.fromJson(message.getText(), Demo.class);            DataVo dataVo = new DataVo();            dataVo.setType(2);            dataVo.setDate(demo.getTime());            dataVo.setValue(demo.getValue());            dataVo.setName(demo.getName());            demoWebSocketHandler.send(dataVo);        }    }}
ログイン後にコピー
public class DemoMessageListener implements MessageListener {     private static final LoggerLOGGER = LoggerFactory.getLogger(RepayMessageListener.class);     @Autowired    private DemoWebSocketHandlerdemoWebSocketHandler;     public void onMessage(List<Message> list) throws Exception {         for (Messagemessage : list) {            LOGGER.info("还款消息体是:" + message.getText());            Gsongson = new Gson();            Demodemo = gson.fromJson(message.getText(), Demo.class);            DataVodataVo = new DataVo();            dataVo.setType(2);            dataVo.setDate(demo.getTime());            dataVo.setValue(demo.getValue());            dataVo.setName(demo.getName());            demoWebSocketHandler.send(dataVo);        }     }}
ログイン後にコピー

これまでのところ、これらはバックグラウンド システムの一部です 関連する実装また、上記のメッセージ消費については、メッセージ ミドルウェアが異なれば実装方法も異なる場合がありますが、ここでの一般的な考え方は、メッセージを受信し、メッセージ Json をオブジェクトに変換し、それに応じて処理し、その後応答データを処理するというものにすぎません。ハンドラーの send メソッドによって、対応するルーティング アドレスに送信されます。

次にフロントエンド、サーバーに接続してルーティングアドレスを監視する方法ですが、冒頭でフロントエンドのWebSocket管理をSTOMPを使って実装したと書きましたので、jsライブラリが2つあります。前のセクションで使用した 2 つ:socketjs-1.0.3.js と stomp.js。Google で検索すると、特定のダウンロード アドレスを取得できます。

最初に WebSocket ツール js をカプセル化します

WebSocket ツール js

JavaScript

var websocket = ( function() {

var stompClient = null;/** * 创建WebSocket链接 * * @param url * @param databackurl * @param callback */var createConnectFunc = function connect(url, databackurl, callback) {    var socket = new SockJS(url);    stompClient = Stomp.over(socket);    stompClient.connect({}, function (frame) {        console.log('Connected: ' + frame);        stompClient.subscribe(databackurl, function (response) {            if (typeof callback === "function") {                callback(response);            } else {                console.log("Not Function!");            }        });    });};/** * 断开WebSocket链接 */var disconnectFunc = function disconnect() {    if (stompClient != null) {        stompClient.disconnect();    }    console.log("WebSocket has Disconnected!");};/** * 发送数据到服务端 * * @param url * @param data */var sendDataFunc = function sendDate(url, data) {    stompClient.send("/app" + url, {}, JSON.stringify(data));};/** * 判断是否已经链接 * * @returns {boolean} */var hasConnectedFunc = function hasConnected(){    if (stompClient != null) {        return true;    }    return false;};return {    createConnect: createConnectFunc,    sendData: sendDataFunc,    disconnect: disconnectFunc,    hasConnected: hasConnectedFunc}
ログイン後にコピー

})();

var websocket = (function () {     var stompClient = null;     /**     * 创建WebSocket链接     *     * @param url     * @param databackurl     * @param callback     */    var createConnectFunc = function connect(url, databackurl, callback) {        var socket = new SockJS(url);        stompClient = Stomp.over(socket);        stompClient.connect({}, function (frame) {            console.log('Connected: ' + frame);            stompClient.subscribe(databackurl, function (response) {                if (typeof callback === "function") {                    callback(response);                } else {                    console.log("Not Function!");                }            });        });    };     /**     * 断开WebSocket链接     */    var disconnectFunc = function disconnect() {        if (stompClient != null) {            stompClient.disconnect();        }        console.log("WebSocket has Disconnected!");    };     /**     * 发送数据到服务端     *     * @param url     * @param data     */    var sendDataFunc = function sendDate(url, data) {        stompClient.send("/app" + url, {}, JSON.stringify(data));    };     /**     * 判断是否已经链接     *     * @returns {boolean}     */    var hasConnectedFunc = function hasConnected(){        if (stompClient != null) {            return true;        }        return false;    };     return {        createConnect: createConnectFunc,        sendData: sendDataFunc,        disconnect: disconnectFunc,        hasConnected: hasConnectedFunc    }})();
ログイン後にコピー

と、demo.js はページのビジネス メソッドです。たとえば、次のとおりです。 echarts の線図の作成方法、ルーティング データが受信されて処理されている

JavaScript

var Demon = (function () { // Based準備された dom 上で、初期化 echarts インスタンス var myChart = echarts.init(document.getElementById('main'));

var loanDataValues = [];var repayDataValues = [];// 使用刚指定的配置项和数据显示图表。var showChartFunc = function () {    myChart.setOption({        title: {            show: false,            text: '图表详情'        },        tooltip: {            trigger: 'item',            formatter: function (params) {                var date = new Date(params.value[0]);                data = date.getFullYear() + '-'                    + (date.getMonth() + 1) + '-'                    + date.getDate() + ' '                    + date.getHours() + ':'                    + date.getMinutes() + ':'                    + date.getSeconds();                return data + '<br/>'                    + '金额:' + params.value[1] + '<br/>'                    + '公司:' + params.value[2];            }        },        legend: {            data: ['Demo1金额', 'Demo2金额']        },        toolbox: {            show: true,            feature: {                mark: {show: true},                dataView: {show: true, readOnly: false},                restore: {show: true},                saveAsImage: {show: true}            }        },        xAxis: [            {                type: 'time',                splitNumber:10,                boundaryGap: ['20%', '20%'],                min: 'dataMin',                max: 'dataMax'            }        ],        yAxis: [            {                type: 'value',                scale: true,                name: '金额(元)',                min: 0,                boundaryGap: ['20%', '20%']            }        ],        dataZoom: {            type: 'inside',            start: 0,            end: 100        },        series: [            {                name: 'Demo1金额',                type: 'line',                smooth: true,                symbol: 'circle',                data: loanDataValues            },            {                name: 'Demo2金额',                type: 'line',                smooth: true,                symbol: 'rect',                data: repayDataValues            }        ]    });};/** * 实时接受消息并绘制图标 * * @param message */var addPointFunc = function addPoint(message) {    var dataVo = JSON.parse(message.body);    addData(dataVo);    showChartFunc();};function addData(dataVo) {    if (dataVo.type == 1) {        loanDataValues.push([dataVo.date, dataVo.value, dataVo.name]);    } else if (dataVo.type == 2) {        repayDataValues.push([dataVo.date, dataVo.value, dataVo.name]);    }}/** * WebSocket连接 */var connectFunc = function connect() {    websocket.createConnect("/getLoanPoints", "/topic/addLoanPoint", addPointFunc);};/** * 发送数据到服务器(暂时不用) */var sendValueFunc = function sendValue() {    var value = document.getElementById('name').value;    websocket.sendData("/getLoanPoints", value);};/** * 获取当日借贷信息 */var getLoanFunc = function () {    $.getJSON('getLoanInfo').done(function (data) {        if (data.success) {            loanDataValues = data.loanInfos.datas;            repayDataValues = data.repayInfos.datas;            showChartFunc();        } else {            alert(data.message);        }    });};return {    getLoan: getLoanFunc,    connect: connectFunc}
ログイン後にコピー

})();

var demo = (function () {    // 基于准备好的dom,初始化echarts实例    var myChart = echarts.init(document.getElementById('main'));     var loanDataValues = [];    var repayDataValues = [];     // 使用刚指定的配置项和数据显示图表。    var showChartFunc = function () {        myChart.setOption({            title: {                show: false,                text: '图表详情'            },            tooltip: {                trigger: 'item',                formatter: function (params) {                    var date = new Date(params.value[0]);                    data = date.getFullYear() + '-'                        + (date.getMonth() + 1) + '-'                        + date.getDate() + ' '                        + date.getHours() + ':'                        + date.getMinutes() + ':'                        + date.getSeconds();                    return data + '<br/>'                        + '金额:' + params.value[1] + '<br/>'                        + '公司:' + params.value[2];                }            },            legend: {                data: ['Demo1金额', 'Demo2金额']            },            toolbox: {                show: true,                feature: {                    mark: {show: true},                    dataView: {show: true, readOnly: false},                    restore: {show: true},                    saveAsImage: {show: true}                }            },            xAxis: [                {                    type: 'time',                    splitNumber:10,                    boundaryGap: ['20%', '20%'],                    min: 'dataMin',                    max: 'dataMax'                }            ],            yAxis: [                {                    type: 'value',                    scale: true,                    name: '金额(元)',                    min: 0,                    boundaryGap: ['20%', '20%']                }            ],            dataZoom: {                type: 'inside',                start: 0,                end: 100            },            series: [                {                    name: 'Demo1金额',                    type: 'line',                    smooth: true,                    symbol: 'circle',                    data: loanDataValues                },                {                    name: 'Demo2金额',                    type: 'line',                    smooth: true,                    symbol: 'rect',                    data: repayDataValues                }            ]        });    };     /**     * 实时接受消息并绘制图标     *     * @param message     */    var addPointFunc = function addPoint(message) {        var dataVo = JSON.parse(message.body);        addData(dataVo);        showChartFunc();    };     function addData(dataVo) {        if (dataVo.type == 1) {            loanDataValues.push([dataVo.date, dataVo.value, dataVo.name]);        } else if (dataVo.type == 2) {            repayDataValues.push([dataVo.date, dataVo.value, dataVo.name]);        }    }     /**     * WebSocket连接     */    var connectFunc = function connect() {        websocket.createConnect("/getLoanPoints", "/topic/addLoanPoint", addPointFunc);    };     /**     * 发送数据到服务器(暂时不用)     */    var sendValueFunc = function sendValue() {        var value = document.getElementById('name').value;        websocket.sendData("/getLoanPoints", value);    };     /**     * 获取当日借贷信息     */    var getLoanFunc = function () {        $.getJSON('getLoanInfo').done(function (data) {            if (data.success) {                loanDataValues = data.loanInfos.datas;                repayDataValues = data.repayInfos.datas;                showChartFunc();            } else {                alert(data.message);            }        });    };     return {        getLoan: getLoanFunc,        connect: connectFunc    }})();
ログイン後にコピー

while page 必要なのは、ページ要素をロードした後にdemo.connect()を呼び出し、WebSocketリンクを作成し、データがプッシュされるのを待ってから、グラフを描画することだけです。この時点で、簡単なリアルタイム ダイナミック チャートの描画が完了しました。ご不明な点がございましたら、お気軽にメッセージを残してください。 O(∩_∩)O

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート