fix(agent): 修复资产重复注册(每10s新建) + 批量删除400

agent 自动注册前按 hostname 显式查重(asset_servers.hostname 无 UNIQUE 索引,OnConflict 失效导致 Agent 每次上报都 INSERT 新记录,资产越来越多)。

批量删除 BatchDeleteServers 对齐前端 {ids} 格式(原期望裸 []uint 导致 ShouldBindJSON 解析失败 400)。

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
LoganandClaude committed 2026-06-28 23:43:34 +08:00
1 parent f820f0271f
commit b7780c5a5a
2 files changed
+51 -33

No files matched your search

+45 -31
View File
@@ -163,43 +163,57 @@ func (h *AgentHandler) ReceiveReport(c *gin.Context) {
result = h.db.Exec(updateSQL2, args2...) result = h.db.Exec(updateSQL2, args2...)
} }
// 如果还是没匹配到 —— 自动注册新服务器(UPSERT 防重复) // 如果还是没匹配到 —— 按 hostname 显式查重后再注册 // Logan.Li
// asset_servers.hostname 无 UNIQUE 索引,OnConflict 不可靠,故先 First 查重,
// 避免 Agent 每次上报(10s)都新建记录导致资产越来越多。
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
osName := "" osName := ""
if req.System != nil { if req.System != nil {
osName = req.System.OSVersion osName = req.System.OSVersion
} }
newServer := model.Server{ var existing model.Server
Hostname: req.Hostname, if h.db.Where("hostname = ?", req.Hostname).First(&existing).Error == nil {
IP: matchIP, // 已自动注册过 → 仅更新实时指标,不重复创建
Port: 22, h.db.Model(&existing).Updates(map[string]interface{}{
Username: "root", "ip": matchIP, "os": osName, "cpu": req.CPU.CoreCount,
AuthType: "password", "memory": memMB, "disk": diskGB, "status": "running",
OS: osName, "agent_status": "online", "agent_version": req.AgentVersion,
CPU: req.CPU.CoreCount, "cpu_usage": req.CPU.UsagePercent, "memory_usage": req.Memory.UsagePercent,
Memory: memMB, "disk_usage": diskAvg,
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)
} else { } else {
fmt.Printf("[agent] 自动注册新主机: %q (IP: %s, CPU=%d核, 内存=%dMB, 磁盘=%dGB, OS=%q)\n", // 真正的新主机 → 注册(OnConflict 作为并发双保险)
req.Hostname, matchIP, req.CPU.CoreCount, memMB, diskGB, osName) 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)
}
} }
} }
+6 -2
View File
@@ -169,11 +169,15 @@ func (h *ServerHandlerV2) BatchCreateServers(c *gin.Context) {
} }
func (h *ServerHandlerV2) BatchDeleteServers(c *gin.Context) { func (h *ServerHandlerV2) BatchDeleteServers(c *gin.Context) {
var ids []uint // 前端发 { ids: [...] },对齐格式(原期望裸 []uint 导致解析失败 400) // Logan.Li
if err := c.ShouldBindJSON(&ids); err != nil { var req struct {
IDs []uint `json:"ids"`
}
if err := c.ShouldBindJSON(&req); err != nil || len(req.IDs) == 0 {
utils.BadRequest(c, "请求参数错误") utils.BadRequest(c, "请求参数错误")
return return
} }
ids := req.IDs
// 清理关联数据 // 清理关联数据
for _, id := range ids { for _, id := range ids {
var s model.Server var s model.Server