package sse import ( "fmt" "net/http" "sync" ) // SseMsg SSE 事件消息 type SseMsg struct { Type string // 事件名,如表单名 dashboard.* Data string // JSON 字符串 } // Hub SSE 推送中心(支持看板局部刷新) type Hub struct { mu sync.Mutex clients map[chan SseMsg]struct{} closed map[chan SseMsg]bool } func NewHub() *Hub { return &Hub{clients: make(map[chan SseMsg]struct{})} } // Add 新增客户端 func (h *Hub) Add() chan SseMsg { ch := make(chan SseMsg, 16) h.mu.Lock() h.clients[ch] = struct{}{} h.mu.Unlock() return ch } func (h *Hub) Remove(ch chan SseMsg) { h.mu.Lock() if _, ok := h.clients[ch]; ok { delete(h.clients, ch) close(ch) } h.mu.Unlock() } // Publish 广播事件 func (h *Hub) Publish(typ string, data string) { h.mu.Lock() defer h.mu.Unlock() for ch := range h.clients { select { case ch <- SseMsg{Type: typ, Data: data}: default: } } } // Handler SSE 长连接端点 func (h *Hub) Handler(w http.ResponseWriter, r *http.Request) { fl, ok := w.(http.Flusher) if !ok { http.Error(w, "streaming unsupported", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") ch := h.Add() defer h.Remove(ch) h.Publish("dashboard.hello", "{}") for { select { case m := <-ch: if _, err := fmt.Fprintf(w, "event: %s\ndata: %s\n\n", m.Type, m.Data); err != nil { return } fl.Flush() case <-r.Context().Done(): return } } }