基于Vue3+Node.js实现实时可视化监控系统

 更新时间:2026年02月02日 10:00:22   作者:Anthony_hui  
在日常运维和开发工作中,服务器监控是必不可少的环节,市面上有不少优秀的监控方案,但对于中小型团队或个人开发者来说,这些工具往往过于复杂,学习成本较高,本文将介绍我自己开发的 ServWatch 监控系统——一个轻量级、易部署、界面美观的实时监控解决方案

前言

在日常运维和开发工作中,服务器监控是必不可少的环节。市面上有不少优秀的监控方案(如 Prometheus、Grafana、Zabbix 等),但对于中小型团队或个人开发者来说,这些工具往往过于复杂,学习成本较高。

本文将介绍我自己开发的 ServWatch 监控系统——一个轻量级、易部署、界面美观的实时监控解决方案。

一、系统架构

1.1 整体架构图

┌─────────────────────────────────────────────────────────────────┐
│                         ServWatch 系统架构                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌──────────────┐      ┌──────────────┐      ┌──────────────┐  │
│  │              │      │              │      │              │  │
│  │   Browser    │◄────►│  Frontend    │◄────►│   Backend    │  │
│  │              │ WS   │  Vue 3 +     │ HTTP │  Node.js     │  │
│  │              │      │  ECharts     │      │  Express     │  │
│  └──────────────┘      └──────────────┘      └──────┬───────┘  │
│                                                      │          │
│                                              ┌───────▼───────┐  │
│  ┌──────────────┐      ┌──────────────┐      │   Postgres   │  │
│  │              │      │              │      │  TimescaleDB │  │
│  │    Agent     │─────►│   Redis      │─────►│              │  │
│  │   采集器     │ HTTP │   缓存        │      │   时序数据   │  │
│  │              │      │              │      │              │  │
│  └──────────────┘      └──────────────┘      └──────────────┘  │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

1.2 技术栈选型

层级技术选型理由
前端框架Vue 3Composition API 开发体验好
构建工具Vite开发速度快,HMR 体验优秀
状态管理PiniaVue 3 官方推荐,API 简洁
UI 组件Element Plus组件丰富,文档完善
图表库Apache ECharts图表功能强大,交互性好
后端框架Express轻量灵活,中间件丰富
实时通信Socket.IOWebSocket 封装,兼容性好
数据库PostgreSQL + TimescaleDB关系型 + 时序数据扩展
缓存Redis高性能 KV 存储

二、核心功能实现

2.1 实时监控仪表板

仪表板是监控系统的核心界面,展示所有监控目标的整体状态。

WebSocket 实时推送实现:

// 前端:建立 WebSocket 连接
import { io } from 'socket.io-client'

const socket = io('http://localhost:3001', {
  auth: { token: localStorage.getItem('token') }
})

// 注册为仪表板客户端
socket.emit('dashboard:connect')

// 订阅实时指标
socket.emit('metrics:subscribe', { targetIds: ['1', '2', '3'] })

// 接收实时更新
socket.on('metrics:update', (data) => {
  console.log('实时指标:', data)
  // 更新图表数据
})
// 后端:WebSocket 服务
class WebSocketService {
  constructor(io) {
    this.io = io
    this.dashboardClients = new Set()
    this.setupEventHandlers()
  }

  setupEventHandlers() {
    this.io.on('connection', (socket) => {
      socket.on('dashboard:connect', () => {
        this.dashboardClients.add(socket.id)
        socket.emit('dashboard:connected', { clientId: socket.id })
      })

      socket.on('metrics:subscribe', ({ targetIds }) => {
        socket.join(`metrics:${targetIds.join(',')}`)
      })

      socket.on('disconnect', () => {
        this.dashboardClients.delete(socket.id)
      })
    })
  }

  // 推送实时指标到所有仪表板客户端
  broadcastMetrics(targetId, metrics) {
    this.io.emit('metrics:update', {
      targetId,
      metrics,
      timestamp: new Date().toISOString()
    })
  }

  // 推送告警通知
  broadcastAlert(alert, value) {
    this.io.emit('alert:triggered', {
      alert,
      value,
      timestamp: new Date().toISOString()
    })
  }
}

2.2 监控数据可视化

使用 ECharts 实现实时折线图:

<template>
  <div ref="chartRef" style="width: 100%; height: 300px"></div>
</template>

<script setup>
import { ref, onMounted, watch } from 'vue'
import * as echarts from 'echarts'

const chartRef = ref(null)
let chart = null

const initChart = () => {
  chart = echarts.init(chartRef.value)
  chart.setOption({
    title: { text: 'CPU 使用率' },
    tooltip: { trigger: 'axis' },
    xAxis: {
      type: 'category',
      data: []
    },
    yAxis: {
      type: 'value',
      max: 100,
      axisLabel: { formatter: '{value}%' }
    },
    series: [{
      name: 'CPU',
      type: 'line',
      smooth: true,
      data: [],
      areaStyle: {
        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          { offset: 0, color: 'rgba(64, 158, 255, 0.5)' },
          { offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
        ])
      }
    }]
  })
}

// 实时更新图表数据
const updateChart = (timestamp, value) => {
  const option = chart.getOption()
  option.xAxis[0].data.push(timestamp)
  option.series[0].data.push(value)

  // 保持最近 60 个数据点
  if (option.xAxis[0].data.length > 60) {
    option.xAxis[0].data.shift()
    option.series[0].data.shift()
  }

  chart.setOption(option)
}

onMounted(() => {
  initChart()

  // 监听 WebSocket 数据更新
  socket.on('metrics:update', ({ metrics }) => {
    updateChart(new Date().toLocaleTimeString(), metrics.cpu)
  })
})
</script>

2.3 告警规则引擎

告警评估服务实现:

class AlertService {
  constructor() {
    this.alertRules = new Map()
    this.alertHistory = []
    this.cooldowns = new Map()
  }

  // 添加告警规则
  addRule(rule) {
    this.alertRules.set(rule.id, rule)
  }

  // 评估指标是否触发告警
  async evaluate(targetId, metricType, value) {
    const rules = Array.from(this.alertRules.values())
      .filter(r => r.targetId === targetId && r.metricType === metricType && r.enabled)

    for (const rule of rules) {
      const shouldAlert = this.checkCondition(value, rule)

      if (shouldAlert && !this.isInCooldown(rule.id)) {
        await this.triggerAlert(rule, value)
        this.setCooldown(rule.id, rule.cooldown || 300)
      }
    }
  }

  // 检查条件
  checkCondition(value, rule) {
    switch (rule.condition) {
      case 'greater_than':
        return value > rule.threshold
      case 'less_than':
        return value < rule.threshold
      case 'equals':
        return value === rule.threshold
      default:
        return false
    }
  }

  // 触发告警
  async triggerAlert(rule, value) {
    const alertHistory = {
      id: generateId(),
      alertId: rule.id,
      alertName: rule.name,
      severity: rule.severity,
      status: 'active',
      message: `${rule.name}触发: 当前值 ${value}, 阈值 ${rule.threshold}`,
      value,
      threshold: rule.threshold,
      createdAt: new Date().toISOString()
    }

    this.alertHistory.push(alertHistory)
    rule.triggerCount++

    // 通知 WebSocket 客户端
    websocketService.broadcastAlert(rule, value)

    // TODO: 发送邮件/钉钉通知
  }

  isInCooldown(ruleId) {
    const cooldownEnd = this.cooldowns.get(ruleId)
    return cooldownEnd && Date.now() < cooldownEnd
  }

  setCooldown(ruleId, seconds) {
    this.cooldowns.set(ruleId, Date.now() + seconds * 1000)
  }
}

2.4 指标采集 Agent

Agent 负责在被监控服务器上采集系统指标:

const os = require('os')
const axios = require('axios')

class SystemCollector {
  constructor(config) {
    this.interval = config.collectInterval || 1000
  }

  // 采集 CPU 指标
  collectCPU() {
    const cpus = os.cpus()
    const totalIdle = cpus.reduce((acc, cpu) => acc + cpu.times.idle, 0)
    const totalTick = cpus.reduce((acc, cpu) => {
      return acc + Object.values(cpu.times).reduce((a, b) => a + b, 0)
    }, 0)

    return {
      usage: ((1 - totalIdle / totalTick) * 100).toFixed(2),
      cores: cpus.map(cpu => ({
        usage: ((1 - cpu.times.idle / Object.values(cpu.times).reduce((a, b) => a + b, 0)) * 100).toFixed(2)
      }))
    }
  }

  // 采集内存指标
  collectMemory() {
    const total = os.totalmem()
    const free = os.freemem()
    const used = total - free

    return {
      total: (total / 1024 / 1024 / 1024).toFixed(2), // GB
      used: (used / 1024 / 1024 / 1024).toFixed(2),
      free: (free / 1024 / 1024 / 1024).toFixed(2),
      usage: ((used / total) * 100).toFixed(2)
    }
  }

  // 采集网络指标
  async collectNetwork() {
    const stats = await getNetworkStats()
    return {
      rx: stats.rx, // bytes/s
      tx: stats.tx
    }
  }

  // 采集所有指标
  async collectAll() {
    return {
      cpu: this.collectCPU(),
      memory: this.collectMemory(),
      network: await this.collectNetwork(),
      disk: await this.collectDisk(),
      timestamp: new Date().toISOString()
    }
  }
}

// 定时采集并发送
const collector = new SystemCollector(config)
const transmitter = new HttpTransmitter(config.serverUrl)

setInterval(async () => {
  const metrics = await collector.collectAll()
  await transmitter.send(metrics)
}, collector.interval)

三、API 设计

3.1 RESTful API

分类端点方法说明
认证/auth/loginPOST用户登录
认证/auth/registerPOST用户注册
认证/auth/refreshPOST刷新 Token
目标/targetsGET获取所有监控目标
目标/targetsPOST创建监控目标
目标/targets/:idPUT更新监控目标
告警/alertsGET获取告警规则
告警/alertsPOST创建告警规则
指标/metrics/realtimeGET获取实时指标
指标/metrics/aggregatedGET获取聚合指标
统计/stats/overviewGET系统概览统计

3.2 认证机制

使用 JWT Bearer Token 认证:

// 登录获取 Token
POST /auth/login
{
  "identifier": "admin",
  "password": "admin123"
}

// 响应
{
  "user": { "id": "1", "username": "admin", ... },
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

// 使用 Token 访问 API
GET /targets
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

四、快速部署

4.1 模拟模式(最快体验)

无需后端,直接体验前端界面:

cd frontend
npm install
npm run dev -- --port 5175

# 访问 http://localhost:5175
# 登录账号: admin / admin123

4.2 Docker Compose 部署

一键启动所有服务:

docker-compose -f docker/docker-compose.yml up -d

# 服务列表:
# - Frontend:  http://localhost:5173
# - Backend:   http://localhost:3001
# - PostgreSQL: localhost:5432
# - Redis:     localhost:6379

4.3 本地开发部署

# 1. 启动数据库
docker run -d --name servwatch-postgres \
  -e POSTGRES_DB=servwatch \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres \
  -p 5432:5432 \
  timescale/timescaledb:latest-pg16

# 2. 启动后端
cd backend
npm install
npm run dev

# 3. 启动前端
cd frontend
cp .env.production .env
npm install
npm run dev -- --port 5175

# 4. (可选) 启动 Agent
cd agent
npm install
npm start

五、项目结构

ServWatch/
├── backend/                    # Node.js 后端服务
│   ├── src/
│   │   ├── config/            # 配置管理
│   │   ├── controllers/       # 请求处理器
│   │   ├── services/          # 业务逻辑
│   │   │   ├── websocketService.js   # WebSocket连接管理
│   │   │   ├── alertService.js       # 告警评估通知
│   │   │   └── metricsService.js     # 指标聚合处理
│   │   ├── models/            # 数据模型
│   │   ├── routes/            # API路由
│   │   ├── websocket/         # WebSocket处理器
│   │   └── app.js
│   └── package.json
│
├── agent/                      # 指标采集代理
│   ├── src/
│   │   ├── collectors/        # 采集器
│   │   │   ├── systemCollector.js   # 系统指标
│   │   │   ├── appCollector.js      # 应用指标
│   │   │   └── apiCollector.js      # API性能
│   │   ├── transmitters/      # 数据传输
│   │   └── agent.js
│   └── package.json
│
├── frontend/                   # Vue.js 前端
│   ├── src/
│   │   ├── components/        # Vue组件
│   │   ├── composables/       # 组合式函数
│   │   ├── stores/            # Pinia状态管理
│   │   ├── services/          # API服务
│   │   └── views/             # 页面视图
│   └── package.json
│
└── docker/                     # Docker 配置
    └── docker-compose.yml

六、界面预览

6.1 登录页面

6.2 GPU 监控

6.3 监控目标管理

七、后续规划

  • 基础框架搭建
  • 系统指标采集
  • 应用性能监控
  • 告警系统
  • WebSocket 实时通信
  • Docker 容器化
  • 邮件/钉钉通知功能
  • 自定义仪表板
  • 数据导出报表
  • 多租户支持
  • 移动端适配

八、总结

ServWatch 是一个功能完整、易于部署的监控系统。相比 Prometheus+Grafana 组合,它更加轻量,学习成本更低,适合中小型团队和个人开发者使用。

以上就是基于Vue3+Node.js实现实时可视化监控系统的详细内容,更多关于Vue3 Node.js实时可视化监控的资料请关注脚本之家其它相关文章!

相关文章

  • 利用Vue的v-for和v-bind实现列表颜色切换

    利用Vue的v-for和v-bind实现列表颜色切换

    这篇文章主要介绍了利用Vue的v-for和v-bind实现列表颜色切换,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-07-07
  • vue3中provide和inject的使用

    vue3中provide和inject的使用

    provide和inject在Vue 2中已经被广泛应用,不是新鲜API,3.0重新认识一下它们两个,本文重点给大家介绍vue3中provide和inject的使用,需要的朋友参考下吧
    2021-07-07
  • vue3.x ref()语法糖赋值方式

    vue3.x ref()语法糖赋值方式

    这篇文章主要介绍了vue3.x ref()语法糖赋值方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • vue cli 3.0 搭建项目的图文教程

    vue cli 3.0 搭建项目的图文教程

    这篇文章主要介绍了vue cli 3.0 搭建项目的图文教程,本文通过图片文字相结合的形式给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-05-05
  • Vue3动态使用KeepAlive组件的实现步骤

    Vue3动态使用KeepAlive组件的实现步骤

    在 Vue 3 项目中,我们有时需要根据路由的 meta 信息来动态决定是否使用 KeepAlive 组件,以控制组件的缓存行为,所以本文给大家介绍了Vue3动态使用KeepAlive组件的实现步骤,通过代码示例讲解的非常详细,需要的朋友可以参考下
    2024-11-11
  • Vue3监听reactive对象中属性变化的方法

    Vue3监听reactive对象中属性变化的方法

    在 Vue 3 中,如果你想监听 reactive 对象中的某个属性发生的变化,你可以使用 watch 函数进行监听,watch 函数允许你观察 reactive 对象的某个属性或者整个对象,所以本文给大家介绍了Vue3监听reactive对象中属性变化的方法,需要的朋友可以参考下
    2024-08-08
  • Vue 第三方字体图标引入 Font Awesome的方法

    Vue 第三方字体图标引入 Font Awesome的方法

    今天小编就为大家分享一篇Vue 第三方字体图标引入 Font Awesome的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-09-09
  • 解决在vue项目中webpack打包后字体不生效的问题

    解决在vue项目中webpack打包后字体不生效的问题

    今天小编就为大家分享一篇解决在vue项目中webpack打包后字体不生效的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-09-09
  • VUE 实现复制内容到剪贴板的两种方法

    VUE 实现复制内容到剪贴板的两种方法

    这篇文章主要介绍了VUE 实现复制内容到剪贴板功能,本文通过两种方法,给大家介绍的非常详细,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2019-04-04
  • Vue源码分析之虚拟DOM详解

    Vue源码分析之虚拟DOM详解

    所谓虚拟DOM就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有 10 次更新 这篇文章主要给大家介绍了关于Vue源码分析之虚拟DOM的相关资料,需要的朋友可以参考下
    2021-05-05

最新评论