初次嘗試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>

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