Files
aiops/backend/controller/server_v2.go
T
LoganandClaude b7780c5a5a 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>
2026-06-28 23:43:34 +08:00

192 lines
5.3 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 handlers_v2 provides thin HTTP handlers that delegate to service layer.
// This is the refactored version — handlers contain ONLY HTTP concerns:
// parse request → call service → return HTTP response.
package controller
import (
"net/http"
"strconv"
"ezops/dto"
"ezops/model"
"ezops/service"
"ezops/utils"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// ━━━ Server Handler (v2 thin) ━━━━━━━━━━━━━━━━━━━━━━━━━━
// ServerHandlerV2 服务器处理器 — thin handler,委托给 ServerService
type ServerHandlerV2 struct {
svc *service.ServerService
db *gorm.DB // retained for audit logging until audit is extracted
}
func NewServerHandlerV2(svc *service.ServerService, db *gorm.DB) *ServerHandlerV2 {
return &ServerHandlerV2{svc: svc, db: db}
}
func (h *ServerHandlerV2) recordAudit(c *gin.Context, action, resource, resourceID string, status int, errMsg string) {
userID, _ := c.Get("userID")
username, _ := c.Get("username")
uid, _ := userID.(uint)
uname, _ := username.(string)
h.db.Create(&model.AuditLog{
UserID: &uid,
Username: uname,
IP: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
Module: "server",
Action: action,
Resource: resource,
ResourceID: resourceID,
Status: status,
Error: errMsg,
})
}
func (h *ServerHandlerV2) ListServers(c *gin.Context) {
var query model.PageQuery
if err := c.ShouldBindQuery(&query); err != nil {
utils.BadRequest(c, "请求参数错误")
return
}
status := c.Query("status")
regionID := c.Query("region_id")
list, total, err := h.svc.ListServers(query.Keyword, status, regionID, query.Page, query.PageSize)
if err != nil {
utils.InternalError(c, "查询失败")
return
}
utils.ListResponse(c, list, total)
}
func (h *ServerHandlerV2) GetServer(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
server, err := h.svc.GetServer(uint(id))
if err != nil {
utils.NotFound(c, "服务器不存在")
return
}
utils.OK(c, server)
}
func (h *ServerHandlerV2) CreateServer(c *gin.Context) {
var req dto.CreateServerRequest
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, "请求参数错误: "+err.Error())
return
}
server, err := h.svc.CreateServer(&req)
if err != nil {
utils.InternalError(c, "创建失败: "+err.Error())
return
}
h.recordAudit(c, "create", server.Hostname+" ("+server.IP+")", strconv.Itoa(int(server.ID)), 1, "")
utils.OK(c, server)
}
func (h *ServerHandlerV2) UpdateServer(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var req dto.UpdateServerRequest
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, "请求参数错误: "+err.Error())
return
}
if err := h.svc.UpdateServer(uint(id), &req); err != nil {
utils.InternalError(c, "更新失败: "+err.Error())
return
}
server, _ := h.svc.GetServer(uint(id))
h.recordAudit(c, "update", server.Hostname+" ("+server.IP+")", strconv.Itoa(int(server.ID)), 1, "")
utils.OK(c, server)
}
func (h *ServerHandlerV2) DeleteServer(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
server, err := h.svc.GetServer(uint(id))
if err != nil {
utils.NotFound(c, "服务器不存在")
return
}
// 清理关联数据
h.db.Unscoped().Where("hostname = ?", server.Hostname).Delete(&model.AgentReport{})
h.db.Unscoped().Where("hostname = ?", server.Hostname).Delete(&model.MetricsAggregate{})
if err := h.svc.DeleteServer(uint(id)); err != nil {
utils.InternalError(c, "删除失败")
return
}
h.recordAudit(c, "delete", server.Hostname+" ("+server.IP+")", strconv.Itoa(int(server.ID)), 1, "")
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "服务器已彻底删除"})
}
func (h *ServerHandlerV2) GetServerStats(c *gin.Context) {
stats, err := h.svc.GetDashboardStats()
if err != nil {
utils.InternalError(c, "获取统计失败")
return
}
utils.OK(c, stats.ServerStats)
}
// BatchCreateServers / BatchDeleteServers — 暂时保留原始逻辑(复杂批量操作待后续迁移)
func (h *ServerHandlerV2) BatchCreateServers(c *gin.Context) {
// 暂时委托给原始 handler 逻辑(从 server.go 复制)
var servers []model.Server
if err := c.ShouldBindJSON(&servers); err != nil {
utils.BadRequest(c, "请求参数错误")
return
}
for i := range servers {
if servers[i].Port == 0 {
servers[i].Port = 22
}
if servers[i].Username == "" {
servers[i].Username = "root"
}
if servers[i].Status == "" {
servers[i].Status = "stopped"
}
}
if err := h.db.Create(&servers).Error; err != nil {
utils.InternalError(c, "批量创建失败")
return
}
utils.OK(c, servers)
}
func (h *ServerHandlerV2) BatchDeleteServers(c *gin.Context) {
// 前端发 { 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
if err := h.db.First(&s, id).Error; err == nil {
h.db.Unscoped().Where("hostname = ?", s.Hostname).Delete(&model.AgentReport{})
h.db.Unscoped().Where("hostname = ?", s.Hostname).Delete(&model.MetricsAggregate{})
}
}
h.db.Unscoped().Delete(&model.Server{}, ids)
utils.OK(c, gin.H{"deleted": len(ids)})
}