package handlers import ( "context" "fmt" "math" "net/http" "strconv" "strings" "time" "ezops/internal/models" "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, models.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, models.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, models.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, models.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, models.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, models.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, models.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, models.Response{Code: 502, Message: "获取主机失败: " + err.Error()}) return } // 获取所有监控项 items, err := client.GetItemsByHost(ctx, hostID) if err != nil { c.JSON(http.StatusBadGateway, models.Response{Code: 502, Message: "获取监控项失败: " + err.Error()}) return } // 获取问题触发器 triggers, _ := client.GetTriggersByHost(ctx, hostID, true) c.JSON(http.StatusOK, models.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 }