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向客户端进行消息推送文章来源:https://www.toymoban.com/news/detail-828914.html
至此 ,完成整个项目的搭建,本人也不太会,都是慢慢摸索的,有啥讲的不对的地方,欢迎大家批评指正!!!!如果觉得对您有帮助的话,请不要吝啬您的点赞+关注+评论哟!!!!文章来源地址https://www.toymoban.com/news/detail-828914.html
到了这里,关于Vue2+Echarts+SpringBoot+Websocket+Scheduled实现大屏图表数据实时展示的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!