/* * Copyright (c) 2025 Beijing Hardman Automation Equipment Co., LTD. All rights reserved. * * Project: adapter * File: sse.go * Last: 2025-05-07 14:12:54 * Author: wangcheng@bj-hardman.com */ package sse import ( "encoding/json" "fmt" "log/slog" "net/http" "sync" "time" "github.com/zeromicro/go-zero/core/logx" ) type Handler struct { mu sync.RWMutex clients map[chan Event]bool } type Event struct { Event string `json:"event"` Data string `json:"data"` } func NewHandler() *Handler { return &Handler{ clients: make(map[chan Event]bool), } } // Serve 处理 SSE 连接 func (h *Handler) Serve(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") // 为每个客户端创建一个 channel clientChan := make(chan Event) h.mu.Lock() h.clients[clientChan] = true h.mu.Unlock() // 客户端断开时清理 defer func() { h.mu.Lock() delete(h.clients, clientChan) h.mu.Unlock() close(clientChan) }() w.(http.Flusher).Flush() // 刷新缓冲区 heartbeat := time.NewTicker(30 * time.Second) defer heartbeat.Stop() // 持续监听并推送事件 for { select { case msg := <-clientChan: // 发送事件数据 var err error if msg.Data != "" { _, err = fmt.Fprintf(w, "event: %s\ndata: %s\n\n", msg.Event, msg.Data) } else { _, err = fmt.Fprintf(w, "event: %s\n\n", msg.Event) } if err != nil { logx.Errorf("sse sent %+v failed: %s", msg, err) return } w.(http.Flusher).Flush() slog.InfoContext(r.Context(), fmt.Sprintf("sse sent %+v", msg)) case <-heartbeat.C: _, err := fmt.Fprintf(w, ": heartbeat\n\n") if err != nil { return } w.(http.Flusher).Flush() case <-r.Context().Done(): //客户端断开连接 logx.Error(r.Context().Err()) return } } } func (h *Handler) Emit(event string, data ...any) { // 广播给所有客户端 var js []byte if len(data) > 0 { if s, ok := data[0].(string); ok { js = []byte(s) } else { js, _ = json.Marshal(data[0]) } } h.mu.RLock() defer h.mu.RUnlock() for msgChan := range h.clients { go func(ch chan Event) { defer func() { recover() }() // 客户端断连后 channel 已关闭,忽略 panic ch <- Event{ Event: event, Data: string(js), } }(msgChan) } } func (h *Handler) Toast(msg ToastMessage) { h.Emit("toast", msg) } func (h *Handler) Notify(msg NoticeMessage) { h.Emit("notify", msg) } // EmitProgressEvent sends a structured progress event to all SSE clients. func (h *Handler) EmitProgressEvent(eventType string, taskID int, stepName string, data map[string]any) { payload := map[string]any{ "event": eventType, "task_id": taskID, "step_name": stepName, "data": data, } h.Emit("workflow", payload) }