Files
monitor/backend/internal/handler/handler.go
Alex Yang bd70dc1748 web修复
2025-12-03 10:27:45 +08:00

766 lines
21 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"log"
"net/http"
"os"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/monitor/backend/internal/device"
"github.com/monitor/backend/internal/storage"
)
// Handler API处理器
type Handler struct {
storage *storage.Storage
}
// NewHandler 创建新的API处理器
func NewHandler(storage *storage.Storage) *Handler {
return &Handler{
storage: storage,
}
}
// RegisterRoutes 注册所有路由
func RegisterRoutes(r *gin.Engine) {
// API路由组
api := r.Group("/api")
{
// 监控数据路由
metrics := api.Group("/metrics")
{
metrics.GET("/cpu", GetCPUMetrics)
metrics.GET("/memory", GetMemoryMetrics)
metrics.GET("/disk", GetDiskMetrics)
metrics.GET("/network", GetNetworkMetrics)
// 添加POST端点接收Agent发送的指标数据
metrics.POST("/", HandleMetricsPost)
}
// 设备管理路由
devices := api.Group("/devices")
{
devices.GET("/", GetDevices) // 获取活跃设备列表
devices.GET("/all", GetAllDevices) // 获取所有设备
devices.GET("/:id", GetDevice) // 获取单个设备详情
devices.POST("/", AddDevice) // 添加设备
devices.PUT("/:id", UpdateDevice) // 更新设备信息
devices.DELETE("/:id", DeleteDevice) // 删除设备
// 添加获取设备状态的端点
devices.GET("/status", GetAllDeviceStatus) // 获取所有活跃设备状态
devices.GET("/:id/status", GetDeviceStatus) // 获取单个设备状态
}
// WebSocket端点
api.GET("/ws", WebSocketHandler)
// 添加设备状态概览端点,作为/api/devices/status的别名
api.GET("/status", GetAllDeviceStatus)
}
}
// NetworkInterfaceMetrics 网卡监控指标
type NetworkInterfaceMetrics struct {
BytesSent uint64 `json:"bytes_sent"`
BytesReceived uint64 `json:"bytes_received"`
}
// MetricsRequest 指标请求结构
type MetricsRequest struct {
CPU float64 `json:"cpu"`
Memory float64 `json:"memory"`
Disk map[string]float64 `json:"disk"`
Network map[string]NetworkInterfaceMetrics `json:"network"`
}
// HandleMetricsPost 处理Agent发送的指标数据
func HandleMetricsPost(c *gin.Context) {
// 获取设备令牌
token := c.GetHeader("X-Device-Token")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Device token is required",
})
return
}
// 通过令牌获取设备
device, ok := deviceStorage.GetDeviceByToken(token)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid device token",
})
return
}
// 获取设备ID用于后续处理
deviceID := device.ID
// 获取Agent名称
agentName := c.GetHeader("X-Agent-Name")
if agentName == "" {
// 如果没有提供名称,使用设备名称
agentName = device.Name
}
// 确保设备状态为active
if device.Status != "active" {
// 更新设备状态为active
if err := deviceStorage.UpdateDeviceStatus(deviceID, "active"); err != nil {
// 只记录警告,不影响指标处理
log.Printf("Warning: Failed to update device status: %v", err)
} else {
log.Printf("Device %s activated successfully", deviceID)
}
}
// 创建基础标签包含Agent名称
baseTags := map[string]string{
"agent_name": agentName,
}
// 处理请求体,支持单指标对象和指标数组
var metricsList []MetricsRequest
// 读取请求体到缓冲区,以便多次解析
body, err := c.GetRawData()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Failed to read request body",
})
return
}
// 首先尝试解析为数组
if err := json.Unmarshal(body, &metricsList); err != nil {
// 如果解析数组失败,尝试解析为单个对象
var singleMetric MetricsRequest
if err := json.Unmarshal(body, &singleMetric); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid request body",
})
return
}
// 将单个对象添加到列表中
metricsList = append(metricsList, singleMetric)
}
// 处理所有指标
for i, req := range metricsList {
// 写入CPU指标
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "cpu", req.CPU, baseTags); err != nil {
// 只记录警告,不影响后续指标处理
log.Printf("Warning: Failed to write CPU metrics: %v", err)
}
// 写入内存指标
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "memory", req.Memory, baseTags); err != nil {
// 只记录警告,不影响后续指标处理
log.Printf("Warning: Failed to write memory metrics: %v", err)
}
// 写入磁盘指标,支持多个挂载点
for mountpoint, usage := range req.Disk {
// 为每个挂载点创建标签,包含基础标签和挂载点
tags := make(map[string]string)
// 复制基础标签
for k, v := range baseTags {
tags[k] = v
}
// 添加挂载点标签
tags["mountpoint"] = mountpoint
// 写入磁盘指标
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "disk", usage, tags); err != nil {
// 只记录警告,不影响后续指标处理
log.Printf("Warning: Failed to write disk metrics for mountpoint %s: %v", mountpoint, err)
}
}
// 写入网络指标,支持多个网卡
var totalBytesSent, totalBytesReceived uint64
for interfaceName, networkMetrics := range req.Network {
// 为每个网卡创建标签,包含基础标签和网卡名称
interfaceTags := make(map[string]string)
// 复制基础标签
for k, v := range baseTags {
interfaceTags[k] = v
}
// 添加网卡标签
interfaceTags["interface"] = interfaceName
// 写入网络发送指标
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "network_sent", float64(networkMetrics.BytesSent), interfaceTags); err != nil {
// 只记录警告,不影响后续指标处理
log.Printf("Warning: Failed to write network sent metrics for interface %s: %v", interfaceName, err)
}
// 写入网络接收指标
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "network_received", float64(networkMetrics.BytesReceived), interfaceTags); err != nil {
// 只记录警告,不影响后续指标处理
log.Printf("Warning: Failed to write network received metrics for interface %s: %v", interfaceName, err)
}
// 累加总流量
totalBytesSent += networkMetrics.BytesSent
totalBytesReceived += networkMetrics.BytesReceived
}
// 广播指标更新消息,只广播最后一个指标
if i == len(metricsList)-1 {
metrics := map[string]interface{}{
"cpu": req.CPU,
"memory": req.Memory,
"disk": req.Disk,
"network": map[string]uint64{
"bytes_sent": totalBytesSent,
"bytes_received": totalBytesReceived,
},
"network_interfaces": req.Network,
}
broadcastMetricsUpdate(deviceID, metrics)
}
}
// 返回成功响应
c.JSON(http.StatusOK, gin.H{
"message": "Metrics received successfully",
"count": len(metricsList),
})
}
// 全局存储实例(简化处理,实际应该使用依赖注入)
var globalStorage *storage.Storage
var deviceStorage device.Storage
// SetStorage 设置全局存储实例
func SetStorage(s *storage.Storage) {
globalStorage = s
}
// SetDeviceStorage 设置设备存储实例
func SetDeviceStorage(s device.Storage) {
deviceStorage = s
}
// 生成安全的设备令牌
func generateDeviceToken() string {
// 生成32字节的随机数据
bytes := make([]byte, 16)
if _, err := rand.Read(bytes); err != nil {
// 如果生成随机数失败使用时间戳和PID作为备选方案
return hex.EncodeToString([]byte(time.Now().String() + "-" + string(os.Getpid())))
}
// 转换为32位的十六进制字符串
return hex.EncodeToString(bytes)
}
// WebSocket相关配置
var (
// 升级器用于将HTTP连接升级为WebSocket连接
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // 允许所有来源,生产环境应该限制
},
}
// 客户端连接管理
clients = make(map[*websocket.Conn]bool)
clientsLock sync.Mutex
// 消息广播通道
broadcast = make(chan map[string]interface{})
)
// 启动WebSocket消息处理
func init() {
go handleMessages()
}
// 处理WebSocket消息
func handleMessages() {
for {
// 从广播通道接收消息
message := <-broadcast
// 序列化消息
jsonMessage, err := json.Marshal(message)
if err != nil {
continue
}
// 广播消息给所有客户端
clientsLock.Lock()
for client := range clients {
if err := client.WriteMessage(websocket.TextMessage, jsonMessage); err != nil {
// 连接出错,关闭连接并从客户端列表中移除
client.Close()
delete(clients, client)
}
}
clientsLock.Unlock()
}
}
// WebSocket端点用于客户端连接
func WebSocketHandler(c *gin.Context) {
// 升级HTTP连接为WebSocket连接
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
defer conn.Close()
// 将客户端添加到客户端列表
clientsLock.Lock()
clients[conn] = true
clientsLock.Unlock()
// 处理客户端消息(这里我们只发送消息,不处理接收的消息)
for {
// 读取消息(如果客户端发送消息,我们不处理,直接忽略)
_, _, err := conn.ReadMessage()
if err != nil {
// 连接出错,从客户端列表中移除
clientsLock.Lock()
delete(clients, conn)
clientsLock.Unlock()
break
}
}
}
// 广播指标更新消息
func broadcastMetricsUpdate(deviceID string, metrics map[string]interface{}) {
message := map[string]interface{}{
"type": "metrics_update",
"device_id": deviceID,
"metrics": metrics,
}
broadcast <- message
}
// GetCPUMetrics 获取CPU指标
func GetCPUMetrics(c *gin.Context) {
// 获取查询参数
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
startTime := c.DefaultQuery("start_time", "-24h")
endTime := c.DefaultQuery("end_time", "now()")
aggregation := c.DefaultQuery("aggregation", "average")
interval := c.DefaultQuery("interval", "10s") // 添加interval参数默认10秒
// 查询数据
points, err := globalStorage.QueryMetrics(context.Background(), deviceID, "cpu", startTime, endTime)
if err != nil {
// 只记录警告,返回空数据
log.Printf("Warning: Failed to query CPU metrics: %v", err)
c.JSON(http.StatusOK, gin.H{
"data": []MetricData{},
})
return
}
// 处理数据传递interval、startTime和endTime参数
processedData := ProcessMetrics(points, aggregation, interval, startTime, endTime)
c.JSON(http.StatusOK, gin.H{
"data": processedData,
})
}
// GetMemoryMetrics 获取内存指标
func GetMemoryMetrics(c *gin.Context) {
// 获取查询参数
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
startTime := c.DefaultQuery("start_time", "-24h")
endTime := c.DefaultQuery("end_time", "now()")
aggregation := c.DefaultQuery("aggregation", "average")
interval := c.DefaultQuery("interval", "10s") // 添加interval参数默认10秒
// 查询数据
points, err := globalStorage.QueryMetrics(context.Background(), deviceID, "memory", startTime, endTime)
if err != nil {
// 只记录警告,返回空数据
log.Printf("Warning: Failed to query memory metrics: %v", err)
c.JSON(http.StatusOK, gin.H{
"data": []MetricData{},
})
return
}
// 处理数据传递interval、startTime和endTime参数
processedData := ProcessMetrics(points, aggregation, interval, startTime, endTime)
c.JSON(http.StatusOK, gin.H{
"data": processedData,
})
}
// GetDiskMetrics 获取磁盘指标
func GetDiskMetrics(c *gin.Context) {
// 获取查询参数
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
startTime := c.DefaultQuery("start_time", "-24h")
endTime := c.DefaultQuery("end_time", "now()")
aggregation := c.DefaultQuery("aggregation", "average")
interval := c.DefaultQuery("interval", "10s") // 添加interval参数默认10秒
// 查询数据
points, err := globalStorage.QueryMetrics(context.Background(), deviceID, "disk", startTime, endTime)
if err != nil {
// 只记录警告,返回空数据
log.Printf("Warning: Failed to query disk metrics: %v", err)
c.JSON(http.StatusOK, gin.H{
"data": map[string][]MetricData{},
})
return
}
// 按挂载点分组
mountpointData := make(map[string][]storage.MetricPoint)
for _, point := range points {
// 获取挂载点标签
mountpoint := "unknown"
if point.Tags != nil && point.Tags["mountpoint"] != "" {
mountpoint = point.Tags["mountpoint"]
}
mountpointData[mountpoint] = append(mountpointData[mountpoint], point)
}
// 处理数据,为每个挂载点创建独立的数据集
result := make(map[string][]MetricData)
for mountpoint, mountpointPoints := range mountpointData {
processedData := ProcessMetrics(mountpointPoints, aggregation, interval, startTime, endTime)
result[mountpoint] = processedData
}
c.JSON(http.StatusOK, gin.H{
"data": result,
})
}
// GetNetworkMetrics 获取网络指标
func GetNetworkMetrics(c *gin.Context) {
// 获取查询参数
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
startTime := c.DefaultQuery("start_time", "-24h")
endTime := c.DefaultQuery("end_time", "now()")
aggregation := c.DefaultQuery("aggregation", "average")
interval := c.DefaultQuery("interval", "10s") // 添加interval参数默认10秒
// 查询发送和接收的网络指标
sentPoints, err1 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_sent", startTime, endTime)
receivedPoints, err2 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_received", startTime, endTime)
// 处理错误
if err1 != nil {
log.Printf("Warning: Failed to query network sent metrics: %v", err1)
sentPoints = []storage.MetricPoint{}
}
if err2 != nil {
log.Printf("Warning: Failed to query network received metrics: %v", err2)
receivedPoints = []storage.MetricPoint{}
}
// 按网卡名称分组发送和接收的指标
sentByInterface := make(map[string][]storage.MetricPoint)
receivedByInterface := make(map[string][]storage.MetricPoint)
// 分组发送的网络指标
for _, point := range sentPoints {
// 获取网卡名称,默认使用"all"表示所有网卡
interfaceName := point.Tags["interface"]
if interfaceName == "" {
interfaceName = "all"
}
sentByInterface[interfaceName] = append(sentByInterface[interfaceName], point)
}
// 分组接收的网络指标
for _, point := range receivedPoints {
// 获取网卡名称,默认使用"all"表示所有网卡
interfaceName := point.Tags["interface"]
if interfaceName == "" {
interfaceName = "all"
}
receivedByInterface[interfaceName] = append(receivedByInterface[interfaceName], point)
}
// 处理数据,为每个网卡创建独立的数据集
result := make(map[string]map[string][]MetricData)
// 合并所有网卡名称
allInterfaces := make(map[string]bool)
for iface := range sentByInterface {
allInterfaces[iface] = true
}
for iface := range receivedByInterface {
allInterfaces[iface] = true
}
// 为每个网卡处理数据
for iface := range allInterfaces {
// 获取该网卡的发送和接收指标
ifaceSentPoints := sentByInterface[iface]
ifaceReceivedPoints := receivedByInterface[iface]
// 处理数据
processedSentData := ProcessMetrics(ifaceSentPoints, aggregation, interval, startTime, endTime)
processedReceivedData := ProcessMetrics(ifaceReceivedPoints, aggregation, interval, startTime, endTime)
// 保存结果
result[iface] = map[string][]MetricData{
"sent": processedSentData,
"received": processedReceivedData,
}
}
c.JSON(http.StatusOK, gin.H{
"data": result,
})
}
// GetDevices 获取设备列表
func GetDevices(c *gin.Context) {
// 从设备存储获取所有设备
allDevices := deviceStorage.GetDevices()
// 转换为前端需要的格式
deviceList := make([]map[string]string, 0, len(allDevices))
for _, device := range allDevices {
// 只返回活跃设备
if device.Status == "active" {
deviceList = append(deviceList, map[string]string{
"id": device.ID,
"name": device.Name,
})
}
}
c.JSON(http.StatusOK, gin.H{
"devices": deviceList,
})
}
// GetDeviceStatus 获取设备状态
func GetDeviceStatus(c *gin.Context) {
// 获取设备ID
deviceID := c.Param("id")
if deviceID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Device ID is required",
})
return
}
// 从设备存储获取设备信息
device, ok := deviceStorage.GetDevice(deviceID)
if !ok {
c.JSON(http.StatusNotFound, gin.H{
"error": "Device not found",
})
return
}
// 查询设备监控数据
_, status, err := globalStorage.QueryDeviceStatus(context.Background(), deviceID)
if err != nil {
// 如果查询监控数据失败,返回设备基本信息和空状态
c.JSON(http.StatusOK, gin.H{
"device_id": deviceID,
"name": device.Name,
"status": make(map[string]float64),
})
return
}
c.JSON(http.StatusOK, gin.H{
"device_id": deviceID,
"name": device.Name,
"status": status,
})
}
// GetAllDeviceStatus 获取所有设备状态
func GetAllDeviceStatus(c *gin.Context) {
// 从设备存储获取所有设备
allDevices := deviceStorage.GetDevices()
// 查询每个设备的状态
result := make([]map[string]interface{}, 0, len(allDevices))
for _, device := range allDevices {
// 查询设备监控数据
_, status, _ := globalStorage.QueryDeviceStatus(context.Background(), device.ID)
// 总是返回设备信息,无论是否有监控数据
result = append(result, map[string]interface{}{
"device_id": device.ID,
"name": device.Name,
"status": status,
})
}
c.JSON(http.StatusOK, gin.H{
"devices": result,
})
}
// AddDevice 添加设备
func AddDevice(c *gin.Context) {
var req struct {
ID string `json:"id" binding:"required"`
Name string `json:"name" binding:"required"`
IP string `json:"ip"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid request: " + err.Error(),
})
return
}
// 生成设备令牌
token := generateDeviceToken()
newDevice := device.Device{
ID: req.ID,
Name: req.Name,
IP: req.IP,
Token: token,
Status: "inactive",
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
if err := deviceStorage.AddDevice(newDevice); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to add device: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Device added successfully",
"device": newDevice,
})
}
// DeleteDevice 删除设备
func DeleteDevice(c *gin.Context) {
deviceID := c.Param("id")
if deviceID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Device ID is required",
})
return
}
if err := deviceStorage.DeleteDevice(deviceID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to delete device: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Device deleted successfully",
})
}
// UpdateDevice 更新设备
func UpdateDevice(c *gin.Context) {
deviceID := c.Param("id")
if deviceID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Device ID is required",
})
return
}
var req struct {
Name string `json:"name"`
IP string `json:"ip"`
Status string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid request: " + err.Error(),
})
return
}
// 获取现有设备
existingDevice, ok := deviceStorage.GetDevice(deviceID)
if !ok {
c.JSON(http.StatusNotFound, gin.H{
"error": "Device not found",
})
return
}
// 更新设备信息保留原有token
if req.Name != "" {
existingDevice.Name = req.Name
}
if req.IP != "" {
existingDevice.IP = req.IP
}
if req.Status != "" {
existingDevice.Status = req.Status
}
existingDevice.UpdatedAt = time.Now().Unix()
if err := deviceStorage.UpdateDevice(existingDevice); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to update device: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Device updated successfully",
"device": existingDevice,
})
}
// GetDevice 获取单个设备
func GetDevice(c *gin.Context) {
deviceID := c.Param("id")
if deviceID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Device ID is required",
})
return
}
device, ok := deviceStorage.GetDevice(deviceID)
if !ok {
c.JSON(http.StatusNotFound, gin.H{
"error": "Device not found",
})
return
}
c.JSON(http.StatusOK, gin.H{
"device": device,
})
}
// GetAllDevices 获取所有设备
func GetAllDevices(c *gin.Context) {
devices := deviceStorage.GetDevices()
c.JSON(http.StatusOK, gin.H{
"devices": devices,
})
}