vdi-cloud/platform/internal/server/session_history.go

111 lines
4.1 KiB
Go

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")})
}