package weblive import ( "encoding/json" "strings" "sync" "time" "unicode/utf8" "yh_web/server/handlers" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) const maxDanmakuRunes = 120 var ( danmakuClientsMu sync.Mutex danmakuClients = make(map[*websocket.Conn]struct{}) ) func writeDanmakuJSON(ws *websocket.Conn, v any) error { b, err := json.Marshal(v) if err != nil { return err } return ws.WriteMessage(websocket.TextMessage, b) } // handleDanmakuWS 弹幕:收 JSON {"text":"..."};未带有效 token 仅可收广播不可发。广播 {"type":"dm","text","ts"},不落库 func handleDanmakuWS(c *gin.Context) { canSend := handlers.SiteDanmakuTokenValid(c.Query("token")) ws, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { return } ws.SetReadLimit(4096) danmakuClientsMu.Lock() danmakuClients[ws] = struct{}{} danmakuClientsMu.Unlock() defer func() { danmakuClientsMu.Lock() delete(danmakuClients, ws) danmakuClientsMu.Unlock() _ = ws.Close() }() for { mt, payload, err := ws.ReadMessage() if err != nil { return } if mt != websocket.TextMessage { continue } if !canSend { _ = writeDanmakuJSON(ws, map[string]interface{}{ "type": "error", "code": "login_required", "message": "请先登录或注册后再发弹幕", }) continue } text := extractDanmakuText(payload) if text == "" { continue } out, err := json.Marshal(map[string]interface{}{ "type": "dm", "text": text, "ts": time.Now().UnixMilli(), }) if err != nil { continue } danmakuBroadcast(out) } } func extractDanmakuText(payload []byte) string { var v struct { Text string `json:"text"` } if err := json.Unmarshal(payload, &v); err != nil { return "" } t := strings.TrimSpace(v.Text) if t == "" { return "" } if utf8.RuneCountInString(t) <= maxDanmakuRunes { return t } runes := []rune(t) return string(runes[:maxDanmakuRunes]) } func danmakuBroadcast(b []byte) { danmakuClientsMu.Lock() defer danmakuClientsMu.Unlock() dead := make([]*websocket.Conn, 0) for conn := range danmakuClients { _ = conn.SetWriteDeadline(time.Now().Add(8 * time.Second)) if err := conn.WriteMessage(websocket.TextMessage, b); err != nil { dead = append(dead, conn) } } for _, conn := range dead { delete(danmakuClients, conn) _ = conn.Close() } }