package ws import ( "crypto/rand" "encoding/hex" "net/url" "time" ) const wsTicketTTL = 60 * time.Second type wsTicket struct { userID string username string role string expiresAt time.Time used bool } // IssueTicket 签发一次性 WebSocket ticket,避免长期 JWT 进入 URL。 func (h *Hub) IssueTicket(userID, username, role string) string { buf := make([]byte, 32) if _, err := rand.Read(buf); err != nil { return "" } ticket := hex.EncodeToString(buf) h.ticketsMu.Lock() h.tickets[ticket] = &wsTicket{ userID: userID, username: username, role: role, expiresAt: time.Now().Add(wsTicketTTL), } h.ticketsMu.Unlock() return ticket } func (h *Hub) consumeTicket(ticket string) (wsTicket, bool) { h.ticketsMu.Lock() defer h.ticketsMu.Unlock() info, ok := h.tickets[ticket] if !ok || info.used || time.Now().After(info.expiresAt) { return wsTicket{}, false } info.used = true return *info, true } func (h *Hub) ticketFromQuery(rawQuery string) string { values, err := url.ParseQuery(rawQuery) if err != nil { return "" } return values.Get("ticket") }