package server import ( "context" "strconv" "time" "github.com/gin-gonic/gin" "github.com/jmoiron/sqlx" "vdi-cloud/platform/internal/store" ) type sessionRecord struct { ID string `json:"id" db:"id"` SessionID string `json:"sessionId" db:"session_id"` TerminalID string `json:"terminalId" db:"terminal_id"` TerminalName string `json:"terminalName" db:"terminal_name"` DesktopID string `json:"desktopId" db:"desktop_id"` DesktopName string `json:"desktopName" db:"desktop_name"` Username string `json:"username" db:"username"` DisplayName string `json:"displayName" db:"display_name"` State string `json:"state" db:"state"` Source string `json:"source" db:"source"` ReservedAt time.Time `json:"reservedAt" db:"reserved_at"` ConnectedAt *time.Time `json:"connectedAt" db:"connected_at"` LastReportedAt time.Time `json:"lastReportedAt" db:"last_reported_at"` EndedAt *time.Time `json:"endedAt" db:"ended_at"` EndReason string `json:"endReason" db:"end_reason"` } func startSessionRecord(ctx context.Context, tx *sqlx.Tx, terminal, session, desktop, state, source string) error { _, err := tx.ExecContext(ctx, `INSERT INTO session_history(id,session_id,terminal_id,terminal_name,desktop_id,desktop_name,username,display_name,state,source,connected_at) SELECT $1,$2,t.id,t.name,d.id,d.name,COALESCE(u.username,''),COALESCE(u.display_name,''),$5,$6,CASE WHEN $5='connected' THEN now() ELSE NULL END FROM terminals t JOIN desktops d ON d.id=$4 LEFT JOIN users u ON u.terminal_id=t.id WHERE t.id=$3`, store.ID("history_"), session, terminal, desktop, state, source) return err } func closeSessionRecords(ctx context.Context, tx *sqlx.Tx, terminal, reason string) error { _, err := tx.ExecContext(ctx, "UPDATE session_history SET state='interrupted',ended_at=now(),end_reason=$2 WHERE terminal_id=$1 AND ended_at IS NULL", terminal, reason) return err } func pageParams(c *gin.Context) (int, int) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) if page < 1 { page = 1 } if page > 1000000 { page = 1000000 } if size < 1 { size = 20 } if size > 100 { size = 100 } return page, size } func timeRange(c *gin.Context, maxDays int) (time.Time, time.Time, bool) { to := time.Now().UTC() from := to.Add(-24 * time.Hour) var err error if v := c.Query("to"); v != "" { to, err = time.Parse(time.RFC3339, v) if err != nil { return from, to, false } from = to.Add(-24 * time.Hour) } if v := c.Query("from"); v != "" { from, err = time.Parse(time.RFC3339, v) if err != nil { return from, to, false } } return from, to, to.After(from) && to.Sub(from) <= time.Duration(maxDays)*24*time.Hour } func (s *Server) sessionHistory(c *gin.Context) { from, to, ok := timeRange(c, 90) if !ok { s.fail(c, 400, "invalid_range", "请选择有效时间范围,最长 90 天") return } state := c.Query("status") if state != "" && state != "connecting" && state != "connected" && state != "ended" && state != "interrupted" { s.fail(c, 400, "invalid_request", "会话状态无效") return } page, size := pageParams(c) filter := ` FROM session_history WHERE reserved_at>=$1 AND reserved_at<=$2 AND ($3='' OR state=$3) AND (terminal_name ILIKE $4 OR desktop_name ILIKE $4 OR username ILIKE $4 OR display_name ILIKE $4)` args := []any{from, to, state, "%" + c.Query("keyword") + "%"} var total int tx, err := s.DB.BeginTxx(c.Request.Context(), nil) if err != nil { s.dbError(c, err) return } defer tx.Rollback() if err = tx.GetContext(c.Request.Context(), &total, "SELECT count(*)"+filter, args...); err != nil { s.dbError(c, err) return } items := []sessionRecord{} args = append(args, size, (page-1)*size) if err = tx.SelectContext(c.Request.Context(), &items, "SELECT *"+filter+" ORDER BY reserved_at DESC,id LIMIT $5 OFFSET $6", args...); err != nil { s.dbError(c, err) return } c.JSON(200, gin.H{"items": items, "total": total, "page": page, "pageSize": size, "requestId": c.GetString("requestId")}) }