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...)
}
// 如果还是没匹配到 —— 自动注册新服务器(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)
}
}
}
+6 -2
View File
@@ -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