1.簡介
近期在學習websocket的相關(guān)技術(shù),用于做前后端的數(shù)據(jù)實時交互,結(jié)合網(wǎng)上資料和個人理解,整理了一個小白入門案例,不喜勿噴?。。。?!
1.1 webSocket
- WebSocket是HTML5下一種新的協(xié)議(websocket協(xié)議本質(zhì)上是一個基于tcp的協(xié)議)
- 它實現(xiàn)了瀏覽器與服務器全雙工通信,能更好的節(jié)省服務器資源和帶寬并達到實時通訊的目的
- Websocket是一個持久化的協(xié)議
WebSocket有以下特點:
- 是真正的全雙工方式,建立連接后客戶端與服務器端是完全平等的,可以互相主動請求。而HTTP長連接基于HTTP,是傳統(tǒng)的客戶端對服務器發(fā)起請求的模式。
- HTTP長連接中,每次數(shù)據(jù)交換除了真正的數(shù)據(jù)部分外,服務器和客戶端還要大量交換HTTP header,信息交換效率很低。Websocket協(xié)議通過第一個request建立了TCP連接之后,之后交換的數(shù)據(jù)都不需要發(fā)送 HTTP header就能交換數(shù)據(jù),這顯然和原有的HTTP協(xié)議有區(qū)別所以它需要對服務器和客戶端都進行升級才能實現(xiàn)(主流瀏覽器都已支持HTML5)
2.效果圖展示
圖 1 初始狀態(tài)
圖 2 推送一段時間之后的狀態(tài)?
socket演示
demo中以餅圖和柱形圖為例,結(jié)合websocket,對后端數(shù)據(jù)進行實時推送
3.代碼實現(xiàn)
3.1 前端代碼
該項目為前后端分離項目,前端主要使用Vue2+原生websocket進行頁面搭建和服務端數(shù)據(jù)交互
- 主要代碼實現(xiàn)部分
<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", // 圖例組件離容器上側(cè)的距離。
},
// 工具欄組件
toolbox: {
show: true, // 是否顯示工具欄組件
feature: {
mark: { show: false },
dataView: { show: true, readOnly: false }, // 數(shù)據(jù)視圖工具
restore: { show: true }, // 配置項還原
saveAsImage: { show: true }, // 保存圖片
},
},
series: [
{
name: "Nightingale Chart", // 名稱
type: "pie", // 類型 餅圖
radius: [50, 150], // 餅圖的半徑 `50, 250 => 內(nèi)半徑 外半徑`
center: ["50%", "50%"], // 餅圖的中心(圓心)坐標,數(shù)組的第一項是橫坐標,第二項是縱坐標。
roseType: "area", // 是否展示成南丁格爾圖,通過半徑區(qū)分數(shù)據(jù)大小
// 圖形的顏色
itemStyle: {
borderRadius: 8,
},
// 圖表的數(shù)據(jù)
data: pies.pie,
},
],
};
// 3.5 將配置參數(shù)和圖表關(guān)聯(lián)
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生態(tài)進行服務端的搭建
3.2.1?項目目錄結(jié)構(gòu)
?3.2.2 導入相關(guān)依賴
<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相關(guān)依賴 -->
<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);
/**靜態(tài)變量,用來記錄當前在線連接數(shù)。應該把它設計成線程安全的。*/
private static int onlineCount = 0;
/**concurrent包的線程安全Set,用來存放每個客戶端對應的MyWebSocket對象。*/
private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/**與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)*/
private Session session;
/**接收userId*/
private String userId="";
@Autowired
private ItemService itemService;
/**
* 連接建立成功調(diào)用的方法*/
@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();
//在線數(shù)加1
}
log.info("用戶連接:"+userId+",當前在線人數(shù)為:" + 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+",網(wǎng)絡異常!!!!!!");
}
}
/**
* 連接關(guān)閉調(diào)用的方法
*/
@OnClose
public void onClose() {
if(webSocketMap.containsKey(userId)){
webSocketMap.remove(userId);
//從set中刪除
subOnlineCount();
}
log.info("用戶退出:"+userId+",當前在線人數(shù)為:" + getOnlineCount());
}
/**
* 收到客戶端消息后調(diào)用的方法
*
* @param message 客戶端發(fā)送過來的消息*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用戶消息:"+userId+",報文:"+message);
//可以群發(fā)消息
//消息保存到數(shù)據(jù)庫、redis
if(StringUtils.isNotBlank(message)){
try {
//解析發(fā)送的報文
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();
}
/**
* 實現(xiàn)服務器主動推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 消息單發(fā)
*/
public static void sendOneMessage(String userId,String message) throws IOException {
WebSocketServer webSocketServer = webSocketMap.get(userId);
webSocketServer.session.getBasicRemote().sendText(message);
}
/**
* 實現(xiàn)服務器主動推送
*/
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實現(xiàn)服務器主動推送成功userIds===="+userIds);
}
}
/**
* 發(fā)送自定義消息
* */
public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
log.info("發(fā)送消息到:"+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服務的搭建,上述中設置了單發(fā)和群發(fā)兩種通信方式
3.3.5 測試實體類
?分別創(chuàng)建柱形圖(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從數(shù)據(jù)庫獲取實時數(shù)據(jù)
此處為服務端從數(shù)據(jù)庫中獲取最新數(shù)據(jù)的過程,demo中,并沒有進行查庫操作,而是寫了一個模擬數(shù)據(jù)進行測試,使用static靜態(tài)變量對數(shù)據(jù)進行靜態(tài)初始化,如需操作數(shù)據(jù)庫可以進行數(shù)據(jù)庫連接即可,持久層可使用mybatis或者JPA對數(shù)據(jù)庫進行操作
?初始化完成后,分別獲取餅圖和柱形圖的相關(guān)數(shù)據(jù),通過Math.random()方法對初始數(shù)據(jù)進行加工,使得每次獲取到的數(shù)據(jù)都是不一樣的,從而模擬數(shù)據(jù)的變化更新
@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));
}
//獲取柱形圖數(shù)據(jù)
public Map<String,?> getData(){
//模擬數(shù)據(jù)更新
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;
}
//獲取餅圖數(shù)據(jù)
public Map<String,?> getPieData(){
//模擬數(shù)據(jù)更新
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秒執(zhí)行一次
* @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秒執(zhí)行一次,即@Scheduled(fixedRate = 3 * 1000),同時將每次獲取到的最新數(shù)據(jù)通過websocket向客戶端進行消息推送文章來源:http://www.zghlxwxcb.cn/news/detail-828914.html
至此 ,完成整個項目的搭建,本人也不太會,都是慢慢摸索的,有啥講的不對的地方,歡迎大家批評指正?。。?!如果覺得對您有幫助的話,請不要吝嗇您的點贊+關(guān)注+評論喲?。。?!文章來源地址http://www.zghlxwxcb.cn/news/detail-828914.html
到了這里,關(guān)于Vue2+Echarts+SpringBoot+Websocket+Scheduled實現(xiàn)大屏圖表數(shù)據(jù)實時展示的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!