Files
aiops/backend/controller/zabbix_proxy.go
T
2026-07-09 14:16:56 +08:00

285 lines
8.3 KiB
Go

package controller
import (
"context"
"fmt"
"math"
"net/http"
"strconv"
"strings"
"time"
"ezops/model"
"ezops/pkg/zabbix"
"github.com/gin-gonic/gin"
)
// ZabbixProxyHandler Zabbix 代理处理器 — 将 Zabbix API 数据转为前端面板可用格式
type ZabbixProxyHandler struct{}
func NewZabbixProxyHandler() *ZabbixProxyHandler {
return &ZabbixProxyHandler{}
}
// ZabbixHostSummary Zabbix 主机汇总(前端面板数据)
type ZabbixHostSummary struct {
HostID string `json:"hostid"`
Hostname string `json:"hostname"`
Name string `json:"name"`
IP string `json:"ip"`
Available string `json:"available"` // 0=未知 1=可用 2=不可用
CPUUsage float64 `json:"cpu_usage"`
MemoryUsage float64 `json:"memory_usage"`
DiskUsage float64 `json:"disk_usage"`
Load5 float64 `json:"load5"`
NetInBPS float64 `json:"net_in_bps"`
NetOutBPS float64 `json:"net_out_bps"`
ProblemCount int `json:"problem_count"`
Uptime int64 `json:"uptime_days"` // 天
CPUCount int `json:"cpu_count"`
TotalMemory float64 `json:"total_memory_gb"`
}
// ZabbixConnectReq 连接 Zabbix 请求
type ZabbixConnectReq struct {
URL string `json:"url" binding:"required"`
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
// ZabbixConnect 连接 Zabbix 并返回主机摘要
// POST /api/v1/zabbix/connect
func (h *ZabbixProxyHandler) Connect(c *gin.Context) {
var req ZabbixConnectReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误: " + err.Error()})
return
}
client := zabbix.NewClient(req.URL, req.Username, req.Password)
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
defer cancel()
if err := client.Auth(ctx); err != nil {
c.JSON(http.StatusBadGateway, model.Response{Code: 502, Message: "Zabbix 认证失败: " + err.Error()})
return
}
// 获取 Zabbix 版本
version, _ := client.GetVersion(ctx)
// 获取主机列表(含接口和组信息)
hosts, err := client.GetHosts(ctx, &zabbix.GetHostsParams{
Status: 0, // 只获取被监控的主机
SelectInterfaces: []string{"interfaceid", "hostid", "main", "type", "useip", "ip", "dns", "port"},
SelectGroups: []string{"groupid", "name"},
Output: []string{"hostid", "host", "name", "status", "available", "description"},
})
if err != nil {
client.Logout(ctx)
c.JSON(http.StatusBadGateway, model.Response{Code: 502, Message: "获取 Zabbix 主机失败: " + err.Error()})
return
}
// 为每台主机查询监控项
var summaries []ZabbixHostSummary
itemKeys := []string{
"system.cpu.util[,idle]", // CPU 空闲率
"system.cpu.util", // CPU 使用率(备用)
"vm.memory.size[total]", // 总内存 bytes
"vm.memory.size[available]", // 可用内存 bytes
"vfs.fs.size[/,total]", // 总磁盘 bytes
"vfs.fs.size[/,used]", // 已用磁盘 bytes
"system.cpu.load[all,avg5]", // 5分钟负载
"net.if.in[ens18]", // 网络流入
"net.if.out[ens18]", // 网络流出
"system.cpu.num", // CPU 数量
"system.uptime", // 运行时间
}
for _, host := range hosts {
summary := ZabbixHostSummary{
HostID: host.HostID,
Hostname: host.Host,
Name: host.Name,
Available: host.Available,
}
// 获取 IP
for _, iface := range host.Interfaces {
if iface.Main == "1" && summary.IP == "" {
if iface.UseIP == "1" && iface.IP != "" {
summary.IP = iface.IP
} else if iface.DNS != "" {
summary.IP = iface.DNS
}
}
}
// 获取监控项
items, err := client.GetItems(ctx, &zabbix.GetItemsParams{
HostIDs: []string{host.HostID},
Filter: map[string]interface{}{"key_": itemKeys},
Output: []string{"itemid", "hostid", "name", "key_", "lastvalue", "units"},
SortField: []string{"name"},
})
if err != nil {
summaries = append(summaries, summary)
continue
}
var totalMemBytes, availMemBytes, totalDiskBytes, usedDiskBytes float64
for _, item := range items {
val, _ := strconv.ParseFloat(item.LastValue, 64)
switch item.Key {
case "system.cpu.util[,idle]":
summary.CPUUsage = math.Round((100-val)*100) / 100
case "system.cpu.util":
if summary.CPUUsage == 0 {
summary.CPUUsage = math.Round(val*100) / 100
}
case "vm.memory.size[total]":
totalMemBytes = val
case "vm.memory.size[available]":
availMemBytes = val
case "vfs.fs.size[/,total]":
totalDiskBytes = val
case "vfs.fs.size[/,used]":
usedDiskBytes = val
case "system.cpu.load[all,avg5]":
summary.Load5 = math.Round(val*100) / 100
case "net.if.in[ens18]":
summary.NetInBPS = math.Round(val*100) / 100
case "net.if.out[ens18]":
summary.NetOutBPS = math.Round(val*100) / 100
case "system.cpu.num":
summary.CPUCount = int(val)
case "system.uptime":
summary.Uptime = int64(val) / 86400 // seconds → days
}
}
// 计算内存使用率
if totalMemBytes > 0 {
summary.TotalMemory = math.Round(totalMemBytes/1024/1024/1024*100) / 100 // GB
if availMemBytes > 0 {
summary.MemoryUsage = math.Round((totalMemBytes-availMemBytes)/totalMemBytes*10000) / 100
}
}
// 计算磁盘使用率
if totalDiskBytes > 0 && usedDiskBytes > 0 {
summary.DiskUsage = math.Round(usedDiskBytes/totalDiskBytes*10000) / 100
}
// 获取问题触发器
triggers, _ := client.GetTriggersByHost(ctx, host.HostID, true)
summary.ProblemCount = len(triggers)
summaries = append(summaries, summary)
}
client.Logout(ctx)
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "success",
Data: gin.H{
"version": version,
"hosts": summaries,
"total": len(summaries),
},
})
}
// ZabbixHostDetail Zabbix 单主机详情
// GET /api/v1/zabbix/host/:hostid
func (h *ZabbixProxyHandler) GetHostDetail(c *gin.Context) {
hostID := c.Param("hostid")
if hostID == "" {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "缺少主机ID"})
return
}
// 从 query 参数获取连接信息
url := c.Query("url")
username := c.Query("username")
password := c.Query("password")
if url == "" || username == "" {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "缺少 Zabbix 连接信息"})
return
}
client := zabbix.NewClient(url, username, password)
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
defer cancel()
if err := client.Auth(ctx); err != nil {
c.JSON(http.StatusBadGateway, model.Response{Code: 502, Message: "Zabbix 认证失败: " + err.Error()})
return
}
defer client.Logout(ctx)
// 获取主机详情
host, err := client.GetHostByID(ctx, hostID)
if err != nil {
c.JSON(http.StatusBadGateway, model.Response{Code: 502, Message: "获取主机失败: " + err.Error()})
return
}
// 获取所有监控项
items, err := client.GetItemsByHost(ctx, hostID)
if err != nil {
c.JSON(http.StatusBadGateway, model.Response{Code: 502, Message: "获取监控项失败: " + err.Error()})
return
}
// 获取问题触发器
triggers, _ := client.GetTriggersByHost(ctx, hostID, true)
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "success",
Data: gin.H{
"host": host,
"items": items,
"triggers": triggers,
"problems": len(triggers),
},
})
}
// formatBps 格式化带宽
func formatBps(bps float64) string {
if bps == 0 {
return "—"
}
units := []string{"B/s", "KB/s", "MB/s", "GB/s", "TB/s"}
unitIdx := 0
val := bps
for val >= 1024 && unitIdx < len(units)-1 {
val /= 1024
unitIdx++
}
return fmt.Sprintf("%.1f %s", val, units[unitIdx])
}
// parseZabbixItemSummary 从 items 中提取汇总信息的辅助函数(供内部使用)
func parseZabbixItemSummary(items []zabbix.Item) map[string]string {
result := make(map[string]string)
for _, item := range items {
key := item.Key
// 标准化 key 名称
if strings.Contains(item.Name, "CPU utilization") || key == "system.cpu.util" {
result["cpu"] = item.LastValue
}
if strings.Contains(item.Name, "Memory utilization") || key == "vm.memory.size[pavailable]" {
result["memory"] = item.LastValue
}
if strings.Contains(item.Name, "Space utilization") || key == "/Space utilization/" {
result["disk"] = item.LastValue
}
}
return result
}