Vue2+Echarts+SpringBoot+Websocket+Scheduled实现大屏图表数据实时展示

1.简介

近期在学习websocket的相关技术,用于做前后端的数据实时交互,结合网上资料和个人理解,整理了一个小白入门案例,不喜勿喷!!!!!

1.1 webSocket

  • WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
  • 它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
  • Websocket是一个持久化的协议

WebSocket有以下特点:

  • 是真正的全双工方式,建立连接后客户端与服务器端是完全平等的,可以互相主动请求。而HTTP长连接基于HTTP,是传统的客户端对服务器发起请求的模式。
  • HTTP长连接中,每次数据交换除了真正的数据部分外,服务器和客户端还要大量交换HTTP header,信息交换效率很低。Websocket协议通过第一个request建立了TCP连接之后,之后交换的数据都不需要发送 HTTP header就能交换数据,这显然和原有的HTTP协议有区别所以它需要对服务器和客户端都进行升级才能实现(主流浏览器都已支持HTML5)

2.效果图展示

图 1 初始状态

图 2 推送一段时间之后的状态 

socket演示

demo中以饼图和柱形图为例,结合websocket,对后端数据进行实时推送

3.代码实现

3.1 前端代码

该项目为前后端分离项目,前端主要使用Vue2+原生websocket进行页面搭建和服务端数据交互

  • 主要代码实现部分
<template>
  <div class="hello">
   <!-- 柱形图渲染 -->
   <div id="chart">

   </div>
   <!-- 饼图渲染 -->
   <div id="pie">

  </div>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data(){
    return {
      ws: null,
      url:"ws://127.0.0.1:8080/websocket/",
      barChart: null,
      pieChart: null,
      userId: null
    }
  },
  mounted(){
    //挂载的时候进行初始化
    this.init()
  },
  created(){
  
  },
  methods:{
    initChart(data){
      if(!this.barChart){
        this.barChart= this.$echarts.init(document.getElementById('chart'))
      }
      let chart = this.barChart
      const option = {
        title: {
          text: 'websocket测试',
          textStyle:{
            color: "white"
          }
        },
        tooltip: {
          trigger: 'axis'
        },
        legend: {
          data: ['日销量'],
          textStyle:{
            color: "white"
          }
        },
        xAxis: {
          name: "日期",
          type: 'category',
          data: data.xAxisData,
          axisLabel:{
            color: "white"
          },
          nameTextStyle:{
            color: "white"
          }
        },
        yAxis: {
          type: 'value',
          name:"销量",
          axisLabel:{
            color: "white"
          },
          nameTextStyle:{
            color: "white"
          }
        },
        series: [
          {
            name: '日销量',
            type: 'bar',
            data: data.yAxisData
          }
        ]
      } 
      chart.setOption(option)  
    },
    init(){
      let obj = this.getUrlParams2(window.location.href)
      this.ws = new WebSocket(this.url+obj.userid)
      this.ws.onopen = this.websocketOnOpen;
      this.ws.onmessage = this.websocketOnMessage;
    },
    websocketOnOpen(){
      console.log("连接成功")
    },
    websocketOnMessage(datas){
      console.log(datas)
      if(datas.data !== "连接成功"){
        let res = JSON.parse(datas.data)
        if(res.type == 1){
            this.$message({
              type: "warning",
              message: res.msg
            })
        }else{
          this.initChart(JSON.parse(datas.data).server)
          this.initPie(JSON.parse(datas.data).pie)
        }
        
      }
    },
    getUrlParams2(url){
      let urlStr = url.split('?')[1]
      const urlSearchParams = new URLSearchParams(urlStr)
      const result = Object.fromEntries(urlSearchParams.entries())
      return result
    },
    initPie(pies){
      if(!this.pieChart){
        this.pieChart= this.$echarts.init(document.getElementById('pie'))
      }
      let chart = this.pieChart
      const option = {
          // legend 图例组件配置项 (图例,其实就是颜色指示器)
          legend: {
            top: "bottom", // 图例组件离容器上侧的距离。
          },
          // 工具栏组件
          toolbox: {
            show: true, // 是否显示工具栏组件
            feature: {
              mark: { show: false },
              dataView: { show: true, readOnly: false }, // 数据视图工具
              restore: { show: true },  // 配置项还原
              saveAsImage: { show: true }, // 保存图片
            },
          },
          series: [
            {
              name: "Nightingale Chart", // 名称
              type: "pie",  // 类型 饼图
              radius: [50, 150], // 饼图的半径 `50, 250 => 内半径 外半径`
              center: ["50%", "50%"], // 饼图的中心(圆心)坐标,数组的第一项是横坐标,第二项是纵坐标。
              roseType: "area", // 是否展示成南丁格尔图,通过半径区分数据大小
              // 图形的颜色
              itemStyle: {
                borderRadius: 8,
              },
              // 图表的数据
              data: pies.pie,
            },
          ],
        };
        // 3.5 将配置参数和图表关联
        chart.setOption(option);
    }
  }
}
</script>

<style scoped lang="less">
  .hello{
    display: flex;
    #chart,#pie{
      width: 600px;
      height: 400px;
      background-color: black;
    }
    #pie{
      margin-left: 20px;
    }
  }
</style>

3.2 后端代码

后端我是用的是Java语言,主要使用SpringBoot生态进行服务端的搭建

3.2.1 项目目录结构

 3.2.2 导入相关依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--   spring-web依赖    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--   websocket相关依赖     -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <!--   lombok     -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--EntityUtils工具类包-->
        <dependency>
            <groupId>xin.altitude.cms</groupId>
            <artifactId>ucode-cms-common</artifactId>
            <version>1.5.8</version>
        </dependency>
        <!--    hutool工具类    -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.18</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

 3.3.3 WebSocket配置文件

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
    /**
     * 	注入ServerEndpointExporter,
     * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
    
}

3.3.4 WebSocket服务类

@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer {
    static Log log= LogFactory.get(WebSocketServer.class);
    /**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
    private static int onlineCount = 0;
    /**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
    private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    /**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
    private Session session;
    /**接收userId*/
    private String userId="";
    @Autowired
    private ItemService itemService;

    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId=userId;
        if(webSocketMap.containsKey(userId)){
            webSocketMap.remove(userId);
            webSocketMap.put(userId,this);
            //加入set中
        }else{
            webSocketMap.put(userId,this);
            //加入set中
            addOnlineCount();
            //在线数加1
        }

        log.info("用户连接:"+userId+",当前在线人数为:" + getOnlineCount());

        try {
            Map<String,Object> map = new HashMap<>();
            map.put("server",itemService.getData());
            map.put("pie", itemService.getPieData());
            JSONObject jsonObject =  new JSONObject(map);
            sendMessage(jsonObject.toString());
        } catch (Exception e) {
            log.error("用户:"+userId+",网络异常!!!!!!");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if(webSocketMap.containsKey(userId)){
            webSocketMap.remove(userId);
            //从set中删除
            subOnlineCount();
        }
        log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("用户消息:"+userId+",报文:"+message);
        //可以群发消息
        //消息保存到数据库、redis
        if(StringUtils.isNotBlank(message)){
            try {
                //解析发送的报文
                JSONObject jsonObject = JSON.parseObject(message);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("用户错误:"+this.userId+",原因:"+error.getMessage());
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }
    /**
     * 消息单发
     */
    public static void sendOneMessage(String userId,String message) throws IOException {
        WebSocketServer webSocketServer = webSocketMap.get(userId);
        webSocketServer.session.getBasicRemote().sendText(message);
    }


    /**
     * 实现服务器主动推送
     */
    public static void sendAllMessage(String message) throws IOException {
        ConcurrentHashMap.KeySetView<String, WebSocketServer> userIds = webSocketMap.keySet();
        for (String userId : userIds) {
            WebSocketServer webSocketServer = webSocketMap.get(userId);
            webSocketServer.session.getBasicRemote().sendText(message);
            System.out.println("webSocket实现服务器主动推送成功userIds===="+userIds);
        }
    }

    /**
     * 发送自定义消息
     * */
    public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
        log.info("发送消息到:"+userId+",报文:"+message);
        if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){
            webSocketMap.get(userId).sendMessage(message);
        }else{
            log.error("用户"+userId+",不在线!");
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}

以上便完成了websocket服务的搭建,上述中设置了单发和群发两种通信方式

3.3.5 测试实体类

 分别创建柱形图(Bar)和饼图(Pie)的实体类

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Bar {
    private String name;
    private int data;
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Pie {
    private String name;
    private int value;
}

3.3.6 编写Service从数据库获取实时数据

此处为服务端从数据库中获取最新数据的过程,demo中,并没有进行查库操作,而是写了一个模拟数据进行测试,使用static静态变量对数据进行静态初始化,如需操作数据库可以进行数据库连接即可,持久层可使用mybatis或者JPA对数据库进行操作

 初始化完成后,分别获取饼图和柱形图的相关数据,通过Math.random()方法对初始数据进行加工,使得每次获取到的数据都是不一样的,从而模拟数据的变化更新

@Service
public class ItemService {
    private static final List<Bar> items = new ArrayList<>();
    private static final List<Pie> pie = new ArrayList<>();
    static {
        items.add(new Bar("周一",12));
        items.add(new Bar("周二",20));
        items.add(new Bar("周三",15));
        items.add(new Bar("周四",8));
        items.add(new Bar("周五",7));
        items.add(new Bar("周六",11));
        items.add(new Bar("周日",13));
        pie.add(new Pie("rose1",38));
        pie.add(new Pie("rose2",46));
        pie.add(new Pie("rose3",12));
        pie.add(new Pie("rose4",30));
        pie.add(new Pie("rose5",54));
        pie.add(new Pie("rose6",36));
    }
    //获取柱形图数据
    public Map<String,?> getData(){
        //模拟数据更新
        items.forEach(e -> e.setData(e.getData()+(int)(Math.random() * 5 + 1)));
        HashMap<String,Object> map = new HashMap<>();
        map.put("xAxisData", EntityUtils.toList(items,Bar::getName));
        map.put("yAxisData", EntityUtils.toList(items,Bar::getData));
        return map;
    }
    //获取饼图数据
    public Map<String,?> getPieData(){
        //模拟数据更新
        pie.forEach(e -> e.setValue(e.getValue()+(int)(Math.random() * 5 + 1)));
        HashMap<String,Object> map = new HashMap<>();
        map.put("pie",pie);
        return map;
    }
}

3.3.7 编写测试接口

@RestController
@RequestMapping("/news")
@Slf4j
public class TestApi {
    @Autowired
    private ItemService itemService;
    /**
     * 3秒执行一次
     * @return
     * @throws Exception
     */
    @Scheduled(fixedRate = 3 * 1000)
    @GetMapping("/send")
    public String send() throws Exception {
        Map<String,Object> map = new HashMap<>();
        map.put("server",itemService.getData());
        map.put("pie",itemService.getPieData());
        JSONObject jsonObject =  new JSONObject(map);
        WebSocketServer.sendAllMessage(jsonObject.toString());
        return jsonObject.toString();
    }
}

以上接口编写完成之后,使用SpringBoot自带的定时任务Scheduled类对接口进行设置,我这里是设置3秒执行一次,即@Scheduled(fixedRate = 3 * 1000),同时将每次获取到的最新数据通过websocket向客户端进行消息推送

至此 ,完成整个项目的搭建,本人也不太会,都是慢慢摸索的,有啥讲的不对的地方,欢迎大家批评指正!!!!如果觉得对您有帮助的话,请不要吝啬您的点赞+关注+评论哟!!!!

  • 8
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: Vue Echarts大屏数据展示150套是指通过使用Vue框架结合Echarts图表库来展示大屏数据的一个需求。 Vue是一个流行的JavaScript框架,用于构建用户界面。它具有简洁的语法和高效的渲染性能,使开发者能够快速构建灵活的应用程序。 Echarts是一种基于JavaScript和HTML5的数据可视化库,提供了丰富的图表类型和交互方式,使用户能够更直观地理解和分析数据大屏数据展示意味着将大量的数据以可视化的形式展示在屏幕上,以便用户更好地理解数据趋势和关联性。 150套表示有150种不同的数据展示方案。可能是根据不同的业务需求和数据类型,展示不同的图表类型、颜色、样式和交互效果。 开发者可以使用VueEcharts配合开发,首先通过Vue构建一个大屏数据展示的页面框架,在页面中引入Echarts图表组件。然后根据需求,在Vue中动态生成Echarts图表实例,并通过Echarts提供的API来配置图表数据和样式,以满足不同的展示需求。 通过编写适当的业务逻辑和数据交互代码,开发者可以将150套不同的数据展示方案分别展示大屏中。 总结来说,Vue Echarts大屏数据展示150套是指使用Vue框架结合Echarts图表库来构建一个大屏数据展示页面,并根据需求通过动态生成和配置Echarts图表实例,展示不同的数据图表类型。这将使用户能更好地理解和分析大量的数据。 ### 回答2: VueECharts结合可以非常方便地实现大屏数据展示大屏数据展示通常需要展示大量的数据,并以可视化的方式直观地呈现给用户。Vue提供了数据绑定和组件化的特性,使得开发大屏数据展示变得简单而高效。而ECharts是一款功能强大的数据可视化库,提供了丰富的图表类型和交互功能,能够满足各种复杂的数据展示需求。 在Vue中使用ECharts,首先需要引入ECharts的库文件,可以通过npm安装或者直接引入CDN链接。然后在Vue组件中使用ECharts的API来创建图表并配置图表的样式和数据。例如,可以创建一个基于柱状图的大屏数据展示组件,将数据传入组件实现动态展示Vue的组件化特性可以让我们将大屏数据展示拆分成多个小组件,每个组件负责展示一个特定的模块。这样可以提高代码的复用性和可维护性,方便扩展和调整。同时,Vue数据绑定机制也可以将数据图表同步更新,实现实时数据展示效果。 总之,通过VueECharts的组合,我们可以轻松地实现大屏数据展示,并且能够快速响应用户的交互操作。无论是展示150套数据还是更多的数据VueECharts都能够满足需求,并保持良好的性能和用户体验。 ### 回答3: Vue是一种用于构建用户界面的JavaScript框架,Echarts是一个基于JavaScript的开源可视化图表库。将VueEcharts结合使用可以实现大屏数据展示。在这个场景下,使用VueEcharts一起开发150套大屏数据展示意味着可以创建150个不同的大屏页面,并在每个页面上展示不同的数据。 首先,我们可以使用Vue框架创建一个基础的大屏数据展示平台。使用Vue的组件化开发方式,我们可以创建一个通用的大屏模板,包括标题、导航栏、底部等。这个模板可以被复用在所有150个大屏页面上。 接下来,我们可以使用Echarts库来创建各种图表展示数据Echarts提供了多种类型的图表,例如柱状图、折线图、饼图等。根据具体的需求,我们可以在每个大屏页面上使用不同类型的图表展示对应的数据。 为了展示150套不同的数据,我们可以使用Vue的动态路由功能。通过配置不同的路由,每个路由对应一个大屏页面,并指定对应的数据源。这样,用户在访问不同的路由时,就可以看到不同的大屏页面和相应的数据。 在处理数据方面,可以使用Vue提供的数据双向绑定机制。通过与后端服务器进行数据交互,我们可以获取实时或静态的数据,并将其绑定到Echarts图表组件上。这样,当数据发生变化时,图表会自动更新。 另外,为了提高用户体验,可以通过使用Vue的异步组件加载机制,将大屏页面进行按需加载。这样页面加载速度将更快,并且用户只需等待所需页面的加载,而不是等待所有页面同时加载完成。 总而言之,使用VueEcharts可以实现大屏数据展示,并且可以创建150套不同的大屏页面来展示不同的数据。通过灵活运用VueEcharts的功能和特性,我们可以打造出一个功能强大、界面美观的大屏数据展示平台。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力努力再努力_pc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值