从0到1全端开发可视化监控系统,Java+OSHI+Vue+ECharts_哔哩哔哩_bilibili
在b站上看了一下尚硅谷的教学视频,保存了一下源码,在百度云(尚硅谷文件夹中)
现在记录一下过程:
运行截图:
使用了jdk17、springboot3.4
maven的设置参考:http://www.haijin.xyz/list/article/291
pom文件:
后端相关代码
R:
package com.atguigu.oshi.common;
import lombok.Data;
@Data //对接前端的R,不用管泛型。对接后端远程调用需要泛型;
public class R {
private Integer code;
private String msg;
private Object data; //json响应出去,要类型有啥用~~~; 序列化
public R(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public R(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
//快速成功
public static R ok() {
return new R(200, "ok");
}
public static R ok(Object data) {
return new R(200, "ok", data);
}
//快速失败
public static R error() {
return new R(500, "error");
}
public static R error(Object data) {
return new R(500, "error", data);
}
public static R error(Integer code, String msg) {
return new R(code, msg);
}
}
CpuLoadRestController:
package com.atguigu.oshi.controller;
import com.atguigu.oshi.common.R;
import com.atguigu.oshi.service.CpuLoadMetricsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@CrossOrigin
@RequestMapping("/metrics")
@RestController
public class CpuLoadRestController {
@Autowired
CpuLoadMetricsService cpuLoadMetricsService;
/**
* {
* code: 200,
* msg: "ok",
* data: null
* }
*/
@GetMapping("/cpuload")
public R getCpuLoad() {
// do something
double[] cpuLoad = cpuLoadMetricsService.getCpuLoad();
return R.ok(cpuLoad);
}
}
CpuLoadMetricsService:
package com.atguigu.oshi.service;
import org.springframework.stereotype.Service;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.HardwareAbstractionLayer;
@Service
public class CpuLoadMetricsService {
//OSHI 提供的获取所有数据的入口
SystemInfo si = new SystemInfo();
public double[] getCpuLoad() {
HardwareAbstractionLayer hardware = si.getHardware();
CentralProcessor processor = hardware.getProcessor();
//统计1s内的使用率,所以阻塞一秒
double[] cpuLoad = processor.getProcessorCpuLoad(1000);
return cpuLoad;
}
}
OshiAppApplication:
package com.atguigu.oshi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OshiAppApplication {
public static void main(String[] args) {
SpringApplication.run(OshiAppApplication.class, args);
}
}
前端相关代码:
package.json:
{
"name": "frontend-app",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@arco-design/web-vue": "^2.56.3",
"axios": "^1.7.8",
"echarts": "^5.5.1",
"pinia": "^2.2.6",
"vue": "^3.5.13",
"vue-router": "^4.4.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"vite": "^6.0.1",
"vite-plugin-vue-devtools": "^7.6.5"
}
}
cpuloadApi.js:
import http from "@/http";
//获取cpu负载数据
const getCpuLoadApi = ()=>{
return http.get("/metrics/cpuload")
}
export {
getCpuLoadApi
}
http/index.js:
// 抽取 axios 发请求的方法
import axios from "axios";
const http = axios.create({
baseURL: 'http://localhost:8080/',
timeout: 3000,
headers: {'X-Custom-Header': 'foobar'}
});
// 添加请求拦截器
http.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
http.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
//原生的响应对象中的 data 才是服务器返回的数据
return response.data; //直接把服务器的数据交给后来的人
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
export default http;
router/index.js:
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/cpu',
name: 'cpu',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/cpu/CpuLoad.vue'),
},
],
})
export default router
stores/counter.js:
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
CpuLoad.vue:
<template>
<a-space wrap>
<!-- 16个 叫 cpu-[1~16] -->
<div
:id="`cpu-${i}`"
style="width: 180px; height: 180px; border: 1px solid black"
v-for="i in 16"
></div>
</a-space>
</template>
<script setup>
import * as echarts from "echarts";
import { onMounted, ref } from "vue";
import {getCpuLoadApi} from "@/api/cpuloadApi"
//保存所有初始化的图表
const chartDom = ref([]);
onMounted(() => {
//页面加载出来,有div dom元素才可以
// drawCpuLoad();
//初始化图表只需要进行一次
initChart();
//以后每次画图
getCpuData();
});
const initChart = ()=>{
for(let i=1;i<=16;i++){
var dom = document.getElementById("cpu-"+i);
//得到一个chart对象
var myChart = echarts.init(dom);
chartDom.value.push(myChart)
}
}
// 16核 cpu 的所有数据。
// 每一核还是一个数组,这个数组中保存的是每秒的数据
const cpuAllData = ref([])
const getCpuData = async ()=>{
//1、拿到服务器真正的响应; 给服务器发送请求获取
let resp = await getCpuLoadApi();
//2、返回的是16核cpu。每个核心当前的负载值
let data = resp.data;
for(let i =0;i<16;i++){
//把当前请求到的这个cpu的使用率放进自己的数组中。第一次要初始化数组
if(!cpuAllData.value[i]){
cpuAllData.value[i] = [];
}
if(cpuAllData.value[i].length > 60){
//把最老的一个数据删除,放入最新获取的这个数据
let arr = cpuAllData.value[i].slice(-60); //移除了最前一个元素的数组
arr.push(data[i])
cpuAllData.value[i] = arr;
}else{
//每次给里面放之前最数组的长度进行一个判断
cpuAllData.value[i].push(data[i])
}
//这里会 OOM;这个数组最多放 60个? 超过 60个删除最老的
drawCpuLoad(i+1,cpuAllData.value[i])
}
await getCpuData();
}
//1、每个图显示CPU名
//2、每个图xy轴不显示
//3、显示为面积图
const drawCpuLoad = (cpuIndex,cpuData) => {
//得到一个chart对象
var myChart = chartDom.value[cpuIndex-1]
var option;
// textStyle. fontSize
option = {
title: {text: 'CPU'+cpuIndex, textStyle: {fontSize: 14}},
grid: {left: "0", right: "0", bottom: "5", top: "0"},
xAxis: {
show: false,
type: "category"
},
yAxis: {
show: false,
type: "value",
min: 0,
max: 1
},
series: [
{
data: cpuData,
type: "line",
symbol: "none",
smooth: true,
areaStyle: {}
},
],
};
myChart.setOption(option);
};
</script>
<style scoped></style>
App.vue:
<script setup>
import NavBar from './views/NavBar.vue';
</script>
<template>
<div class="layout-demo">
<a-layout style="height: 100vh">
<a-layout-header>
<NavBar/>
</a-layout-header>
<a-layout-content>
<RouterView/>
</a-layout-content>
<a-layout-footer>Footer</a-layout-footer>
</a-layout>
</div>
</template>
<style scoped>
.layout-demo :deep(.arco-layout-header),
.layout-demo :deep(.arco-layout-footer),
.layout-demo :deep(.arco-layout-sider-children),
.layout-demo :deep(.arco-layout-content) {
display: flex;
flex-direction: column;
justify-content: center;
font-size: 16px;
font-stretch: condensed;
text-align: center;
}
.layout-demo :deep(.arco-layout-header),
.layout-demo :deep(.arco-layout-footer) {
height: 64px;
}
.layout-demo :deep(.arco-layout-sider) {
width: 206px;
}
</style>
main.js:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import ArcoVue from '@arco-design/web-vue';
import '@arco-design/web-vue/dist/arco.css';
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(ArcoVue);
app.use(router)
app.mount('#app')
vite.config.js:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})
jsconfig.json:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}