Go基于whatsmeow SSE推送WhatsApp登录二维码 前端解析显示异常
核心问题
你的实现存在两个根本性错误,直接导致数据解析失败:
- SSE(
text/event-stream)是纯文本协议,仅支持传输UTF-8编码的文本帧,直接写入二进制图片流会破坏SSE的帧格式,导致客户端解析报错。 - 同时用
EventSource和fetch请求同一个SSE接口会建立两个独立的HTTP连接,两个连接拿到的是完全隔离的响应流,根本无法实现“同一条连接区分文本和图片”的逻辑。
解决方案
两种实现路径,第一种改造成本最低,第二种性能更优,按需选择即可。
方案一:单SSE连接传输所有数据(推荐,改造成本最低)
完全复用单个SSE连接,所有内容按标准SSE帧格式传输,不需要额外发起其他请求:
服务端调整
- 取消直接往SSE流写入二维码二进制的逻辑,生成二维码PNG字节流后做Base64编码,和其他状态事件一样封装为标准SSE帧返回。
- 用SSE规范的
event字段区分不同事件类型,不要把所有内容都塞到默认消息帧里,帧格式参考如下Go代码:// 设置SSE响应头 w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") flusher, _ := w.(http.Flusher) // 已登录事件 fmt.Fprint(w, "event: already_logged\ndata: 已登录\n\n") flusher.Flush() // 新二维码事件,data直接拼接为data URI格式 qrBase64 := base64.StdEncoding.EncodeToString(qrPngBytes) fmt.Fprintf(w, "event: new_qr\ndata: data:image/png;base64,%s\n\n", qrBase64) flusher.Flush() // 二维码超时事件 fmt.Fprint(w, "event: qr_timeout\ndata: 二维码已过期,正在刷新\n\n") flusher.Flush() // 登录成功事件 fmt.Fprint(w, "event: login_success\ndata: 登录成功\n\n") flusher.Flush()
前端调整
- 删除额外的fetch请求逻辑,仅保留单个
EventSource实例,通过监听不同事件名分别处理状态更新和二维码渲染即可,完全不需要做Content-Type判断。 - 参考实现代码:
const statusDom = document.querySelector('#login-status') const qrDom = document.querySelector('#qr-img') const es = new EventSource('http://你的服务地址:9999/login/stream') es.addEventListener('already_logged', () => { statusDom.textContent = '已处于登录状态,正在跳转' es.close() }) es.addEventListener('new_qr', (e) => { statusDom.textContent = '请使用WhatsApp扫码登录' qrDom.src = e.data }) es.addEventListener('qr_timeout', () => { statusDom.textContent = '二维码已过期,等待刷新' qrDom.src = '' }) es.addEventListener('login_success', () => { statusDom.textContent = '登录成功,正在跳转' qrDom.remove() es.close() }) es.onerror = () => { statusDom.textContent = '连接断开,正在尝试重连' }
方案二:SSE只传状态,二维码走独立接口(性能更优)
如果介意Base64编码带来的约33%体积冗余,可以把状态通知和二维码资源拆分传输:
服务端调整
- SSE连接仅推送纯文本状态事件,当生成新二维码时,给二维码分配一个唯一ID(随机字符串、递增版本号均可),把ID放在
new code事件的data里返回给前端。 - 新增独立的二维码获取接口,例如
/login/qrcode/{qrId},接口直接返回对应ID的PNG二进制流,设置Content-Type: image/png,二维码过期后接口直接返回404即可。
前端调整
- 同样仅保留单个
EventSource实例监听SSE事件,收到new code事件时,取出事件里的二维码ID,直接把img标签的src属性设置为二维码接口地址(例如/login/qrcode/xxx),浏览器会自动发起GET请求加载图片,不需要手写fetch处理二进制流。 - 其他状态事件直接更新对应span标签的文本内容即可。
注意事项
- 不要尝试在SSE流中直接混传二进制:SSE以
\n\n作为帧分隔符,二进制PNG数据中大概率会出现匹配的字节序列,会导致客户端把二进制数据拆分为格式错误的文本帧,解析完全混乱。 - whatsmeow生成的默认尺寸二维码PNG体积在150KB以内,Base64编码后体积约200KB,登录场景下这个开销完全可以接受,优先选方案一可以最快跑通流程。
- 如果选择方案二,给二维码接口设置1-2分钟的短缓存即可,避免浏览器重复请求同一个二维码时反复回源。
内容的提问来源于stack exchange,提问作者Hasan A Yousef
相关产品推荐
相关产品推荐

