From b7780c5a5ab392053b66983a72abd26735479207 Mon Sep 17 00:00:00 2001 From: Logan Date: Sun, 28 Jun 2026 23:43:34 +0800 Subject: [PATCH] =?UTF-8?q?fix(agent):=20=E4=BF=AE=E5=A4=8D=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E9=87=8D=E5=A4=8D=E6=B3=A8=E5=86=8C(=E6=AF=8F10s?= =?UTF-8?q?=E6=96=B0=E5=BB=BA)=20+=20=E6=89=B9=E9=87=8F=E5=88=A0=E9=99=A44?= =?UTF-8?q?00?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit agent 自动注册前按 hostname 显式查重(asset_servers.hostname 无 UNIQUE 索引,OnConflict 失效导致 Agent 每次上报都 INSERT 新记录,资产越来越多)。 批量删除 BatchDeleteServers 对齐前端 {ids} 格式(原期望裸 []uint 导致 ShouldBindJSON 解析失败 400)。 Co-Authored-By: Claude --- backend/controller/agent.go | 76 +++++++++++++++++++-------------- backend/controller/server_v2.go | 8 +++- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/backend/controller/agent.go b/backend/controller/agent.go index d23e405..eaa94e2 100644 --- a/backend/controller/agent.go +++ b/backend/controller/agent.go @@ -163,43 +163,57 @@ func (h *AgentHandler) ReceiveReport(c *gin.Context) { result = h.db.Exec(updateSQL2, args2...) } - // 如果还是没匹配到 —— 自动注册新服务器(UPSERT 防重复) + // 如果还是没匹配到 —— 按 hostname 显式查重后再注册 // Logan.Li + // asset_servers.hostname 无 UNIQUE 索引,OnConflict 不可靠,故先 First 查重, + // 避免 Agent 每次上报(10s)都新建记录导致资产越来越多。 if result.RowsAffected == 0 { osName := "" if req.System != nil { osName = req.System.OSVersion } - newServer := model.Server{ - Hostname: req.Hostname, - IP: matchIP, - Port: 22, - Username: "root", - AuthType: "password", - OS: osName, - CPU: req.CPU.CoreCount, - Memory: memMB, - Disk: diskGB, - Status: "running", - AgentStatus: "online", - AgentVersion: req.AgentVersion, - CPUUsage: req.CPU.UsagePercent, - MemoryUsage: req.Memory.UsagePercent, - DiskUsage: diskAvg, - } - // ON DUPLICATE KEY UPDATE — 若 hostname 已存在则更新字段(防重复注册) - if err := h.db.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "hostname"}}, - DoUpdates: clause.AssignmentColumns([]string{ - "ip", "os", "cpu", "memory", "disk", - "status", "agent_status", - "cpu_usage", "memory_usage", "disk_usage", - "updated_at", - }), - }).Create(&newServer).Error; err != nil { - fmt.Printf("[agent] 自动注册主机 %q (IP: %s) 失败: %v\n", req.Hostname, matchIP, err) + var existing model.Server + if h.db.Where("hostname = ?", req.Hostname).First(&existing).Error == nil { + // 已自动注册过 → 仅更新实时指标,不重复创建 + h.db.Model(&existing).Updates(map[string]interface{}{ + "ip": matchIP, "os": osName, "cpu": req.CPU.CoreCount, + "memory": memMB, "disk": diskGB, "status": "running", + "agent_status": "online", "agent_version": req.AgentVersion, + "cpu_usage": req.CPU.UsagePercent, "memory_usage": req.Memory.UsagePercent, + "disk_usage": diskAvg, + }) } else { - fmt.Printf("[agent] 自动注册新主机: %q (IP: %s, CPU=%d核, 内存=%dMB, 磁盘=%dGB, OS=%q)\n", - req.Hostname, matchIP, req.CPU.CoreCount, memMB, diskGB, osName) + // 真正的新主机 → 注册(OnConflict 作为并发双保险) + newServer := model.Server{ + Hostname: req.Hostname, + IP: matchIP, + Port: 22, + Username: "root", + AuthType: "password", + OS: osName, + CPU: req.CPU.CoreCount, + Memory: memMB, + Disk: diskGB, + Status: "running", + AgentStatus: "online", + AgentVersion: req.AgentVersion, + CPUUsage: req.CPU.UsagePercent, + MemoryUsage: req.Memory.UsagePercent, + DiskUsage: diskAvg, + } + if err := h.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "hostname"}}, + DoUpdates: clause.AssignmentColumns([]string{ + "ip", "os", "cpu", "memory", "disk", + "status", "agent_status", + "cpu_usage", "memory_usage", "disk_usage", + "updated_at", + }), + }).Create(&newServer).Error; err != nil { + fmt.Printf("[agent] 自动注册主机 %q (IP: %s) 失败: %v\n", req.Hostname, matchIP, err) + } else { + fmt.Printf("[agent] 自动注册新主机: %q (IP: %s, CPU=%d核, 内存=%dMB, 磁盘=%dGB, OS=%q)\n", + req.Hostname, matchIP, req.CPU.CoreCount, memMB, diskGB, osName) + } } } diff --git a/backend/controller/server_v2.go b/backend/controller/server_v2.go index 260aaba..af8e883 100644 --- a/backend/controller/server_v2.go +++ b/backend/controller/server_v2.go @@ -169,11 +169,15 @@ func (h *ServerHandlerV2) BatchCreateServers(c *gin.Context) { } func (h *ServerHandlerV2) BatchDeleteServers(c *gin.Context) { - var ids []uint - if err := c.ShouldBindJSON(&ids); err != nil { + // 前端发 { ids: [...] },对齐格式(原期望裸 []uint 导致解析失败 400) // Logan.Li + var req struct { + IDs []uint `json:"ids"` + } + if err := c.ShouldBindJSON(&req); err != nil || len(req.IDs) == 0 { utils.BadRequest(c, "请求参数错误") return } + ids := req.IDs // 清理关联数据 for _, id := range ids { var s model.Server