初次尝试SpringBoot+vue-baidu-map+WebSocket

因为项目需求,后台与前端需要保持长期的连接,后台可以主动地向前端发送数据。因为WebSocket一直很热门,因此趁此机会学习一下。

一、后端代码

(1)添加依赖

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

(2)WebSocketConfig.java

我看到别人关于这个类的解释是:注入ServerEndpointExporter,这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint

package com.example.demo;

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

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

(3)WebSocket操作类

在这个类中写WebSocket连接、断开、发送数据等操作。
@ServerEndpoint(value = “/websocket/{sessionId}”)中的URL为前端进行WebSocket连接时会用到的URL。

package com.example.demo;

import org.springframework.stereotype.Component;

import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;

//@ServerEndpoint(value = "/websocket")
@ServerEndpoint(value = "/websocket/{sessionId}")
@Component
public class CustomWebSocket {

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;

    //concurrent包的线程安全Set,用来存放每个客户端对应的CumWebSocket对象。
    private static CopyOnWriteArraySet<CustomWebSocket> webSocketSet = new CopyOnWriteArraySet<>();

    //存储sessionId
    private static Map<String, Session> sessionPool = new HashMap<>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    //连接建立成功调用的方法
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "sessionId")String sessionId){
        this.session = session;

        webSocketSet.add(this);
        sessionPool.put(sessionId,session);

        //添加在线人数
        addOnlineCount();
        System.out.println("新连接接入。当前在线人数为:" + getOnlineCount());
    }

    //连接关闭调用的方法
    @OnClose
    public void onClose() {
        //从set中删除
        webSocketSet.remove(this);
        //在线数减1
        subOnlineCount();
        System.out.println("有连接关闭。当前在线人数为:" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用
     *
     * @param message
     *
     */
    @OnMessage
    public void onMessage(String message) {
        System.out.println("客户端发送的消息:" + message);
    }

    // 此为广播消息
    public void sendAllMessage(String message) {
        for(CustomWebSocket webSocket : webSocketSet) {
            System.out.println("【websocket消息】广播消息:"+message);
            try {
                webSocket.session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 此为单点消息
    public void sendOneMessage(String sessionId, String message) {
        Session session = sessionPool.get(sessionId);
        if (session != null) {
            try {
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 减少在线人数
     */
    private void subOnlineCount() {
        CustomWebSocket.onlineCount--;
    }

    /**
     * 添加在线人数
     */
    private void addOnlineCount() {
        CustomWebSocket.onlineCount++;
    }

    /**
     * 当前在线人数
     *
     * @return
     */
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }


}

(4)Controller

给不同的Vue组件发送不同的消息,这里只是模拟数据,数据写成静态的:

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Random;

@RestController
@RequestMapping("/ws")
public class WebSocketController {
    @Autowired
    private CustomWebSocket webSocket;

    @RequestMapping("/sendLocationData")
    public String testLocationData() throws Exception{

        Random random = new Random();

        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        int i = random.nextInt(10);
                        Location location = new Location(39.930 + i,119.404);
                        //webSocket.sendAllMessage(JsonUtils.objectToJson(location));
                        webSocket.sendOneMessage("location",JsonUtils.objectToJson(location));

                        sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        return "websocket群体发送LocationData!";
    }

    @RequestMapping("/sendCarData")
    public String testCarData() throws Exception{
        CarData carData = new CarData(26,0.0001,2.344444,0.56);
        //webSocket.sendAllMessage(JsonUtils.objectToJson(carData));
        webSocket.sendOneMessage("carData",JsonUtils.objectToJson(carData));
        return "websocket群体发送CarData!";
    }
}

二、前端代码

(1)Map.vue

该组件的功能是接收后端发送的座标参数,在地图上绘制出一条轨迹。

<template>
  <div id="container">
    <baidu-map class="map"
               :center="{lng: 116.404, lat: 39.915}"
               :zoom="15"
               :scroll-wheel-zoom="true" >
               
      <bm-polyline
              stroke-color="blue"
              :stroke-opacity="0.5"
              :stroke-weight="12"
              :path="path">
      </bm-polyline>

    </baidu-map>
  </div>
</template>

<script>
  import axios from 'axios';

  export default {
    name: "Map",
 
    data() {
      return {
     
        path: [

            //如果初始为空,好像数组里面添加进去也不描绘轨迹
          {lng: 116.404, lat: 39.915},
          {lng: 117.405, lat: 39.920},
          {lng: 118.423493, lat: 39.907445}
        ],
        websocket: null
      }
    },
    destroyed() {
      this.websocket.close()
    },
    methods: {

      initWebSocket() {
        if(typeof (WebSocket) == 'undefined'){
          alert('不支持websocket')
          return false
        }

        const wsuri = 'ws://localhost:8080/websocket/location'
        this.websocket = new WebSocket(wsuri)

        this.websocket.onopen = this.websocketonopen
        this.websocket.onmessage = this.websocketonmessage
        this.websocket.onerror = this.websocketonerror
        this.websocket.onclose = this.websocketclose
      },
      //连接成功
      websocketonopen(){
        console.log('WebSocket连接成功')
      },
      //接收数据
      websocketonmessage(e) {
        console.log('array数组:')
        console.log(this.path)
        console.log("message-----------")
       // console.log(e)

        //收到的数据:
        // {
        //   "lat" : "40.7838351",
        //     "lng" : "-74.6143763"
        // }
        console.log(e.data)
        var obj = JSON.parse(e.data)

        //转换后的对象:
        //{lat: "40.7838351", lng: "-74.6143763"}
        console.log(obj)
        this.path.push(obj)

        console.log('增加后的array数组:')
        console.log(this.path)
      },
      //连接建立失败重连
      websocketonerror(e){
        console.log(`连接失败的信息:`, e)
        this.initWebSocket() // 连接失败后尝试重新连接
      },
      //关闭连接
      websocketclose(e){
        console.log('断开连接',e)
      },
    created() {
      //暂时屏蔽
     // this.connect()

      //测试websocket
      this.initWebSocket()
    }
  }
</script>

<style scoped>
  /*#map {*/
  /*  width: 100%;*/
  /*  height: 90%;*/
  /*}*/

  .map {
    width: 100%;
    height: 850px;
  }
</style>

(2)Car.vue

该组件的功能是接收后端发送的传感器数据并显示。

<template>
  <div id="container">

    <div id="data-card">
      <el-row :gutter="20">

        <el-col :span="6">
          <div class="grid-content bg-purple">
            <el-card :body-style="{ padding: '0px' }">
              <div class="weather-pic">
                <img src="../assets/xiugaihou/weather.jpg" class="image">
              </div>
              <div style="padding: 14px;">
                <span class="weather-data">当前温度:</span>
                <span class="data">{{weatherData}}</span>
                <span class="data"></span>
                <div class="bottom clearfix">
                </div>
              </div>
            </el-card>
          </div>
        </el-col>

        <el-col :span="6">
          <div class="grid-content bg-purple">
            <el-card :body-style="{ padding: '0px' }">
              <div >
                <img src="../assets/xiugaihou/rainy.jpg" class="rainy-image">
              </div>
              <div style="padding: 14px;">
                <span class="weather-data">当前湿度:</span>
                <span class="data">{{moistData}}</span>
                <span class="data">hPa</span>
                <div class="bottom clearfix">
                </div>
              </div>
            </el-card>
          </div>
        </el-col>

        <el-col :span="6">
          <div class="grid-content bg-purple">
            <el-card :body-style="{ padding: '0px' }">
              <img src="../assets/xiugaihou/smoke.jpg" class="image">
              <div style="padding: 14px;">
                <span class="weather-data">当前烟雾浓度:</span>
                <span class="data">{{smokeData}}</span>
                <span class="data">ppm</span>
                <div class="bottom clearfix">
                </div>
              </div>
            </el-card>
          </div>
        </el-col>

        <el-col :span="6">
          <div class="grid-content bg-purple">
            <el-card :body-style="{ padding: '0px' }">
              <img src="../assets/xiugaihou/wine.jpg" class="image">
              <div style="padding: 14px;">
                <span class="weather-data">当前酒精浓度:</span>
                <span class="data">{{alcoholData}}</span>
                <span class="data">%</span>
                <div class="bottom clearfix">
                </div>
              </div>
            </el-card>
          </div>
        </el-col>
      </el-row>
    </div>


    <el-divider><i class="el-icon-s-promotion"></i></el-divider>

    <div id="data-table">
      <span>现在是北京时间: {{ currentDate }}</span>
      <el-divider></el-divider>
      <span>当前车上共有: {{ peopleNum }}名乘客</span>
      <el-divider></el-divider>
      <span>您的驾驶时长:{{ driveTime }}</span>
    </div>

  </div>
</template>

<script>
  export default {
    name: "Car",
    data() {
      return {
        currentDate: new Date(),
        peopleNum: 0,
        driveTime: 0,
        weatherData: 0,
        moistData: 0,
        smokeData: 0,
        alcoholData: 0,

        websocket: null
      };
    },
    methods: {

      initWebSocket() {
        if(typeof (WebSocket) == 'undefined'){
          alert('不支持websocket')
          return false
        }

        const wsuri = 'ws://localhost:8080/websocket/carData'
        this.websocket = new WebSocket(wsuri)

        this.websocket.onopen = this.websocketonopen
        this.websocket.onmessage = this.websocketonmessage
        this.websocket.onerror = this.websocketonerror
        this.websocket.onclose = this.websocketclose
      },
      //连接成功
      websocketonopen(){
        console.log('WebSocket连接成功')
      },
      //接收数据
      websocketonmessage(e) {
        console.log(e.data)
        var obj = JSON.parse(e.data)

        //转换后的对象:
        //{weatherData: 26, moistData: 0.0001, smokeData: 2.344444, alcoholData: 0.56}
        console.log(obj)

        //赋值
        this.weatherData = obj.weatherData;
        this.moistData = obj.moistData
        this.smokeData = obj.smokeData
        this.alcoholData = obj.alcoholData

      },
      //连接建立失败重连
      websocketonerror(e){
        console.log(`连接失败的信息:`, e)
        this.initWebSocket() // 连接失败后尝试重新连接
      },
      //关闭连接
      websocketclose(e){
        console.log('断开连接',e)
      },
      getCarData() {

      }
    },
    created() {
      this.initWebSocket()
    },
    destroyed() {
      this.websocket.close()
    }
  }
</script>

<style scoped>
  /*布局属性*/
  .el-row {
    margin-bottom: 20px;
  &:last-child {
     margin-bottom: 0;
   }
  }
  .el-col {
    border-radius: 4px;
  }
  .bg-purple-dark {
    background: #99a9bf;
  }
  .bg-purple {
    background: #d3dce6;
  }
  .bg-purple-light {
    background: #e5e9f2;
  }
  .grid-content {
    border-radius: 4px;
    min-height: 36px;
  }
  .row-bg {
    padding: 10px 0;
    background-color: #f9fafc;
  }

  /*卡片属性*/
  #data-card {
    padding-bottom: 35px;
  }
  .weather-data {
    font-size: 13px;
    color: #999;
  }

  .bottom {
    margin-top: 13px;
    line-height: 12px;
  }

  .button {
    padding: 0;
    float: right;
  }

  .image {
    width: 100%;
    height: 100%;
    /*display: block;*/
    object-fit: fill;
  }

  .weather-pic {
    /*padding-top: 10px;*/
  }

  .rainy-pic {
    margin-bottom: 10px;
  }

  .rainy-image{
    width: 100%;
    height: 100%;
    /*display: block;*/
    object-fit: fill;
    /*padding-bottom: 5px;*/
  }

  .data {
    font-size: 26px;
    color: #7FA0A7;
  }
  .clearfix:before,
  .clearfix:after {
    display: table;
    content: "";
  }

  .clearfix:after {
    clear: both
  }
</style>

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