ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从Zabbix到ECharts:C#构建企业级大屏监控系统实战

从Zabbix到ECharts:C#构建企业级大屏监控系统实战 简介基于Grafana与Zabbix的ECharts大屏监控系统源码面向运维监控及可视化开发工程师可用于快速搭建企业级监控数据大屏解决Zabbix监控数据接入与ECharts图表展示问题。压缩包共158个文件大小13.3MB以C#服务端逻辑、cshtml页面模板、JavaScript脚本、JSON配置、CSS样式及PNG图标素材为主覆盖后端控制器、权限认证、前端图表渲染、样式布局等方面。源码内置账户管理、Zabbix数据提供、图表展示、权限过滤等典型模块提供完整的ASP.NET Core工程文件、SQL数据库脚本及发布配置并包含用户信息提供器、权限拦截器等可复用组件。压缩包内目录按功能分层组织工程结构清晰便于按模块检索与二次开发。目前已有48人学习/下载适合需要掌握监控大屏开发全流程的技术人员参考。1. Grafana、Zabbix 与 ECharts 的大屏监控这套压缩包里到底装了什么很多人一听说大屏监控第一反应是下载 Grafana 现成的 dashboard JSON。可真把 Zabbix 的监控数据接进去再投到一块 ECharts 大屏上中间全是手工活API 鉴权、指标清洗、图表接口拆分任何一环断了大屏不是黑屏就是数据错位。这套基于 Grafana、Zabbix 与 ECharts 的大屏监控系统压缩包走的正是另一条路——不依赖 Grafana 模板生态而是用 C# 后端打通 Zabbix API把 CPU、内存、磁盘等监控指标封装成 ECharts 可直接消费的 JSON 接口。解压后是一组典型的 ASP.NET Core 文件ZabbixProvider.cs 负责与 Zabbix API 交互ZabbixController.cs 和 ChartsController.cs 提供原始数据与图表数据两组接口AccountController.cs、UserProvider.cs 和两个权限类控制登录与访问。Project 里还有 Startup.cs 做中间件注册AccountViewModel.cs 绑定登录参数。适合已有 Zabbix 环境、想快速搭内部运维大屏的开发者和运维工程师。2. Zabbix 数据接入层ZabbixProvider 封装与监控项字段设计2.1 为什么选 Zabbix API 而不是直连数据库Zabbix 监控数据最终落在 MySQL 或 PostgreSQL 里但直接连数据库读取风险很大数据库密码落在大屏服务器上、表结构随版本升级变化、连接数被报表任务拖垮。Zabbix 官方 API 是目前唯一值得信任的外部接入方式认证通过 JSON-RPC 完成先调用 user.login 获取 auth token之后所有请求都带这个 token。这套项目里 ZabbixProvider.cs 承担的就是这件事。常见做法是在 Provider 内部缓存 token而不是每次请求都重新登录。Zabbix 默认会话有效期一般是 15 分钟到 1 小时取决于服务端配置如果每个图表接口都 login 一次不仅慢还可能在并发时把会话挤下线。我习惯在 Provider 里加两个字段_authToken和_tokenExpireAt在有效期内复用 token过期前 5 分钟重登。2.2 ZabbixProvider 的核心方法登录、取主机、取监控项、取历史数据下面这段是这套系统里最核心的调用骨架按 Zabbix API 的四个常用方法拆开public class ZabbixProvider { private readonly HttpClient _http; private readonly IConfiguration _config; private string _authToken ; private DateTime _tokenExpireAt DateTime.MinValue; public ZabbixProvider(HttpClient http, IConfiguration config) { _http http; _config config; } // 登录并缓存 tokenZabbix API 是 JSON-RPC 2.0 协议 private async Taskstring GetTokenAsync() { if (!string.IsNullOrEmpty(_authToken) DateTime.Now _tokenExpireAt.AddMinutes(-5)) { return _authToken; } var body new { jsonrpc 2.0, method user.login, params new { username _config[Zabbix:User], password _config[Zabbix:Password] }, id 1 }; var resp await _http.PostAsJsonAsync(_config[Zabbix:Url], body); var obj await resp.Content.ReadFromJsonAsyncZabbixApiResult(); _authToken obj.result; _tokenExpireAt DateTime.Now.AddMinutes(30); return _authToken; } // 取主机列表大屏的左上角一般就是这个列表 public async TaskIReadOnlyListZabbixHostDto GetHostsAsync() { var token await GetTokenAsync(); var body new { jsonrpc 2.0, method host.get, params new { output new[] { hostid, host, name, status }, selectInterfaces new[] { ip } }, auth token, id 2 }; var resp await _http.PostAsJsonAsync(_config[Zabbix:Url], body); var obj await resp.Content.ReadFromJsonAsyncZabbixApiResult(); return obj.result.ToObjectIReadOnlyListZabbixHostDto(); } // 取监控项Zabbix 中 itemid 是后续查历史数据的钥匙 public async TaskIReadOnlyListZabbixItemDto GetItemsAsync(string hostId, string[] itemKeys) { var token await GetTokenAsync(); var body new { jsonrpc 2.0, method item.get, params new { hostids hostId, search new { key_ string.Join(, itemKeys.Select(k $({k}))) }, output new[] { itemid, name, key_, lastvalue, units } }, auth token, id 3 }; var resp await _http.PostAsJsonAsync(_config[Zabbix:Url], body); var obj await resp.Content.ReadFromJsonAsyncZabbixApiResult(); // 这里要自己把 result 反序列化成 DTO 列表 return obj.result.ToObjectIReadOnlyListZabbixItemDto(); } }这个骨架里有几个参数值得注意。id字段只要保证同一个会话内自增即可不同会话允许复用别把它当成自增主键来设计。search会对 key 字段做模糊匹配所以如果你要精确取system.cpu.util[,idle]最好先用item.get的filter精确过滤或者取回来后再在内存里按 key 匹配一次避免把所有带 cpu 字样的监控项都捞出来。output数组只取需要的字段能显著降低响应体大小。Zabbix 服务端对大屏这种轮询客户端并不友好接口尽量轻量。2.3 把监控项映射成图表需要的字段Zabbix 的监控项 key 是模板里定义的字符串直接返回给 ECharts 并不合适。比较常见的做法是 ZabbixController 做一个简单的字段清洗层[ApiController] [Route(api/zabbix)] public class ZabbixController : ControllerBase { private readonly ZabbixProvider _provider; public ZabbixController(ZabbixProvider provider) { _provider provider; } [HttpGet(hosts)] public async TaskIActionResult Hosts() { var hosts await _provider.GetHostsAsync(); var view hosts.Select(h new { id h.hostid, name h.name, ip h.interfaces.FirstOrDefault()?.ip }); return Ok(new { code 0, data view }); } [HttpGet(metrics)] public async TaskIActionResult Metrics(string hostId) { var keys new[] { system.cpu.util, vm.memory.size, vfs.fs.size, net.if.in, net.tcp.port }; var items await _provider.GetItemsAsync(hostId, keys); var view items.Select(i new { key i.key_.Replace(,, _), value i.lastvalue, units i.units }); return Ok(new { code 0, data view }); } }这里把net.tcp.port[8080]这类 key 替换成net.tcp.port_8080是为了方便前端直接作为 series name 使用。units 字段统一保留因为 Zabbix 中 CPU 的 units 是空字符串或 %内存是 B磁盘是 B网络是 B/s前端拿到以后要自己判断除以多少换算成 KB / MB / GB这块项目里通常进到一个通用的转换函数里处理。Zabbix API 返回的值是字符串lastvalue这种字段尤其容易忽略。取值后第一时间转成 double可以有效避免在 ECharts 里出现类型错误。3. 登录与授权AccountController 和两个权限类如何各司其职3.1 AccountController 与 UserProvider 的登录流程这个项目里 AccountController.cs 和 UserProvider.cs 承担账户相关的职责。UserProvider 负责到用户存储中比对用户名和密码AccountController 只做 HTTP 层的数据绑定与结果返回。这样一个瘦 Controller、一个胖 Provider 的分法在中小项目里很实用因为以后如果需要把用户源换成数据库或 AD只改 UserProvider 就行Controller 不用动。[ApiController] [Route(api/account)] public class AccountController : ControllerBase { private readonly UserProvider _userProvider; public AccountController(UserProvider userProvider) { _userProvider userProvider; } [HttpPost(login)] public async TaskIActionResult Login(AccountViewModel model) { if (string.IsNullOrEmpty(model.Username) || string.IsNullOrEmpty(model.Password)) { return BadRequest(new { message 用户名和密码不能为空 }); } var user await _userProvider.FindUserAsync(model.Username, model.Password); if (user null) { return Unauthorized(new { message 用户名或密码错误 }); } var token await _userProvider.CreateSessionAsync(user); return Ok(new { token token, username user.Username, role user.Role }); } }AccountViewModel 在这里承担数据绑定作用。它一般只有 Username 和 Password 两个属性必要时加一个校验特性防止空字符串提交。把 ViewModel 单独拆出来而不是直接复用用户实体是为了避免登录接口把 Salt、密码哈希等字段暴露出去。3.2 PermissionFilter 与 PermissionAuthorizationHandler自定义授权如何生效权限部分由 PermissionFilter.cs 和 PermissionAuthorizationHandler.cs 两个文件配合完成。PermissionFilter 是一个 ASP.NET Core 过滤器在请求进入 Action 之前检查目标 Action 上有没有标注权限特性PermissionAuthorizationHandler 是真正的判断逻辑负责拿当前登录用户去权限表里比对。常见的做法是给 Action 加一个 PermissionAttribute 特性标注权限名称public class PermissionAttribute : Attribute { public string Name { get; } public PermissionAttribute(string name) Name name; }然后是过滤器public class PermissionFilter : IAsyncAuthorizationFilter { public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { var endpoint context.HttpContext.GetEndpoint(); var permission endpoint?.Metadata.GetMetadataPermissionAttribute(); if (permission null) { return; // 没有标注权限的接口可以直接访问 } var handler context.HttpContext.RequestServices .GetRequiredServicePermissionAuthorizationHandler(); var allowed await handler.HasPermissionAsync( context.HttpContext.User, permission.Name); if (!allowed) { context.Result new ContentResult { StatusCode 403, Content 无权限访问 }; } } }这段里有两个细节需要注意。第一GetEndpoint在 ASP.NET Core 3.0 之后才能拿到 Action 的元数据如果你的项目还在 2.x 上这段要改用 ControllerActionDescriptor 的方式。第二过滤器只做拦截不做业务判断真正的规则放在 PermissionAuthorizationHandler 里这样以后要改成基于角色的权限系统时只动 Handler 就可以。在 Startup.cs 里注册过滤器有两种方式// 方式一全局注册所有接口自动有权限检查 services.AddControllers(options { options.Filters.AddPermissionFilter(); }); // 方式二按需标注在 Action 上用 ServiceFilter 特性 // [ServiceFilter(typeof(PermissionFilter))] // public IActionResult SomeAction() { ... }全局注册的优点是所有新接口自动有权限检查缺点是像 ZabbixController 这种可能要提供给大屏匿名轮询的接口会在调试时多一层干扰。一般来说内部大屏系统会全局注册再对需要匿名访问的接口标注[AllowAnonymous]配一个扩展方法去跳过权限检查。3.3 权限模型的最小实现与适配思路Zabbix 大屏的实际场景里权限其实只需要两种能看图的人和能改配置的人。这个项目里 PermissionAuthorizationHandler 如果按最小实现来写通常只需要判断当前用户名是否在 admin 列表里再决定是否放行public class PermissionAuthorizationHandler { private readonly UserProvider _userProvider; private readonly IConfiguration _config; public PermissionAuthorizationHandler(UserProvider userProvider, IConfiguration config) { _userProvider userProvider; _config config; } public async Taskbool HasPermissionAsync(ClaimsPrincipal user, string permissionName) { var username user.Identity?.Name; if (string.IsNullOrEmpty(username)) { return false; } var account await _userProvider.GetUserByUsernameAsync(username); if (account null) { return false; } // 管理员不参与细粒度权限判断 if (account.Role admin) { return true; } // 具体权限项按项目需要从数据库或配置文件读取 var allowedPermissions _config.GetSection($Permissions:{username}).Getstring[](); return allowedPermissions?.Contains(permissionName) true; } }我建议把权限配置放到配置文件而不是硬编码在代码里这样运维同事调整大屏可见范围时不用重新编译。上面的例子把权限表放在 appsettings.json 的 Permissions 节点下每个用户对应一个数组。以后如果你想把它改到数据库里只需要替换 HasPermissionAsync 内部实现即可。4. ChartsController 到 ECharts大屏数据接口与图表绑定实战4.1 大屏需要哪些数据接口大屏监控系统一般包含主机总览、CPU 走势、内存分布、磁盘占用、网络流量、服务端口存活状态这几块。对应到 ChartsController比较顺的设计是每个图表一个端点而不是一个大而全的聚合接口。聚合接口虽然前端写起来省事但其中一个 Zabbix item 超时整块大屏都会失败拆开之后每个图表可以独立加载、独立重试黑屏故障的容灾范围更小。[ApiController] [Route(api/charts)] public class ChartsController : ControllerBase { private readonly ZabbixProvider _provider; public ChartsController(ZabbixProvider provider) { _provider provider; } // 大屏左上角近 30 分钟 CPU 使用率折线图 [HttpGet(cpu/trend)] public async TaskIActionResult CpuTrend(string hostId, int minutes 30) { var idleItems await _provider.GetItemsAsync(hostId, new[] { system.cpu.util[,idle] }); if (idleItems.Count 0) { return Ok(new { code 0, data new { categories Array.Emptystring(), series Array.Emptyobject() } }); } var itemId idleItems[0].ItemId; var history await _provider.GetHistoryAsync(itemId, minutes); var categories history.Select(h DateTimeOffset.FromUnixTimeSeconds(h.clock).ToLocalTime().ToString(HH:mm)).ToArray(); var values history.Select(h Math.Round(100 - double.Parse(h.value), 2)).ToArray(); return Ok(new { code 0, data new { categories, series new object[] { new { name CPU 使用率, type line, data values } } } }); } }这段代码里system.cpu.util[,idle]是 Zabbix Linux 模板里自带的监控项返回的是空闲百分比所以用 100 减去它得到使用率。minutes参数控制查询窗口前端点选 30 分钟和 24 小时都走同一个接口。历史接口的 clock 是秒级 Unix 时间戳这里用FromUnixTimeSeconds转成本地时间字符串避免时区问题。4.2 前端绑定一个能直接抄的 HTML 片段大屏前端用 ECharts 渲染时第一步是拿到上面的接口返回把它填入 option.categories 和 option.series。下面的片段是折线图最简版本依赖 echarts 的 CDN适合快速验证接口通不通!DOCTYPE html html langzh-cn head meta charsetutf-8 title运维大屏 - CPU 走势/title script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script style #cpu { width: 100%; height: 340px; } /style /head body div idcpu/div script const chart echarts.init(document.getElementById(cpu)); const hostId new URLSearchParams(location.search).get(host) || 10001; fetch(/api/charts/cpu/trend?hostId${hostId}minutes30) .then(r r.json()) .then(res { if (res.code ! 0) return; chart.setOption({ tooltip: { trigger: axis }, grid: { left: 50, right: 30, top: 40, bottom: 30 }, xAxis: { type: category, boundaryGap: false, data: res.data.categories }, yAxis: { type: value, max: 100, axisLabel: { formatter: {value}% } }, series: res.data.series }); }); /script /body /html这段代码里有三个容易翻车的点。一是容器 div 必须显式给高度否则 canvas 高度为 0图表画出来看不见。二是echarts.init在页面加载时就会读取容器尺寸如果容器当时被隐藏比如大屏切换页签宽度会是 0后续 setOption 也不会自动重算需要用chart.resize()或稍等几秒再 init。三是如果是 Vue 项目图表实例要放在mounted生命周期里创建不要在created阶段去 init因为那时 DOM 还没挂载。4.3 多图表与大屏自适应折线、饼图、柱状图的参数差异大屏上半部一般是折线下半部一般放饼图和柱状图。饼图在 Zabbix 大屏里最常见的用途是显示内存分布或磁盘占用分区占比ECharts 饼图中间的文字通过title的text和subtext实现而不是用 label 居中很多人在这填错字段。// 内存分布饼图中间显示已用百分比 option { title: { text: 64%, subtext: 内存占用, left: center, top: center }, tooltip: { trigger: item }, series: [{ type: pie, radius: [45%, 70%], label: { show: false }, data: [ { name: 已用, value: 41.2, itemStyle: { color: #ff7f50 } }, { name: 空闲, value: 22.8, itemStyle: { color: #2f7f6f } } ] }] };柱状图设置渐变色的常规写法是给itemStyle.color传入一个LinearGradient实例// 横向进度条样式的磁盘使用率 const used 73.2; option { title: { text: /dev/sda1 使用率 ${used}%, left: center }, grid: { left: 20, right: 20, top: 40, bottom: 20 }, xAxis: { type: value, max: 100, splitLine: { show: false } }, yAxis: { type: category, data: [磁盘占用] }, series: [{ type: bar, data: [used], barWidth: 18, itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ { offset: 0, color: #0f7f9f }, { offset: 1, color: #ff7f50 } ]) } }] };这里关键是new echarts.graphic.LinearGradient每次都要新实例不能在循环外复用同一个对象否则多根柱子渐变效果都一样。如果大屏需要中国地图ECharts 5 之后默认不内置地图 GeoJSON需要单独引入 china.js 文件接口数据也要给到省名而不是编码不然地图会白屏。5. 部署与避坑从 Startup.cs 到大屏上线的常见问题排查5.1 Startup.cs 的中间件顺序与 CORS 配置这套项目是 ASP.NET Core 应用启动文件 Startup.cs 里的中间件顺序决定了一整条请求管道。常见的一个坑是把 UseCors 写在 UseAuthorization 之后然后发现浏览器跨域请求返回 401。正确的顺序是 Routing → CORS → Authentication → Authorization → Endpoints。public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseCors(Screen); app.UseAuthentication(); app.UseAuthorization(); app.UseStaticFiles(); app.UseEndpoints(endpoints endpoints.MapControllers()); }CORS 策略在 ConfigureServices 中定义public void ConfigureServices(IServiceCollection services) { services.AddControllers(options { options.Filters.AddPermissionFilter(); }); services.AddCors(options { options.AddPolicy(Screen, policy { policy.WithOrigins(http://192.168.1.50:8080) // 大屏页面地址 .AllowAnyHeader() .AllowAnyMethod(); }); }); services.AddScopedZabbixProvider(); services.AddScopedUserProvider(); services.AddScopedPermissionAuthorizationHandler(); }这里有两个设计取舍。CORS 原始地址建议用大屏页面的确切 IP 而不是*否则任何内网页面都能跨域调 Zabbix 接口数据暴露面太大。ZabbixProvider 注册为 Scoped 就够了同一个请求内所有图表接口共享一个 Provider登录状态可以复用也不会引入全局静态状态导致并发问题。注意UseStaticFiles放在认证之后。如果放在认证之前静态文件和大屏页面要走不同的访问控制逻辑容易出现页面能开、接口全 401 的割裂状态。内部大屏系统一般页面不设防、接口设防保持这个中间件顺序最省心。5.2 部署到 Linux 服务器发布与运行Zabbix 大屏系统一般会部署到 CentOS 7.9 或 Ubuntu 服务器上部署时使用 dotnet publish 发布再用 systemd 守护进程运行。发布过程中常见的是运行用户对目录没有写权限导致应用启动即失败。使用这个命令发布dotnet publish -c Release -o /opt/screen-app然后写一个 systemd 单元文件[Unit] DescriptionOps Screen App Afternetwork.target [Service] EnvironmentASPNETCORE_ENVIRONMENTProduction EnvironmentASPNETCORE_URLShttp://0.0.0.0:5000 WorkingDirectory/opt/screen-app ExecStart/usr/bin/dotnet /opt/screen-app/ScreenApp.dll Restartalways RestartSec5 Userwww-data [Install] WantedBymulti-user.targetASPNETCORE_URLS指定监听地址和端口。实际部署里很多人把端口写成 80导致普通用户无法绑定建议用 5000 这类非特权端口前面再套一层 Nginx 反向代理。在 appsettings.json 中配置 Zabbix 连接参数{ Zabbix: { Url: http://192.168.1.10/zabbix/api_jsonrpc.php, User: api-user, Password: your-password }, Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning } } }注意 Zabbix 的 API 地址是api_jsonrpc.php。很多人第一次配置时习惯性地填成 Zabbix Web 根地址结果接口一直返回 404 或者拿到一个 HTML 页面解析 JSON 直接报错。这一点是最常见的 Zabbix API 接入失误。5.3 五个真实踩坑记录现象、原因与解决坑 1token 过期后接口集体返回空数据现象大屏刚打开前十分钟正常之后所有图表接口返回 code0 但 data 为空页面不报错。原因ZabbixProvider 只在首次启动时登录token 过期后没有重登机制Zabbix 侧返回错误码被全局异常处理器包装成空数据。解决在 Provider 里维护 token 有效期每次请求前校验收到 Zabbix 错误码 -32602 或 401 时重新登录并重试一次请求。坑 2历史数据时间戳单位不一致现象折线图横轴出现 1970 年或者时间整体偏移 8 小时。原因Zabbix history.get 返回的 clock 是秒级 Unix 时间戳ECharts 时间轴要求毫秒级同时服务器时区设置的 UTC 与本地时区差 8 小时转换时没做 ToLocalTime。解决在 Provider 输出 DTO 前统一乘以 1000 转 long并在转换函数里显式写DateTimeOffset.FromUnixTimeSeconds(...).ToLocalTime()不要依赖系统默认时区。坑 3图例区出现未命名项现象大屏图例区多出一个没有名字的条目鼠标放上去 tooltip 显示 undefined数据本身没错。原因series 对象缺少 name 字段ECharts 会生成一个匿名序列如果你的 name 是直接拼接 Zabbix keykey 里的空格和逗号也会造成显示混乱。解决组装 series 时强制判断string.IsNullOrEmpty(name)给默认值并对 name 做 trim 处理。不要在前端再做二次拼接。坑 4跨域请求时 OPTIONS 预检失败现象页面无法加载数据浏览器 Console 显示 Access-Control-Allow-Origin 头缺失。原因UseCors 没有在正确的中间件位置执行或者 CORS 策略名称对不上——Startup 里注册的是 Screen 策略UseCors 却没传策略名称导致中间件没找到任何 CORS 策略。解决确保 Configure 中按 Routing → CORS → Authentication → Authorization → Endpoints 的顺序注册并且app.UseCors(Screen)与AddCors里的策略名称一致。如果只是临时联调可以先用 AllowAnyOrigin 验证通断再收紧为具体的 IP。坑 5item.get 匹配到多个监控项现象一个主机有多个 CPU 监控项item.get 用system.cpu.util模糊搜索返回多条记录图表里曲线重叠或直接报错。原因Zabbix 模板可能给每个 CPU 核心都定义了 system.cpu.util 系列 itemkey 里有逗号和下标模糊搜索全捞出来了。解决按带参数的完整 key 精确过滤比如system.cpu.util[,idle]如果确实有多个核取总和或用trends.get聚合后再算百分比。更稳妥的方法是在 Zabbix 页面里确认 itemid在 Controller 里匹配到唯一 itemid 再查历史数据。6. 用 Postman 核对 Zabbix 到 ECharts 的完整链路最后一道防线大屏上线前我都会用 Postman 把整条链路挨个打一遍从 Zabbix API 到 ChartsController再到浏览器。这个过程可以快速判断问题出在哪个环节省得在大屏页面里反复刷新看热闹。第一步在 Postman 里调用 Zabbix 的 user.login确认账号有权限访问 host.get。登录返回的 token 单独存到一个环境变量里因为后面几个步骤都要用。然后调 host.get 拿到 hostid 和主机名把 hostid 记下来它会出现在之后几乎所有请求的参数里。第二步用 hostid 调 item.get确认关键监控项的 itemid 存在。这一步能暴露 80% 的配置问题item key 拼错、模板没链接、主机未启用、监控项被禁用。建议在 Postman 里把返回的 itemid、key_、lastvalue 三列并排看一下子就能发现哪些监控项没有数据。第三步调 history.get 拉一段历史数据确认 clock 和 value 都有值。然后调 ChartsController 相应接口比如/api/charts/cpu/trend?hostId10001minutes30对比返回的 categories 是否和 Postman 里看到的时间段一致。这个时候可以顺带检查 CPU 计算公式取system.cpu.util[,idle]的结果在 Postman 里用 100 减去历史值看看和 ChartsController 返回的最后一个点差多少能定位到是计算问题还是数据源问题。第四步打开浏览器 Network 面板看大屏页面加载后所有/api/charts/*请求是不是 200、返回的 JSON 结构是否和前端setOption里的字段一致。最常见的问题是字段名不匹配后端返回 categories前端却用了 time后端是 data前端却取 value。这一步在浏览器里现场改就能验证。第五步把 Zabbix 里某台测试机的监控项停掉或者制造一个告警观察大屏是否在下一个刷新周期内变化。如果 Zabbix 告警已经解决了但大屏还处于异常状态先看浏览器请求时间戳和 Zabbix 侧的最近值确认是接口缓存还是数据源本身没更新。这套流程走下来大概需要十分钟但它能在开会投屏之前把八成的数据问题挡在门口。从那以后我每次上线新主机都要强制自己按这个顺序走一遍 Postman 和浏览器检查大屏翻车的概率低了非常多。这套项目的文件结构不算复杂但 ZabbixProvider、权限过滤器和 ChartsController 之间的数据约定才是真正需要复现时自己维护的部分希望帮到你。本文还有配套的精品资源点击获取
返回列表