package controller import ( "fmt" "net/http" "strconv" "time" "ezops/model" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // TerminalSessionHandler 终端会话审计处理器 // Logan.Li type TerminalSessionHandler struct { db *gorm.DB killFunc func(sessionID uint) bool // 外部 Kill 回调 } // NewTerminalSessionHandler 创建终端会话审计处理器 func NewTerminalSessionHandler(db *gorm.DB, killFunc func(sessionID uint) bool) *TerminalSessionHandler { return &TerminalSessionHandler{db: db, killFunc: killFunc} } // ListTerminalSessions 终端会话列表 // GET /api/v1/terminal-sessions?server_id=&username=&status=&page=&page_size= func (h *TerminalSessionHandler) ListTerminalSessions(c *gin.Context) { var req model.TerminalSessionQuery if err := c.ShouldBindQuery(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: err.Error()}) return } if req.Page <= 0 { req.Page = 1 } if req.PageSize <= 0 { req.PageSize = 20 } query := h.db.Model(&model.TerminalSession{}) if req.ServerID > 0 { query = query.Where("server_id = ?", req.ServerID) } if req.Username != "" { query = query.Where("username LIKE ?", "%"+req.Username+"%") } if req.Status != "" { query = query.Where("status = ?", req.Status) } var total int64 if err := query.Count(&total).Error; err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "查询失败"}) return } var sessions []model.TerminalSession if err := query.Order("id DESC"). Offset((req.Page - 1) * req.PageSize). Limit(req.PageSize). Find(&sessions).Error; err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "查询失败"}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Data: model.ListResponse{ List: sessions, Total: total, }}) } // GetTerminalSession 终端会话详情 // GET /api/v1/terminal-sessions/:id func (h *TerminalSessionHandler) GetTerminalSession(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "无效的会话ID"}) return } var session model.TerminalSession if err := h.db.First(&session, id).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "会话不存在"}) return } c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "查询失败"}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Data: session}) } // GetTerminalRecord 终端会话录制数据(用于回放) // GET /api/v1/terminal-sessions/:id/record func (h *TerminalSessionHandler) GetTerminalRecord(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "无效的会话ID"}) return } var session model.TerminalSession if err := h.db.First(&session, id).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "会话不存在"}) return } c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "查询失败"}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Data: session.RecordData}) } // DeleteTerminalSession 删除终端会话记录 // DELETE /api/v1/terminal-sessions/:id func (h *TerminalSessionHandler) DeleteTerminalSession(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "无效的会话ID"}) return } if err := h.db.Delete(&model.TerminalSession{}, id).Error; err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "删除失败"}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "删除成功"}) } // BatchDeleteTerminalSessions 批量删除终端会话记录 // POST /api/v1/terminal-sessions/batch-delete func (h *TerminalSessionHandler) BatchDeleteTerminalSessions(c *gin.Context) { var req model.IDsRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "无效的请求参数"}) return } result := h.db.Where("id IN ?", req.IDs).Delete(&model.TerminalSession{}) if result.Error != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "删除失败"}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "删除成功", Data: gin.H{"deleted": result.RowsAffected}}) } // KillSession 强制终止终端会话 // POST /api/v1/terminal-sessions/:id/kill func (h *TerminalSessionHandler) KillSession(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "无效的会话ID"}) return } var session model.TerminalSession if err := h.db.First(&session, id).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "会话不存在"}) return } c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "查询失败"}) return } if session.Status == "closed" { c.JSON(http.StatusOK, model.Response{Code: 200, Message: "already closed"}) return } // 强制终止 WebShell 连接 if h.killFunc != nil { h.killFunc(session.ID) } now := time.Now() duration := int(now.Sub(session.StartTime).Seconds()) if err := h.db.Model(&session).Updates(map[string]interface{}{ "status": "closed", "end_time": now, "duration": duration, }).Error; err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "终止失败"}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "已终止"}) } // DownloadSession 下载终端会话录制文件 // GET /api/v1/terminal-sessions/:id/download func (h *TerminalSessionHandler) DownloadSession(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "无效的会话ID"}) return } var session model.TerminalSession if err := h.db.First(&session, id).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "会话不存在"}) return } c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "查询失败"}) return } if session.RecordData == "" { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "无录制数据"}) return } c.Header("Content-Type", "text/plain; charset=utf-8") c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%d.cast"`, session.ID)) c.Header("Content-Length", fmt.Sprintf("%d", len(session.RecordData))) c.String(http.StatusOK, session.RecordData) } // StreamSession 终端会话实时流(WebSocket 暂未实现) // GET /api/v1/terminal-sessions/:id/stream func (h *TerminalSessionHandler) StreamSession(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "无效的会话ID"}) return } var session model.TerminalSession if err := h.db.First(&session, id).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "会话不存在"}) return } c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "查询失败"}) return } c.JSON(http.StatusOK, gin.H{ "message": "WebSocket streaming not yet implemented", "session_id": id, "status": session.Status, }) }