评论实时更新:SSE 订阅文章频道,发评/编辑/删除/审核通过即时刷新打开中的页面
- internal/hub:按文章 ID 的进程内发布/订阅,信号不携带内容(未审核评论不外泄) - GET /api/comments/stream?post_id=N:SSE 长连 + 25s 心跳 + X-Accel-Buffering off - 广播点:发评论、作者编辑/删除、后台审核通过、后台删除(与公开端共享同一 hub) - 前端 EventSource 静默刷新(不亮加载态、不干扰正在输入/编辑的状态) - requestLog 的 statusRecorder 补 Flush 透传——包装器没实现 Flusher 时流式端点整体不可用
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
// 评论变更的 SSE 端点:GET /api/comments/stream?post_id=N。
|
||||
// 事件里只有「这篇文章的评论变了」——不带任何内容,前端收到后
|
||||
// 自己 refetch。这样未审核的评论内容不会经 SSE 泄给围观者。
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"oneblog/internal/httpx"
|
||||
)
|
||||
|
||||
// commentsStream 长连推送。EventSource 断线会自动重连,所以这里
|
||||
// 只需要:连上即发注释行、25s 心跳防代理掐线、事件触发刷新。
|
||||
func (a *API) commentsStream(w http.ResponseWriter, r *http.Request) {
|
||||
postID, err := strconv.ParseInt(r.URL.Query().Get("post_id"), 10, 64)
|
||||
if err != nil || postID <= 0 {
|
||||
httpx.BadRequest(w, "post_id required")
|
||||
return
|
||||
}
|
||||
fl, ok := w.(http.Flusher)
|
||||
if !ok {
|
||||
httpx.Error(w, http.StatusInternalServerError, "streaming unsupported")
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
// 反向代理(nginx 等)默认缓冲会吞掉流式响应
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
|
||||
fmt.Fprint(w, ": connected\n\n")
|
||||
fl.Flush()
|
||||
|
||||
ch, off := a.Hub.Subscribe(postID)
|
||||
defer off()
|
||||
heartbeat := time.NewTicker(25 * time.Second)
|
||||
defer heartbeat.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
return
|
||||
case <-heartbeat.C:
|
||||
fmt.Fprint(w, ": ping\n\n")
|
||||
fl.Flush()
|
||||
case <-ch:
|
||||
fmt.Fprintf(w, "event: comments\ndata: {\"post_id\":%d}\n\n", postID)
|
||||
fl.Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user