欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  web前端

基于WebSocket的实时动态图表_html/css_WEB-ITnose

程序员文章站 2022-03-21 12:13:12
...
本文介绍一下基于WebSocket的实时数据双向通讯的小范畴应用,来实现实时动态图表的展示功能。其实实现图表动态更新又岂止是只有这一种方法。用户页面端的js心跳轮询一样可以获取来自后台的最新数据,只是我感觉那是伪实时。

首先介绍一下什么是WebSocket?

WebSocket是HTML5开始提供的一种在单个TCP 连接上进行全双工通讯的协议。 WebSocket通讯协议定于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。 在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。 两者之间就直接可以数据互相传送。或者看一下来自国内知乎上的解释: https://www.zhihu.com/question/20215561

项目需求:

其实标题说的很清晰了,就是要实现图标的实时动态更新,当时我的第一感觉就是要采用WebSocket去解决这个问题。而我的数据来源是来自MQ(消息队列),也就是触发数据推送就是在消息消费的地方。

关于STOMP:

这里需要提一下STOMP,这也是我在调研过程中,在Spring中发现的一个新协议。全称:Simple Text-Orientated Messaging Protocol. 协议官网: http://jmesnil.net/stomp-websocket/doc/。个人将它理解成为WebSocket协议的一个封装实现。当然Spring针对STOMP的实现做了很好的封装,官方文档的解释也是很全面的。

系统配置:

全系统采用JavaConfig模式配置。所以给出的配置方式均为class文件,有喜好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 {    /**     * WebSocket发送消息方法     *     * @param t     */    void send(T t);}
public interface WebSocketCommonHandler {     /**     * 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 implements WebSocketCommonHandler {     @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 {    /**     * 设置消息返回路由     *     * @return     */    @Override    public String setTopic() {        return "/topic/addLoanPoint";    }}
@Componentpublic class DemoWebSocketHandler extends AbstractWebSocketCommonHandler {     /**     * 设置消息返回路由     *     * @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 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 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转对象,然后做相应处理,再把响应数据交由Handler的send方法发送到对应的路由地址。

接下来是前端的一些,连接服务器及监听路由地址的方法实现,我开始已经提到,我用了STOMP去实现了前端的WebSocket管理,所以前段用的的js库有两个: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的line图,已经接收处理路由数据

JavaScript

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 + '
' + '金额:' + params.value[1] + '
' + '公司:' + 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 + '
' + '金额:' + params.value[1] + '
' + '公司:' + 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 }})();

而页面所需要做的就是在加载页面元素完毕之后,调用demo.connect(),去创建WebSocket链接,等待数据的推送,然后绘制图表。至此一个简单的实时动态图表的绘制就完成了,如有任何问题欢迎随时留言提问。O(∩_∩)O