EventSource 在 Gin + Vue 中连不上,主要是 Gin 未正确设置 SSE 响应头、未及时 flush 数据,且需避免 Goroutine 泄漏、Nginx 缓存干扰及前端内存泄漏。

为什么 EventSource 在 Gin + Vue 里经常连不上
直接用 new EventSource("/api/events") 却收不到消息?大概率是 Gin 默认没处理长连接的响应头,或者 Vue 端没正确捕获重连逻辑。Gin 的 HTTP handler 默认写完就关闭连接,而 EventSource 要求服务端持续保持连接、分块输出(text/event-stream),且每条消息末尾必须有两个换行符。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 在 Gin handler 中显式设置响应头:
c.Writer.Header().Set("Content-Type", "text/event-stream")、c.Writer.Header().Set("Cache-Control", "no-cache")、c.Writer.Header().Set("Connection", "keep-alive") - 每次发送数据后调用
c.Writer.Flush()—— 否则 Gin 的缓冲机制会攒着不发,前端永远等不到第一条消息 - 避免在 handler 中使用
c.JSON()或c.String(),它们会覆盖 Content-Type 并提前结束响应 - 如果用了 Nginx 反向代理,需额外配置:
proxy_buffering off、proxy_cache off、proxy_read_timeout 300
Gin 后端如何安全地推送滚动数据流
大屏滚动通常是高频更新(比如每秒 1–5 条),但直接在 handler 里 for 循环 sleep 推送会导致 Goroutine 泄漏和阻塞路由。正确做法是把数据源和推送逻辑解耦,用 channel 做中转,让 handler 只负责消费。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 启动一个常驻 Goroutine 监听业务数据源(如数据库变更、MQ 消息、定时生成的滚动项),将结构化数据(如
{id: 1, title: "xxx", time: "10:20:30"})推入全局eventCh chan string - 每个 EventSource 连接对应一个独立的 goroutine,从
eventCh读取并格式化为 SSE 标准格式:"data: " + jsonStr + "\n\n" - 务必监听
c.Request.Context().Done(),连接断开时及时退出 goroutine,防止堆积 - 不要在 handler 内做耗时操作(如查库、加锁),否则会卡住整个流;所有计算应在上游完成,只推送最终结果
Vue 前端怎么避免重复连接和内存泄漏
页面切换、组件卸载时如果没手动关闭 EventSource,它会在后台继续尝试重连,既浪费服务端资源,又可能触发重复渲染导致滚动错乱。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 在 Vue 组件
onMounted中创建实例,并赋值给响应式 ref(如const es = ref(null)),方便后续控制 - 监听
message事件时,用JSON.parse(e.data)解析,注意e.data是字符串,不是自动解析的 JSON - 在
onUnmounted中调用es.value?.close(),并置空 ref - 设置重连间隔:通过
es.value.addEventListener("error", () => { ... })捕获断连,手动延迟重建新实例,避免瞬间重试压垮服务端 - 滚动 DOM 更新推荐用
requestAnimationFrame批量处理,而不是每条消息都直接appendChild
滚动数据如何做到“平滑追加+自动裁剪”
大屏运行数小时后,DOM 节点越来越多,浏览器渲染变慢甚至卡死。不能只往里 append,得有主动管理策略。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 限制最大显示条数(如 50 条),每次新数据进来前,先检查
container.children.length > 50,满足则container.firstChild.remove() - 用 CSS 实现无跳动滚动:给容器设
overflow: hidden,子项用transform: translateY()配合 transition,比scrollTop更稳定 - 避免在
message回调里直接操作 DOM,改用ref存数组,再用v-for渲染,让 Vue 控制 diff 和复用 - 若需高亮最新一条,别用 JS 加 class,改用 CSS 的
:last-child或动态绑定class到数组末尾元素
真正难的不是推数据,而是当 20 个大屏同时连上来、每秒推送 3 条、持续跑 72 小时后,连接是否还稳、内存是否涨、滚动是否卡顿——这些细节全藏在 flush 时机、channel 缓冲大小、ES 错误重试退避策略里。


















