从0到1全端开发可视化监控系统,Java+OSHI+Vue+ECharts

我爱海鲸 2024-12-05 22:42:08 暂无标签

简介jdk17、maven3.9.9、尚硅谷

从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"]
}

 

你好:我的2025